diff --git a/CHANGELOG.md b/CHANGELOG.md index a0dd5543..88b60563 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Next Version * Basic fullscreen support for Android MediaPlayer [#1138](https://github.com/react-native-community/react-native-video/pull/1138) +* Simplify default Android SDK code [#1145](https://github.com/react-native-community/react-native-video/pull/1145) [#1146](https://github.com/react-native-community/react-native-video/pull/1146) * Support video cachging for iOS ([#955](https://github.com/react-native-community/react-native-video/pull/955)) ### Version 3.1.0 diff --git a/README.md b/README.md index dce6b6ec..af4df361 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,10 @@ using System.Collections.Generic; ## Usage ```javascript +// Load the module + +import Video from 'react-native-video'; + // Within your render function, assuming you have a file called // "background.mp4" in your project. You can include multiple videos // on a single screen if you like. @@ -245,6 +249,7 @@ var styles = StyleSheet.create({ * [rate](#rate) * [repeat](#repeat) * [resizeMode](#resizemode) +* [selectedAudioTrack](#selectedaudiotrack) * [selectedTextTrack](#selectedtexttrack) * [stereoPan](#stereopan) * [textTracks](#texttracks) @@ -375,6 +380,36 @@ Determines how to resize the video when the frame doesn't match the raw video di Platforms: Android ExoPlayer, Android MediaPlayer, iOS, Windows UWP +#### selectedAudioTrack +Configure which audio track, if any, is played. + +``` +selectedAudioTrack={{ + type: Type, + value: Value +}} +``` + +Example: +``` +selectedAudioTrack={{ + type: "title", + value: "Dubbing" +}} +``` + +Type | Value | Description +--- | --- | --- +"system" (default) | N/A | Play the audio track that matches the system language. If none match, play the first track. +"disabled" | N/A | Turn off audio +"title" | string | Play the audio track with the title specified as the Value, e.g. "French" +"language" | string | Play the audio track with the language specified as the Value, e.g. "fr" +"index" | number | Play the audio track with the index specified as the value, e.g. 0 + +If a track matching the specified Type (and Value if appropriate) is unavailable, the first audio track will be played. If multiple tracks match the criteria, the first match will be used. + +Platforms: Android ExoPlayer, iOS + #### selectedTextTrack Configure which text track (caption or subtitle), if any, is shown. @@ -517,7 +552,8 @@ Property | Type | Description currentPosition | number | Time in seconds where the media will start duration | number | Length of the media in seconds naturalSize | object | Properties:
* width - Width in pixels that the video was encoded at
* height - Height in pixels that the video was encoded at
* orientation - "portrait" or "landscape" -textTracks | array | An array of text track info objects with the following properties:
* index - Index number
* title - Description of the track
* language - 2 letter [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) language code
* type - Mime type of track +audioTracks | array | An array of audio track info objects with the following properties:
* index - Index number
* title - Description of the track
* language - 2 letter [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) or 3 letter [ISO639-2](https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes) language code
* type - Mime type of track +textTracks | array | An array of text track info objects with the following properties:
* index - Index number
* title - Description of the track
* language - 2 letter [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) or 3 letter [ISO 639-2](https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes) language code
* type - Mime type of track Example: ``` @@ -535,6 +571,10 @@ Example: orientation: 'landscape' width: '1920' }, + audioTracks: [ + { language: 'es', title: 'Spanish', type: 'audio/mpeg', index: 0 }, + { language: 'en', title: 'English', type: 'audio/mpeg', index: 1 } + ], textTracks: [ { title: '#1 French', language: 'fr', index: 0, type: 'text/vtt' }, { title: '#2 English CC', language: 'en', index: 1, type: 'text/vtt' }, diff --git a/Video.js b/Video.js index dda530d3..f32725f3 100644 --- a/Video.js +++ b/Video.js @@ -316,6 +316,13 @@ Video.propTypes = { posterResizeMode: Image.propTypes.resizeMode, repeat: PropTypes.bool, allowsExternalPlayback: PropTypes.bool, + selectedAudioTrack: PropTypes.shape({ + type: PropTypes.string.isRequired, + value: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number + ]) + }), selectedTextTrack: PropTypes.shape({ type: PropTypes.string.isRequired, value: PropTypes.oneOfType([ diff --git a/android-exoplayer/build.gradle b/android-exoplayer/build.gradle index a957dd62..4c1e9c32 100644 --- a/android-exoplayer/build.gradle +++ b/android-exoplayer/build.gradle @@ -1,20 +1,16 @@ apply plugin: 'com.android.library' -def _ext = rootProject.ext - -def _reactNativeVersion = _ext.has('reactNative') ? _ext.reactNative : '+' -def _compileSdkVersion = _ext.has('compileSdkVersion') ? _ext.compileSdkVersion : 27 -def _buildToolsVersion = _ext.has('buildToolsVersion') ? _ext.buildToolsVersion : '27.0.3' -def _minSdkVersion = _ext.has('minSdkVersion') ? _ext.minSdkVersion : 16 -def _targetSdkVersion = _ext.has('targetSdkVersion') ? _ext.targetSdkVersion : 27 +def safeExtGet(prop, fallback) { + rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback +} android { - compileSdkVersion _compileSdkVersion - buildToolsVersion _buildToolsVersion + compileSdkVersion safeExtGet('compileSdkVersion', 27) + buildToolsVersion safeExtGet('buildToolsVersion', '27.0.3') defaultConfig { - minSdkVersion _minSdkVersion - targetSdkVersion _targetSdkVersion + minSdkVersion safeExtGet('minSdkVersion', 16) + targetSdkVersion safeExtGet('targetSdkVersion', 27) versionCode 1 versionName "1.0" } @@ -22,7 +18,7 @@ android { dependencies { //noinspection GradleDynamicVersion - provided "com.facebook.react:react-native:${_reactNativeVersion}" + provided "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}" compile 'com.google.android.exoplayer:exoplayer:2.7.3' compile('com.google.android.exoplayer:extension-okhttp:2.7.3') { exclude group: 'com.squareup.okhttp3', module: 'okhttp' diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 9a036c25..487da343 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -113,6 +113,9 @@ class ReactExoplayerView extends FrameLayout implements private Uri srcUri; private String extension; private boolean repeat; + private String audioTrackType; + private Dynamic audioTrackValue; + private ReadableArray audioTracks; private String textTrackType; private Dynamic textTrackValue; private ReadableArray textTracks; @@ -501,20 +504,43 @@ class ReactExoplayerView extends FrameLayout implements private void videoLoaded() { if (loadVideoStarted) { loadVideoStarted = false; + setSelectedAudioTrack(audioTrackType, audioTrackValue); setSelectedTextTrack(textTrackType, textTrackValue); Format videoFormat = player.getVideoFormat(); int width = videoFormat != null ? videoFormat.width : 0; int height = videoFormat != null ? videoFormat.height : 0; eventEmitter.load(player.getDuration(), player.getCurrentPosition(), width, height, - getTextTrackInfo()); + getAudioTrackInfo(), getTextTrackInfo()); } } + private WritableArray getAudioTrackInfo() { + WritableArray audioTracks = Arguments.createArray(); + + MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo(); + int index = getTrackRendererIndex(C.TRACK_TYPE_AUDIO); + if (info == null || index == C.INDEX_UNSET) { + return audioTracks; + } + + TrackGroupArray groups = info.getTrackGroups(index); + for (int i = 0; i < groups.length; ++i) { + Format format = groups.get(i).getFormat(0); + WritableMap textTrack = Arguments.createMap(); + textTrack.putInt("index", i); + textTrack.putString("title", format.id != null ? format.id : ""); + textTrack.putString("type", format.sampleMimeType); + textTrack.putString("language", format.language != null ? format.language : ""); + audioTracks.pushMap(textTrack); + } + return audioTracks; + } + private WritableArray getTextTrackInfo() { WritableArray textTracks = Arguments.createArray(); MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo(); - int index = getTextTrackRendererIndex(); + int index = getTrackRendererIndex(C.TRACK_TYPE_TEXT); if (info == null || index == C.INDEX_UNSET) { return textTracks; } @@ -647,10 +673,10 @@ class ReactExoplayerView extends FrameLayout implements return false; } - public int getTextTrackRendererIndex() { + public int getTrackRendererIndex(int trackType) { int rendererCount = player.getRendererCount(); for (int rendererIndex = 0; rendererIndex < rendererCount; rendererIndex++) { - if (player.getRendererType(rendererIndex) == C.TRACK_TYPE_TEXT) { + if (player.getRendererType(rendererIndex) == trackType) { return rendererIndex; } } @@ -724,12 +750,9 @@ class ReactExoplayerView extends FrameLayout implements this.repeat = repeat; } - public void setSelectedTextTrack(String type, Dynamic value) { - textTrackType = type; - textTrackValue = value; - - int index = getTextTrackRendererIndex(); - if (index == C.INDEX_UNSET) { + public void setSelectedTrack(int trackType, String type, Dynamic value) { + int rendererIndex = getTrackRendererIndex(trackType); + if (rendererIndex == C.INDEX_UNSET) { return; } MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo(); @@ -737,13 +760,15 @@ class ReactExoplayerView extends FrameLayout implements return; } - TrackGroupArray groups = info.getTrackGroups(index); + TrackGroupArray groups = info.getTrackGroups(rendererIndex); int trackIndex = C.INDEX_UNSET; - trackSelector.setSelectionOverride(index, groups, null); if (TextUtils.isEmpty(type)) { - // Do nothing - } else if (type.equals("disabled")) { + type = "default"; + } + + if (type.equals("disabled")) { + trackSelector.setSelectionOverride(rendererIndex, groups, null); return; } else if (type.equals("language")) { for (int i = 0; i < groups.length; ++i) { @@ -762,26 +787,25 @@ class ReactExoplayerView extends FrameLayout implements } } } else if (type.equals("index")) { - trackIndex = value.asInt(); - } else { // default. Use system settings if possible - int sdk = android.os.Build.VERSION.SDK_INT; - if (sdk>18 && groups.length>0) { - CaptioningManager captioningManager = (CaptioningManager) themedReactContext.getSystemService(Context.CAPTIONING_SERVICE); - if (captioningManager.isEnabled()) { - // default is to take the first object - trackIndex = 0; - - String locale = Locale.getDefault().getDisplayLanguage(); - for (int i = 0; i < groups.length; ++i) { - Format format = groups.get(i).getFormat(0); - if (format.language != null && format.language.equals(locale)) { - trackIndex = i; - break; - } + if (value.asInt() < groups.length) { + trackIndex = value.asInt(); + } + } else { // default + if (rendererIndex == C.TRACK_TYPE_TEXT) { // Use system settings if possible + int sdk = android.os.Build.VERSION.SDK_INT; + if (sdk > 18 && groups.length > 0) { + CaptioningManager captioningManager + = (CaptioningManager)themedReactContext.getSystemService(Context.CAPTIONING_SERVICE); + if (captioningManager != null && captioningManager.isEnabled()) { + trackIndex = getTrackIndexForDefaultLocale(groups); } + } else { + trackSelector.setSelectionOverride(rendererIndex, groups, null); + return; } - } else return; - + } else if (rendererIndex == C.TRACK_TYPE_AUDIO) { + trackIndex = getTrackIndexForDefaultLocale(groups); + } } if (trackIndex == C.INDEX_UNSET) { @@ -791,8 +815,35 @@ class ReactExoplayerView extends FrameLayout implements MappingTrackSelector.SelectionOverride override = new MappingTrackSelector.SelectionOverride( - new FixedTrackSelection.Factory(), trackIndex, 0); - trackSelector.setSelectionOverride(index, groups, override); + new FixedTrackSelection.Factory(), trackIndex, 0); + trackSelector.setSelectionOverride(rendererIndex, groups, override); + } + + private int getTrackIndexForDefaultLocale(TrackGroupArray groups) { + int trackIndex = 0; // default if no match + String locale2 = Locale.getDefault().getLanguage(); // 2 letter code + String locale3 = Locale.getDefault().getISO3Language(); // 3 letter code + for (int i = 0; i < groups.length; ++i) { + Format format = groups.get(i).getFormat(0); + String language = format.language; + if (language != null && (language.equals(locale2) || language.equals(locale3))) { + trackIndex = i; + break; + } + } + return trackIndex; + } + + public void setSelectedAudioTrack(String type, Dynamic value) { + audioTrackType = type; + audioTrackValue = value; + setSelectedTrack(C.TRACK_TYPE_AUDIO, audioTrackType, audioTrackValue); + } + + public void setSelectedTextTrack(String type, Dynamic value) { + textTrackType = type; + textTrackValue = value; + setSelectedTrack(C.TRACK_TYPE_TEXT, textTrackType, textTrackValue); } public void setPausedModifier(boolean paused) { diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java index e3775a6e..fbc8a9ad 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -28,6 +28,9 @@ public class ReactExoplayerViewManager extends ViewGroupManager { return (