From 9764fe3be436b38ee369920934673a0512df3771 Mon Sep 17 00:00:00 2001 From: Hampton Maxwell Date: Sat, 2 Jun 2018 02:24:13 -0700 Subject: [PATCH 1/7] First pass at text track selection, mostly complete --- Video.js | 7 +++ .../exoplayer/ReactExoplayerView.java | 63 ++++++++++++++++++- .../exoplayer/ReactExoplayerViewManager.java | 14 +++++ ios/RCTVideo.m | 46 ++++++++++++++ ios/RCTVideoManager.m | 1 + 5 files changed, 130 insertions(+), 1 deletion(-) diff --git a/Video.js b/Video.js index aced6e55..849305ea 100644 --- a/Video.js +++ b/Video.js @@ -274,6 +274,13 @@ Video.propTypes = { poster: PropTypes.string, posterResizeMode: Image.propTypes.resizeMode, repeat: PropTypes.bool, + selectedTextTrack: PropTypes.shape({ + type: PropTypes.string.isRequired, + value: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number + ]) + }), paused: PropTypes.bool, muted: PropTypes.bool, volume: PropTypes.number, 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 aa60ac14..b16899c8 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -16,6 +16,7 @@ import android.widget.FrameLayout; import com.brentvatne.react.R; import com.brentvatne.receiver.AudioBecomingNoisyReceiver; import com.brentvatne.receiver.BecomingNoisyListener; +import com.facebook.react.bridge.Dynamic; import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.uimanager.ThemedReactContext; import com.google.android.exoplayer2.C; @@ -45,6 +46,7 @@ import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; +import com.google.android.exoplayer2.trackselection.FixedTrackSelection; import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; @@ -535,7 +537,8 @@ class ReactExoplayerView extends FrameLayout implements decoderInitializationException.decoderName); } } - } else if (e.type == ExoPlaybackException.TYPE_SOURCE) { + } + else if (e.type == ExoPlaybackException.TYPE_SOURCE) { ex = e.getSourceException(); errorString = getResources().getString(R.string.unrecognized_media_format); } @@ -565,6 +568,16 @@ class ReactExoplayerView extends FrameLayout implements return false; } + public int getTextTrackRendererIndex() { + int rendererCount = player.getRendererCount(); + for (int rendererIndex = 0; rendererIndex < rendererCount; rendererIndex++) { + if (player.getRendererType(rendererIndex) == C.TRACK_TYPE_TEXT) { + return rendererIndex; + } + } + return C.INDEX_UNSET; + } + @Override public void onMetadata(Metadata metadata) { eventEmitter.timedMetadata(metadata); @@ -626,6 +639,54 @@ class ReactExoplayerView extends FrameLayout implements this.repeat = repeat; } + public void setSelectedTextTrack(String type, Dynamic value) { + int index = getTextTrackRendererIndex(); + if (index == C.INDEX_UNSET) { + return; + } + MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo(); + if (info == null) { + return; + } + + TrackGroupArray groups = info.getTrackGroups(index); + int trackIndex = C.INDEX_UNSET; + if (TextUtils.isEmpty(type)) { + // Do nothing + } else if (type.equals("language")) { + for (int i = 0; i < groups.length; ++i) { + Format format = groups.get(i).getFormat(0); + if (format.language != null && format.language.equals(value.asString())) { + trackIndex = i; + break; + } + } + } else if (type.equals("title")) { + for (int i = 0; i < groups.length; ++i) { + Format format = groups.get(i).getFormat(0); + if (format.id != null && format.id.equals(value.asString())) { + trackIndex = i; + break; + } + } + } else if (type.equals("index")) { + trackIndex = value.asInt(); + } else { // default. invalid type or "system" + trackSelector.clearSelectionOverrides(index); + return; + } + + if (trackIndex == C.INDEX_UNSET) { + trackSelector.clearSelectionOverrides(trackIndex); + return; + } + + MappingTrackSelector.SelectionOverride override + = new MappingTrackSelector.SelectionOverride( + new FixedTrackSelection.Factory(), trackIndex, 0); + trackSelector.setSelectionOverride(index, groups, override); + } + public void setPausedModifier(boolean paused) { isPaused = paused; if (player != null) { 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 b500f400..1f42d5ca 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -4,6 +4,7 @@ import android.content.Context; import android.net.Uri; import android.text.TextUtils; +import com.facebook.react.bridge.Dynamic; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.common.MapBuilder; import com.facebook.react.uimanager.ThemedReactContext; @@ -24,6 +25,9 @@ public class ReactExoplayerViewManager extends ViewGroupManager index) { + option = [group.options objectAtIndex:index]; + } + } + } else { // default. invalid type or "system" + // _player.appliesMediaSelectionCriteriaAutomatically = YES; + return; + } + + // If a match isn't found, option will be nil and text tracks will be disabled + // _player.appliesMediaSelectionCriteriaAutomatically = NO; + [_player.currentItem selectMediaOption:option inMediaSelectionGroup:group]; +} + - (BOOL)getFullscreen { return _fullscreenPlayerPresented; diff --git a/ios/RCTVideoManager.m b/ios/RCTVideoManager.m index c1edb9b7..f187502f 100644 --- a/ios/RCTVideoManager.m +++ b/ios/RCTVideoManager.m @@ -22,6 +22,7 @@ RCT_EXPORT_MODULE(); RCT_EXPORT_VIEW_PROPERTY(src, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(resizeMode, NSString); RCT_EXPORT_VIEW_PROPERTY(repeat, BOOL); +RCT_EXPORT_VIEW_PROPERTY(selectedTextTrack, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(paused, BOOL); RCT_EXPORT_VIEW_PROPERTY(muted, BOOL); RCT_EXPORT_VIEW_PROPERTY(controls, BOOL); From b44ae2c0c299d90507ef684e6cf5123063515467 Mon Sep 17 00:00:00 2001 From: Hampton Maxwell Date: Sat, 2 Jun 2018 19:41:25 -0700 Subject: [PATCH 2/7] Get automatic system track selection working --- ios/RCTVideo.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ios/RCTVideo.m b/ios/RCTVideo.m index 15464162..94189e27 100644 --- a/ios/RCTVideo.m +++ b/ios/RCTVideo.m @@ -640,11 +640,10 @@ static NSString *const timedMetadata = @"timedMetadata"; - (void)setSelectedTextTrack:(NSDictionary *)selectedTextTrack { _selectedTextTrack = selectedTextTrack; NSString *type = selectedTextTrack[@"type"]; - AVMediaSelectionGroup *group = [_player.currentItem.asset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible]; - AVMediaSelectionOption *option; + if ([type isEqualToString:@"disabled"]) { // Do nothing. We want to ensure option is nil } else if ([type isEqualToString:@"language"] || [type isEqualToString:@"title"]) { @@ -664,6 +663,8 @@ static NSString *const timedMetadata = @"timedMetadata"; break; } } + //} else if ([type isEqualToString:@"default"]) { + // option = group.defaultOption; */ } else if ([type isEqualToString:@"index"]) { if ([selectedTextTrack[@"value"] isKindOfClass:[NSNumber class]]) { int index = [selectedTextTrack[@"value"] intValue]; @@ -672,12 +673,11 @@ static NSString *const timedMetadata = @"timedMetadata"; } } } else { // default. invalid type or "system" - // _player.appliesMediaSelectionCriteriaAutomatically = YES; + [_player.currentItem selectMediaOptionAutomaticallyInMediaSelectionGroup:group]; return; } // If a match isn't found, option will be nil and text tracks will be disabled - // _player.appliesMediaSelectionCriteriaAutomatically = NO; [_player.currentItem selectMediaOption:option inMediaSelectionGroup:group]; } From 4d008e56f2ec7feaa07e0ad41245545b53018ab7 Mon Sep 17 00:00:00 2001 From: Hampton Maxwell Date: Sat, 2 Jun 2018 19:41:50 -0700 Subject: [PATCH 3/7] Add disabled text track selection option --- .../java/com/brentvatne/exoplayer/ReactExoplayerView.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 b16899c8..5f1585ed 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -5,12 +5,14 @@ import android.app.Activity; import android.content.Context; import android.media.AudioManager; import android.net.Uri; +import android.os.Build; import android.os.Handler; import android.os.Message; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.view.Window; +import android.view.accessibility.CaptioningManager; import android.widget.FrameLayout; import com.brentvatne.react.R; @@ -59,6 +61,7 @@ import java.net.CookieManager; import java.net.CookiePolicy; import java.lang.Math; import java.lang.Object; +import java.util.Locale; @SuppressLint("ViewConstructor") class ReactExoplayerView extends FrameLayout implements @@ -653,6 +656,9 @@ class ReactExoplayerView extends FrameLayout implements int trackIndex = C.INDEX_UNSET; if (TextUtils.isEmpty(type)) { // Do nothing + } else if (type.equals("disabled")) { + trackSelector.setSelectionOverride(index, groups, null); + return; } else if (type.equals("language")) { for (int i = 0; i < groups.length; ++i) { Format format = groups.get(i).getFormat(0); From 11584f28d1374e49b44b1b1fcc2d5ce0e7b5c0eb Mon Sep 17 00:00:00 2001 From: Hampton Maxwell Date: Mon, 4 Jun 2018 11:48:59 -0700 Subject: [PATCH 4/7] Apply initial text track prop when the video is loaded --- .../java/com/brentvatne/exoplayer/ReactExoplayerView.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 5f1585ed..a6e88409 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -104,6 +104,8 @@ class ReactExoplayerView extends FrameLayout implements private Uri srcUri; private String extension; private boolean repeat; + private String textTrackType; + private Dynamic textTrackValue; private boolean disableFocus; private float mProgressUpdateInterval = 250.0f; private boolean playInBackground = false; @@ -449,6 +451,7 @@ class ReactExoplayerView extends FrameLayout implements private void videoLoaded() { if (loadVideoStarted) { loadVideoStarted = false; + setSelectedTextTrack(textTrackType, textTrackValue); Format videoFormat = player.getVideoFormat(); int width = videoFormat != null ? videoFormat.width : 0; int height = videoFormat != null ? videoFormat.height : 0; @@ -643,6 +646,9 @@ class ReactExoplayerView extends FrameLayout implements } public void setSelectedTextTrack(String type, Dynamic value) { + textTrackType = type; + textTrackValue = value; + int index = getTextTrackRendererIndex(); if (index == C.INDEX_UNSET) { return; From ed79b790b5cc269344a1160abd83c3775c3fad91 Mon Sep 17 00:00:00 2001 From: Hampton Maxwell Date: Tue, 5 Jun 2018 18:39:25 -0700 Subject: [PATCH 5/7] Add documenation for the selectedTextTrack prop --- README.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/README.md b/README.md index 9e09e01f..ec74d738 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,7 @@ var styles = StyleSheet.create({ * [rate](#rate) * [repeat](#repeat) * [resizeMode](#resize-mode) +* [selectedTextTrack](#selectedtexttrack) * [volume](#volume) #### ignoreSilentSwitch @@ -304,6 +305,38 @@ Determines how to resize the video when the frame doesn't match the raw video di Platforms: Android ExoPlayer, Android MediaPlayer, iOS, Windows UWP +#### selectedTextTrack +Configure which text track (caption or subtitle), if any, is shown. + +``` +selectedTextTrack={{ + type: Type, + value: Value +}} +``` + +Example: +``` +selectedTextTrack={{ + type: "title", + value: "English Subtitles" +}} +``` + +Type | Description +--- | --- +"system" | (default) Display captions only if the system preference for captions is enabled +"disabled" | Don't display a text track +"title" | Display the text track with the title specified as the Value, e.g. "French 1" +"language" | Display the text track with the language specified as the Value, e.g. "fr" +"index" | Display the text track with the index specified as the value, e.g. 0 + +Both iOS & Android offer Settings to enable Captions for hearing impaired people. If "system" is selected and the Captions Setting is enabled, iOS/Android will look for a caption that matches that customer's language and display it. + +If a track matching the specified Type (and Value if appropriate) is unavailable, no text track will be displayed. If multiple tracks match the criteria, the first match will be used. + +Platforms: Android ExoPlayer, iOS + #### volume Adjust the volume. * **1.0 (default)** - Play at full volume From 86b200396f4772a57b7ea7f7b1a6cee96e1d4ec3 Mon Sep 17 00:00:00 2001 From: Hampton Maxwell Date: Tue, 5 Jun 2018 18:41:51 -0700 Subject: [PATCH 6/7] Fix link for resizeMode --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9e09e01f..586e11e0 100644 --- a/README.md +++ b/README.md @@ -229,7 +229,7 @@ var styles = StyleSheet.create({ * [progressUpdateInterval](#progressupdateinterval) * [rate](#rate) * [repeat](#repeat) -* [resizeMode](#resize-mode) +* [resizeMode](#resizemode) * [volume](#volume) #### ignoreSilentSwitch From b00df58755c90a3c6d3d2740779954d99a9f0d9b Mon Sep 17 00:00:00 2001 From: Hampton Maxwell Date: Tue, 5 Jun 2018 18:45:05 -0700 Subject: [PATCH 7/7] Add Value to the usage chart --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ec74d738..154c0943 100644 --- a/README.md +++ b/README.md @@ -323,13 +323,13 @@ selectedTextTrack={{ }} ``` -Type | Description ---- | --- -"system" | (default) Display captions only if the system preference for captions is enabled -"disabled" | Don't display a text track -"title" | Display the text track with the title specified as the Value, e.g. "French 1" -"language" | Display the text track with the language specified as the Value, e.g. "fr" -"index" | Display the text track with the index specified as the value, e.g. 0 +Type | Value | Description +--- | --- | --- +"system" (default) | N/A | Display captions only if the system preference for captions is enabled +"disabled" | N/A | Don't display a text track +"title" | string | Display the text track with the title specified as the Value, e.g. "French 1" +"language" | string | Display the text track with the language specified as the Value, e.g. "fr" +"index" | number | Display the text track with the index specified as the value, e.g. 0 Both iOS & Android offer Settings to enable Captions for hearing impaired people. If "system" is selected and the Captions Setting is enabled, iOS/Android will look for a caption that matches that customer's language and display it.