Support selecting audio tracks
Implements audio track selection on iOS & Android ExoPlayer. The prop mirrors the API for selectedTextTrack.
This commit is contained in:
parent
933d3dd817
commit
06eb1c57d8
38
README.md
38
README.md
@ -232,6 +232,7 @@ var styles = StyleSheet.create({
|
|||||||
* [rate](#rate)
|
* [rate](#rate)
|
||||||
* [repeat](#repeat)
|
* [repeat](#repeat)
|
||||||
* [resizeMode](#resizemode)
|
* [resizeMode](#resizemode)
|
||||||
|
* [selectedAudioTrack](#selectedaudiotrack)
|
||||||
* [selectedTextTrack](#selectedtexttrack)
|
* [selectedTextTrack](#selectedtexttrack)
|
||||||
* [stereoPan](#stereopan)
|
* [stereoPan](#stereopan)
|
||||||
* [textTracks](#texttracks)
|
* [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
|
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
|
#### selectedTextTrack
|
||||||
Configure which text track (caption or subtitle), if any, is shown.
|
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
|
currentPosition | number | Time in seconds where the media will start
|
||||||
duration | number | Length of the media in seconds
|
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"
|
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:
|
Example:
|
||||||
```
|
```
|
||||||
@ -488,6 +520,10 @@ Example:
|
|||||||
orientation: 'landscape'
|
orientation: 'landscape'
|
||||||
width: '1920'
|
width: '1920'
|
||||||
},
|
},
|
||||||
|
audioTracks: [
|
||||||
|
{ language: 'es', title: 'Spanish', type: 'audio/mpeg', index: 0 },
|
||||||
|
{ language: 'en', title: 'English', type: 'audio/mpeg', index: 1 } ],
|
||||||
|
],
|
||||||
textTracks: [
|
textTracks: [
|
||||||
{ title: '#1 French', language: 'fr', index: 0, type: 'text/vtt' },
|
{ title: '#1 French', language: 'fr', index: 0, type: 'text/vtt' },
|
||||||
{ title: '#2 English CC', language: 'en', index: 1, type: 'text/vtt' },
|
{ title: '#2 English CC', language: 'en', index: 1, type: 'text/vtt' },
|
||||||
|
7
Video.js
7
Video.js
@ -316,6 +316,13 @@ Video.propTypes = {
|
|||||||
posterResizeMode: Image.propTypes.resizeMode,
|
posterResizeMode: Image.propTypes.resizeMode,
|
||||||
repeat: PropTypes.bool,
|
repeat: PropTypes.bool,
|
||||||
allowsExternalPlayback: PropTypes.bool,
|
allowsExternalPlayback: PropTypes.bool,
|
||||||
|
selectedAudioTrack: PropTypes.shape({
|
||||||
|
type: PropTypes.string.isRequired,
|
||||||
|
value: PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.number
|
||||||
|
])
|
||||||
|
}),
|
||||||
selectedTextTrack: PropTypes.shape({
|
selectedTextTrack: PropTypes.shape({
|
||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
value: PropTypes.oneOfType([
|
value: PropTypes.oneOfType([
|
||||||
|
@ -113,6 +113,9 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
private Uri srcUri;
|
private Uri srcUri;
|
||||||
private String extension;
|
private String extension;
|
||||||
private boolean repeat;
|
private boolean repeat;
|
||||||
|
private String audioTrackType;
|
||||||
|
private Dynamic audioTrackValue;
|
||||||
|
private ReadableArray audioTracks;
|
||||||
private String textTrackType;
|
private String textTrackType;
|
||||||
private Dynamic textTrackValue;
|
private Dynamic textTrackValue;
|
||||||
private ReadableArray textTracks;
|
private ReadableArray textTracks;
|
||||||
@ -501,20 +504,43 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
private void videoLoaded() {
|
private void videoLoaded() {
|
||||||
if (loadVideoStarted) {
|
if (loadVideoStarted) {
|
||||||
loadVideoStarted = false;
|
loadVideoStarted = false;
|
||||||
|
setSelectedAudioTrack(audioTrackType, audioTrackValue);
|
||||||
setSelectedTextTrack(textTrackType, textTrackValue);
|
setSelectedTextTrack(textTrackType, textTrackValue);
|
||||||
Format videoFormat = player.getVideoFormat();
|
Format videoFormat = player.getVideoFormat();
|
||||||
int width = videoFormat != null ? videoFormat.width : 0;
|
int width = videoFormat != null ? videoFormat.width : 0;
|
||||||
int height = videoFormat != null ? videoFormat.height : 0;
|
int height = videoFormat != null ? videoFormat.height : 0;
|
||||||
eventEmitter.load(player.getDuration(), player.getCurrentPosition(), width, height,
|
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() {
|
private WritableArray getTextTrackInfo() {
|
||||||
WritableArray textTracks = Arguments.createArray();
|
WritableArray textTracks = Arguments.createArray();
|
||||||
|
|
||||||
MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo();
|
MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo();
|
||||||
int index = getTextTrackRendererIndex();
|
int index = getTrackRendererIndex(C.TRACK_TYPE_TEXT);
|
||||||
if (info == null || index == C.INDEX_UNSET) {
|
if (info == null || index == C.INDEX_UNSET) {
|
||||||
return textTracks;
|
return textTracks;
|
||||||
}
|
}
|
||||||
@ -647,10 +673,10 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getTextTrackRendererIndex() {
|
public int getTrackRendererIndex(int trackType) {
|
||||||
int rendererCount = player.getRendererCount();
|
int rendererCount = player.getRendererCount();
|
||||||
for (int rendererIndex = 0; rendererIndex < rendererCount; rendererIndex++) {
|
for (int rendererIndex = 0; rendererIndex < rendererCount; rendererIndex++) {
|
||||||
if (player.getRendererType(rendererIndex) == C.TRACK_TYPE_TEXT) {
|
if (player.getRendererType(rendererIndex) == trackType) {
|
||||||
return rendererIndex;
|
return rendererIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -724,12 +750,9 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
this.repeat = repeat;
|
this.repeat = repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSelectedTextTrack(String type, Dynamic value) {
|
public void setSelectedTrack(int trackType, String type, Dynamic value) {
|
||||||
textTrackType = type;
|
int rendererIndex = getTrackRendererIndex(trackType);
|
||||||
textTrackValue = value;
|
if (rendererIndex == C.INDEX_UNSET) {
|
||||||
|
|
||||||
int index = getTextTrackRendererIndex();
|
|
||||||
if (index == C.INDEX_UNSET) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo();
|
MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo();
|
||||||
@ -737,13 +760,15 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackGroupArray groups = info.getTrackGroups(index);
|
TrackGroupArray groups = info.getTrackGroups(rendererIndex);
|
||||||
int trackIndex = C.INDEX_UNSET;
|
int trackIndex = C.INDEX_UNSET;
|
||||||
trackSelector.setSelectionOverride(index, groups, null);
|
|
||||||
|
|
||||||
if (TextUtils.isEmpty(type)) {
|
if (TextUtils.isEmpty(type)) {
|
||||||
// Do nothing
|
type = "default";
|
||||||
} else if (type.equals("disabled")) {
|
}
|
||||||
|
|
||||||
|
if (type.equals("disabled")) {
|
||||||
|
trackSelector.setSelectionOverride(rendererIndex, groups, null);
|
||||||
return;
|
return;
|
||||||
} else if (type.equals("language")) {
|
} else if (type.equals("language")) {
|
||||||
for (int i = 0; i < groups.length; ++i) {
|
for (int i = 0; i < groups.length; ++i) {
|
||||||
@ -762,26 +787,25 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (type.equals("index")) {
|
} else if (type.equals("index")) {
|
||||||
|
if (value.asInt() < groups.length) {
|
||||||
trackIndex = value.asInt();
|
trackIndex = value.asInt();
|
||||||
} else { // default. Use system settings if possible
|
}
|
||||||
|
} else { // default
|
||||||
|
if (rendererIndex == C.TRACK_TYPE_TEXT) { // Use system settings if possible
|
||||||
int sdk = android.os.Build.VERSION.SDK_INT;
|
int sdk = android.os.Build.VERSION.SDK_INT;
|
||||||
if (sdk>18 && groups.length>0) {
|
if (sdk > 18 && groups.length > 0) {
|
||||||
CaptioningManager captioningManager = (CaptioningManager) themedReactContext.getSystemService(Context.CAPTIONING_SERVICE);
|
CaptioningManager captioningManager
|
||||||
if (captioningManager.isEnabled()) {
|
= (CaptioningManager)themedReactContext.getSystemService(Context.CAPTIONING_SERVICE);
|
||||||
// default is to take the first object
|
if (captioningManager != null && captioningManager.isEnabled()) {
|
||||||
trackIndex = 0;
|
trackIndex = getTrackIndexForDefaultLocale(groups);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
trackSelector.setSelectionOverride(rendererIndex, groups, null);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
} else if (rendererIndex == C.TRACK_TYPE_AUDIO) {
|
||||||
|
trackIndex = getTrackIndexForDefaultLocale(groups);
|
||||||
}
|
}
|
||||||
} else return;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trackIndex == C.INDEX_UNSET) {
|
if (trackIndex == C.INDEX_UNSET) {
|
||||||
@ -792,7 +816,34 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
MappingTrackSelector.SelectionOverride override
|
MappingTrackSelector.SelectionOverride override
|
||||||
= new MappingTrackSelector.SelectionOverride(
|
= new MappingTrackSelector.SelectionOverride(
|
||||||
new FixedTrackSelection.Factory(), trackIndex, 0);
|
new FixedTrackSelection.Factory(), trackIndex, 0);
|
||||||
trackSelector.setSelectionOverride(index, groups, override);
|
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) {
|
public void setPausedModifier(boolean paused) {
|
||||||
|
@ -28,6 +28,9 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
private static final String PROP_SRC_HEADERS = "requestHeaders";
|
private static final String PROP_SRC_HEADERS = "requestHeaders";
|
||||||
private static final String PROP_RESIZE_MODE = "resizeMode";
|
private static final String PROP_RESIZE_MODE = "resizeMode";
|
||||||
private static final String PROP_REPEAT = "repeat";
|
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 = "selectedTextTrack";
|
||||||
private static final String PROP_SELECTED_TEXT_TRACK_TYPE = "type";
|
private static final String PROP_SELECTED_TEXT_TRACK_TYPE = "type";
|
||||||
private static final String PROP_SELECTED_TEXT_TRACK_VALUE = "value";
|
private static final String PROP_SELECTED_TEXT_TRACK_VALUE = "value";
|
||||||
@ -127,6 +130,20 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
videoView.setRepeatModifier(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)
|
@ReactProp(name = PROP_SELECTED_TEXT_TRACK)
|
||||||
public void setSelectedTextTrack(final ReactExoplayerView videoView,
|
public void setSelectedTextTrack(final ReactExoplayerView videoView,
|
||||||
@Nullable ReadableMap selectedTextTrack) {
|
@Nullable ReadableMap selectedTextTrack) {
|
||||||
|
@ -109,6 +109,7 @@ class VideoEventEmitter {
|
|||||||
private static final String EVENT_PROP_WIDTH = "width";
|
private static final String EVENT_PROP_WIDTH = "width";
|
||||||
private static final String EVENT_PROP_HEIGHT = "height";
|
private static final String EVENT_PROP_HEIGHT = "height";
|
||||||
private static final String EVENT_PROP_ORIENTATION = "orientation";
|
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_TEXT_TRACKS = "textTracks";
|
||||||
private static final String EVENT_PROP_HAS_AUDIO_FOCUS = "hasAudioFocus";
|
private static final String EVENT_PROP_HAS_AUDIO_FOCUS = "hasAudioFocus";
|
||||||
private static final String EVENT_PROP_IS_BUFFERING = "isBuffering";
|
private static final String EVENT_PROP_IS_BUFFERING = "isBuffering";
|
||||||
@ -130,7 +131,7 @@ class VideoEventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void load(double duration, double currentPosition, int videoWidth, int videoHeight,
|
void load(double duration, double currentPosition, int videoWidth, int videoHeight,
|
||||||
WritableArray textTracks) {
|
WritableArray audioTracks, WritableArray textTracks) {
|
||||||
WritableMap event = Arguments.createMap();
|
WritableMap event = Arguments.createMap();
|
||||||
event.putDouble(EVENT_PROP_DURATION, duration / 1000D);
|
event.putDouble(EVENT_PROP_DURATION, duration / 1000D);
|
||||||
event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000D);
|
event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000D);
|
||||||
@ -145,6 +146,7 @@ class VideoEventEmitter {
|
|||||||
}
|
}
|
||||||
event.putMap(EVENT_PROP_NATURAL_SIZE, naturalSize);
|
event.putMap(EVENT_PROP_NATURAL_SIZE, naturalSize);
|
||||||
|
|
||||||
|
event.putArray(EVENT_PROP_AUDIO_TRACKS, audioTracks);
|
||||||
event.putArray(EVENT_PROP_TEXT_TRACKS, textTracks);
|
event.putArray(EVENT_PROP_TEXT_TRACKS, textTracks);
|
||||||
|
|
||||||
// TODO: Actually check if you can.
|
// TODO: Actually check if you can.
|
||||||
|
@ -47,6 +47,7 @@ static NSString *const timedMetadata = @"timedMetadata";
|
|||||||
BOOL _allowsExternalPlayback;
|
BOOL _allowsExternalPlayback;
|
||||||
NSArray * _textTracks;
|
NSArray * _textTracks;
|
||||||
NSDictionary * _selectedTextTrack;
|
NSDictionary * _selectedTextTrack;
|
||||||
|
NSDictionary * _selectedAudioTrack;
|
||||||
BOOL _playbackStalled;
|
BOOL _playbackStalled;
|
||||||
BOOL _playInBackground;
|
BOOL _playInBackground;
|
||||||
BOOL _playWhenInactive;
|
BOOL _playWhenInactive;
|
||||||
@ -498,6 +499,7 @@ static NSString *const timedMetadata = @"timedMetadata";
|
|||||||
@"height": height,
|
@"height": height,
|
||||||
@"orientation": orientation
|
@"orientation": orientation
|
||||||
},
|
},
|
||||||
|
@"audioTracks": [self getAudioTrackInfo],
|
||||||
@"textTracks": [self getTextTrackInfo],
|
@"textTracks": [self getTextTrackInfo],
|
||||||
@"target": self.reactTag});
|
@"target": self.reactTag});
|
||||||
}
|
}
|
||||||
@ -729,6 +731,7 @@ static NSString *const timedMetadata = @"timedMetadata";
|
|||||||
[_player setMuted:NO];
|
[_player setMuted:NO];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[self setSelectedAudioTrack:_selectedAudioTrack];
|
||||||
[self setSelectedTextTrack:_selectedTextTrack];
|
[self setSelectedTextTrack:_selectedTextTrack];
|
||||||
[self setResizeMode:_resizeMode];
|
[self setResizeMode:_resizeMode];
|
||||||
[self setRepeat:_repeat];
|
[self setRepeat:_repeat];
|
||||||
@ -741,12 +744,64 @@ static NSString *const timedMetadata = @"timedMetadata";
|
|||||||
_repeat = repeat;
|
_repeat = repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)setMediaSelectionTrackForCharacteristic:(AVMediaCharacteristic)characteristic
|
||||||
|
withCriteria:(NSDictionary *)criteria
|
||||||
|
{
|
||||||
|
NSString *type = criteria[@"type"];
|
||||||
|
AVMediaSelectionGroup *group = [_player.currentItem.asset
|
||||||
|
mediaSelectionGroupForMediaCharacteristic:characteristic];
|
||||||
|
AVMediaSelectionOption *mediaOption;
|
||||||
|
|
||||||
|
if ([type isEqualToString:@"disabled"]) {
|
||||||
|
// Do nothing. We want to ensure option is nil
|
||||||
|
} else if ([type isEqualToString:@"language"] || [type isEqualToString:@"title"]) {
|
||||||
|
NSString *value = criteria[@"value"];
|
||||||
|
for (int i = 0; i < group.options.count; ++i) {
|
||||||
|
AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i];
|
||||||
|
NSString *optionValue;
|
||||||
|
if ([type isEqualToString:@"language"]) {
|
||||||
|
optionValue = [currentOption extendedLanguageTag];
|
||||||
|
} else {
|
||||||
|
optionValue = [[[currentOption commonMetadata]
|
||||||
|
valueForKey:@"value"]
|
||||||
|
objectAtIndex:0];
|
||||||
|
}
|
||||||
|
if ([value isEqualToString:optionValue]) {
|
||||||
|
mediaOption = currentOption;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//} else if ([type isEqualToString:@"default"]) {
|
||||||
|
// option = group.defaultOption; */
|
||||||
|
} else if ([type isEqualToString:@"index"]) {
|
||||||
|
if ([criteria[@"value"] isKindOfClass:[NSNumber class]]) {
|
||||||
|
int index = [criteria[@"value"] intValue];
|
||||||
|
if (group.options.count > 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 {
|
- (void)setSelectedTextTrack:(NSDictionary *)selectedTextTrack {
|
||||||
_selectedTextTrack = selectedTextTrack;
|
_selectedTextTrack = selectedTextTrack;
|
||||||
if (_textTracks) {
|
if (_textTracks) { // sideloaded text tracks
|
||||||
[self setSideloadedText];
|
[self setSideloadedText];
|
||||||
} else {
|
} else { // text tracks included in the HLS playlist
|
||||||
[self setStreamingText];
|
[self setMediaSelectionTrackForCharacteristic:AVMediaCharacteristicLegible
|
||||||
|
withCriteria:_selectedTextTrack];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -869,9 +924,31 @@ static NSString *const timedMetadata = @"timedMetadata";
|
|||||||
if (_selectedTextTrack) [self setSelectedTextTrack:_selectedTextTrack];
|
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
|
- (NSArray *)getTextTrackInfo
|
||||||
{
|
{
|
||||||
|
|
||||||
// if sideloaded, textTracks will already be set
|
// if sideloaded, textTracks will already be set
|
||||||
if (_textTracks) return _textTracks;
|
if (_textTracks) return _textTracks;
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ RCT_EXPORT_VIEW_PROPERTY(repeat, BOOL);
|
|||||||
RCT_EXPORT_VIEW_PROPERTY(allowsExternalPlayback, BOOL);
|
RCT_EXPORT_VIEW_PROPERTY(allowsExternalPlayback, BOOL);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(textTracks, NSArray);
|
RCT_EXPORT_VIEW_PROPERTY(textTracks, NSArray);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(selectedTextTrack, NSDictionary);
|
RCT_EXPORT_VIEW_PROPERTY(selectedTextTrack, NSDictionary);
|
||||||
|
RCT_EXPORT_VIEW_PROPERTY(selectedAudioTrack, NSDictionary);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(paused, BOOL);
|
RCT_EXPORT_VIEW_PROPERTY(paused, BOOL);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(muted, BOOL);
|
RCT_EXPORT_VIEW_PROPERTY(muted, BOOL);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(controls, BOOL);
|
RCT_EXPORT_VIEW_PROPERTY(controls, BOOL);
|
||||||
|
Loading…
Reference in New Issue
Block a user