diff --git a/README.md b/README.md
index cac87ad2..c1625eee 100644
--- a/README.md
+++ b/README.md
@@ -232,6 +232,7 @@ var styles = StyleSheet.create({
* [rate](#rate)
* [repeat](#repeat)
* [resizeMode](#resizemode)
+* [selectedAudioTrack](#selectedaudiotrack)
* [selectedTextTrack](#selectedtexttrack)
* [stereoPan](#stereopan)
* [textTracks](#texttracks)
@@ -356,6 +357,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.
@@ -470,7 +501,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:
```
@@ -488,6 +520,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/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 index) {
+ mediaOption = [group.options objectAtIndex:index];
+ }
+ }
+ } else { // default. invalid type or "system"
+ [_player.currentItem selectMediaOptionAutomaticallyInMediaSelectionGroup:group];
+ return;
+ }
+
+ // If a match isn't found, option will be nil and text tracks will be disabled
+ [_player.currentItem selectMediaOption:mediaOption inMediaSelectionGroup:group];
+}
+
+- (void)setSelectedAudioTrack:(NSDictionary *)selectedAudioTrack {
+ _selectedAudioTrack = selectedAudioTrack;
+ [self setMediaSelectionTrackForCharacteristic:AVMediaCharacteristicAudible
+ withCriteria:_selectedAudioTrack];
+}
+
- (void)setSelectedTextTrack:(NSDictionary *)selectedTextTrack {
_selectedTextTrack = selectedTextTrack;
- if (_textTracks) {
+ if (_textTracks) { // sideloaded text tracks
[self setSideloadedText];
- } else {
- [self setStreamingText];
+ } else { // text tracks included in the HLS playlist
+ [self setMediaSelectionTrackForCharacteristic:AVMediaCharacteristicLegible
+ withCriteria:_selectedTextTrack];
}
}
@@ -869,9 +924,31 @@ static NSString *const timedMetadata = @"timedMetadata";
if (_selectedTextTrack) [self setSelectedTextTrack:_selectedTextTrack];
}
+- (NSArray *)getAudioTrackInfo
+{
+ NSMutableArray *audioTracks = [[NSMutableArray alloc] init];
+ AVMediaSelectionGroup *group = [_player.currentItem.asset
+ mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible];
+ for (int i = 0; i < group.options.count; ++i) {
+ AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i];
+ NSString *title = @"";
+ NSArray *values = [[currentOption commonMetadata] valueForKey:@"value"];
+ if (values.count > 0) {
+ title = [values objectAtIndex:0];
+ }
+ NSString *language = [currentOption extendedLanguageTag] ? [currentOption extendedLanguageTag] : @"";
+ NSDictionary *audioTrack = @{
+ @"index": [NSNumber numberWithInt:i],
+ @"title": title,
+ @"language": language
+ };
+ [audioTracks addObject:audioTrack];
+ }
+ return audioTracks;
+}
+
- (NSArray *)getTextTrackInfo
{
-
// if sideloaded, textTracks will already be set
if (_textTracks) return _textTracks;
diff --git a/ios/RCTVideoManager.m b/ios/RCTVideoManager.m
index 8e566602..e0e0162e 100644
--- a/ios/RCTVideoManager.m
+++ b/ios/RCTVideoManager.m
@@ -25,6 +25,7 @@ RCT_EXPORT_VIEW_PROPERTY(repeat, BOOL);
RCT_EXPORT_VIEW_PROPERTY(allowsExternalPlayback, BOOL);
RCT_EXPORT_VIEW_PROPERTY(textTracks, NSArray);
RCT_EXPORT_VIEW_PROPERTY(selectedTextTrack, NSDictionary);
+RCT_EXPORT_VIEW_PROPERTY(selectedAudioTrack, NSDictionary);
RCT_EXPORT_VIEW_PROPERTY(paused, BOOL);
RCT_EXPORT_VIEW_PROPERTY(muted, BOOL);
RCT_EXPORT_VIEW_PROPERTY(controls, BOOL);