diff --git a/CHANGELOG.md b/CHANGELOG.md index b717fa0c..5f0646fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## Changelog +- Fix Android AudioFocus bug that could cause player to not respond to play/pause in some instances [#2311](https://github.com/react-native-video/react-native-video/pull/2311) + ### Version 5.1.0-alpha9 - Add ARM64 support for windows [#2137](https://github.com/react-native-community/react-native-video/pull/2137) diff --git a/android-exoplayer/build.gradle b/android-exoplayer/build.gradle index 8ebe1d25..d005a58f 100644 --- a/android-exoplayer/build.gradle +++ b/android-exoplayer/build.gradle @@ -28,7 +28,7 @@ android { dependencies { implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}" - implementation('com.google.android.exoplayer:exoplayer:2.11.4') { + implementation('com.google.android.exoplayer:exoplayer:2.13.2') { exclude group: 'com.android.support' } @@ -37,7 +37,7 @@ dependencies { implementation "androidx.core:core:1.1.0" implementation "androidx.media:media:1.1.0" - implementation('com.google.android.exoplayer:extension-okhttp:2.11.4') { + implementation('com.google.android.exoplayer:extension-okhttp:2.13.2') { 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/ExoPlayerView.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java index 1a244f29..2e2f3169 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java @@ -17,6 +17,7 @@ import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.video.VideoListener; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.text.Cue; @@ -85,6 +86,14 @@ public final class ExoPlayerView extends FrameLayout { addViewInLayout(layout, 0, aspectRatioParams); } + private void clearVideoView() { + if (surfaceView instanceof TextureView) { + player.clearVideoTextureView((TextureView) surfaceView); + } else if (surfaceView instanceof SurfaceView) { + player.clearVideoSurfaceView((SurfaceView) surfaceView); + } + } + private void setVideoView() { if (surfaceView instanceof TextureView) { player.setVideoTextureView((TextureView) surfaceView); @@ -113,26 +122,26 @@ public final class ExoPlayerView extends FrameLayout { } /** - * Set the {@link SimpleExoPlayer} to use. The {@link SimpleExoPlayer#setTextOutput} and - * {@link SimpleExoPlayer#setVideoListener} method of the player will be called and previous + * Set the {@link SimpleExoPlayer} to use. The {@link SimpleExoPlayer#addTextOutput} and + * {@link SimpleExoPlayer#addVideoListener} method of the player will be called and previous * assignments are overridden. * * @param player The {@link SimpleExoPlayer} to use. */ public void setPlayer(SimpleExoPlayer player) { if (this.player != null) { - this.player.setTextOutput(null); - this.player.setVideoListener(null); + this.player.removeTextOutput(componentListener); + this.player.removeVideoListener(componentListener); this.player.removeListener(componentListener); - this.player.setVideoSurface(null); + clearVideoView(); } this.player = player; shutterView.setVisibility(VISIBLE); if (player != null) { setVideoView(); - player.setVideoListener(componentListener); + player.addVideoListener(componentListener); player.addListener(componentListener); - player.setTextOutput(componentListener); + player.addTextOutput(componentListener); } } @@ -202,7 +211,7 @@ public final class ExoPlayerView extends FrameLayout { layout.invalidateAspectRatio(); } - private final class ComponentListener implements SimpleExoPlayer.VideoListener, + private final class ComponentListener implements VideoListener, TextOutput, ExoPlayer.EventListener { // TextRenderer.Output implementation 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 6823bc33..9ada2571 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -36,7 +36,7 @@ import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.drm.DefaultDrmSessionManager; -import com.google.android.exoplayer2.drm.DefaultDrmSessionEventListener; +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.FrameworkMediaDrm; @@ -61,7 +61,7 @@ import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; 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.TrackSelection; +import com.google.android.exoplayer2.trackselection.ExoTrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.ui.PlayerControlView; import com.google.android.exoplayer2.upstream.BandwidthMeter; @@ -88,7 +88,7 @@ class ReactExoplayerView extends FrameLayout implements BecomingNoisyListener, AudioManager.OnAudioFocusChangeListener, MetadataOutput, - DefaultDrmSessionEventListener { + DrmSessionEventListener { private static final String TAG = "ReactExoplayerView"; @@ -129,6 +129,7 @@ class ReactExoplayerView extends FrameLayout implements private boolean isPaused; private boolean isBuffering; private boolean muted = false; + private boolean hasAudioFocus = false; private float rate = 1f; private float audioVolume = 1f; private int minLoadRetryCount = 3; @@ -443,7 +444,7 @@ class ReactExoplayerView extends FrameLayout implements @Override public void run() { if (player == null) { - TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(); + ExoTrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(); trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); trackSelector.setParameters(trackSelector.buildUponParameters() .setMaxVideoBitrate(maxBitRate == 0 ? Integer.MAX_VALUE : maxBitRate)); @@ -458,23 +459,11 @@ class ReactExoplayerView extends FrameLayout implements DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(getContext()) .setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF); - // DRM - DrmSessionManager drmSessionManager = null; - if (self.drmUUID != null) { - try { - drmSessionManager = buildDrmSessionManager(self.drmUUID, self.drmLicenseUrl, - self.drmLicenseHeader); - } catch (UnsupportedDrmException e) { - int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported - : (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME - ? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown); - eventEmitter.error(getResources().getString(errorStringId), e); - return; - } - } - // End DRM - player = ExoPlayerFactory.newSimpleInstance(getContext(), renderersFactory, - trackSelector, defaultLoadControl, drmSessionManager, bandwidthMeter); + player = new SimpleExoPlayer.Builder(getContext(), renderersFactory) + .setTrackSelector​(trackSelector) + .setBandwidthMeter(bandwidthMeter) + .setLoadControl(defaultLoadControl) + .build(); player.addListener(self); player.addMetadataOutput(self); exoPlayerView.setPlayer(player); @@ -489,8 +478,24 @@ class ReactExoplayerView extends FrameLayout implements if (playerNeedsSource && srcUri != null) { exoPlayerView.invalidateAspectRatio(); + // DRM + DrmSessionManager drmSessionManager = null; + if (self.drmUUID != null) { + try { + drmSessionManager = buildDrmSessionManager(self.drmUUID, self.drmLicenseUrl, + self.drmLicenseHeader); + } catch (UnsupportedDrmException e) { + int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported + : (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME + ? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown); + eventEmitter.error(getResources().getString(errorStringId), e); + return; + } + } + // End DRM + ArrayList mediaSourceList = buildTextSources(); - MediaSource videoSource = buildMediaSource(srcUri, extension); + MediaSource videoSource = buildMediaSource(srcUri, extension, drmSessionManager); MediaSource mediaSource; if (mediaSourceList.size() == 0) { mediaSource = videoSource; @@ -521,7 +526,7 @@ class ReactExoplayerView extends FrameLayout implements }, 1); } - private DrmSessionManager buildDrmSessionManager(UUID uuid, + private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray) throws UnsupportedDrmException { if (Util.SDK_INT < 18) { return null; @@ -534,11 +539,11 @@ class ReactExoplayerView extends FrameLayout implements keyRequestPropertiesArray[i + 1]); } } - return new DefaultDrmSessionManager<>(uuid, + return new DefaultDrmSessionManager(uuid, FrameworkMediaDrm.newInstance(uuid), drmCallback, null, false, 3); } - private MediaSource buildMediaSource(Uri uri, String overrideExtension) { + private MediaSource buildMediaSource(Uri uri, String overrideExtension, DrmSessionManager drmSessionManager) { int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension : uri.getLastPathSegment()); switch (type) { @@ -546,26 +551,30 @@ class ReactExoplayerView extends FrameLayout implements return new SsMediaSource.Factory( new DefaultSsChunkSource.Factory(mediaDataSourceFactory), buildDataSourceFactory(false) - ).setLoadErrorHandlingPolicy( + ).setDrmSessionManager(drmSessionManager) + .setLoadErrorHandlingPolicy( config.buildLoadErrorHandlingPolicy(minLoadRetryCount) ).createMediaSource(uri); case C.TYPE_DASH: return new DashMediaSource.Factory( new DefaultDashChunkSource.Factory(mediaDataSourceFactory), buildDataSourceFactory(false) - ).setLoadErrorHandlingPolicy( + ).setDrmSessionManager(drmSessionManager) + .setLoadErrorHandlingPolicy( config.buildLoadErrorHandlingPolicy(minLoadRetryCount) ).createMediaSource(uri); case C.TYPE_HLS: return new HlsMediaSource.Factory( mediaDataSourceFactory - ).setLoadErrorHandlingPolicy( + ).setDrmSessionManager(drmSessionManager) + .setLoadErrorHandlingPolicy( config.buildLoadErrorHandlingPolicy(minLoadRetryCount) ).createMediaSource(uri); case C.TYPE_OTHER: return new ProgressiveMediaSource.Factory( mediaDataSourceFactory - ).setLoadErrorHandlingPolicy( + ).setDrmSessionManager(drmSessionManager) + .setLoadErrorHandlingPolicy( config.buildLoadErrorHandlingPolicy(minLoadRetryCount) ).createMediaSource(uri); default: { @@ -616,7 +625,7 @@ class ReactExoplayerView extends FrameLayout implements } private boolean requestAudioFocus() { - if (disableFocus || srcUri == null) { + if (disableFocus || srcUri == null || this.hasAudioFocus) { return true; } int result = audioManager.requestAudioFocus(this, @@ -631,8 +640,8 @@ class ReactExoplayerView extends FrameLayout implements } if (playWhenReady) { - boolean hasAudioFocus = requestAudioFocus(); - if (hasAudioFocus) { + this.hasAudioFocus = requestAudioFocus(); + if (this.hasAudioFocus) { player.setPlayWhenReady(true); } } else { @@ -724,6 +733,7 @@ class ReactExoplayerView extends FrameLayout implements public void onAudioFocusChange(int focusChange) { switch (focusChange) { case AudioManager.AUDIOFOCUS_LOSS: + this.hasAudioFocus = false; eventEmitter.audioFocusChanged(false); pausePlayback(); audioManager.abandonAudioFocus(this); @@ -732,6 +742,7 @@ class ReactExoplayerView extends FrameLayout implements eventEmitter.audioFocusChanged(false); break; case AudioManager.AUDIOFOCUS_GAIN: + this.hasAudioFocus = true; eventEmitter.audioFocusChanged(true); break; default: @@ -1371,23 +1382,23 @@ class ReactExoplayerView extends FrameLayout implements @Override - public void onDrmKeysLoaded() { + public void onDrmKeysLoaded(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) { Log.d("DRM Info", "onDrmKeysLoaded"); } @Override - public void onDrmSessionManagerError(Exception e) { + public void onDrmSessionManagerError(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId, Exception e) { Log.d("DRM Info", "onDrmSessionManagerError"); eventEmitter.error("onDrmSessionManagerError", e); } @Override - public void onDrmKeysRestored() { + public void onDrmKeysRestored(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) { Log.d("DRM Info", "onDrmKeysRestored"); } @Override - public void onDrmKeysRemoved() { + public void onDrmKeysRemoved(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) { Log.d("DRM Info", "onDrmKeysRemoved"); } diff --git a/ios/Video/RCTVideo.m b/ios/Video/RCTVideo.m index 989167a6..8394f6b0 100644 --- a/ios/Video/RCTVideo.m +++ b/ios/Video/RCTVideo.m @@ -718,7 +718,10 @@ static int const RCTVideoUnset = -1; } } else if (object == _player) { if([keyPath isEqualToString:playbackRate]) { - if(self.onPlaybackRateChange) { + if (_player.rate > 0 && _rate > 0 && _player.rate != _rate) { + // Playback is resuming, apply rate modifer. + [_player setRate:_rate]; + } else if(self.onPlaybackRateChange) { self.onPlaybackRateChange(@{@"playbackRate": [NSNumber numberWithFloat:_player.rate], @"target": self.reactTag}); }