Skip to content

Commit

Permalink
Merge pull request #1137 from react-native-community/feature/select-a…
Browse files Browse the repository at this point in the history
…udio-track

Select audio tracks on iOS & ExoPlayer
  • Loading branch information
cobarx authored Jul 24, 2018
2 parents 46d0bc8 + 06eb1c5 commit 78257e9
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 40 deletions.
38 changes: 37 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ var styles = StyleSheet.create({
* [rate](#rate)
* [repeat](#repeat)
* [resizeMode](#resizemode)
* [selectedAudioTrack](#selectedaudiotrack)
* [selectedTextTrack](#selectedtexttrack)
* [stereoPan](#stereopan)
* [textTracks](#texttracks)
Expand Down Expand Up @@ -349,6 +350,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.

Expand Down Expand Up @@ -491,7 +522,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:<br> * width - Width in pixels that the video was encoded at<br> * height - Height in pixels that the video was encoded at<br> * orientation - "portrait" or "landscape"
textTracks | array | An array of text track info objects with the following properties:<br> * index - Index number<br> * title - Description of the track<br> * language - 2 letter [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) language code<br> * type - Mime type of track
audioTracks | array | An array of audio track info objects with the following properties:<br> * index - Index number<br> * title - Description of the track<br> * 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<br> * type - Mime type of track
textTracks | array | An array of text track info objects with the following properties:<br> * index - Index number<br> * title - Description of the track<br> * 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<br> * type - Mime type of track

Example:
```
Expand All @@ -509,6 +541,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' },
Expand Down
7 changes: 7 additions & 0 deletions Video.js
Original file line number Diff line number Diff line change
Expand Up @@ -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([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -501,20 +504,43 @@ private void startProgressHandler() {
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;
}
Expand Down Expand Up @@ -647,10 +673,10 @@ private static boolean isBehindLiveWindow(ExoPlaybackException e) {
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;
}
}
Expand Down Expand Up @@ -724,26 +750,25 @@ public void setRepeatModifier(boolean repeat) {
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();
if (info == null) {
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) {
Expand All @@ -762,26 +787,25 @@ public void setSelectedTextTrack(String type, Dynamic value) {
}
}
} 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) {
Expand All @@ -791,8 +815,35 @@ public void setSelectedTextTrack(String type, Dynamic value) {

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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
private static final String PROP_SRC_HEADERS = "requestHeaders";
private static final String PROP_RESIZE_MODE = "resizeMode";
private static final String PROP_REPEAT = "repeat";
private static final String PROP_SELECTED_AUDIO_TRACK = "selectedAudioTrack";
private static final String PROP_SELECTED_AUDIO_TRACK_TYPE = "type";
private static final String PROP_SELECTED_AUDIO_TRACK_VALUE = "value";
private static final String PROP_SELECTED_TEXT_TRACK = "selectedTextTrack";
private static final String PROP_SELECTED_TEXT_TRACK_TYPE = "type";
private static final String PROP_SELECTED_TEXT_TRACK_VALUE = "value";
Expand Down Expand Up @@ -127,6 +130,20 @@ public void setRepeat(final ReactExoplayerView videoView, final boolean repeat)
videoView.setRepeatModifier(repeat);
}

@ReactProp(name = PROP_SELECTED_AUDIO_TRACK)
public void setSelectedAudioTrack(final ReactExoplayerView videoView,
@Nullable ReadableMap selectedAudioTrack) {
String typeString = null;
Dynamic value = null;
if (selectedAudioTrack != null) {
typeString = selectedAudioTrack.hasKey(PROP_SELECTED_AUDIO_TRACK_TYPE)
? selectedAudioTrack.getString(PROP_SELECTED_AUDIO_TRACK_TYPE) : null;
value = selectedAudioTrack.hasKey(PROP_SELECTED_AUDIO_TRACK_VALUE)
? selectedAudioTrack.getDynamic(PROP_SELECTED_AUDIO_TRACK_VALUE) : null;
}
videoView.setSelectedAudioTrack(typeString, value);
}

@ReactProp(name = PROP_SELECTED_TEXT_TRACK)
public void setSelectedTextTrack(final ReactExoplayerView videoView,
@Nullable ReadableMap selectedTextTrack) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ class VideoEventEmitter {
private static final String EVENT_PROP_WIDTH = "width";
private static final String EVENT_PROP_HEIGHT = "height";
private static final String EVENT_PROP_ORIENTATION = "orientation";
private static final String EVENT_PROP_AUDIO_TRACKS = "audioTracks";
private static final String EVENT_PROP_TEXT_TRACKS = "textTracks";
private static final String EVENT_PROP_HAS_AUDIO_FOCUS = "hasAudioFocus";
private static final String EVENT_PROP_IS_BUFFERING = "isBuffering";
Expand All @@ -130,7 +131,7 @@ void loadStart() {
}

void load(double duration, double currentPosition, int videoWidth, int videoHeight,
WritableArray textTracks) {
WritableArray audioTracks, WritableArray textTracks) {
WritableMap event = Arguments.createMap();
event.putDouble(EVENT_PROP_DURATION, duration / 1000D);
event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000D);
Expand All @@ -145,6 +146,7 @@ void load(double duration, double currentPosition, int videoWidth, int videoHeig
}
event.putMap(EVENT_PROP_NATURAL_SIZE, naturalSize);

event.putArray(EVENT_PROP_AUDIO_TRACKS, audioTracks);
event.putArray(EVENT_PROP_TEXT_TRACKS, textTracks);

// TODO: Actually check if you can.
Expand Down
Loading

0 comments on commit 78257e9

Please sign in to comment.