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 c7cba52c..af324371 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,9 +32,11 @@ - 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) +- Fixed `onReadyForDisplay` not being called [#2721](https://github.com/react-native-video/react-native-video/pull/2721) - Fix type of `_eventDispatcher` on iOS target to match `bridge.eventDispatcher()` [#2720](https://github.com/react-native-video/react-native-video/pull/2720) ### Version 5.2.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..81aaebcd --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,31 @@ +## Issues + +* New issues are reviewed and if they require additional work will be marked with the [`triage needed`](https://github.com/react-native-video/react-native-video/labels/triage%20needed) label. This is an open call for help from the community to verify the issue and help categorize it. If an issue stays in this state for a long time, it will be closed unresolved. +* Once an issue has been reviewed it will be labeled with [`help wanted`](https://github.com/react-native-video/react-native-video/labels/help%20wanted) to indicate it is ready to be worked on. Please wait for this label before submitting a PR to avoid spending time on something that is likely to be rejected. + +## Cleanup + +* Given the history of this project, we are going to be more aggressive than usual in keeping things clean. We are working with limited resources and do not want to return to the 1000+ open issues state. This is not meant to be disrespectful or hostile. It is just a way to keep the limited resources we have focused. If your issue was closed prematurely, just chime in and engage! +* Issues and pull requests that become stale (60 days of inactivity) will be closed unless assigned and show progress. +* If the issue creator fails to provide additional information within a week when asked, we may close the issue to keep things tidy (but you can always comment back and we can reopen). + +## Pull Requests + +* Please open an issue before opening a PR to make sure the proposed change is wanted and is likely to be merged. We don't want you to waste your time! +* Pull requests require 1-3 approved reviews to be merged. +* The number of reviews depends on the complexity by adding up (max of 3): + * `1` reviewer for each PR + * `1` if more than 3 files and/or 30 lines of code changed + * `1` for each native platform code changes involved + +For example, a single file JS code change requires 1 review while a 3 files iOS code change requires 3 reviews. As soon as the reviews show up as approved without any requested changes, the PR will be merged into the next milestone. + +* Reviewers will be asked to assign a risk level when they are done from 1 (super safe) to 5 (super risky). A release with any risk level 4 or 5 will be published as a major version, otherwise as a patch or minor based on the changes. Prepare for some large version increments while we get more comfortable... (but remember versions are free). + +* If you have time to help out, look for the [`review requested`](https://github.com/react-native-video/react-native-video/labels/review%20requested) label. It will have another numeric label with it (`1`, `2`, or `3` indicating how many more reviews are needed to merge). + +## Releases + +* Aim for a bi-weekly (every other week) release to flush out whatever was approved and merge. Most people use this with a lock file (and if you don't you are doing it wrong) and should not have any issues with new bugs showing up. This is already a high risk dependency which must be tested well before going into production. Let's take advantage of that and move faster. + +Please do not harass people to review your pull request! You can tag those you feel have relevant experience but please don't abuse this as people will unfollow or mute the project if they are called too many times! diff --git a/README.md b/README.md index 998d7ded..a707071c 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ Version 3.0 features a number of changes to existing behavior. See [Updating](#u * [Audio Mixing](#audio-mixing) * [Android Expansion File Usage](#android-expansion-file-usage) * [Updating](#updating) +* [Contributing](#contributing) ## Installation @@ -1598,10 +1599,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 } } @@ -1617,6 +1618,9 @@ If your video work on Debug mode, but on Release you see only black screen, plea > ``` +## Contributing +You can find our contribution guidelines [here](CONTRIBUTING.md) + ## TODOS - [ ] Add support for playing multiple videos in a sequence (will interfere with current `repeat` implementation) 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 @@