Support selecting audio tracks
Implements audio track selection on iOS & Android ExoPlayer. The prop mirrors the API for selectedTextTrack.
This commit is contained in:
		
							
								
								
									
										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")) { | ||||||
|             trackIndex = value.asInt(); |             if (value.asInt() < groups.length) { | ||||||
|         } else { // default. Use system settings if possible |                 trackIndex = value.asInt(); | ||||||
|             int sdk = android.os.Build.VERSION.SDK_INT; |             } | ||||||
|             if (sdk>18 && groups.length>0) { |         } else { // default | ||||||
|                 CaptioningManager captioningManager = (CaptioningManager) themedReactContext.getSystemService(Context.CAPTIONING_SERVICE); |             if (rendererIndex == C.TRACK_TYPE_TEXT) { // Use system settings if possible | ||||||
|                 if (captioningManager.isEnabled()) { |                 int sdk = android.os.Build.VERSION.SDK_INT; | ||||||
|                     // default is to take the first object |                 if (sdk > 18 && groups.length > 0) { | ||||||
|                     trackIndex = 0; |                     CaptioningManager captioningManager | ||||||
|  |                             = (CaptioningManager)themedReactContext.getSystemService(Context.CAPTIONING_SERVICE); | ||||||
|                     String locale = Locale.getDefault().getDisplayLanguage(); |                     if (captioningManager != null && captioningManager.isEnabled()) { | ||||||
|                     for (int i = 0; i < groups.length; ++i) { |                         trackIndex = getTrackIndexForDefaultLocale(groups); | ||||||
|                         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 return; |             } else if (rendererIndex == C.TRACK_TYPE_AUDIO) { | ||||||
|  |                 trackIndex = getTrackIndexForDefaultLocale(groups); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (trackIndex == C.INDEX_UNSET) { |         if (trackIndex == C.INDEX_UNSET) { | ||||||
| @@ -791,8 +815,35 @@ 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); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user