From 8aac3dd9057125340b598ecbc2e694d29e2d6302 Mon Sep 17 00:00:00 2001 From: Hampton Maxwell Date: Mon, 11 Jun 2018 15:23:43 -0700 Subject: [PATCH 1/9] Testing sideloaded text tracks --- .../exoplayer/ReactExoplayerView.java | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) 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 22f147594a..c80cf8da25 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -37,6 +37,8 @@ import com.google.android.exoplayer2.source.BehindLiveWindowException; import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.MergingMediaSource; +import com.google.android.exoplayer2.source.SingleSampleMediaSource; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.dash.DashMediaSource; import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource; @@ -51,6 +53,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; +import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import java.net.CookieHandler; @@ -58,6 +61,8 @@ import java.net.CookiePolicy; import java.lang.Math; import java.lang.Object; +import java.util.ArrayList; +import java.util.Arrays; @SuppressLint("ViewConstructor") class ReactExoplayerView extends FrameLayout implements @@ -229,7 +234,27 @@ private void initializePlayer() { player.setPlaybackParameters(params); } if (playerNeedsSource && srcUri != null) { - MediaSource mediaSource = buildMediaSource(srcUri, extension); + ArrayList mediaSources = new ArrayList<>(); + mediaSources.add(buildMediaSource(srcUri, extension)); // video source + MediaSource text0 = buildTextSource( + "ES VTT", + Uri.parse("https://bitdash-a.akamaihd.net/content/sintel/subtitles/subtitles_es.vtt"), + "vtt", + "es" + ); + if (text0 != null) { + mediaSources.add(text0); + } + MediaSource mediaSource; + if (mediaSources.size() > 1) { + MediaSource[] textSourceArray = mediaSources.toArray( + new MediaSource[mediaSources.size()] + ); + mediaSource = new MergingMediaSource(textSourceArray); + } else { + mediaSource = mediaSources.get(0); + } + boolean haveResumePosition = resumeWindow != C.INDEX_UNSET; if (haveResumePosition) { player.seekTo(resumeWindow, resumePosition); @@ -263,6 +288,22 @@ private MediaSource buildMediaSource(Uri uri, String overrideExtension) { } } + private MediaSource buildTextSource(String title, Uri uri, String mimeType, String language) { + String sampleType; + switch (mimeType) { + case "srt": + sampleType = MimeTypes.APPLICATION_SUBRIP; + break; + case "vtt": + sampleType = MimeTypes.TEXT_VTT; + break; + default: + return null; + } + Format textFormat = Format.createTextSampleFormat(title, sampleType, Format.NO_VALUE, language); + return new SingleSampleMediaSource(uri, mediaDataSourceFactory, textFormat, C.TIME_UNSET); + } + private void releasePlayer() { if (player != null) { isPaused = player.getPlayWhenReady(); From a1a4e0c44bb2b3d7a71d1f602dfc7d64ccef5c1a Mon Sep 17 00:00:00 2001 From: Hampton Maxwell Date: Mon, 11 Jun 2018 20:55:23 -0700 Subject: [PATCH 2/9] Report textTracks in onLoad --- ios/RCTVideo.m | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/ios/RCTVideo.m b/ios/RCTVideo.m index 253f48daeb..d2cc31147f 100644 --- a/ios/RCTVideo.m +++ b/ios/RCTVideo.m @@ -415,6 +415,7 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N @"height": height, @"orientation": orientation }, + @"textTracks": [self getTextTrackInfo], @"target": self.reactTag}); } @@ -694,6 +695,26 @@ - (void)setSelectedTextTrack:(NSDictionary *)selectedTextTrack { [_player.currentItem selectMediaOption:option inMediaSelectionGroup:group]; } +- (NSArray *)getTextTrackInfo +{ + NSMutableArray *textTracks = [[NSMutableArray alloc] init]; + AVMediaSelectionGroup *group = [_player.currentItem.asset + mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible]; + for (int i = 0; i < group.options.count; ++i) { + AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i]; + NSString *title = [[[currentOption commonMetadata] + valueForKey:@"value"] + objectAtIndex:0]; + NSDictionary *textTrack = @{ + @"index": [NSNumber numberWithInt:i], + @"title": title, + @"language": [currentOption extendedLanguageTag] + }; + [textTracks addObject:textTrack]; + } + return textTracks; +} + - (BOOL)getFullscreen { return _fullscreenPlayerPresented; From e2ea849d9e6dcca6d4215ed6013398f0b51bf7f3 Mon Sep 17 00:00:00 2001 From: Hampton Maxwell Date: Mon, 11 Jun 2018 21:25:58 -0700 Subject: [PATCH 3/9] Add ability to sideload text tracks and report them in onLoad --- .../exoplayer/ReactExoplayerView.java | 82 +++++++++++++++---- .../exoplayer/ReactExoplayerViewManager.java | 8 ++ .../exoplayer/VideoEventEmitter.java | 6 +- 3 files changed, 77 insertions(+), 19 deletions(-) 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 c80cf8da25..d659025ed7 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -16,8 +16,13 @@ import com.brentvatne.react.R; import com.brentvatne.receiver.AudioBecomingNoisyReceiver; import com.brentvatne.receiver.BecomingNoisyListener; +import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Dynamic; import com.facebook.react.bridge.LifecycleEventListener; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableArray; +import com.facebook.react.bridge.WritableMap; import com.facebook.react.uimanager.ThemedReactContext; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.DefaultLoadControl; @@ -62,7 +67,6 @@ import java.lang.Math; import java.lang.Object; import java.util.ArrayList; -import java.util.Arrays; @SuppressLint("ViewConstructor") class ReactExoplayerView extends FrameLayout implements @@ -108,6 +112,7 @@ class ReactExoplayerView extends FrameLayout implements private boolean repeat; private String textTrackType; private Dynamic textTrackValue; + private ReadableArray textTracks; private boolean disableFocus; private float mProgressUpdateInterval = 250.0f; private boolean playInBackground = false; @@ -234,25 +239,17 @@ private void initializePlayer() { player.setPlaybackParameters(params); } if (playerNeedsSource && srcUri != null) { - ArrayList mediaSources = new ArrayList<>(); - mediaSources.add(buildMediaSource(srcUri, extension)); // video source - MediaSource text0 = buildTextSource( - "ES VTT", - Uri.parse("https://bitdash-a.akamaihd.net/content/sintel/subtitles/subtitles_es.vtt"), - "vtt", - "es" - ); - if (text0 != null) { - mediaSources.add(text0); - } + ArrayList mediaSourceList = buildTextSources(); + MediaSource videoSource = buildMediaSource(srcUri, extension); MediaSource mediaSource; - if (mediaSources.size() > 1) { - MediaSource[] textSourceArray = mediaSources.toArray( - new MediaSource[mediaSources.size()] + if (mediaSourceList.size() == 0) { + mediaSource = videoSource; + } else { + mediaSourceList.add(0, videoSource); + MediaSource[] textSourceArray = mediaSourceList.toArray( + new MediaSource[mediaSourceList.size()] ); mediaSource = new MergingMediaSource(textSourceArray); - } else { - mediaSource = mediaSources.get(0); } boolean haveResumePosition = resumeWindow != C.INDEX_UNSET; @@ -288,6 +285,27 @@ private MediaSource buildMediaSource(Uri uri, String overrideExtension) { } } + private ArrayList buildTextSources() { + ArrayList textSources = new ArrayList<>(); + if (textTracks == null) { + return textSources; + } + + for (int i = 0; i < textTracks.size(); ++i) { + ReadableMap textTrack = textTracks.getMap(i); + String language = textTrack.getString("language"); + String title = textTrack.hasKey("title") + ? textTrack.getString("title") : language + " " + i; + Uri uri = Uri.parse(textTrack.getString("uri")); + MediaSource textSource = buildTextSource(title, uri, textTrack.getString("type"), + language); + if (textSource != null) { + textSources.add(textSource); + } + } + return textSources; + } + private MediaSource buildTextSource(String title, Uri uri, String mimeType, String language) { String sampleType; switch (mimeType) { @@ -494,8 +512,31 @@ private void videoLoaded() { 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); + eventEmitter.load(player.getDuration(), player.getCurrentPosition(), width, height, + getTextTrackInfo()); + } + } + + private WritableArray getTextTrackInfo() { + WritableArray textTracks = Arguments.createArray(); + + MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo(); + int index = getTextTrackRendererIndex(); + if (info == null || index == C.INDEX_UNSET) { + return textTracks; } + + 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); + textTrack.putString("type", format.sampleMimeType); + textTrack.putString("language", format.language); + textTracks.pushMap(textTrack); + } + return textTracks; } private void onBuffering(boolean buffering) { @@ -664,6 +705,11 @@ public void setRawSrc(final Uri uri, final String extension) { } } + public void setTextTracks(ReadableArray textTracks) { + this.textTracks = textTracks; + reloadSource(); + } + private void reloadSource() { playerNeedsSource = true; initializePlayer(); 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 4611c078c0..e204a4e762 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -5,6 +5,7 @@ import android.text.TextUtils; import com.facebook.react.bridge.Dynamic; +import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.common.MapBuilder; import com.facebook.react.uimanager.ThemedReactContext; @@ -28,6 +29,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager Date: Tue, 12 Jun 2018 20:57:30 -0700 Subject: [PATCH 4/9] Specify the text track type without any conversion --- .../brentvatne/exoplayer/ReactExoplayerView.java | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) 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 d659025ed7..04c4bb2c19 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -307,18 +307,7 @@ private ArrayList buildTextSources() { } private MediaSource buildTextSource(String title, Uri uri, String mimeType, String language) { - String sampleType; - switch (mimeType) { - case "srt": - sampleType = MimeTypes.APPLICATION_SUBRIP; - break; - case "vtt": - sampleType = MimeTypes.TEXT_VTT; - break; - default: - return null; - } - Format textFormat = Format.createTextSampleFormat(title, sampleType, Format.NO_VALUE, language); + Format textFormat = Format.createTextSampleFormat(title, mimeType, Format.NO_VALUE, language); return new SingleSampleMediaSource(uri, mediaDataSourceFactory, textFormat, C.TIME_UNSET); } From 659ca8fad9f61e508b4b9c81044b74e0c47e01d7 Mon Sep 17 00:00:00 2001 From: Hampton Maxwell Date: Tue, 12 Jun 2018 21:03:55 -0700 Subject: [PATCH 5/9] Document onLoad, onLoadStart & textTracks --- README.md | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 102 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9aac649552..caf69cf3af 100644 --- a/README.md +++ b/README.md @@ -191,8 +191,6 @@ using System.Collections.Generic; onFullscreenPlayerDidPresent={this.fullScreenPlayerDidPresent} // Callback after fullscreen started onFullscreenPlayerWillDismiss={this.fullScreenPlayerWillDismiss} // Callback before fullscreen stops onFullscreenPlayerDidDismiss={this.fullScreenPlayerDidDismiss} // Callback after fullscreen stopped - onLoadStart={this.loadStart} // Callback when video starts to load - onLoad={this.setDuration} // Callback when video loads onProgress={this.setTime} // Callback every ~250ms with currentTime onTimedMetadata={this.onTimedMetadata} // Callback when the stream receive some metadata style={styles.backgroundVideo} /> @@ -233,9 +231,14 @@ var styles = StyleSheet.create({ * [resizeMode](#resizemode) * [selectedTextTrack](#selectedtexttrack) * [stereoPan](#stereopan) +* [textTracks](#texttracks) * [useTextureView](#usetextureview) * [volume](#volume) +### Event props +* [onLoad](#onload) +* [onLoadStart](#onloadstart) + #### allowsExternalPlayback Indicates whether the player allows switching to external playback mode such as AirPlay or HDMI. * **true (default)** - allow switching to external playback mode @@ -359,7 +362,7 @@ Type | Value | Description "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. +Both iOS & Android (only 4.4 and higher) 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. @@ -373,6 +376,40 @@ Adjust the balance of the left and right audio channels. Any value between –1 Platforms: Android MediaPlayer +#### textTracks +Load one or more "sidecar" text tracks. This takes an array of objects representing each track. Each object should have the format: + +Property | Description +--- | --- +title | Descriptive name for the track +language | 2 character [ISO 639-1 code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) representing the language +type | Mime type of the track
* TextTrackType.SRT - .srt SubRip Subtitle
* TextTrackType.TTML - .ttml TTML
* TextTrackType.VTT - .vtt WebVTT +uri | URL for the text track. Currently, only tracks hosted on a webserver are supported + +Example: +``` +import { TextTrackType }, Video from 'react-native-video'; + +textTracks={[ + { + title: "English CC", + language: "en", + type: "text/vtt", TextTrackType.VTT, + uri: "https://bitdash-a.akamaihd.net/content/sintel/subtitles/subtitles_en.vtt" + }, + { + title: "Spanish Subtitles", + language: "es", + type: "application/x-subrip", TextTrackType.SRT, + uri: "https://durian.blender.org/wp-content/content/subtitles/sintel_es.srt" + } +]} +``` + +This isn't support on iOS because AVPlayer doesn't support it. Text tracks must be loaded as part of an HLS playlist. + +Platforms: Android ExoPlayer + #### useTextureView Output to a TextureView instead of the default SurfaceView. In general, you will want to use SurfaceView because it is more efficient and provides better performance. However, SurfaceViews has two limitations: * It can't be animated, transformed or scaled @@ -393,6 +430,68 @@ Adjust the volume. Platforms: all +### Event props + +#### onLoad +Callback function that is called when the media is loaded and ready to play. + +Payload: + +Property | Description +--- | --- +currentPosition | Time in seconds where the media will start +duration | Length of the media in seconds +naturalSize | * width - Width in pixels that the video was encoding at
* height - Height in pixels that the video was encoding at
* orientation - "portrait" or "landscape" +textTracks | An array with info about the text tracks
* index - Index number
* title - Description of the track
* language - IOS 639-1 2 letter language code
* type - Mime type of track + +Example: +``` +{ + canPlaySlowForward: true, + canPlayReverse: false, + canPlaySlowReverse: false, + canPlayFastForward: false, + canStepForward: false, + canStepBackward: false, + currentTime: 0, + duration: 5910.208984375, + naturalSize: { + height: 1080 + orientation: 'landscape' + width: '1920' + }, + textTracks: [ + { title: '#1 French', language: 'fr', index: 0, type: 'text/vtt' }, + { title: '#2 English CC', language: 'en', index: 1, type: 'text/vtt' }, + { title: '#3 English Director Commentary', language: 'en', index: 2, type: 'text/vtt' } + ] +} +``` + +Platforms: all + +#### onLoadStart +Callback function that is called when the media starts loading. + +Payload: + +Property | Description +--- | --- +isNetwork | Boolean indicating if the media is being loaded from the network +type | Type of the media. Not available on Windows +uri | URI for the media source. Not available on Windows + +Example: +``` +{ + isNetwork: true, + type: '', + uri: 'https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8' +} +``` + +Platforms: all + ### Additional props To see the full list of available props, you can check the [propTypes](https://github.com/react-native-community/react-native-video/blob/master/Video.js#L246) of the Video.js component. From fdf34c50fe274f84ff5a3c0cf29aa024430aba76 Mon Sep 17 00:00:00 2001 From: Hampton Maxwell Date: Tue, 12 Jun 2018 21:04:00 -0700 Subject: [PATCH 6/9] Initial commit --- TextTrackType.js | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 TextTrackType.js diff --git a/TextTrackType.js b/TextTrackType.js new file mode 100644 index 0000000000..caff65a9d4 --- /dev/null +++ b/TextTrackType.js @@ -0,0 +1,7 @@ +import keyMirror from 'keymirror'; + +export default { + SRT: 'application/x-subrip', + TTML: 'application/ttml+xml', + VTT: 'text/vtt' +}; From 7793e00e9f2a761313bae842b5d45218b1cff86c Mon Sep 17 00:00:00 2001 From: Hampton Maxwell Date: Tue, 12 Jun 2018 21:04:15 -0700 Subject: [PATCH 7/9] Add TextTrackType export and textTracks prop --- Video.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Video.js b/Video.js index 5d11936cc8..ddee232ac8 100644 --- a/Video.js +++ b/Video.js @@ -2,6 +2,7 @@ import React, {Component} from 'react'; import PropTypes from 'prop-types'; import {StyleSheet, requireNativeComponent, NativeModules, View, ViewPropTypes, Image} from 'react-native'; import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource'; +import TextTrackType from './TextTrackType'; import VideoResizeMode from './VideoResizeMode.js'; const styles = StyleSheet.create({ @@ -10,6 +11,8 @@ const styles = StyleSheet.create({ }, }); +export { TextTrackType }; + export default class Video extends Component { constructor(props) { @@ -282,6 +285,18 @@ Video.propTypes = { PropTypes.number ]) }), + textTracks: PropTypes.arrayOf( + PropTypes.shape({ + title: PropTypes.string, + uri: PropTypes.string.isRequired, + type: PropTypes.oneOf([ + TextTrackType.SRT, + TextTrackType.TTML, + TextTrackType.VTT, + ]), + language: PropTypes.string.isRequired + }) + ), paused: PropTypes.bool, muted: PropTypes.bool, volume: PropTypes.number, From 146cbe3183481ef7f291101b1b726020c9e3df0a Mon Sep 17 00:00:00 2001 From: Hampton Maxwell Date: Tue, 12 Jun 2018 21:17:47 -0700 Subject: [PATCH 8/9] Clean up onLoad info --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index caf69cf3af..dc5ae17f5e 100644 --- a/README.md +++ b/README.md @@ -437,12 +437,12 @@ Callback function that is called when the media is loaded and ready to play. Payload: -Property | Description ---- | --- -currentPosition | Time in seconds where the media will start -duration | Length of the media in seconds -naturalSize | * width - Width in pixels that the video was encoding at
* height - Height in pixels that the video was encoding at
* orientation - "portrait" or "landscape" -textTracks | An array with info about the text tracks
* index - Index number
* title - Description of the track
* language - IOS 639-1 2 letter language code
* type - Mime type of track +Property | Type | Description +--- | --- | --- +currentPosition | number | Time in seconds where the media will start +duration | number | Length of the media in seconds +naturalSize | object | Properties:
* width - Width in pixels that the video was encoded at
* height - Height in pixels that the video was encoded at
* orientation - "portrait" or "landscape" +textTracks | array | An array of text track info objects with the following properties:
* index - Index number
* title - Description of the track
* language - IOS 639-1 2 letter language code
* type - Mime type of track Example: ``` From 3da2489484a715fa6f028dd284648fe47b8addcd Mon Sep 17 00:00:00 2001 From: Hampton Maxwell Date: Tue, 12 Jun 2018 21:21:04 -0700 Subject: [PATCH 9/9] Fix type in ISO name --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dc5ae17f5e..98898b9a4d 100644 --- a/README.md +++ b/README.md @@ -382,7 +382,7 @@ Load one or more "sidecar" text tracks. This takes an array of objects represent Property | Description --- | --- title | Descriptive name for the track -language | 2 character [ISO 639-1 code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) representing the language +language | 2 letter [ISO 639-1 code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) representing the language type | Mime type of the track
* TextTrackType.SRT - .srt SubRip Subtitle
* TextTrackType.TTML - .ttml TTML
* TextTrackType.VTT - .vtt WebVTT uri | URL for the text track. Currently, only tracks hosted on a webserver are supported @@ -442,7 +442,7 @@ Property | Type | Description currentPosition | number | Time in seconds where the media will start duration | number | Length of the media in seconds naturalSize | object | Properties:
* width - Width in pixels that the video was encoded at
* height - Height in pixels that the video was encoded at
* orientation - "portrait" or "landscape" -textTracks | array | An array of text track info objects with the following properties:
* index - Index number
* title - Description of the track
* language - IOS 639-1 2 letter language code
* type - Mime type of track +textTracks | array | An array of text track info objects with the following properties:
* index - Index number
* title - Description of the track
* language - 2 letter [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) language code
* type - Mime type of track Example: ```