From daf5e595eced2b2f6580e8d8a6205f14facdec46 Mon Sep 17 00:00:00 2001 From: Tai Le Tuan Date: Thu, 16 Jun 2022 00:24:55 +0700 Subject: [PATCH] feat: upgrade exoplayer to v2.17.1 (#2498) Describe the changes Upgrade ExoPlayer to version 2.17.1 Provide an example of how to test the change Tested with a forked of react-native-video-test * Update exoplayer to v2.15.1 * feat: upgrade ExoPlayer to version 2.17.1 * chore: update CHANGELOG * remove ExoPlayerFullscreenVideoActivity * Fix build issues * Fix build & runtime issues Co-authored-by: Eran Hammer Co-authored-by: Armands Malejev --- API.md | 6 +- CHANGELOG.md | 1 + README.md | 6 +- android-exoplayer/build.gradle | 10 +- .../brentvatne/exoplayer/DataSourceUtil.java | 13 +- .../brentvatne/exoplayer/ExoPlayerView.java | 57 ++-- .../exoplayer/ReactExoplayerView.java | 279 ++++++++---------- android/build.gradle | 6 +- examples/basic/android/build.gradle | 6 +- .../video-caching/android/app/build.gradle | 6 +- .../android/app/src/main/AndroidManifest.xml | 2 +- 11 files changed, 175 insertions(+), 217 deletions(-) diff --git a/API.md b/API.md index daec7e99..24ff05b7 100644 --- a/API.md +++ b/API.md @@ -1519,10 +1519,10 @@ allprojects { ... // Various other settings go here project.ext { - compileSdkVersion = 23 - buildToolsVersion = "23.0.1" + compileSdkVersion = 31 + buildToolsVersion = "30.0.2" - minSdkVersion = 16 + minSdkVersion = 21 targetSdkVersion = 22 } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 36f9a78c..0e269be5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ - Convert iOS implementation to Swift [#2527](https://github.com/react-native-video/react-native-video/pull/2527) - Add iOS support for decoding offline sources [#2527](https://github.com/react-native-video/react-native-video/pull/2527) - Update basic example applications (React Native 0.63.4) [#2527](https://github.com/react-native-video/react-native-video/pull/2527) +- Upgrade ExoPlayer to 2.17.1 [#2498](https://github.com/react-native-video/react-native-video/pull/2498) - Fix volume reset issue in exoPlayer [#2371](https://github.com/react-native-video/react-native-video/pull/2371) - Change WindowsTargetPlatformVersion to 10.0 [#2706](https://github.com/react-native-video/react-native-video/pull/2706) - Fixed Android seeking bug [#2712](https://github.com/react-native-video/react-native-video/pull/2712) diff --git a/README.md b/README.md index 998d7ded..42240117 100644 --- a/README.md +++ b/README.md @@ -1598,10 +1598,10 @@ allprojects { ... // Various other settings go here project.ext { - compileSdkVersion = 23 - buildToolsVersion = "23.0.1" + compileSdkVersion = 31 + buildToolsVersion = "30.0.2" - minSdkVersion = 16 + minSdkVersion = 21 targetSdkVersion = 22 } } diff --git a/android-exoplayer/build.gradle b/android-exoplayer/build.gradle index 048b96af..a93e7599 100644 --- a/android-exoplayer/build.gradle +++ b/android-exoplayer/build.gradle @@ -5,8 +5,8 @@ def safeExtGet(prop, fallback) { } android { - compileSdkVersion safeExtGet('compileSdkVersion', 28) - buildToolsVersion safeExtGet('buildToolsVersion', '28.0.3') + compileSdkVersion safeExtGet('compileSdkVersion', 31) + buildToolsVersion safeExtGet('buildToolsVersion', '30.0.2') compileOptions { targetCompatibility JavaVersion.VERSION_1_8 @@ -14,7 +14,7 @@ android { } defaultConfig { - minSdkVersion safeExtGet('minSdkVersion', 16) + minSdkVersion safeExtGet('minSdkVersion', 21) targetSdkVersion safeExtGet('targetSdkVersion', 28) versionCode 1 versionName "1.0" @@ -33,7 +33,7 @@ repositories { dependencies { implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}" - implementation('com.google.android.exoplayer:exoplayer:2.13.3') { + implementation('com.google.android.exoplayer:exoplayer:2.17.1') { exclude group: 'com.android.support' } @@ -42,7 +42,7 @@ dependencies { implementation "androidx.core:core:1.1.0" implementation "androidx.media:media:1.1.0" - implementation('com.google.android.exoplayer:extension-okhttp:2.13.3') { + implementation('com.google.android.exoplayer:extension-okhttp:2.17.1') { exclude group: 'com.squareup.okhttp3', module: 'okhttp' } implementation 'com.squareup.okhttp3:okhttp:${OKHTTP_VERSION}' diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/DataSourceUtil.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/DataSourceUtil.java index 19dda002..c5e7047b 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/DataSourceUtil.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/DataSourceUtil.java @@ -4,13 +4,14 @@ import com.facebook.react.bridge.ReactContext; import com.facebook.react.modules.network.CookieJarContainer; import com.facebook.react.modules.network.ForwardingCookieHandler; import com.facebook.react.modules.network.OkHttpClientProvider; -import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSourceFactory; +import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; -import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; +import com.google.android.exoplayer2.upstream.DefaultDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.util.Util; +import okhttp3.Call; import okhttp3.JavaNetCookieJar; import okhttp3.OkHttpClient; import java.util.Map; @@ -75,7 +76,7 @@ public class DataSourceUtil { } private static DataSource.Factory buildDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter, Map requestHeaders) { - return new DefaultDataSourceFactory(context, bandwidthMeter, + return new DefaultDataSource.Factory(context, buildHttpDataSourceFactory(context, bandwidthMeter, requestHeaders)); } @@ -84,10 +85,12 @@ public class DataSourceUtil { CookieJarContainer container = (CookieJarContainer) client.cookieJar(); ForwardingCookieHandler handler = new ForwardingCookieHandler(context); container.setCookieJar(new JavaNetCookieJar(handler)); - OkHttpDataSourceFactory okHttpDataSourceFactory = new OkHttpDataSourceFactory(client, getUserAgent(context), bandwidthMeter); + OkHttpDataSource.Factory okHttpDataSourceFactory = new OkHttpDataSource.Factory((Call.Factory) client) + .setUserAgent(getUserAgent(context)) + .setTransferListener(bandwidthMeter); if (requestHeaders != null) - okHttpDataSourceFactory.getDefaultRequestProperties().set(requestHeaders); + okHttpDataSourceFactory.setDefaultRequestProperties(requestHeaders); return okHttpDataSourceFactory; } diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java index 099c6646..a6cd1c4f 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java @@ -13,18 +13,16 @@ import android.view.ViewGroup; import android.widget.FrameLayout; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.ExoPlaybackException; -import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.PlaybackParameters; -import com.google.android.exoplayer2.SimpleExoPlayer; -import com.google.android.exoplayer2.video.VideoListener; +import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.TracksInfo; import com.google.android.exoplayer2.text.Cue; -import com.google.android.exoplayer2.text.TextRenderer; -import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.ui.SubtitleView; +import com.google.android.exoplayer2.video.VideoSize; import java.util.List; @@ -36,7 +34,7 @@ public final class ExoPlayerView extends FrameLayout { private final SubtitleView subtitleLayout; private final AspectRatioFrameLayout layout; private final ComponentListener componentListener; - private SimpleExoPlayer player; + private ExoPlayer player; private Context context; private ViewGroup.LayoutParams layoutParams; @@ -131,19 +129,17 @@ public final class ExoPlayerView extends FrameLayout { } /** - * Set the {@link SimpleExoPlayer} to use. The {@link SimpleExoPlayer#addTextOutput} and - * {@link SimpleExoPlayer#addVideoListener} method of the player will be called and previous + * Set the {@link ExoPlayer} to use. The {@link ExoPlayer#addListener} method of the + * player will be called and previous * assignments are overridden. * - * @param player The {@link SimpleExoPlayer} to use. + * @param player The {@link ExoPlayer} to use. */ - public void setPlayer(SimpleExoPlayer player) { + public void setPlayer(ExoPlayer player) { if (this.player == player) { return; } if (this.player != null) { - this.player.removeTextOutput(componentListener); - this.player.removeVideoListener(componentListener); this.player.removeListener(componentListener); clearVideoView(); } @@ -151,9 +147,7 @@ public final class ExoPlayerView extends FrameLayout { shutterView.setVisibility(this.hideShutterView ? View.INVISIBLE : View.VISIBLE); if (player != null) { setVideoView(); - player.addVideoListener(componentListener); player.addListener(componentListener); - player.addTextOutput(componentListener); } } @@ -230,8 +224,7 @@ public final class ExoPlayerView extends FrameLayout { layout.invalidateAspectRatio(); } - private final class ComponentListener implements VideoListener, - TextOutput, ExoPlayer.EventListener { + private final class ComponentListener implements Player.Listener { // TextRenderer.Output implementation @@ -240,12 +233,12 @@ public final class ExoPlayerView extends FrameLayout { subtitleLayout.onCues(cues); } - // SimpleExoPlayer.VideoListener implementation + // ExoPlayer.VideoListener implementation @Override - public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { + public void onVideoSizeChanged(VideoSize videoSize) { boolean isInitialRatio = layout.getAspectRatio() == 0; - layout.setAspectRatio(height == 0 ? 1 : (width * pixelWidthHeightRatio) / height); + layout.setAspectRatio(videoSize.height == 0 ? 1 : (videoSize.width * videoSize.pixelWidthHeightRatio) / videoSize.height); // React native workaround for measuring and layout on initial load. if (isInitialRatio) { @@ -261,32 +254,37 @@ public final class ExoPlayerView extends FrameLayout { // ExoPlayer.EventListener implementation @Override - public void onLoadingChanged(boolean isLoading) { + public void onIsLoadingChanged(boolean isLoading) { // Do nothing. } @Override - public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { + public void onPlaybackStateChanged(int playbackState) { // Do nothing. } @Override - public void onPlayerError(ExoPlaybackException e) { + public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) { // Do nothing. } @Override - public void onPositionDiscontinuity(int reason) { + public void onPlayerError(PlaybackException e) { // Do nothing. } @Override - public void onTimelineChanged(Timeline timeline, Object manifest, int reason) { + public void onPositionDiscontinuity(Player.PositionInfo oldPosition, Player.PositionInfo newPosition, int reason) { // Do nothing. } @Override - public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { + public void onTimelineChanged(Timeline timeline, int reason) { + // Do nothing. + } + + @Override + public void onTracksInfoChanged(TracksInfo tracksInfo) { updateForCurrentTrackSelections(); } @@ -295,11 +293,6 @@ public final class ExoPlayerView extends FrameLayout { // Do nothing } - @Override - public void onSeekProcessed() { - // Do nothing. - } - @Override public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { // Do nothing. 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 314345bb..9a790fdc 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -35,16 +35,19 @@ 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.ExoPlayerFactory; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.MediaItem; +import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.TracksInfo; import com.google.android.exoplayer2.drm.DefaultDrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionEventListener; import com.google.android.exoplayer2.drm.DrmSessionManager; -import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; +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; @@ -53,7 +56,6 @@ 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.metadata.MetadataOutput; import com.google.android.exoplayer2.source.BehindLiveWindowException; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MergingMediaSource; @@ -70,7 +72,8 @@ 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.TrackSelectionOverrides; +import com.google.android.exoplayer2.trackselection.TrackSelectionOverrides.TrackSelectionOverride; import com.google.android.exoplayer2.ui.PlayerControlView; import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.upstream.DataSource; @@ -91,6 +94,7 @@ import java.net.CookieHandler; import java.net.CookieManager; import java.net.CookiePolicy; import java.util.ArrayList; +import java.util.List; import java.util.Locale; import java.util.UUID; import java.util.Map; @@ -108,11 +112,10 @@ import java.lang.Integer; @SuppressLint("ViewConstructor") class ReactExoplayerView extends FrameLayout implements LifecycleEventListener, - Player.EventListener, + Player.Listener, BandwidthMeter.EventListener, BecomingNoisyListener, AudioManager.OnAudioFocusChangeListener, - MetadataOutput, DrmSessionEventListener { public static final double DEFAULT_MAX_HEAP_ALLOCATION_PERCENT = 1; @@ -134,12 +137,12 @@ class ReactExoplayerView extends FrameLayout implements private final DefaultBandwidthMeter bandwidthMeter; private PlayerControlView playerControlView; private View playPauseControlContainer; - private Player.EventListener eventListener; + private Player.Listener eventListener; private ExoPlayerView exoPlayerView; private DataSource.Factory mediaDataSourceFactory; - private SimpleExoPlayer player; + private ExoPlayer player; private DefaultTrackSelector trackSelector; private boolean playerNeedsSource; @@ -202,7 +205,7 @@ class ReactExoplayerView extends FrameLayout implements private final AudioManager audioManager; private final AudioBecomingNoisyReceiver audioBecomingNoisyReceiver; - private final Handler progressHandler = new Handler() { + private final Handler progressHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -225,7 +228,7 @@ class ReactExoplayerView extends FrameLayout implements public double getPositionInFirstPeriodMsForCurrentWindow(long currentPosition) { Timeline.Window window = new Timeline.Window(); if(!player.getCurrentTimeline().isEmpty()) { - player.getCurrentTimeline().getWindow(player.getCurrentWindowIndex(), window); + player.getCurrentTimeline().getWindow(player.getCurrentMediaItemIndex(), window); } return window.windowStartTimeMs + currentPosition; } @@ -385,10 +388,17 @@ class ReactExoplayerView extends FrameLayout implements } }); - // Invoking onPlayerStateChanged event for Player - eventListener = new Player.EventListener() { + // Invoking onPlaybackStateChanged and onPlayWhenReadyChanged events for Player + eventListener = new Player.Listener() { @Override - public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { + public void onPlaybackStateChanged(int playbackState) { + reLayout(playPauseControlContainer); + //Remove this eventListener once its executed. since UI will work fine once after the reLayout is done + player.removeListener(eventListener); + } + + @Override + public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) { reLayout(playPauseControlContainer); //Remove this eventListener once its executed. since UI will work fine once after the reLayout is done player.removeListener(eventListener); @@ -472,7 +482,7 @@ class ReactExoplayerView extends FrameLayout implements } private void startBufferCheckTimer() { - SimpleExoPlayer player = this.player; + Player player = this.player; VideoEventEmitter eventEmitter = this.eventEmitter; Handler mainHandler = this.mainHandler; @@ -525,8 +535,6 @@ class ReactExoplayerView extends FrameLayout implements } else if (srcUri != null) { initializePlayerSource(self, null); } - - } catch (Exception ex) { self.playerNeedsSource = true; Log.e("ExoPlayer Exception", "Failed to initialize Player!"); @@ -559,13 +567,12 @@ class ReactExoplayerView extends FrameLayout implements DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(getContext()) .setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF); - player = new SimpleExoPlayer.Builder(getContext(), renderersFactory) + player = new ExoPlayer.Builder(getContext(), renderersFactory) .setTrackSelector​(self.trackSelector) .setBandwidthMeter(bandwidthMeter) .setLoadControl(loadControl) .build(); player.addListener(self); - player.addMetadataOutput(self); exoPlayerView.setPlayer(player); audioBecomingNoisyReceiver.setListener(self); bandwidthMeter.addEventListener(new Handler(), self); @@ -682,37 +689,47 @@ class ReactExoplayerView extends FrameLayout implements int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension : uri.getLastPathSegment()); config.setDisableDisconnectError(this.disableDisconnectError); + MediaItem mediaItem = new MediaItem.Builder().setUri(uri).build(); + DrmSessionManagerProvider drmProvider = null; + if (drmSessionManager != null) { + drmProvider = new DrmSessionManagerProvider() { + @Override + public DrmSessionManager get(MediaItem mediaItem) { + return drmSessionManager; + } + }; + } switch (type) { case C.TYPE_SS: return new SsMediaSource.Factory( new DefaultSsChunkSource.Factory(mediaDataSourceFactory), buildDataSourceFactory(false) - ).setDrmSessionManager(drmSessionManager) + ).setDrmSessionManagerProvider(drmProvider) .setLoadErrorHandlingPolicy( config.buildLoadErrorHandlingPolicy(minLoadRetryCount) - ).createMediaSource(uri); + ).createMediaSource(mediaItem); case C.TYPE_DASH: return new DashMediaSource.Factory( new DefaultDashChunkSource.Factory(mediaDataSourceFactory), buildDataSourceFactory(false) - ).setDrmSessionManager(drmSessionManager) + ).setDrmSessionManagerProvider(drmProvider) .setLoadErrorHandlingPolicy( config.buildLoadErrorHandlingPolicy(minLoadRetryCount) - ).createMediaSource(uri); + ).createMediaSource(mediaItem); case C.TYPE_HLS: return new HlsMediaSource.Factory( mediaDataSourceFactory - ).setDrmSessionManager(drmSessionManager) + ).setDrmSessionManagerProvider(drmProvider) .setLoadErrorHandlingPolicy( config.buildLoadErrorHandlingPolicy(minLoadRetryCount) - ).createMediaSource(uri); + ).createMediaSource(mediaItem); case C.TYPE_OTHER: return new ProgressiveMediaSource.Factory( mediaDataSourceFactory - ).setDrmSessionManager(drmSessionManager) + ).setDrmSessionManagerProvider(drmProvider) .setLoadErrorHandlingPolicy( config.buildLoadErrorHandlingPolicy(minLoadRetryCount) - ).createMediaSource(uri); + ).createMediaSource(mediaItem); default: { throw new IllegalStateException("Unsupported type: " + type); } @@ -741,16 +758,22 @@ class ReactExoplayerView extends FrameLayout implements } private MediaSource buildTextSource(String title, Uri uri, String mimeType, String language) { - Format textFormat = Format.createTextSampleFormat(title, mimeType, Format.NO_VALUE, language); + MediaItem.SubtitleConfiguration subtitleConfiguration = new MediaItem.SubtitleConfiguration.Builder(uri) + .setMimeType(mimeType) + .setLanguage(language) + .setSelectionFlags(C.SELECTION_FLAG_DEFAULT) + .setRoleFlags(C.ROLE_FLAG_SUBTITLE) + .setLabel(title) + .build(); return new SingleSampleMediaSource.Factory(mediaDataSourceFactory) - .createMediaSource(uri, textFormat, C.TIME_UNSET); + .createMediaSource(subtitleConfiguration, C.TIME_UNSET); } private void releasePlayer() { if (player != null) { updateResumePosition(); player.release(); - player.removeMetadataOutput(this); + player.removeListener(this); trackSelector = null; player = null; } @@ -831,8 +854,8 @@ class ReactExoplayerView extends FrameLayout implements } private void updateResumePosition() { - resumeWindow = player.getCurrentWindowIndex(); - resumePosition = player.isCurrentWindowSeekable() ? Math.max(0, player.getCurrentPosition()) + resumeWindow = player.getCurrentMediaItemIndex(); + resumePosition = player.isCurrentMediaItemSeekable() ? Math.max(0, player.getCurrentPosition()) : C.TIME_UNSET; } @@ -909,25 +932,28 @@ class ReactExoplayerView extends FrameLayout implements eventEmitter.audioBecomingNoisy(); } - // Player.EventListener implementation + // Player.Listener implementation @Override - public void onLoadingChanged(boolean isLoading) { + public void onIsLoadingChanged(boolean isLoading) { // Do nothing. } @Override - public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { - String text = "onStateChanged: playWhenReady=" + playWhenReady + ", playbackState="; - switch (playbackState) { - case Player.STATE_IDLE: - text += "idle"; - eventEmitter.idle(); - clearProgressMessageHandler(); - if (!playWhenReady) { - setKeepScreenOn(false); - } - break; + public void onEvents(Player player, Player.Events events) { + if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED) || events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)) { + int playbackState = player.getPlaybackState(); + boolean playWhenReady = player.getPlayWhenReady(); + String text = "onStateChanged: playWhenReady=" + playWhenReady + ", playbackState="; + switch (playbackState) { + case Player.STATE_IDLE: + text += "idle"; + eventEmitter.idle(); + clearProgressMessageHandler(); + if (!player.getPlayWhenReady()) { + setKeepScreenOn(false); + } + break; case Player.STATE_BUFFERING: text += "buffering"; onBuffering(true); @@ -959,8 +985,8 @@ class ReactExoplayerView extends FrameLayout implements default: text += "unknown"; break; + } } - Log.d(TAG, text); } private void startProgressHandler() { @@ -1178,7 +1204,7 @@ class ReactExoplayerView extends FrameLayout implements } @Override - public void onPositionDiscontinuity(int reason) { + public void onPositionDiscontinuity(Player.PositionInfo oldPosition, Player.PositionInfo newPosition, int reason) { if (playerNeedsSource) { // This will only occur if the user has performed a seek whilst in the error state. Update the // resume position so that if the user then retries, playback will resume from the position to @@ -1192,13 +1218,18 @@ class ReactExoplayerView extends FrameLayout implements } // When repeat is turned on, reaching the end of the video will not cause a state change // so we need to explicitly detect it. - if (reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION + if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION && player.getRepeatMode() == Player.REPEAT_MODE_ONE) { eventEmitter.end(); } } + @Override + public void onTimelineChanged(Timeline timeline, int reason) { + // Do nothing. + } + @Override public void onPlaybackStateChanged(int playbackState) { if (playbackState == Player.STATE_READY && seekTime != C.TIME_UNSET) { @@ -1211,11 +1242,6 @@ class ReactExoplayerView extends FrameLayout implements } } - @Override - public void onTimelineChanged(Timeline timeline, Object manifest, int reason) { - // Do nothing. - } - @Override public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { // Do nothing. @@ -1227,8 +1253,8 @@ class ReactExoplayerView extends FrameLayout implements } @Override - public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { - // Do Nothing. + public void onTracksInfoChanged(TracksInfo tracksInfo) { + // Do nothing. } @Override @@ -1242,51 +1268,20 @@ class ReactExoplayerView extends FrameLayout implements } @Override - public void onPlayerError(ExoPlaybackException e) { - String errorString = "ExoPlaybackException type : " + e.type; - String errorCode = "2001"; // Playback error code 2xxx (2001 - unknown playback exception) - boolean needsReInitialization = false; - Exception ex = e; - if (e.type == ExoPlaybackException.TYPE_RENDERER) { - Exception cause = e.getRendererException(); - if (cause instanceof MediaCodecRenderer.DecoderInitializationException) { - // Special case for decoder initialization failures. - MediaCodecRenderer.DecoderInitializationException decoderInitializationException = - (MediaCodecRenderer.DecoderInitializationException) cause; - if (decoderInitializationException.codecInfo == null - || decoderInitializationException.codecInfo.name == null) { - if (decoderInitializationException.getCause() instanceof MediaCodecUtil.DecoderQueryException) { - errorCode = "2011"; - errorString = getResources().getString(R.string.error_querying_decoders); - } else if (decoderInitializationException.secureDecoderRequired) { - errorCode = "2012"; - errorString = getResources().getString(R.string.error_no_secure_decoder, - decoderInitializationException.mimeType); - } else { - errorCode = "2013"; - errorString = getResources().getString(R.string.error_no_decoder, - decoderInitializationException.mimeType); - } - } else { - errorCode = "2014"; - errorString = getResources().getString(R.string.error_instantiating_decoder, - decoderInitializationException.codecInfo.name); - } - } + public void onPlayerError(PlaybackException e) { + if (e == null) { + return; } - else if (e.type == ExoPlaybackException.TYPE_SOURCE) { - // Re-initialization improves recovery speed and properly resumes - needsReInitialization = true; - errorString = getResources().getString(R.string.unrecognized_media_format); - Exception cause = e.getSourceException(); - if (cause instanceof DefaultDrmSessionManager.MissingSchemeDataException) { - errorCode = "3004"; - errorString = getResources().getString(R.string.unrecognized_media_format); - } else if(cause instanceof MediaDrmCallbackException || cause instanceof DrmSessionException) { - errorCode = "3005"; - errorString = getResources().getString(R.string.unrecognized_media_format); - // DrmSessionExceptions can be caused by a lot internal reasons for failure, in most cases they can be safely retried and playback will recover - if (!hasDrmFailed || cause instanceof DrmSessionException) { + String errorString = "ExoPlaybackException: " + PlaybackException.getErrorCodeName(e.errorCode); + String errorCode = "2" + String.valueOf(e.errorCode); + boolean needsReInitialization = false; + switch(e.errorCode) { + case PlaybackException.ERROR_CODE_DRM_DEVICE_REVOKED: + case PlaybackException.ERROR_CODE_DRM_LICENSE_ACQUISITION_FAILED: + case PlaybackException.ERROR_CODE_DRM_PROVISIONING_FAILED: + case PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR: + case PlaybackException.ERROR_CODE_DRM_UNSPECIFIED: + if (!hasDrmFailed) { // When DRM fails to reach the app level certificate server it will fail with a source error so we assume that it is DRM related and try one more time hasDrmFailed = true; playerNeedsSource = true; @@ -1295,34 +1290,11 @@ class ReactExoplayerView extends FrameLayout implements setPlayWhenReady(true); return; } - } else if (cause instanceof HttpDataSource.HttpDataSourceException) { - // this exception happens when connectivity is lost - updateResumePosition(); - initializePlayer(); - setPlayWhenReady(true); - return; - } else { - errorCode = "2021"; - errorString = getResources().getString(R.string.unrecognized_media_format); - } - if (cause != null) { - Throwable rootCause = cause.getCause(); - if (rootCause instanceof MediaDrmCallbackException) { - errorCode = "3005"; - errorString = getResources().getString(R.string.unrecognized_media_format); - if (!hasDrmFailed) { - // When DRM fails to reach the app level certificate server it will fail with a source error so we assume that it is DRM related and try one more time - hasDrmFailed = true; - playerNeedsSource = true; - updateResumePosition(); - initializePlayer(); - setPlayWhenReady(true); - return; - } - } - } + break; + default: + break; } - eventEmitter.error(errorString, ex, errorCode); + eventEmitter.error(errorString, e, errorCode); playerNeedsSource = true; if (isBehindLiveWindow(e)) { clearResumePosition(); @@ -1335,20 +1307,8 @@ class ReactExoplayerView extends FrameLayout implements } } - private static boolean isBehindLiveWindow(ExoPlaybackException e) { - Log.e("ExoPlayer Exception", e.toString()); - if (e.type != ExoPlaybackException.TYPE_SOURCE) { - return false; - } - Throwable cause = e.getSourceException(); - while (cause != null) { - if (cause instanceof BehindLiveWindowException || - cause instanceof HttpDataSource.HttpDataSourceException) { - return true; - } - cause = cause.getCause(); - } - return false; + private static boolean isBehindLiveWindow(PlaybackException e) { + return e.errorCode == PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW; } public int getTrackRendererIndex(int trackType) { @@ -1389,7 +1349,8 @@ class ReactExoplayerView extends FrameLayout implements public void clearSrc() { if (srcUri != null) { - player.stop(true); + player.stop(); + player.clearMediaItems(); this.srcUri = null; this.extension = null; this.requestHeaders = null; @@ -1466,7 +1427,8 @@ class ReactExoplayerView extends FrameLayout implements TrackGroupArray groups = info.getTrackGroups(rendererIndex); int groupIndex = C.INDEX_UNSET; - int[] tracks = {0} ; + List tracks = new ArrayList<>(); + tracks.add(0); if (TextUtils.isEmpty(type)) { type = "default"; @@ -1511,7 +1473,7 @@ class ReactExoplayerView extends FrameLayout implements Format format = group.getFormat(j); if (format.height == height) { groupIndex = i; - tracks[0] = j; + tracks.set(0, j); closestFormat = null; closestTrackIndex = -1; usingExactMatch = true; @@ -1539,7 +1501,7 @@ class ReactExoplayerView extends FrameLayout implements if (format.height < minHeight) { minHeight = format.height; groupIndex = i; - tracks[0] = j; + tracks.set(0, j); } } } @@ -1547,7 +1509,7 @@ class ReactExoplayerView extends FrameLayout implements if (closestFormat != null && closestTrackIndex != -1) { // We found the closest match instead of an exact one groupIndex = i; - tracks[0] = closestTrackIndex; + tracks.set(0, closestTrackIndex); } } } else if (trackType == C.TRACK_TYPE_TEXT && Util.SDK_INT > 18) { // Text default @@ -1564,34 +1526,32 @@ class ReactExoplayerView extends FrameLayout implements if (groupIndex == C.INDEX_UNSET && trackType == C.TRACK_TYPE_VIDEO && groups.length != 0) { // Video auto // Add all tracks as valid options for ABR to choose from TrackGroup group = groups.get(0); - int[] allTracks = new int[group.length]; + tracks = new ArrayList(group.length); + ArrayList allTracks = new ArrayList(group.length); groupIndex = 0; - for (int j = 0; j < group.length; j++) { - allTracks[j] = j; + allTracks.add(j); } // Valiate list of all tracks and add only supported formats int supportedFormatLength = 0; ArrayList supportedTrackList = new ArrayList(); - for (int g = 0; g < allTracks.length; g++) { + for (int g = 0; g < allTracks.size(); g++) { Format format = group.getFormat(g); if (isFormatSupported(format)) { supportedFormatLength++; } } - if (allTracks.length == 1) { + if (allTracks.size() == 1) { // With only one tracks we can't remove any tracks so attempt to play it anyway tracks = allTracks; } else { - tracks = new int[supportedFormatLength + 1]; - int o = 0; - for (int k = 0; k < allTracks.length; k++) { + tracks = new ArrayList<>(supportedFormatLength + 1); + for (int k = 0; k < allTracks.size(); k++) { Format format = group.getFormat(k); if (isFormatSupported(format)) { - tracks[o] = allTracks[k]; - supportedTrackList.add(allTracks[k]); - o++; + tracks.add(allTracks.get(k)); + supportedTrackList.add(allTracks.get(k)); } } } @@ -1602,11 +1562,12 @@ class ReactExoplayerView extends FrameLayout implements return; } + TrackSelectionOverride selectionOverride = new TrackSelectionOverride(groups.get(groupIndex), tracks); + DefaultTrackSelector.Parameters selectionParameters = trackSelector.getParameters() .buildUpon() .setRendererDisabled(rendererIndex, false) - .setSelectionOverride(rendererIndex, groups, - new DefaultTrackSelector.SelectionOverride(groupIndex, tracks)) + .setTrackSelectionOverrides(new TrackSelectionOverrides.Builder().addOverride(selectionOverride).build()) .build(); trackSelector.setParameters(selectionParameters); } @@ -1694,8 +1655,8 @@ class ReactExoplayerView extends FrameLayout implements public void seekTo(long positionMs) { if (player != null) { - seekTime = positionMs; player.seekTo(positionMs); + eventEmitter.seek(player.getCurrentPosition(), positionMs); } } diff --git a/android/build.gradle b/android/build.gradle index 4120458b..b382a85f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -5,11 +5,11 @@ def safeExtGet(prop, fallback) { } android { - compileSdkVersion safeExtGet('compileSdkVersion', 28) - buildToolsVersion safeExtGet('buildToolsVersion', '28.0.3') + compileSdkVersion safeExtGet('compileSdkVersion', 31) + buildToolsVersion safeExtGet('buildToolsVersion', '30.0.2') defaultConfig { - minSdkVersion safeExtGet('minSdkVersion', 16) + minSdkVersion safeExtGet('minSdkVersion', 21) targetSdkVersion safeExtGet('targetSdkVersion', 28) versionCode 1 versionName "1.0" diff --git a/examples/basic/android/build.gradle b/examples/basic/android/build.gradle index ef27ce6c..f9da7acc 100644 --- a/examples/basic/android/build.gradle +++ b/examples/basic/android/build.gradle @@ -2,9 +2,9 @@ buildscript { ext { - buildToolsVersion = "29.0.2" - minSdkVersion = 16 - compileSdkVersion = 29 + buildToolsVersion = "30.0.2" + minSdkVersion = 21 + compileSdkVersion = 31 targetSdkVersion = 29 } repositories { diff --git a/examples/video-caching/android/app/build.gradle b/examples/video-caching/android/app/build.gradle index 7a6b5205..c0195dab 100644 --- a/examples/video-caching/android/app/build.gradle +++ b/examples/video-caching/android/app/build.gradle @@ -94,12 +94,12 @@ def enableSeparateBuildPerCPUArchitecture = false def enableProguardInReleaseBuilds = false android { - compileSdkVersion 23 - buildToolsVersion "23.0.1" + compileSdkVersion 31 + buildToolsVersion "30.0.2" defaultConfig { applicationId "com.videocaching" - minSdkVersion 16 + minSdkVersion 21 targetSdkVersion 22 versionCode 1 versionName "1.0" diff --git a/examples/video-caching/android/app/src/main/AndroidManifest.xml b/examples/video-caching/android/app/src/main/AndroidManifest.xml index c0050352..51b666bc 100644 --- a/examples/video-caching/android/app/src/main/AndroidManifest.xml +++ b/examples/video-caching/android/app/src/main/AndroidManifest.xml @@ -7,7 +7,7 @@