Merge remote-tracking branch 'upstream/master' into implement-ios-caching

This commit is contained in:
Laurin Quast 2018-07-30 21:20:34 +02:00
commit 2260ae5b8c
11 changed files with 252 additions and 65 deletions

View File

@ -2,6 +2,7 @@
### Next Version ### Next Version
* Basic fullscreen support for Android MediaPlayer [#1138](https://github.com/react-native-community/react-native-video/pull/1138) * 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)) * Support video cachging for iOS ([#955](https://github.com/react-native-community/react-native-video/pull/955))
### Version 3.1.0 ### Version 3.1.0

View File

@ -206,6 +206,10 @@ using System.Collections.Generic;
## Usage ## Usage
```javascript ```javascript
// Load the module
import Video from 'react-native-video';
// Within your render function, assuming you have a file called // Within your render function, assuming you have a file called
// "background.mp4" in your project. You can include multiple videos // "background.mp4" in your project. You can include multiple videos
// on a single screen if you like. // on a single screen if you like.
@ -245,6 +249,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)
@ -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 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.
@ -517,7 +552,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:
``` ```
@ -535,6 +571,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' },

View File

@ -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([

View File

@ -1,20 +1,16 @@
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
def _ext = rootProject.ext def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
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
android { android {
compileSdkVersion _compileSdkVersion compileSdkVersion safeExtGet('compileSdkVersion', 27)
buildToolsVersion _buildToolsVersion buildToolsVersion safeExtGet('buildToolsVersion', '27.0.3')
defaultConfig { defaultConfig {
minSdkVersion _minSdkVersion minSdkVersion safeExtGet('minSdkVersion', 16)
targetSdkVersion _targetSdkVersion targetSdkVersion safeExtGet('targetSdkVersion', 27)
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
} }
@ -22,7 +18,7 @@ android {
dependencies { dependencies {
//noinspection GradleDynamicVersion //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:exoplayer:2.7.3'
compile('com.google.android.exoplayer:extension-okhttp:2.7.3') { compile('com.google.android.exoplayer:extension-okhttp:2.7.3') {
exclude group: 'com.squareup.okhttp3', module: 'okhttp' exclude group: 'com.squareup.okhttp3', module: 'okhttp'

View File

@ -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) {

View File

@ -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) {

View File

@ -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.

View File

@ -1,20 +1,16 @@
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
def _ext = rootProject.ext def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
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
android { android {
compileSdkVersion _compileSdkVersion compileSdkVersion safeExtGet('compileSdkVersion', 27)
buildToolsVersion _buildToolsVersion buildToolsVersion safeExtGet('buildToolsVersion', '27.0.3')
defaultConfig { defaultConfig {
minSdkVersion _minSdkVersion minSdkVersion safeExtGet('minSdkVersion', 16)
targetSdkVersion _targetSdkVersion targetSdkVersion safeExtGet('targetSdkVersion', 27)
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
ndk { ndk {
@ -25,6 +21,6 @@ android {
dependencies { dependencies {
//noinspection GradleDynamicVersion //noinspection GradleDynamicVersion
provided "com.facebook.react:react-native:${_reactNativeVersion}" provided "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
compile 'com.yqritc:android-scalablevideoview:1.0.4' compile 'com.yqritc:android-scalablevideoview:1.0.4'
} }

View File

@ -16,7 +16,6 @@ export default class App extends Component<Props> {
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Video <Video
cache={true}
source={{ source={{
uri: uri:
"https://rawgit.com/mediaelement/mediaelement-files/master/big_buck_bunny.mp4" "https://rawgit.com/mediaelement/mediaelement-files/master/big_buck_bunny.mp4"

View File

@ -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;
@ -529,6 +530,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});
} }
@ -760,6 +762,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];
@ -772,12 +775,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];
} }
} }
@ -900,9 +955,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;

View File

@ -26,6 +26,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);