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) | ||||
| * [repeat](#repeat) | ||||
| * [resizeMode](#resizemode) | ||||
| * [selectedAudioTrack](#selectedaudiotrack) | ||||
| * [selectedTextTrack](#selectedtexttrack) | ||||
| * [stereoPan](#stereopan) | ||||
| * [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 | ||||
|  | ||||
| #### 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 | ||||
| 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 | ||||
| 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" | ||||
| 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: | ||||
| ``` | ||||
| @@ -488,6 +520,10 @@ Example: | ||||
|      orientation: 'landscape' | ||||
|      width: '1920' | ||||
|   }, | ||||
|   audioTracks: [ | ||||
|     { language: 'es', title: 'Spanish', type: 'audio/mpeg', index: 0 }, | ||||
|     { language: 'en', title: 'English', type: 'audio/mpeg', index: 1 } ], | ||||
|   ], | ||||
|   textTracks: [ | ||||
|     { title: '#1 French', language: 'fr', index: 0, 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, | ||||
|   repeat: PropTypes.bool, | ||||
|   allowsExternalPlayback: PropTypes.bool, | ||||
|   selectedAudioTrack: PropTypes.shape({ | ||||
|     type: PropTypes.string.isRequired, | ||||
|     value: PropTypes.oneOfType([ | ||||
|       PropTypes.string, | ||||
|       PropTypes.number | ||||
|     ]) | ||||
|   }), | ||||
|   selectedTextTrack: PropTypes.shape({ | ||||
|     type: PropTypes.string.isRequired, | ||||
|     value: PropTypes.oneOfType([ | ||||
|   | ||||
| @@ -113,6 +113,9 @@ class ReactExoplayerView extends FrameLayout implements | ||||
|     private Uri srcUri; | ||||
|     private String extension; | ||||
|     private boolean repeat; | ||||
|     private String audioTrackType; | ||||
|     private Dynamic audioTrackValue; | ||||
|     private ReadableArray audioTracks; | ||||
|     private String textTrackType; | ||||
|     private Dynamic textTrackValue; | ||||
|     private ReadableArray textTracks; | ||||
| @@ -501,20 +504,43 @@ class ReactExoplayerView extends FrameLayout implements | ||||
|     private void videoLoaded() { | ||||
|         if (loadVideoStarted) { | ||||
|             loadVideoStarted = false; | ||||
|             setSelectedAudioTrack(audioTrackType, audioTrackValue); | ||||
|             setSelectedTextTrack(textTrackType, textTrackValue); | ||||
|             Format videoFormat = player.getVideoFormat(); | ||||
|             int width = videoFormat != null ? videoFormat.width : 0; | ||||
|             int height = videoFormat != null ? videoFormat.height : 0; | ||||
|             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() { | ||||
|         WritableArray textTracks = Arguments.createArray(); | ||||
|  | ||||
|         MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo(); | ||||
|         int index = getTextTrackRendererIndex(); | ||||
|         int index = getTrackRendererIndex(C.TRACK_TYPE_TEXT); | ||||
|         if (info == null || index == C.INDEX_UNSET) { | ||||
|             return textTracks; | ||||
|         } | ||||
| @@ -647,10 +673,10 @@ class ReactExoplayerView extends FrameLayout implements | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     public int getTextTrackRendererIndex() { | ||||
|     public int getTrackRendererIndex(int trackType) { | ||||
|         int rendererCount = player.getRendererCount(); | ||||
|         for (int rendererIndex = 0; rendererIndex < rendererCount; rendererIndex++) { | ||||
|             if (player.getRendererType(rendererIndex) == C.TRACK_TYPE_TEXT) { | ||||
|             if (player.getRendererType(rendererIndex) == trackType) { | ||||
|                 return rendererIndex; | ||||
|             } | ||||
|         } | ||||
| @@ -724,12 +750,9 @@ 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) { | ||||
|     public void setSelectedTrack(int trackType, String type, Dynamic value) { | ||||
|         int rendererIndex = getTrackRendererIndex(trackType); | ||||
|         if (rendererIndex == C.INDEX_UNSET) { | ||||
|             return; | ||||
|         } | ||||
|         MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo(); | ||||
| @@ -737,13 +760,15 @@ class ReactExoplayerView extends FrameLayout implements | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         TrackGroupArray groups = info.getTrackGroups(index); | ||||
|         TrackGroupArray groups = info.getTrackGroups(rendererIndex); | ||||
|         int trackIndex = C.INDEX_UNSET; | ||||
|         trackSelector.setSelectionOverride(index, groups, null); | ||||
|  | ||||
|         if (TextUtils.isEmpty(type)) { | ||||
|             // Do nothing | ||||
|         } else if (type.equals("disabled")) { | ||||
|             type = "default"; | ||||
|         } | ||||
|  | ||||
|         if (type.equals("disabled")) { | ||||
|             trackSelector.setSelectionOverride(rendererIndex, groups, null); | ||||
|             return; | ||||
|         } else if (type.equals("language")) { | ||||
|             for (int i = 0; i < groups.length; ++i) { | ||||
| @@ -762,26 +787,25 @@ class ReactExoplayerView extends FrameLayout implements | ||||
|                 } | ||||
|             } | ||||
|         } else if (type.equals("index")) { | ||||
|             trackIndex = value.asInt(); | ||||
|         } else { // default. Use system settings if possible | ||||
|             int sdk = android.os.Build.VERSION.SDK_INT; | ||||
|             if (sdk>18 && groups.length>0) { | ||||
|                 CaptioningManager captioningManager = (CaptioningManager) themedReactContext.getSystemService(Context.CAPTIONING_SERVICE); | ||||
|                 if (captioningManager.isEnabled()) { | ||||
|                     // default is to take the first object | ||||
|                     trackIndex = 0; | ||||
|  | ||||
|                     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; | ||||
|                         } | ||||
|             if (value.asInt() < groups.length) { | ||||
|                 trackIndex = value.asInt(); | ||||
|             } | ||||
|         } else { // default | ||||
|             if (rendererIndex == C.TRACK_TYPE_TEXT) { // Use system settings if possible | ||||
|                 int sdk = android.os.Build.VERSION.SDK_INT; | ||||
|                 if (sdk > 18 && groups.length > 0) { | ||||
|                     CaptioningManager captioningManager | ||||
|                             = (CaptioningManager)themedReactContext.getSystemService(Context.CAPTIONING_SERVICE); | ||||
|                     if (captioningManager != null && captioningManager.isEnabled()) { | ||||
|                         trackIndex = getTrackIndexForDefaultLocale(groups); | ||||
|                     } | ||||
|                 } else { | ||||
|                     trackSelector.setSelectionOverride(rendererIndex, groups, null); | ||||
|                     return; | ||||
|                 } | ||||
|             } else return; | ||||
|  | ||||
|             } else if (rendererIndex == C.TRACK_TYPE_AUDIO) { | ||||
|                 trackIndex = getTrackIndexForDefaultLocale(groups); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (trackIndex == C.INDEX_UNSET) { | ||||
| @@ -791,8 +815,35 @@ class ReactExoplayerView extends FrameLayout implements | ||||
|  | ||||
|         MappingTrackSelector.SelectionOverride override | ||||
|                 = new MappingTrackSelector.SelectionOverride( | ||||
|                         new FixedTrackSelection.Factory(), trackIndex, 0); | ||||
|         trackSelector.setSelectionOverride(index, groups, override); | ||||
|                 new FixedTrackSelection.Factory(), trackIndex, 0); | ||||
|         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) { | ||||
|   | ||||
| @@ -28,6 +28,9 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi | ||||
|     private static final String PROP_SRC_HEADERS = "requestHeaders"; | ||||
|     private static final String PROP_RESIZE_MODE = "resizeMode"; | ||||
|     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_TYPE = "type"; | ||||
|     private static final String PROP_SELECTED_TEXT_TRACK_VALUE = "value"; | ||||
| @@ -127,6 +130,20 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi | ||||
|         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) | ||||
|     public void setSelectedTextTrack(final ReactExoplayerView videoView, | ||||
|                                      @Nullable ReadableMap selectedTextTrack) { | ||||
|   | ||||
| @@ -109,6 +109,7 @@ class VideoEventEmitter { | ||||
|     private static final String EVENT_PROP_WIDTH = "width"; | ||||
|     private static final String EVENT_PROP_HEIGHT = "height"; | ||||
|     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_HAS_AUDIO_FOCUS = "hasAudioFocus"; | ||||
|     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, | ||||
|               WritableArray textTracks) { | ||||
|               WritableArray audioTracks, WritableArray textTracks) { | ||||
|         WritableMap event = Arguments.createMap(); | ||||
|         event.putDouble(EVENT_PROP_DURATION, duration / 1000D); | ||||
|         event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000D); | ||||
| @@ -145,6 +146,7 @@ class VideoEventEmitter { | ||||
|         } | ||||
|         event.putMap(EVENT_PROP_NATURAL_SIZE, naturalSize); | ||||
|  | ||||
|         event.putArray(EVENT_PROP_AUDIO_TRACKS, audioTracks); | ||||
|         event.putArray(EVENT_PROP_TEXT_TRACKS, textTracks); | ||||
|  | ||||
|         // TODO: Actually check if you can. | ||||
|   | ||||
| @@ -47,6 +47,7 @@ static NSString *const timedMetadata = @"timedMetadata"; | ||||
|   BOOL _allowsExternalPlayback; | ||||
|   NSArray * _textTracks; | ||||
|   NSDictionary * _selectedTextTrack; | ||||
|   NSDictionary * _selectedAudioTrack; | ||||
|   BOOL _playbackStalled; | ||||
|   BOOL _playInBackground; | ||||
|   BOOL _playWhenInactive; | ||||
| @@ -498,6 +499,7 @@ static NSString *const timedMetadata = @"timedMetadata"; | ||||
|                                  @"height": height, | ||||
|                                  @"orientation": orientation | ||||
|                                  }, | ||||
|                              @"audioTracks": [self getAudioTrackInfo], | ||||
|                              @"textTracks": [self getTextTrackInfo], | ||||
|                              @"target": self.reactTag}); | ||||
|         } | ||||
| @@ -729,6 +731,7 @@ static NSString *const timedMetadata = @"timedMetadata"; | ||||
|     [_player setMuted:NO]; | ||||
|   } | ||||
|  | ||||
|   [self setSelectedAudioTrack:_selectedAudioTrack]; | ||||
|   [self setSelectedTextTrack:_selectedTextTrack]; | ||||
|   [self setResizeMode:_resizeMode]; | ||||
|   [self setRepeat:_repeat]; | ||||
| @@ -741,12 +744,64 @@ static NSString *const timedMetadata = @"timedMetadata"; | ||||
|   _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 { | ||||
|   _selectedTextTrack = selectedTextTrack; | ||||
|   if (_textTracks) { | ||||
|   if (_textTracks) { // sideloaded text tracks | ||||
|     [self setSideloadedText]; | ||||
|   } else { | ||||
|     [self setStreamingText]; | ||||
|   } else { // text tracks included in the HLS playlist | ||||
|     [self setMediaSelectionTrackForCharacteristic:AVMediaCharacteristicLegible | ||||
|                                      withCriteria:_selectedTextTrack]; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -869,9 +924,31 @@ static NSString *const timedMetadata = @"timedMetadata"; | ||||
|   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 | ||||
| { | ||||
|    | ||||
|   // if sideloaded, textTracks will already be set | ||||
|   if (_textTracks) return _textTracks; | ||||
|    | ||||
|   | ||||
| @@ -25,6 +25,7 @@ RCT_EXPORT_VIEW_PROPERTY(repeat, BOOL); | ||||
| RCT_EXPORT_VIEW_PROPERTY(allowsExternalPlayback, BOOL); | ||||
| RCT_EXPORT_VIEW_PROPERTY(textTracks, NSArray); | ||||
| RCT_EXPORT_VIEW_PROPERTY(selectedTextTrack, NSDictionary); | ||||
| RCT_EXPORT_VIEW_PROPERTY(selectedAudioTrack, 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