diff --git a/API.md b/API.md index 625efeb5..f298abf5 100644 --- a/API.md +++ b/API.md @@ -320,6 +320,7 @@ var styles = StyleSheet.create({ | Name | Platforms Support | |-------------------------------------------------------------------------------------------------|---------------------------| | [onAudioBecomingNoisy](#onaudiobecomingnoisy) | Android, iOS | +| [onAudioTracks](#onAudioTracks) | Android | | [onBandwidthUpdate](#onbandwidthupdate) | Android | | [onBuffer](#onbuffer) | Android, iOS | | [onEnd](#onend) | All | @@ -339,6 +340,8 @@ var styles = StyleSheet.create({ | [onRestoreUserInterfaceForPictureInPictureStop](#onrestoreuserinterfaceforpictureinpicturestop) | iOS | | [onSeek](#onseek) | Android, iOS, Windows UWP | | [onTimedMetadata](#ontimedmetadata) | Android, iOS | +| [onTextTracks](#onTextTracks) | Android | +| [onVideoTracks](#onVideoTracks) | Android | ### Methods | Name |Plateforms Support | @@ -684,6 +687,34 @@ Determine whether to repeat the video when the end is reached Platforms: all + +#### onAudioTracks +Callback function that is called when audio tracks change + +Payload: + +Property | Type | Description +--- | --- | --- +index | number | Internal track ID +title | string | Descriptive name for the track +language | string | 2 letter [ISO 639-1 code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) representing the language +bitrate | number | bitrate of track +type | string | Mime type of track +selected | boolean | true if track is playing + +Example: +``` +{ + audioTracks: [ + { language: 'es', title: 'Spanish', type: 'audio/mpeg', index: 0, selected: true }, + { language: 'en', title: 'English', type: 'audio/mpeg', index: 1 } + ], +} +``` + + +Platforms: Android + #### reportBandwidth Determine whether to generate onBandwidthUpdate events. This is needed due to the high frequency of these events on ExoPlayer. @@ -1347,6 +1378,69 @@ Example: Platforms: Android, iOS +#### onTextTracks +Callback function that is called when text tracks change + +Payload: + +Property | Type | Description +--- | --- | --- +index | number | Internal track ID +title | string | Descriptive name for the track +language | string | 2 letter [ISO 639-1 code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) representing the language +type | string | Mime type of the track
* TextTrackType.SRT - SubRip (.srt)
* TextTrackType.TTML - TTML (.ttml)
* TextTrackType.VTT - WebVTT (.vtt)
iOS only supports VTT, Android supports all 3 +selected | boolean | true if track is playing + + +Example: +``` +{ + textTracks: [ + { + index: 0, + title: 'Any Time You Like', + type: 'srt', + selected: true + } + ] +} +``` + +Platforms: Android + +#### onVideoTracks +Callback function that is called when video tracks change + +Payload: + +Property | Type | Description +--- | --- | --- +trackId | number | Internal track ID +codecs | string | MimeType of codec used for this track +width | number | Track width +height | number | Track height +bitrate | number | Bitrate in bps +selected | boolean | true if track is selected for playing + + +Example: +``` +{ + videoTracks: [ + { + trackId: 0, + codecs: 'video/mp4', + width: 1920, + height: 1080, + bitrate: 10000, + selected: true + } + ] +} +``` + +Platforms: Android + ### Methods Methods operate on a ref to the Video element. You can create a ref using code like: ``` diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e3ecbca..48703525 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Version 6.0.0-alpha.5 + - Android: add new events on tracks changed to be notified of audio/text/video Tracks update during playback [2806](https://github.com/react-native-video/react-native-video/pull/2806) - iOS: app crashes on call to presentFullScreenPlayer [#2808](https://github.com/react-native-video/react-native-video/pull/2971) - Android: Fix publicated progress handler causing duplicated progress event [#2972](https://github.com/react-native-video/react-native-video/pull/2972) - Android: Fix audio/Subtitle tracks selection [#2979](https://github.com/react-native-video/react-native-video/pull/2979) diff --git a/Video.js b/Video.js index e1374725..ac6bc085 100644 --- a/Video.js +++ b/Video.js @@ -117,6 +117,24 @@ export default class Video extends Component { } }; + _onAudioTracks = (event) => { + if (this.props.onAudioTracks) { + this.props.onAudioTracks(event.nativeEvent); + } + }; + + _onTextTracks = (event) => { + if (this.props.onTextTracks) { + this.props.onTextTracks(event.nativeEvent); + } + }; + + _onVideoTracks = (event) => { + if (this.props.onVideoTracks) { + this.props.onVideoTracks(event.nativeEvent); + } + }; + _onError = (event) => { if (this.props.onError) { this.props.onError(event.nativeEvent); @@ -328,6 +346,9 @@ export default class Video extends Component { onVideoLoadStart: this._onLoadStart, onVideoPlaybackStateChanged: this._onPlaybackStateChanged, onVideoLoad: this._onLoad, + onAudioTracks: this._onAudioTracks, + onTextTracks: this._onTextTracks, + onVideoTracks: this._onVideoTracks, onVideoError: this._onError, onVideoProgress: this._onProgress, onVideoSeek: this._onSeek, @@ -516,6 +537,9 @@ Video.propTypes = { onLoadStart: PropTypes.func, onPlaybackStateChanged: PropTypes.func, onLoad: PropTypes.func, + onAudioTracks: PropTypes.func, + onTextTracks: PropTypes.func, + onVideoTracks: PropTypes.func, onBuffer: PropTypes.func, onError: PropTypes.func, onProgress: PropTypes.func, diff --git a/android/src/main/java/com/brentvatne/common/Track.java b/android/src/main/java/com/brentvatne/common/Track.java new file mode 100644 index 00000000..198f9ec7 --- /dev/null +++ b/android/src/main/java/com/brentvatne/common/Track.java @@ -0,0 +1,13 @@ +package com.brentvatne.common; +import android.net.Uri; + +public class Track +{ + public String m_title; + public Uri m_uri; + public String m_mimeType; + public String m_language; + public boolean m_isSelected; + public int m_bitrate; + public int m_index; +} diff --git a/android/src/main/java/com/brentvatne/common/VideoTrack.java b/android/src/main/java/com/brentvatne/common/VideoTrack.java new file mode 100644 index 00000000..6eccd983 --- /dev/null +++ b/android/src/main/java/com/brentvatne/common/VideoTrack.java @@ -0,0 +1,12 @@ +package com.brentvatne.common; + +public class VideoTrack +{ + public int m_width = 0; + public int m_height = 0; + public int m_bitrate = 0; + public String m_codecs = ""; + public int m_id = -1; + public String m_trackId = ""; + public boolean m_isSelected = false; +} diff --git a/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java index 10cecff3..3b7df5b2 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java @@ -4,7 +4,6 @@ import android.annotation.TargetApi; import android.content.Context; import androidx.core.content.ContextCompat; import android.util.AttributeSet; -import android.util.Log; import android.util.TypedValue; import android.view.Gravity; import android.view.SurfaceView; @@ -29,7 +28,6 @@ import com.google.android.exoplayer2.video.VideoSize; import java.util.List; -@TargetApi(16) public final class ExoPlayerView extends FrameLayout implements AdViewProvider { private View surfaceView; diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index c2cd274e..d7ab0761 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -1,5 +1,10 @@ package com.brentvatne.exoplayer; +import static com.google.android.exoplayer2.C.CONTENT_TYPE_DASH; +import static com.google.android.exoplayer2.C.CONTENT_TYPE_HLS; +import static com.google.android.exoplayer2.C.CONTENT_TYPE_OTHER; +import static com.google.android.exoplayer2.C.CONTENT_TYPE_SS; + import android.annotation.SuppressLint; import android.app.Activity; import android.app.ActivityManager; @@ -17,27 +22,23 @@ import android.view.accessibility.CaptioningManager; import android.widget.FrameLayout; import android.widget.ImageButton; +import androidx.annotation.WorkerThread; import androidx.activity.OnBackPressedCallback; +import com.brentvatne.common.Track; +import com.brentvatne.common.VideoTrack; 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.facebook.react.util.RNLog; import com.google.ads.interactivemedia.v3.api.AdEvent; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.DefaultRenderersFactory; -import com.google.android.exoplayer2.ExoPlaybackException; -import com.google.android.exoplayer2.drm.MediaDrmCallbackException; -import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.PlaybackException; @@ -51,16 +52,12 @@ import com.google.android.exoplayer2.drm.DefaultDrmSessionManagerProvider; import com.google.android.exoplayer2.drm.DrmSessionEventListener; import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManagerProvider; -import com.google.android.exoplayer2.drm.ExoMediaDrm; import com.google.android.exoplayer2.drm.FrameworkMediaDrm; import com.google.android.exoplayer2.drm.HttpMediaDrmCallback; -import com.google.android.exoplayer2.drm.MediaDrmCallbackException; import com.google.android.exoplayer2.drm.UnsupportedDrmException; import com.google.android.exoplayer2.mediacodec.MediaCodecInfo; -import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil; import com.google.android.exoplayer2.metadata.Metadata; -import com.google.android.exoplayer2.source.BehindLiveWindowException; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MergingMediaSource; import com.google.android.exoplayer2.source.ProgressiveMediaSource; @@ -76,6 +73,7 @@ import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.trackselection.ExoTrackSelection; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionOverride; import com.google.android.exoplayer2.ui.PlayerControlView; import com.google.android.exoplayer2.upstream.BandwidthMeter; @@ -84,7 +82,6 @@ import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.upstream.HttpDataSource; -import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.source.dash.DashUtil; @@ -92,7 +89,6 @@ import com.google.android.exoplayer2.source.dash.manifest.DashManifest; import com.google.android.exoplayer2.source.dash.manifest.Period; import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet; import com.google.android.exoplayer2.source.dash.manifest.Representation; -import com.google.android.exoplayer2.source.dash.manifest.Descriptor; import com.google.android.exoplayer2.ext.ima.ImaAdsLoader; import com.google.android.exoplayer2.source.ads.AdsMediaSource; @@ -107,9 +103,6 @@ import java.util.List; import java.util.Locale; import java.util.UUID; import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; -import java.util.List; import java.lang.Thread; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -799,7 +792,7 @@ class ReactExoplayerView extends FrameLayout implements drmProvider = new DefaultDrmSessionManagerProvider(); } switch (type) { - case C.TYPE_SS: + case CONTENT_TYPE_SS: return new SsMediaSource.Factory( new DefaultSsChunkSource.Factory(mediaDataSourceFactory), buildDataSourceFactory(false) @@ -807,7 +800,7 @@ class ReactExoplayerView extends FrameLayout implements .setLoadErrorHandlingPolicy( config.buildLoadErrorHandlingPolicy(minLoadRetryCount) ).createMediaSource(mediaItem); - case C.TYPE_DASH: + case CONTENT_TYPE_DASH: return new DashMediaSource.Factory( new DefaultDashChunkSource.Factory(mediaDataSourceFactory), buildDataSourceFactory(false) @@ -815,14 +808,14 @@ class ReactExoplayerView extends FrameLayout implements .setLoadErrorHandlingPolicy( config.buildLoadErrorHandlingPolicy(minLoadRetryCount) ).createMediaSource(mediaItem); - case C.TYPE_HLS: + case CONTENT_TYPE_HLS: return new HlsMediaSource.Factory( mediaDataSourceFactory ).setDrmSessionManagerProvider(drmProvider) .setLoadErrorHandlingPolicy( config.buildLoadErrorHandlingPolicy(minLoadRetryCount) ).createMediaSource(mediaItem); - case C.TYPE_OTHER: + case CONTENT_TYPE_OTHER: return new ProgressiveMediaSource.Factory( mediaDataSourceFactory ).setDrmSessionManagerProvider(drmProvider) @@ -1129,25 +1122,42 @@ class ReactExoplayerView extends FrameLayout implements // Properties that must be accessed on the main thread long duration = player.getDuration(); long currentPosition = player.getCurrentPosition(); - WritableArray audioTrackInfo = getAudioTrackInfo(); - WritableArray textTrackInfo = getTextTrackInfo(); - int trackRendererIndex = getTrackRendererIndex(C.TRACK_TYPE_VIDEO); + ArrayList audioTracks = getAudioTrackInfo(); + ArrayList textTracks = getTextTrackInfo(); - ExecutorService es = Executors.newSingleThreadExecutor(); - es.execute(new Runnable() { - @Override - public void run() { - // To prevent ANRs caused by getVideoTrackInfo we run this on a different thread and notify the player only when we're done - eventEmitter.load(duration, currentPosition, width, height, - audioTrackInfo, textTrackInfo, getVideoTrackInfo(trackRendererIndex), trackId); - } - }); + if (this.contentStartTime != -1L) { + ExecutorService es = Executors.newSingleThreadExecutor(); + es.execute(new Runnable() { + @Override + public void run() { + // To prevent ANRs caused by getVideoTrackInfo we run this on a different thread and notify the player only when we're done + ArrayList videoTracks = getVideoTrackInfoFromManifest(); + if (videoTracks != null) { + isUsingContentResolution = true; + } + eventEmitter.load(duration, currentPosition, width, height, + audioTracks, textTracks, videoTracks, trackId ); + + } + }); + return; + } + + ArrayList videoTracks = getVideoTrackInfo(); + + eventEmitter.load(duration, currentPosition, width, height, + audioTracks, textTracks, videoTracks, trackId); } } - private WritableArray getAudioTrackInfo() { - WritableArray audioTracks = Arguments.createArray(); + private static boolean isTrackSelected(TrackSelection selection, TrackGroup group, + int trackIndex){ + return selection != null && selection.getTrackGroup() == group + && selection.indexOf( trackIndex ) != C.INDEX_UNSET; + } + private ArrayList getAudioTrackInfo() { + ArrayList audioTracks = new ArrayList<>(); if (trackSelector == null) { // Likely player is unmounting so no audio tracks are available anymore return audioTracks; @@ -1158,78 +1168,76 @@ class ReactExoplayerView extends FrameLayout implements if (info == null || index == C.INDEX_UNSET) { return audioTracks; } - TrackGroupArray groups = info.getTrackGroups(index); + TrackSelectionArray selectionArray = player.getCurrentTrackSelections(); + TrackSelection selection = selectionArray.get( C.TRACK_TYPE_AUDIO ); + for (int i = 0; i < groups.length; ++i) { - Format format = groups.get(i).getFormat(0); - WritableMap audioTrack = Arguments.createMap(); - audioTrack.putInt("index", i); - audioTrack.putString("title", format.id != null ? format.id : ""); - audioTrack.putString("type", format.sampleMimeType); - audioTrack.putString("language", format.language != null ? format.language : ""); - audioTrack.putString("bitrate", format.bitrate == Format.NO_VALUE ? "" - : String.format(Locale.US, "%.2fMbps", format.bitrate / 1000000f)); - audioTracks.pushMap(audioTrack); + TrackGroup group = groups.get(i); + Format format = group.getFormat(0); + Track audioTrack = new Track(); + audioTrack.m_index = i; + audioTrack.m_title = format.id != null ? format.id : ""; + audioTrack.m_mimeType = format.sampleMimeType; + audioTrack.m_language = format.language != null ? format.language : ""; + audioTrack.m_bitrate = format.bitrate == Format.NO_VALUE ? 0 : format.bitrate; + audioTrack.m_isSelected = isTrackSelected(selection, group, 0 ); + audioTracks.add(audioTrack); } return audioTracks; } - private WritableArray getVideoTrackInfo(int trackRendererIndex) { - if (this.contentStartTime != -1L) { - WritableArray contentVideoTracks = this.getVideoTrackInfoFromManifest(); - if (contentVideoTracks != null) { - isUsingContentResolution = true; - return contentVideoTracks; - } + private ArrayList getVideoTrackInfo() { + ArrayList videoTracks = new ArrayList<>(); + if (trackSelector == null) { + // Likely player is unmounting so no audio tracks are available anymore + return videoTracks; } - - WritableArray videoTracks = Arguments.createArray(); - MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo(); - - if (info == null || trackRendererIndex == C.INDEX_UNSET) { + int index = getTrackRendererIndex(C.TRACK_TYPE_VIDEO); + if (info == null || index == C.INDEX_UNSET) { return videoTracks; } - TrackGroupArray groups = info.getTrackGroups(trackRendererIndex); + TrackGroupArray groups = info.getTrackGroups(index); for (int i = 0; i < groups.length; ++i) { TrackGroup group = groups.get(i); for (int trackIndex = 0; trackIndex < group.length; trackIndex++) { Format format = group.getFormat(trackIndex); if (isFormatSupported(format)) { - WritableMap videoTrack = Arguments.createMap(); - videoTrack.putInt("width", format.width == Format.NO_VALUE ? 0 : format.width); - videoTrack.putInt("height",format.height == Format.NO_VALUE ? 0 : format.height); - videoTrack.putInt("bitrate", format.bitrate == Format.NO_VALUE ? 0 : format.bitrate); - videoTrack.putString("codecs", format.codecs != null ? format.codecs : ""); - videoTrack.putString("trackId", format.id == null ? String.valueOf(trackIndex) : format.id); - videoTracks.pushMap(videoTrack); + VideoTrack videoTrack = new VideoTrack(); + videoTrack.m_width = format.width == Format.NO_VALUE ? 0 : format.width; + videoTrack.m_height = format.height == Format.NO_VALUE ? 0 : format.height; + videoTrack.m_bitrate = format.bitrate == Format.NO_VALUE ? 0 : format.bitrate; + videoTrack.m_codecs = format.codecs != null ? format.codecs : ""; + videoTrack.m_trackId = format.id == null ? String.valueOf(trackIndex) : format.id; + videoTracks.add(videoTrack); } } } - return videoTracks; } - private WritableArray getVideoTrackInfoFromManifest() { + private ArrayList getVideoTrackInfoFromManifest() { return this.getVideoTrackInfoFromManifest(0); } // We need retry count to in case where minefest request fails from poor network conditions - private WritableArray getVideoTrackInfoFromManifest(int retryCount) { + @WorkerThread + private ArrayList getVideoTrackInfoFromManifest(int retryCount) { ExecutorService es = Executors.newSingleThreadExecutor(); final DataSource dataSource = this.mediaDataSourceFactory.createDataSource(); final Uri sourceUri = this.srcUri; final long startTime = this.contentStartTime * 1000 - 100; // s -> ms with 100ms offset - Future result = es.submit(new Callable() { + Future> result = es.submit(new Callable>() { DataSource ds = dataSource; Uri uri = sourceUri; long startTimeUs = startTime * 1000; // ms -> us - public WritableArray call() throws Exception { - WritableArray videoTracks = Arguments.createArray(); + public ArrayList call() throws Exception { + ArrayList videoTracks = new ArrayList<>(); try { DashManifest manifest = DashUtil.loadManifest(this.ds, this.uri); int periodCount = manifest.getPeriodCount(); @@ -1244,19 +1252,18 @@ class ReactExoplayerView extends FrameLayout implements for (int representationIndex = 0; representationIndex < adaptation.representations.size(); representationIndex++) { Representation representation = adaptation.representations.get(representationIndex); Format format = representation.format; - if (representation.presentationTimeOffsetUs <= startTimeUs) { - break; - } - hasFoundContentPeriod = true; - WritableMap videoTrack = Arguments.createMap(); - videoTrack.putInt("width", format.width == Format.NO_VALUE ? 0 : format.width); - videoTrack.putInt("height",format.height == Format.NO_VALUE ? 0 : format.height); - videoTrack.putInt("bitrate", format.bitrate == Format.NO_VALUE ? 0 : format.bitrate); - videoTrack.putString("codecs", format.codecs != null ? format.codecs : ""); - videoTrack.putString("trackId", - format.id == null ? String.valueOf(representationIndex) : format.id); if (isFormatSupported(format)) { - videoTracks.pushMap(videoTrack); + if (representation.presentationTimeOffsetUs <= startTimeUs) { + break; + } + hasFoundContentPeriod = true; + VideoTrack videoTrack = new VideoTrack(); + videoTrack.m_width = format.width == Format.NO_VALUE ? 0 : format.width; + videoTrack.m_height = format.height == Format.NO_VALUE ? 0 : format.height; + videoTrack.m_bitrate = format.bitrate == Format.NO_VALUE ? 0 : format.bitrate; + videoTrack.m_codecs = format.codecs != null ? format.codecs : ""; + videoTrack.m_trackId = format.id == null ? String.valueOf(representationIndex) : format.id; + videoTracks.add(videoTrack); } } if (hasFoundContentPeriod) { @@ -1270,7 +1277,7 @@ class ReactExoplayerView extends FrameLayout implements }); try { - WritableArray results = result.get(3000, TimeUnit.MILLISECONDS); + ArrayList results = result.get(3000, TimeUnit.MILLISECONDS); if (results == null && retryCount < 1) { return this.getVideoTrackInfoFromManifest(++retryCount); } @@ -1281,24 +1288,31 @@ class ReactExoplayerView extends FrameLayout implements return null; } - private WritableArray getTextTrackInfo() { - WritableArray textTracks = Arguments.createArray(); - + private ArrayList getTextTrackInfo() { + ArrayList textTracks = new ArrayList<>(); + if (trackSelector == null) { + return textTracks; + } MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo(); int index = getTrackRendererIndex(C.TRACK_TYPE_TEXT); if (info == null || index == C.INDEX_UNSET) { return textTracks; } - + TrackSelectionArray selectionArray = player.getCurrentTrackSelections(); + TrackSelection selection = selectionArray.get( C.TRACK_TYPE_VIDEO ); 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 : ""); - textTracks.pushMap(textTrack); + TrackGroup group = groups.get(i); + Format format = group.getFormat(0); + + Track textTrack = new Track(); + textTrack.m_index = i; + textTrack.m_title = format.id != null ? format.id : ""; + textTrack.m_mimeType = format.sampleMimeType; + textTrack.m_language = format.language != null ? format.language : ""; + textTrack.m_isSelected = isTrackSelected(selection, group, 0 ); + textTracks.add(textTrack); } return textTracks; } @@ -1367,7 +1381,9 @@ class ReactExoplayerView extends FrameLayout implements @Override public void onTracksChanged(Tracks tracks) { - // Do nothing. + eventEmitter.textTracks(getTextTrackInfo()); + eventEmitter.audioTracks(getAudioTrackInfo()); + eventEmitter.videoTracks(getVideoTrackInfo()); } @Override diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java index 7a7d8822..8da025e7 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -330,7 +330,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager videoHeight) { naturalSize.putString(EVENT_PROP_ORIENTATION, "landscape"); - } else { + } else if (videoWidth < videoHeight) { naturalSize.putString(EVENT_PROP_ORIENTATION, "portrait"); + } else { + naturalSize.putString(EVENT_PROP_ORIENTATION, "square"); } + return naturalSize; + } + + WritableArray audioTracksToArray(ArrayList audioTracks) { + WritableArray waAudioTracks = Arguments.createArray(); + if( audioTracks != null ){ + for (int i = 0; i < audioTracks.size(); ++i) { + Track format = audioTracks.get(i); + WritableMap audioTrack = Arguments.createMap(); + audioTrack.putInt("index", i); + audioTrack.putString("title", format.m_title != null ? format.m_title : ""); + audioTrack.putString("type", format.m_mimeType != null ? format.m_mimeType : ""); + audioTrack.putString("language", format.m_language != null ? format.m_language : ""); + audioTrack.putInt("bitrate", format.m_bitrate); + audioTrack.putBoolean("selected", format.m_isSelected); + waAudioTracks.pushMap(audioTrack); + } + } + return waAudioTracks; + } + + WritableArray videoTracksToArray(ArrayList videoTracks) { + WritableArray waVideoTracks = Arguments.createArray(); + if( videoTracks != null ){ + for (int i = 0; i < videoTracks.size(); ++i) { + VideoTrack vTrack = videoTracks.get(i); + WritableMap videoTrack = Arguments.createMap(); + videoTrack.putInt("width", vTrack.m_width); + videoTrack.putInt("height",vTrack.m_height); + videoTrack.putInt("bitrate", vTrack.m_bitrate); + videoTrack.putString("codecs", vTrack.m_codecs); + videoTrack.putInt("trackId",vTrack.m_id); + videoTrack.putBoolean("selected", vTrack.m_isSelected); + waVideoTracks.pushMap(videoTrack); + } + } + return waVideoTracks; + } + + WritableArray textTracksToArray(ArrayList textTracks) { + WritableArray waTextTracks = Arguments.createArray(); + if (textTracks != null) { + for (int i = 0; i < textTracks.size(); ++i) { + Track format = textTracks.get(i); + WritableMap textTrack = Arguments.createMap(); + textTrack.putInt("index", i); + textTrack.putString("title", format.m_title != null ? format.m_title : ""); + textTrack.putString("type", format.m_mimeType != null ? format.m_mimeType : ""); + textTrack.putString("language", format.m_language != null ? format.m_language : ""); + textTrack.putBoolean("selected", format.m_isSelected); + waTextTracks.pushMap(textTrack); + } + } + return waTextTracks; + } + + public void load(double duration, double currentPosition, int videoWidth, int videoHeight, + ArrayList audioTracks, ArrayList textTracks, ArrayList videoTracks, String trackId){ + WritableArray waAudioTracks = audioTracksToArray(audioTracks); + WritableArray waVideoTracks = videoTracksToArray(videoTracks); + WritableArray waTextTracks = textTracksToArray(textTracks); + + load( duration, currentPosition, videoWidth, videoHeight, waAudioTracks, waTextTracks, waVideoTracks, trackId); + } + + + private void load(double duration, double currentPosition, int videoWidth, int videoHeight, + WritableArray audioTracks, WritableArray textTracks, WritableArray videoTracks, String trackId) { + WritableMap event = Arguments.createMap(); + event.putDouble(EVENT_PROP_DURATION, duration / 1000D); + event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000D); + + WritableMap naturalSize = aspectRatioToNaturalSize(videoWidth, videoHeight); event.putMap(EVENT_PROP_NATURAL_SIZE, naturalSize); event.putString(EVENT_PROP_TRACK_ID, trackId); event.putArray(EVENT_PROP_VIDEO_TRACKS, videoTracks); @@ -184,6 +266,26 @@ class VideoEventEmitter { receiveEvent(EVENT_LOAD, event); } + + + WritableMap arrayToObject(String field, WritableArray array) { + WritableMap event = Arguments.createMap(); + event.putArray(field, array); + return event; + } + + public void audioTracks(ArrayList audioTracks){ + receiveEvent(EVENT_AUDIO_TRACKS, arrayToObject(EVENT_PROP_AUDIO_TRACKS, audioTracksToArray(audioTracks))); + } + + public void textTracks(ArrayList textTracks){ + receiveEvent(EVENT_TEXT_TRACKS, arrayToObject(EVENT_PROP_TEXT_TRACKS, textTracksToArray(textTracks))); + } + + public void videoTracks(ArrayList videoTracks){ + receiveEvent(EVENT_VIDEO_TRACKS, arrayToObject(EVENT_PROP_VIDEO_TRACKS, videoTracksToArray(videoTracks))); + } + void progressChanged(double currentPosition, double bufferedDuration, double seekableDuration, double currentPlaybackTime) { WritableMap event = Arguments.createMap(); event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000D); diff --git a/examples/basic/src/VideoPlayer.android.tsx b/examples/basic/src/VideoPlayer.android.tsx index ef17ac5b..a3206a84 100644 --- a/examples/basic/src/VideoPlayer.android.tsx +++ b/examples/basic/src/VideoPlayer.android.tsx @@ -108,8 +108,8 @@ class VideoPlayer extends Component { onLoad = (data: any) => { this.setState({ duration: data.duration, loading: false, }); - this.onAudioTracks(data.audioTracks) - this.onTextTracks(data.textTracks) + this.onAudioTracks(data) + this.onTextTracks(data) }; onProgress = (data: any) => { @@ -132,7 +132,7 @@ class VideoPlayer extends Component { return x.selected }) this.setState({ - audioTracks: data, + audioTracks: data.audioTracks, }) if (selectedTrack?.language) { this.setState({ @@ -151,7 +151,7 @@ class VideoPlayer extends Component { }) this.setState({ - textTracks: data, + textTracks: data.textTracks, }) if (selectedTrack?.language) { this.setState({ @@ -683,7 +683,6 @@ class VideoPlayer extends Component { }} > - {this.state.textTracks.map((track) => ( )