diff --git a/android-exoplayer/build.gradle b/android-exoplayer/build.gradle index 5baf953a..ce5c82fb 100644 --- a/android-exoplayer/build.gradle +++ b/android-exoplayer/build.gradle @@ -7,7 +7,7 @@ def safeExtGet(prop, fallback) { android { compileSdkVersion safeExtGet('compileSdkVersion', 28) buildToolsVersion safeExtGet('buildToolsVersion', '28.0.3') - + compileOptions { targetCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8 @@ -23,18 +23,18 @@ android { dependencies { implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}" - implementation('com.google.android.exoplayer:exoplayer:2.9.3') { + implementation('com.google.android.exoplayer:exoplayer:2.10.4') { exclude group: 'com.android.support' } // All support libs must use the same version - implementation "androidx.annotation:annotation:1.0.0" - implementation "androidx.core:core:1.0.0" - implementation "androidx.media:media:1.0.0" + implementation "androidx.annotation:annotation:1.1.0" + implementation "androidx.core:core:1.1.0" + implementation "androidx.media:media:1.1.0" - implementation('com.google.android.exoplayer:extension-okhttp:2.9.3') { + implementation('com.google.android.exoplayer:extension-okhttp:2.10.4') { exclude group: 'com.squareup.okhttp3', module: 'okhttp' } - implementation 'com.squareup.okhttp3:okhttp:3.12.1' + implementation 'com.squareup.okhttp3:okhttp:3.14.3' } 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 4140fa10..487efeb0 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/DataSourceUtil.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/DataSourceUtil.java @@ -1,8 +1,5 @@ package com.brentvatne.exoplayer; -import android.content.Context; -import android.content.ContextWrapper; - import com.facebook.react.bridge.ReactContext; import com.facebook.react.modules.network.CookieJarContainer; import com.facebook.react.modules.network.ForwardingCookieHandler; @@ -14,12 +11,10 @@ import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.util.Util; -import okhttp3.Cookie; import okhttp3.JavaNetCookieJar; import okhttp3.OkHttpClient; import java.util.Map; - public class DataSourceUtil { private DataSourceUtil() { diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/DefaultReactExoplayerConfig.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/DefaultReactExoplayerConfig.java new file mode 100644 index 00000000..d68274b7 --- /dev/null +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/DefaultReactExoplayerConfig.java @@ -0,0 +1,26 @@ +package com.brentvatne.exoplayer; + +import android.content.Context; + +import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; +import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; +import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; + +public class DefaultReactExoplayerConfig implements ReactExoplayerConfig { + + private final DefaultBandwidthMeter bandwidthMeter; + + public DefaultReactExoplayerConfig(Context context) { + this.bandwidthMeter = new DefaultBandwidthMeter.Builder(context).build(); + } + + @Override + public LoadErrorHandlingPolicy buildLoadErrorHandlingPolicy(int minLoadRetryCount) { + return new DefaultLoadErrorHandlingPolicy(minLoadRetryCount); + } + + @Override + public DefaultBandwidthMeter getBandwidthMeter() { + return bandwidthMeter; + } +} diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/RawResourceDataSourceFactory.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/RawResourceDataSourceFactory.java index f2e7e7de..1faa6023 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/RawResourceDataSourceFactory.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/RawResourceDataSourceFactory.java @@ -15,6 +15,6 @@ class RawResourceDataSourceFactory implements DataSource.Factory { @Override public DataSource createDataSource() { - return new RawResourceDataSource(context, null); + return new RawResourceDataSource(context); } } diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerConfig.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerConfig.java new file mode 100644 index 00000000..522578b7 --- /dev/null +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerConfig.java @@ -0,0 +1,13 @@ +package com.brentvatne.exoplayer; + +import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; +import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; + +/** + * Extension points to configure the Exoplayer instance + */ +public interface ReactExoplayerConfig { + LoadErrorHandlingPolicy buildLoadErrorHandlingPolicy(int minLoadRetryCount); + + DefaultBandwidthMeter getBandwidthMeter(); +} 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 cd9b4c4d..a1fd402a 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -27,26 +27,25 @@ import com.facebook.react.bridge.WritableMap; import com.facebook.react.uimanager.ThemedReactContext; 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.ExoPlayer; import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; 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.MetadataRenderer; +import com.google.android.exoplayer2.metadata.MetadataOutput; import com.google.android.exoplayer2.source.BehindLiveWindowException; -import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MergingMediaSource; +import com.google.android.exoplayer2.source.ProgressiveMediaSource; import com.google.android.exoplayer2.source.SingleSampleMediaSource; -import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroup; +import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.dash.DashMediaSource; import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource; import com.google.android.exoplayer2.source.hls.HlsMediaSource; @@ -54,42 +53,36 @@ import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource 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.FixedTrackSelection; import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; +import com.google.android.exoplayer2.ui.PlayerControlView; +import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultAllocator; -import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; -import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; -import com.google.android.exoplayer2.ui.PlayerControlView; import java.net.CookieHandler; import java.net.CookieManager; import java.net.CookiePolicy; -import java.lang.Math; -import java.util.Map; -import java.lang.Object; import java.util.ArrayList; import java.util.Locale; +import java.util.Map; @SuppressLint("ViewConstructor") class ReactExoplayerView extends FrameLayout implements LifecycleEventListener, - ExoPlayer.EventListener, + Player.EventListener, BandwidthMeter.EventListener, BecomingNoisyListener, AudioManager.OnAudioFocusChangeListener, - MetadataRenderer.Output { + MetadataOutput { private static final String TAG = "ReactExoplayerView"; - private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter(); private static final CookieManager DEFAULT_COOKIE_MANAGER; private static final int SHOW_PROGRESS = 1; - private static final int REPORT_BANDWIDTH = 1; static { DEFAULT_COOKIE_MANAGER = new CookieManager(); @@ -97,11 +90,12 @@ class ReactExoplayerView extends FrameLayout implements } private final VideoEventEmitter eventEmitter; + private final ReactExoplayerConfig config; + private final DefaultBandwidthMeter bandwidthMeter; private PlayerControlView playerControlView; private View playPauseControlContainer; private Player.EventListener eventListener; - - private Handler mainHandler; + private ExoPlayerView exoPlayerView; private DataSource.Factory mediaDataSourceFactory; @@ -135,8 +129,7 @@ class ReactExoplayerView extends FrameLayout implements private String audioTrackType; private Dynamic audioTrackValue; private String videoTrackType; - private Dynamic videoTrackValue; - private ReadableArray audioTracks; + private Dynamic videoTrackValue; private String textTrackType; private Dynamic textTrackValue; private ReadableArray textTracks; @@ -159,7 +152,7 @@ class ReactExoplayerView extends FrameLayout implements switch (msg.what) { case SHOW_PROGRESS: if (player != null - && player.getPlaybackState() == ExoPlayer.STATE_READY + && player.getPlaybackState() == Player.STATE_READY && player.getPlayWhenReady() ) { long pos = player.getCurrentPosition(); @@ -173,14 +166,15 @@ class ReactExoplayerView extends FrameLayout implements } }; - public ReactExoplayerView(ThemedReactContext context) { + public ReactExoplayerView(ThemedReactContext context, ReactExoplayerConfig config) { super(context); this.themedReactContext = context; - this.eventEmitter = new VideoEventEmitter(context); + this.config = config; + this.bandwidthMeter = config.getBandwidthMeter(); createViews(); - + audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); themedReactContext.addLifecycleEventListener(this); audioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver(themedReactContext); @@ -198,7 +192,6 @@ class ReactExoplayerView extends FrameLayout implements private void createViews() { clearResumePosition(); mediaDataSourceFactory = buildDataSourceFactory(true); - mainHandler = new Handler(); if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) { CookieHandler.setDefault(DEFAULT_COOKIE_MANAGER); } @@ -266,7 +259,7 @@ class ReactExoplayerView extends FrameLayout implements // Internal methods /** - * Toggling the visibility of the player control view + * Toggling the visibility of the player control view */ private void togglePlayerControlVisibility() { if(player == null) return; @@ -342,24 +335,34 @@ class ReactExoplayerView extends FrameLayout implements private void initializePlayer() { ReactExoplayerView self = this; - // This ensures all props have been setted, to avoid async racing conditions. + // This ensures all props have been settled, to avoid async racing conditions. new Handler().postDelayed(new Runnable() { @Override public void run() { if (player == null) { - TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(BANDWIDTH_METER); + TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(); trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); trackSelector.setParameters(trackSelector.buildUponParameters() .setMaxVideoBitrate(maxBitRate == 0 ? Integer.MAX_VALUE : maxBitRate)); DefaultAllocator allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE); - DefaultLoadControl defaultLoadControl = new DefaultLoadControl(allocator, minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs, -1, true); - player = ExoPlayerFactory.newSimpleInstance(getContext(), trackSelector, defaultLoadControl); + DefaultLoadControl.Builder defaultLoadControlBuilder = new DefaultLoadControl.Builder(); + defaultLoadControlBuilder.setAllocator(allocator); + defaultLoadControlBuilder.setBufferDurationsMs(minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs); + defaultLoadControlBuilder.setTargetBufferBytes(-1); + defaultLoadControlBuilder.setPrioritizeTimeOverSizeThresholds(true); + DefaultLoadControl defaultLoadControl = defaultLoadControlBuilder.createDefaultLoadControl(); + DefaultRenderersFactory renderersFactory = + new DefaultRenderersFactory(getContext()) + .setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF); + // TODO: Add drmSessionManager to 5th param from: https://github.com/react-native-community/react-native-video/pull/1445 + player = ExoPlayerFactory.newSimpleInstance(getContext(), renderersFactory, + trackSelector, defaultLoadControl, null, bandwidthMeter); player.addListener(self); - player.setMetadataOutput(self); + player.addMetadataOutput(self); exoPlayerView.setPlayer(player); audioBecomingNoisyReceiver.setListener(self); - BANDWIDTH_METER.addEventListener(new Handler(), self); + bandwidthMeter.addEventListener(new Handler(), self); setPlayWhenReady(!isPaused); playerNeedsSource = true; @@ -404,21 +407,31 @@ class ReactExoplayerView extends FrameLayout implements : uri.getLastPathSegment()); switch (type) { case C.TYPE_SS: - return new SsMediaSource(uri, buildDataSourceFactory(false), - new DefaultSsChunkSource.Factory(mediaDataSourceFactory), - minLoadRetryCount, SsMediaSource.DEFAULT_LIVE_PRESENTATION_DELAY_MS, - mainHandler, null); + return new SsMediaSource.Factory( + new DefaultSsChunkSource.Factory(mediaDataSourceFactory), + buildDataSourceFactory(false) + ).setLoadErrorHandlingPolicy( + config.buildLoadErrorHandlingPolicy(minLoadRetryCount) + ).createMediaSource(uri); case C.TYPE_DASH: - return new DashMediaSource(uri, buildDataSourceFactory(false), - new DefaultDashChunkSource.Factory(mediaDataSourceFactory), - minLoadRetryCount, DashMediaSource.DEFAULT_LIVE_PRESENTATION_DELAY_MS, - mainHandler, null); + return new DashMediaSource.Factory( + new DefaultDashChunkSource.Factory(mediaDataSourceFactory), + buildDataSourceFactory(false) + ).setLoadErrorHandlingPolicy( + config.buildLoadErrorHandlingPolicy(minLoadRetryCount) + ).createMediaSource(uri); case C.TYPE_HLS: - return new HlsMediaSource(uri, mediaDataSourceFactory, - minLoadRetryCount, mainHandler, null); + return new HlsMediaSource.Factory( + mediaDataSourceFactory + ).setLoadErrorHandlingPolicy( + config.buildLoadErrorHandlingPolicy(minLoadRetryCount) + ).createMediaSource(uri); case C.TYPE_OTHER: - return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(), - mainHandler, null); + return new ProgressiveMediaSource.Factory( + mediaDataSourceFactory + ).setLoadErrorHandlingPolicy( + config.buildLoadErrorHandlingPolicy(minLoadRetryCount) + ).createMediaSource(uri); default: { throw new IllegalStateException("Unsupported type: " + type); } @@ -448,21 +461,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); - return new SingleSampleMediaSource(uri, mediaDataSourceFactory, textFormat, C.TIME_UNSET); + return new SingleSampleMediaSource.Factory(mediaDataSourceFactory) + .createMediaSource(uri, textFormat, C.TIME_UNSET); } private void releasePlayer() { if (player != null) { updateResumePosition(); player.release(); - player.setMetadataOutput(null); + player.removeMetadataOutput(this); trackSelector = null; player = null; } progressHandler.removeMessages(SHOW_PROGRESS); themedReactContext.removeLifecycleEventListener(this); audioBecomingNoisyReceiver.removeListener(); - BANDWIDTH_METER.removeEventListener(this); + bandwidthMeter.removeEventListener(this); } private boolean requestAudioFocus() { @@ -493,12 +507,12 @@ class ReactExoplayerView extends FrameLayout implements private void startPlayback() { if (player != null) { switch (player.getPlaybackState()) { - case ExoPlayer.STATE_IDLE: - case ExoPlayer.STATE_ENDED: + case Player.STATE_IDLE: + case Player.STATE_ENDED: initializePlayer(); break; - case ExoPlayer.STATE_BUFFERING: - case ExoPlayer.STATE_READY: + case Player.STATE_BUFFERING: + case Player.STATE_READY: if (!player.getPlayWhenReady()) { setPlayWhenReady(true); } @@ -551,12 +565,13 @@ class ReactExoplayerView extends FrameLayout implements /** * Returns a new DataSource factory. * - * @param useBandwidthMeter Whether to set {@link #BANDWIDTH_METER} as a listener to the new + * @param useBandwidthMeter Whether to set {@link #bandwidthMeter} as a listener to the new * DataSource factory. * @return A new DataSource factory. */ private DataSource.Factory buildDataSourceFactory(boolean useBandwidthMeter) { - return DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, useBandwidthMeter ? BANDWIDTH_METER : null, requestHeaders); + return DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, + useBandwidthMeter ? bandwidthMeter : null, requestHeaders); } // AudioManager.OnAudioFocusChangeListener implementation @@ -596,7 +611,7 @@ class ReactExoplayerView extends FrameLayout implements eventEmitter.audioBecomingNoisy(); } - // ExoPlayer.EventListener implementation + // Player.EventListener implementation @Override public void onLoadingChanged(boolean isLoading) { @@ -607,26 +622,26 @@ class ReactExoplayerView extends FrameLayout implements public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { String text = "onStateChanged: playWhenReady=" + playWhenReady + ", playbackState="; switch (playbackState) { - case ExoPlayer.STATE_IDLE: + case Player.STATE_IDLE: text += "idle"; eventEmitter.idle(); break; - case ExoPlayer.STATE_BUFFERING: + case Player.STATE_BUFFERING: text += "buffering"; onBuffering(true); break; - case ExoPlayer.STATE_READY: + case Player.STATE_READY: text += "ready"; eventEmitter.ready(); onBuffering(false); startProgressHandler(); videoLoaded(); //Setting the visibility for the playerControlView - if(playerControlView != null) { + if (playerControlView != null) { playerControlView.show(); } break; - case ExoPlayer.STATE_ENDED: + case Player.STATE_ENDED: text += "ended"; eventEmitter.end(); onStopPlayback(); @@ -752,7 +767,7 @@ 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 == ExoPlayer.DISCONTINUITY_REASON_PERIOD_TRANSITION + if (reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION && player.getRepeatMode() == Player.REPEAT_MODE_ONE) { eventEmitter.end(); } @@ -870,7 +885,9 @@ class ReactExoplayerView extends FrameLayout implements this.srcUri = uri; this.extension = extension; this.requestHeaders = headers; - this.mediaDataSourceFactory = DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, BANDWIDTH_METER, this.requestHeaders); + this.mediaDataSourceFactory = + DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, bandwidthMeter, + this.requestHeaders); if (!isOriginalSourceNull && !isSourceEqual) { reloadSource(); @@ -884,7 +901,7 @@ class ReactExoplayerView extends FrameLayout implements public void setReportBandwidth(boolean reportBandwidth) { mReportBandwidth = reportBandwidth; - } + } public void setRawSrc(final Uri uri, final String extension) { if (uri != null) { @@ -1010,7 +1027,7 @@ class ReactExoplayerView extends FrameLayout implements for (int j = 0; j < group.length; j++) { tracks[j] = j; } - } + } if (groupIndex == C.INDEX_UNSET) { trackSelector.setParameters(disableParameters); @@ -1180,7 +1197,7 @@ class ReactExoplayerView extends FrameLayout implements /** * Handling controls prop - * + * * @param controls Controls prop, if true enable controls, if false disable them */ public void setControls(boolean controls) { diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java index baa33075..c7c96346 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -60,6 +60,12 @@ public class ReactExoplayerViewManager extends ViewGroupManager createNativeModules(ReactApplicationContext reactContext) { return Collections.emptyList(); } - // Deprecated RN 0.47 - public List> createJSModules() { - return Collections.emptyList(); - } + // Deprecated RN 0.47 + public List> createJSModules() { + return Collections.emptyList(); + } @Override public List createViewManagers(ReactApplicationContext reactContext) { - return Collections.singletonList(new ReactExoplayerViewManager()); + if (config == null) { + config = new DefaultReactExoplayerConfig(reactContext); + } + return Collections.singletonList(new ReactExoplayerViewManager(config)); } }