Merge branch 'master' into master
This commit is contained in:
		
							
								
								
									
										35
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								README.md
									
									
									
									
									
								
							| @@ -229,7 +229,8 @@ var styles = StyleSheet.create({ | ||||
| * [progressUpdateInterval](#progressupdateinterval) | ||||
| * [rate](#rate) | ||||
| * [repeat](#repeat) | ||||
| * [resizeMode](#resize-mode) | ||||
| * [resizeMode](#resizemode) | ||||
| * [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 | 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. | ||||
|  | ||||
| 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 | ||||
|   | ||||
							
								
								
									
										7
									
								
								Video.js
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								Video.js
									
									
									
									
									
								
							| @@ -275,6 +275,13 @@ Video.propTypes = { | ||||
|   posterResizeMode: Image.propTypes.resizeMode, | ||||
|   repeat: PropTypes.bool, | ||||
|   allowsExternalPlayback: 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, | ||||
|   | ||||
| @@ -5,17 +5,20 @@ 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; | ||||
| 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 +48,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; | ||||
| @@ -57,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 | ||||
| @@ -99,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; | ||||
| @@ -444,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; | ||||
| @@ -535,7 +543,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 +574,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 +645,60 @@ 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) { | ||||
|             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("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); | ||||
|                 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) { | ||||
|   | ||||
| @@ -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<ReactExoplayerVi | ||||
|     private static final String PROP_SRC_TYPE = "type"; | ||||
|     private static final String PROP_RESIZE_MODE = "resizeMode"; | ||||
|     private static final String PROP_REPEAT = "repeat"; | ||||
|     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"; | ||||
|     private static final String PROP_PAUSED = "paused"; | ||||
|     private static final String PROP_MUTED = "muted"; | ||||
|     private static final String PROP_VOLUME = "volume"; | ||||
| @@ -116,6 +120,16 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi | ||||
|         videoView.setRepeatModifier(repeat); | ||||
|     } | ||||
|  | ||||
|     @ReactProp(name = PROP_SELECTED_TEXT_TRACK) | ||||
|     public void setSelectedTextTrack(final ReactExoplayerView videoView, | ||||
|                                      @Nullable ReadableMap selectedTextTrack) { | ||||
|         String typeString = selectedTextTrack.hasKey(PROP_SELECTED_TEXT_TRACK_TYPE) | ||||
|                 ? selectedTextTrack.getString(PROP_SELECTED_TEXT_TRACK_TYPE) : null; | ||||
|         Dynamic value = selectedTextTrack.hasKey(PROP_SELECTED_TEXT_TRACK_VALUE) | ||||
|                 ? selectedTextTrack.getDynamic(PROP_SELECTED_TEXT_TRACK_VALUE) : null; | ||||
|         videoView.setSelectedTextTrack(typeString, value); | ||||
|     } | ||||
|  | ||||
|     @ReactProp(name = PROP_PAUSED, defaultBoolean = false) | ||||
|     public void setPaused(final ReactExoplayerView videoView, final boolean paused) { | ||||
|         videoView.setPausedModifier(paused); | ||||
|   | ||||
| @@ -41,6 +41,7 @@ static NSString *const timedMetadata = @"timedMetadata"; | ||||
|   BOOL _paused; | ||||
|   BOOL _repeat; | ||||
|   BOOL _allowsExternalPlayback; | ||||
|   NSDictionary * _selectedTextTrack; | ||||
|   BOOL _playbackStalled; | ||||
|   BOOL _playInBackground; | ||||
|   BOOL _playWhenInactive; | ||||
| @@ -637,6 +638,7 @@ static NSString *const timedMetadata = @"timedMetadata"; | ||||
|     [_player setMuted:NO]; | ||||
|   } | ||||
|  | ||||
|   [self setSelectedTextTrack:_selectedTextTrack]; | ||||
|   [self setResizeMode:_resizeMode]; | ||||
|   [self setRepeat:_repeat]; | ||||
|   [self setPaused:_paused]; | ||||
| @@ -648,6 +650,50 @@ static NSString *const timedMetadata = @"timedMetadata"; | ||||
|   _repeat = repeat; | ||||
| } | ||||
|  | ||||
| - (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"]) { | ||||
|     NSString *value = selectedTextTrack[@"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]) { | ||||
|         option = currentOption; | ||||
|         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]; | ||||
|       if (group.options.count > index) { | ||||
|         option = [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:option inMediaSelectionGroup:group]; | ||||
| } | ||||
|  | ||||
| - (BOOL)getFullscreen | ||||
| { | ||||
|     return _fullscreenPlayerPresented; | ||||
|   | ||||
| @@ -23,6 +23,7 @@ RCT_EXPORT_VIEW_PROPERTY(src, NSDictionary); | ||||
| RCT_EXPORT_VIEW_PROPERTY(resizeMode, NSString); | ||||
| RCT_EXPORT_VIEW_PROPERTY(repeat, BOOL); | ||||
| RCT_EXPORT_VIEW_PROPERTY(allowsExternalPlayback, 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); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user