diff --git a/android/build.gradle b/android/build.gradle index 443d0557..4c8a9cbd 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -19,28 +19,24 @@ def safeExtGet(prop) { return rootProject.ext.has(prop) ? rootProject.ext.get(prop) : project.properties["RNVideo_" + prop] } -def getExtOrDefault(name, defaultValue) { - return rootProject.ext.has(name) ? rootProject.ext.get(name) : defaultValue -} - def isNewArchitectureEnabled() { return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true" } def supportsNamespace() { - def parsed = Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.') - def major = parsed[0].toInteger() - def minor = parsed[1].toInteger() + def parsed = Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.') + def major = parsed[0].toInteger() + def minor = parsed[1].toInteger() - // Namespace support was added in 7.3.0 - if (major == 7 && minor >= 3) { - return true - } + // Namespace support was added in 7.3.0 + if (major == 7 && minor >= 3) { + return true + } - return major >= 8 + return major >= 8 } -def useExoplayerIMA = getExtOrDefault("RNVUseExoplayerIMA", false) +def useExoplayerIMA = safeExtGet("RNVUseExoplayerIMA")?.toBoolean() ?: false println "useExoplayerIMA:" + useExoplayerIMA @@ -58,13 +54,13 @@ if (isNewArchitectureEnabled()) { android { if (supportsNamespace()) { - namespace 'com.brentvatne.react' + namespace 'com.brentvatne.react' - sourceSets { - main { - manifest.srcFile "src/main/AndroidManifestNew.xml" + sourceSets { + main { + manifest.srcFile "src/main/AndroidManifestNew.xml" + } } - } } compileSdkVersion safeExtGet('compileSdkVersion') @@ -72,14 +68,14 @@ android { def agpVersion = Version.ANDROID_GRADLE_PLUGIN_VERSION if (agpVersion.tokenize('.')[0].toInteger() < 8) { - compileOptions { - sourceCompatibility JavaVersion.VERSION_11 - targetCompatibility JavaVersion.VERSION_11 - } + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } - kotlinOptions { - jvmTarget = JavaVersion.VERSION_11.majorVersion - } + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.majorVersion + } } defaultConfig { @@ -119,12 +115,12 @@ android { java { if (isNewArchitectureEnabled()) { srcDirs += [ - "src/fabric/java", - "${project.buildDir}/generated/source/codegen/java" + "src/fabric/java", + "${project.buildDir}/generated/source/codegen/java" ] } else { srcDirs += [ - "src/oldarch/java" + "src/oldarch/java" ] } } @@ -145,6 +141,7 @@ repositories { mavenCentral() } +def media3_version = safeExtGet('media3Version') def kotlin_version = safeExtGet('kotlinVersion') dependencies { @@ -152,22 +149,36 @@ dependencies { // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin //noinspection GradleDynamicVersion implementation "com.facebook.react:react-native:+" - implementation('com.google.android.exoplayer:exoplayer:2.18.1') { - exclude group: 'com.android.support' - } - implementation "androidx.annotation:annotation:1.7.0" implementation "androidx.core:core:1.9.0" - implementation "androidx.media:media:1.6.0" - implementation "androidx.activity:activity:1.6.0" - implementation('com.google.android.exoplayer:extension-okhttp:2.18.1') { - exclude group: 'com.squareup.okhttp3', module: 'okhttp' - } + // For media playback using ExoPlayer + implementation "androidx.media3:media3-exoplayer:$media3_version" + // For Smooth Streaming playback support with ExoPlayer + implementation "androidx.media3:media3-exoplayer-smoothstreaming:$media3_version" + // For DASH playback support with ExoPlayer + implementation "androidx.media3:media3-exoplayer-dash:$media3_version" + // For HLS playback support with ExoPlayer + implementation "androidx.media3:media3-exoplayer-hls:$media3_version" + // For ad insertion using the Interactive Media Ads SDK with ExoPlayer if (useExoplayerIMA) { - implementation 'com.google.android.exoplayer:extension-ima:2.18.1' + implementation "androidx.media3:media3-exoplayer-ima:$media3_version" } - implementation "com.squareup.okhttp3:okhttp:" + '$OKHTTP_VERSION' + + // For loading data using the OkHttp network stack + implementation "androidx.media3:media3-datasource-okhttp:$media3_version" + + // For building media playback UIs + implementation "androidx.media3:media3-ui:$media3_version" + + // For exposing and controlling media sessions + implementation "androidx.media3:media3-session:$media3_version" + + // Common functionality for loading data + implementation "androidx.media3:media3-datasource:$media3_version" + // Common functionality used across multiple media libraries + implementation "androidx.media3:media3-common:$media3_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" } diff --git a/android/gradle.properties b/android/gradle.properties index fda29955..72deab30 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -3,4 +3,6 @@ RNVideo_minSdkVersion=21 RNVideo_targetSdkVersion=31 RNVideo_compileSdkVersion=31 RNVideo_ndkversion=21.4.7075529 -RNVideo_buildToolsVersion=30.0.2 \ No newline at end of file +RNVideo_buildToolsVersion=30.0.2 +RNVideo_media3Version=1.1.1 +RNVideo_RNVUseExoplayerIMA=false \ No newline at end of file diff --git a/android/src/main/java/com/brentvatne/ReactBridgeUtils.java b/android/src/main/java/com/brentvatne/ReactBridgeUtils.java deleted file mode 100644 index c3391283..00000000 --- a/android/src/main/java/com/brentvatne/ReactBridgeUtils.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.brentvatne; - -import com.facebook.react.bridge.ReadableMap; - -/* -* This file define static helpers to parse in an easier way input props - */ -public class ReactBridgeUtils { - /* - retrieve key from map as int. fallback is returned if not available - */ - static public int safeGetInt(ReadableMap map, String key, int fallback) { - return map != null && map.hasKey(key) && !map.isNull(key) ? map.getInt(key) : fallback; - } - - /* - retrieve key from map as double. fallback is returned if not available - */ - static public double safeGetDouble(ReadableMap map, String key, double fallback) { - return map != null && map.hasKey(key) && !map.isNull(key) ? map.getDouble(key) : fallback; - } -} diff --git a/android/src/main/java/com/brentvatne/common/API/SubtitleStyle.kt b/android/src/main/java/com/brentvatne/common/API/SubtitleStyle.kt index cf23af7f..6e9c16f2 100644 --- a/android/src/main/java/com/brentvatne/common/API/SubtitleStyle.kt +++ b/android/src/main/java/com/brentvatne/common/API/SubtitleStyle.kt @@ -1,6 +1,6 @@ package com.brentvatne.common.API -import com.brentvatne.ReactBridgeUtils +import com.brentvatne.common.toolbox.ReactBridgeUtils import com.facebook.react.bridge.ReadableMap /** diff --git a/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.java b/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.java index bdbdf0af..b8718287 100644 --- a/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.java +++ b/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.java @@ -1,6 +1,7 @@ package com.brentvatne.common.react; import androidx.annotation.StringDef; + import android.view.View; import com.brentvatne.common.API.TimedMetadata; @@ -12,10 +13,10 @@ import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.uimanager.events.RCTEventEmitter; +import java.io.PrintWriter; +import java.io.StringWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.io.StringWriter; -import java.io.PrintWriter; import java.util.ArrayList; public class VideoEventEmitter { @@ -124,8 +125,6 @@ public class VideoEventEmitter { private static final String EVENT_PROP_STEP_FORWARD = "canStepForward"; private static final String EVENT_PROP_STEP_BACKWARD = "canStepBackward"; - private static final String EVENT_PROP_BUFFER_START = "bufferStart"; - private static final String EVENT_PROP_BUFFER_END = "bufferEnd"; private static final String EVENT_PROP_DURATION = "duration"; private static final String EVENT_PROP_PLAYABLE_DURATION = "playableDuration"; private static final String EVENT_PROP_SEEKABLE_DURATION = "seekableDuration"; @@ -241,8 +240,7 @@ public class VideoEventEmitter { load( duration, currentPosition, videoWidth, videoHeight, waAudioTracks, waTextTracks, waVideoTracks, trackId); } - - public void load(double duration, double currentPosition, int videoWidth, int videoHeight, + void load(double duration, double currentPosition, int videoWidth, int videoHeight, WritableArray audioTracks, WritableArray textTracks, WritableArray videoTracks, String trackId) { WritableMap event = Arguments.createMap(); event.putDouble(EVENT_PROP_DURATION, duration / 1000D); @@ -267,8 +265,6 @@ public class VideoEventEmitter { receiveEvent(EVENT_LOAD, event); } - - WritableMap arrayToObject(String field, WritableArray array) { WritableMap event = Arguments.createMap(); event.putArray(field, array); diff --git a/android/src/main/java/com/brentvatne/exoplayer/AudioOutput.java b/android/src/main/java/com/brentvatne/exoplayer/AudioOutput.java index 0745536b..a2609e69 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/AudioOutput.java +++ b/android/src/main/java/com/brentvatne/exoplayer/AudioOutput.java @@ -1,17 +1,18 @@ package com.brentvatne.exoplayer; import android.annotation.SuppressLint; -import com.google.android.exoplayer2.C; + +import androidx.media3.common.C; @SuppressLint("InlinedApi") public enum AudioOutput { SPEAKER("speaker", C.STREAM_TYPE_MUSIC), EARPIECE("earpiece", C.STREAM_TYPE_VOICE_CALL); - private final int streamType; + private final @C.StreamType int streamType; private final String mName; - AudioOutput(final String name, int stream) { + AudioOutput(final String name, @C.StreamType int stream) { mName = name; streamType = stream; } diff --git a/android/src/main/java/com/brentvatne/exoplayer/DataSourceUtil.java b/android/src/main/java/com/brentvatne/exoplayer/DataSourceUtil.java index c5e7047b..907d72e6 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/DataSourceUtil.java +++ b/android/src/main/java/com/brentvatne/exoplayer/DataSourceUtil.java @@ -1,20 +1,22 @@ package com.brentvatne.exoplayer; +import androidx.media3.common.util.Util; +import androidx.media3.datasource.DataSource; +import androidx.media3.datasource.DefaultDataSource; +import androidx.media3.datasource.HttpDataSource; +import androidx.media3.datasource.okhttp.OkHttpDataSource; +import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter; + 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.OkHttpDataSource; -import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; -import com.google.android.exoplayer2.upstream.DefaultDataSource; -import com.google.android.exoplayer2.upstream.HttpDataSource; -import com.google.android.exoplayer2.util.Util; + +import java.util.Map; import okhttp3.Call; import okhttp3.JavaNetCookieJar; import okhttp3.OkHttpClient; -import java.util.Map; public class DataSourceUtil { @@ -76,8 +78,7 @@ public class DataSourceUtil { } private static DataSource.Factory buildDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter, Map requestHeaders) { - return new DefaultDataSource.Factory(context, - buildHttpDataSourceFactory(context, bandwidthMeter, requestHeaders)); + return new DefaultDataSource.Factory(context, buildHttpDataSourceFactory(context, bandwidthMeter, requestHeaders)); } private static HttpDataSource.Factory buildHttpDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter, Map requestHeaders) { diff --git a/android/src/main/java/com/brentvatne/exoplayer/DefaultReactExoplayerConfig.java b/android/src/main/java/com/brentvatne/exoplayer/DefaultReactExoplayerConfig.java index 3475273c..2a9b640e 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/DefaultReactExoplayerConfig.java +++ b/android/src/main/java/com/brentvatne/exoplayer/DefaultReactExoplayerConfig.java @@ -2,9 +2,9 @@ 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; +import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter; +import androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy; +import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy; public class DefaultReactExoplayerConfig implements ReactExoplayerConfig { diff --git a/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java index 8f4a8a37..895b472f 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java @@ -2,6 +2,20 @@ package com.brentvatne.exoplayer; import android.content.Context; import androidx.core.content.ContextCompat; +import androidx.media3.common.AdViewProvider; +import androidx.media3.common.C; +import androidx.media3.common.PlaybackException; +import androidx.media3.common.PlaybackParameters; +import androidx.media3.common.Player; +import androidx.media3.common.Timeline; +import androidx.media3.common.Tracks; +import androidx.media3.common.VideoSize; +import androidx.media3.common.text.Cue; +import androidx.media3.common.util.Assertions; +import androidx.media3.exoplayer.ExoPlayer; +import androidx.media3.exoplayer.trackselection.TrackSelectionArray; +import androidx.media3.ui.SubtitleView; + import android.util.AttributeSet; import android.util.TypedValue; import android.view.Gravity; @@ -13,19 +27,6 @@ import android.widget.FrameLayout; import com.brentvatne.common.API.ResizeMode; import com.brentvatne.common.API.SubtitleStyle; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.PlaybackException; -import com.google.android.exoplayer2.PlaybackParameters; -import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.Tracks; -import com.google.android.exoplayer2.text.Cue; -import com.google.android.exoplayer2.trackselection.TrackSelectionArray; -import com.google.android.exoplayer2.ui.AdViewProvider; -import com.google.android.exoplayer2.ui.SubtitleView; -import com.google.android.exoplayer2.util.Assertions; -import com.google.android.exoplayer2.video.VideoSize; import java.util.List; @@ -106,6 +107,7 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider { player.setVideoSurfaceView((SurfaceView) surfaceView); } } + public void setSubtitleStyle(SubtitleStyle style) { // ensure we reset subtile style before reapplying it subtitleLayout.setUserDefaultStyle(); @@ -154,7 +156,7 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider { post(measureAndLayout); } - // AdsLoader.AdViewProvider implementation. + // AdsLoader.AdViewProvider implementation. @Override public ViewGroup getAdViewGroup() { @@ -194,7 +196,6 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider { layout.setResizeMode(resizeMode); post(measureAndLayout); } - } /** @@ -226,14 +227,11 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider { updateShutterViewVisibility(); } - private final Runnable measureAndLayout = new Runnable() { - @Override - public void run() { - measure( - MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY)); - layout(getLeft(), getTop(), getRight(), getBottom()); - } + private final Runnable measureAndLayout = () -> { + measure( + MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY)); + layout(getLeft(), getTop(), getRight(), getBottom()); }; private void updateForCurrentTrackSelections() { @@ -259,15 +257,11 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider { private final class ComponentListener implements Player.Listener { - // TextRenderer.Output implementation - @Override public void onCues(List cues) { subtitleLayout.setCues(cues); } - // ExoPlayer.VideoListener implementation - @Override public void onVideoSizeChanged(VideoSize videoSize) { boolean isInitialRatio = layout.getAspectRatio() == 0; @@ -284,8 +278,6 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider { shutterView.setVisibility(INVISIBLE); } - // ExoPlayer.EventListener implementation - @Override public void onIsLoadingChanged(boolean isLoading) { // Do nothing. diff --git a/android/src/main/java/com/brentvatne/exoplayer/FullScreenPlayerView.java b/android/src/main/java/com/brentvatne/exoplayer/FullScreenPlayerView.java index f57bb964..93080eae 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/FullScreenPlayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/FullScreenPlayerView.java @@ -7,74 +7,73 @@ import android.widget.FrameLayout; import android.widget.ImageButton; import androidx.activity.OnBackPressedCallback; - -import com.google.android.exoplayer2.ui.PlayerControlView; +import androidx.media3.ui.LegacyPlayerControlView; public class FullScreenPlayerView extends Dialog { - private final PlayerControlView playerControlView; - private final ExoPlayerView exoPlayerView; - private ViewGroup parent; - private final FrameLayout containerView; - private final OnBackPressedCallback onBackPressedCallback; + private final LegacyPlayerControlView playerControlView; + private final ExoPlayerView exoPlayerView; + private ViewGroup parent; + private final FrameLayout containerView; + private final OnBackPressedCallback onBackPressedCallback; - public FullScreenPlayerView(Context context, ExoPlayerView exoPlayerView, PlayerControlView playerControlView, OnBackPressedCallback onBackPressedCallback) { - super(context, android.R.style.Theme_Black_NoTitleBar_Fullscreen); - this.playerControlView = playerControlView; - this.exoPlayerView = exoPlayerView; - this.onBackPressedCallback = onBackPressedCallback; - containerView = new FrameLayout(context); - setContentView(containerView, generateDefaultLayoutParams()); - } - - @Override - public void onBackPressed() { - super.onBackPressed(); - onBackPressedCallback.handleOnBackPressed(); - } - - @Override - protected void onStart() { - parent = (FrameLayout)(exoPlayerView.getParent()); - - parent.removeView(exoPlayerView); - containerView.addView(exoPlayerView, generateDefaultLayoutParams()); - - if (playerControlView != null) { - ImageButton imageButton = playerControlView.findViewById(com.brentvatne.react.R.id.exo_fullscreen); - imageButton.setImageResource(com.google.android.exoplayer2.ui.R.drawable.exo_icon_fullscreen_exit); - imageButton.setContentDescription(getContext().getString(com.google.android.exoplayer2.ui.R.string.exo_controls_fullscreen_exit_description)); - parent.removeView(playerControlView); - containerView.addView(playerControlView, generateDefaultLayoutParams()); + public FullScreenPlayerView(Context context, ExoPlayerView exoPlayerView, LegacyPlayerControlView playerControlView, OnBackPressedCallback onBackPressedCallback) { + super(context, android.R.style.Theme_Black_NoTitleBar_Fullscreen); + this.playerControlView = playerControlView; + this.exoPlayerView = exoPlayerView; + this.onBackPressedCallback = onBackPressedCallback; + containerView = new FrameLayout(context); + setContentView(containerView, generateDefaultLayoutParams()); } - super.onStart(); - } - - @Override - protected void onStop() { - containerView.removeView(exoPlayerView); - parent.addView(exoPlayerView, generateDefaultLayoutParams()); - - if (playerControlView != null) { - ImageButton imageButton = playerControlView.findViewById(com.brentvatne.react.R.id.exo_fullscreen); - imageButton.setImageResource(com.google.android.exoplayer2.ui.R.drawable.exo_icon_fullscreen_enter); - imageButton.setContentDescription(getContext().getString(com.google.android.exoplayer2.ui.R.string.exo_controls_fullscreen_enter_description)); - containerView.removeView(playerControlView); - parent.addView(playerControlView, generateDefaultLayoutParams()); + @Override + public void onBackPressed() { + super.onBackPressed(); + onBackPressedCallback.handleOnBackPressed(); } - parent.requestLayout(); - parent = null; + @Override + protected void onStart() { + parent = (FrameLayout)(exoPlayerView.getParent()); - super.onStop(); - } + parent.removeView(exoPlayerView); + containerView.addView(exoPlayerView, generateDefaultLayoutParams()); - private FrameLayout.LayoutParams generateDefaultLayoutParams() { - FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT - ); - layoutParams.setMargins(0, 0, 0, 0); - return layoutParams; - } + if (playerControlView != null) { + ImageButton imageButton = playerControlView.findViewById(com.brentvatne.react.R.id.exo_fullscreen); + imageButton.setImageResource(androidx.media3.ui.R.drawable.exo_icon_fullscreen_exit); + imageButton.setContentDescription(getContext().getString(androidx.media3.ui.R.string.exo_controls_fullscreen_exit_description)); + parent.removeView(playerControlView); + containerView.addView(playerControlView, generateDefaultLayoutParams()); + } + + super.onStart(); + } + + @Override + protected void onStop() { + containerView.removeView(exoPlayerView); + parent.addView(exoPlayerView, generateDefaultLayoutParams()); + + if (playerControlView != null) { + ImageButton imageButton = playerControlView.findViewById(com.brentvatne.react.R.id.exo_fullscreen); + imageButton.setImageResource(androidx.media3.ui.R.drawable.exo_icon_fullscreen_enter); + imageButton.setContentDescription(getContext().getString(androidx.media3.ui.R.string.exo_controls_fullscreen_enter_description)); + containerView.removeView(playerControlView); + parent.addView(playerControlView, generateDefaultLayoutParams()); + } + + parent.requestLayout(); + parent = null; + + super.onStop(); + } + + private FrameLayout.LayoutParams generateDefaultLayoutParams() { + FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT + ); + layoutParams.setMargins(0, 0, 0, 0); + return layoutParams; + } } diff --git a/android/src/main/java/com/brentvatne/exoplayer/RawResourceDataSourceFactory.java b/android/src/main/java/com/brentvatne/exoplayer/RawResourceDataSourceFactory.java index 1faa6023..abdb42f5 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/RawResourceDataSourceFactory.java +++ b/android/src/main/java/com/brentvatne/exoplayer/RawResourceDataSourceFactory.java @@ -2,8 +2,8 @@ package com.brentvatne.exoplayer; import android.content.Context; -import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.RawResourceDataSource; +import androidx.media3.datasource.DataSource; +import androidx.media3.datasource.RawResourceDataSource; class RawResourceDataSourceFactory implements DataSource.Factory { diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerConfig.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerConfig.java index 2cc56f9a..1abb0408 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerConfig.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerConfig.java @@ -1,7 +1,7 @@ package com.brentvatne.exoplayer; -import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; -import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; +import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter; +import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy; /** * Extension points to configure the Exoplayer instance diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerLoadErrorHandlingPolicy.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerLoadErrorHandlingPolicy.java index cc55c2b7..d1793b0b 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerLoadErrorHandlingPolicy.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerLoadErrorHandlingPolicy.java @@ -1,36 +1,36 @@ package com.brentvatne.exoplayer; -import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; -import com.google.android.exoplayer2.upstream.HttpDataSource.HttpDataSourceException; -import com.google.android.exoplayer2.C; +import androidx.media3.common.C; +import androidx.media3.datasource.HttpDataSource.HttpDataSourceException; +import androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy; public final class ReactExoplayerLoadErrorHandlingPolicy extends DefaultLoadErrorHandlingPolicy { - private final int minLoadRetryCount; + private final int minLoadRetryCount; - public ReactExoplayerLoadErrorHandlingPolicy(int minLoadRetryCount) { - super(minLoadRetryCount); - this.minLoadRetryCount = minLoadRetryCount; - } - - @Override - public long getRetryDelayMsFor(LoadErrorInfo loadErrorInfo) { - String errorMessage = loadErrorInfo.exception.getMessage(); - - if ( - loadErrorInfo.exception instanceof HttpDataSourceException && - errorMessage != null && (errorMessage.equals("Unable to connect") || errorMessage.equals("Software caused connection abort")) - ) { - // Capture the error we get when there is no network connectivity and keep retrying it - return 1000; // Retry every second - } else if(loadErrorInfo.errorCount < this.minLoadRetryCount) { - return Math.min((loadErrorInfo.errorCount - 1) * 1000, 5000); // Default timeout handling - } else { - return C.TIME_UNSET; // Done retrying and will return the error immediately + public ReactExoplayerLoadErrorHandlingPolicy(int minLoadRetryCount) { + super(minLoadRetryCount); + this.minLoadRetryCount = minLoadRetryCount; } - } - @Override - public int getMinimumLoadableRetryCount(int dataType) { - return Integer.MAX_VALUE; - } + @Override + public long getRetryDelayMsFor(LoadErrorInfo loadErrorInfo) { + String errorMessage = loadErrorInfo.exception.getMessage(); + + if ( + loadErrorInfo.exception instanceof HttpDataSourceException && + errorMessage != null && (errorMessage.equals("Unable to connect") || errorMessage.equals("Software caused connection abort")) + ) { + // Capture the error we get when there is no network connectivity and keep retrying it + return 1000; // Retry every second + } else if(loadErrorInfo.errorCount < this.minLoadRetryCount) { + return Math.min((loadErrorInfo.errorCount - 1) * 1000, 5000); // Default timeout handling + } else { + return C.TIME_UNSET; // Done retrying and will return the error immediately + } + } + + @Override + public int getMinimumLoadableRetryCount(int dataType) { + return Integer.MAX_VALUE; + } } diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 5bad9ba3..903705ca 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -1,10 +1,10 @@ package com.brentvatne.exoplayer; -import static com.google.android.exoplayer2.C.CONTENT_TYPE_DASH; -import static com.google.android.exoplayer2.C.CONTENT_TYPE_HLS; -import static com.google.android.exoplayer2.C.CONTENT_TYPE_OTHER; -import static com.google.android.exoplayer2.C.CONTENT_TYPE_SS; -import static com.google.android.exoplayer2.C.TIME_END_OF_SOURCE; +import static androidx.media3.common.C.CONTENT_TYPE_DASH; +import static androidx.media3.common.C.CONTENT_TYPE_HLS; +import static androidx.media3.common.C.CONTENT_TYPE_OTHER; +import static androidx.media3.common.C.CONTENT_TYPE_SS; +import static androidx.media3.common.C.TIME_END_OF_SOURCE; import android.annotation.SuppressLint; import android.app.Activity; @@ -22,9 +22,70 @@ import android.view.accessibility.CaptioningManager; import android.widget.FrameLayout; import android.widget.ImageButton; +import androidx.activity.OnBackPressedCallback; import androidx.annotation.NonNull; import androidx.annotation.WorkerThread; -import androidx.activity.OnBackPressedCallback; +import androidx.media3.common.AudioAttributes; +import androidx.media3.common.C; +import androidx.media3.common.Format; +import androidx.media3.common.MediaItem; +import androidx.media3.common.Metadata; +import androidx.media3.common.PlaybackException; +import androidx.media3.common.PlaybackParameters; +import androidx.media3.common.Player; +import androidx.media3.common.StreamKey; +import androidx.media3.common.Timeline; +import androidx.media3.common.TrackGroup; +import androidx.media3.common.TrackSelectionOverride; +import androidx.media3.common.Tracks; +import androidx.media3.common.util.Util; +import androidx.media3.datasource.DataSource; +import androidx.media3.datasource.DataSpec; +import androidx.media3.datasource.HttpDataSource; +import androidx.media3.exoplayer.DefaultLoadControl; +import androidx.media3.exoplayer.DefaultRenderersFactory; +import androidx.media3.exoplayer.ExoPlayer; +import androidx.media3.exoplayer.dash.DashMediaSource; +import androidx.media3.exoplayer.dash.DashUtil; +import androidx.media3.exoplayer.dash.DefaultDashChunkSource; +import androidx.media3.exoplayer.dash.manifest.AdaptationSet; +import androidx.media3.exoplayer.dash.manifest.DashManifest; +import androidx.media3.exoplayer.dash.manifest.Period; +import androidx.media3.exoplayer.dash.manifest.Representation; +import androidx.media3.exoplayer.drm.DefaultDrmSessionManager; +import androidx.media3.exoplayer.drm.DefaultDrmSessionManagerProvider; +import androidx.media3.exoplayer.drm.DrmSessionEventListener; +import androidx.media3.exoplayer.drm.DrmSessionManager; +import androidx.media3.exoplayer.drm.DrmSessionManagerProvider; +import androidx.media3.exoplayer.drm.FrameworkMediaDrm; +import androidx.media3.exoplayer.drm.HttpMediaDrmCallback; +import androidx.media3.exoplayer.drm.UnsupportedDrmException; +import androidx.media3.exoplayer.hls.HlsMediaSource; +import androidx.media3.exoplayer.mediacodec.MediaCodecInfo; +import androidx.media3.exoplayer.mediacodec.MediaCodecUtil; +import androidx.media3.exoplayer.smoothstreaming.DefaultSsChunkSource; +import androidx.media3.exoplayer.smoothstreaming.SsMediaSource; +import androidx.media3.exoplayer.source.ClippingMediaSource; +import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; +import androidx.media3.exoplayer.source.MediaSource; +import androidx.media3.exoplayer.source.MergingMediaSource; +import androidx.media3.exoplayer.source.ProgressiveMediaSource; +import androidx.media3.exoplayer.source.SingleSampleMediaSource; +import androidx.media3.exoplayer.source.TrackGroupArray; +import androidx.media3.exoplayer.source.ads.AdsMediaSource; +import androidx.media3.exoplayer.trackselection.AdaptiveTrackSelection; +import androidx.media3.exoplayer.trackselection.DefaultTrackSelector; +import androidx.media3.exoplayer.trackselection.ExoTrackSelection; +import androidx.media3.exoplayer.trackselection.MappingTrackSelector; +import androidx.media3.exoplayer.trackselection.TrackSelection; +import androidx.media3.exoplayer.trackselection.TrackSelectionArray; +import androidx.media3.exoplayer.upstream.BandwidthMeter; +import androidx.media3.exoplayer.upstream.DefaultAllocator; +import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter; +import androidx.media3.extractor.metadata.emsg.EventMessage; +import androidx.media3.extractor.metadata.id3.Id3Frame; +import androidx.media3.extractor.metadata.id3.TextInformationFrame; +import androidx.media3.ui.LegacyPlayerControlView; import com.brentvatne.common.API.ResizeMode; import com.brentvatne.common.API.SubtitleStyle; @@ -42,86 +103,23 @@ import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.uimanager.ThemedReactContext; import com.google.ads.interactivemedia.v3.api.AdEvent; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.DefaultLoadControl; -import com.google.android.exoplayer2.DefaultRenderersFactory; -import com.google.android.exoplayer2.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.ExoPlayer; -import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.Tracks; -import com.google.android.exoplayer2.audio.AudioAttributes; -import com.google.android.exoplayer2.drm.DefaultDrmSessionManager; -import com.google.android.exoplayer2.drm.DefaultDrmSessionManagerProvider; -import com.google.android.exoplayer2.drm.DrmSessionEventListener; -import com.google.android.exoplayer2.drm.DrmSessionManager; -import com.google.android.exoplayer2.drm.DrmSessionManagerProvider; -import com.google.android.exoplayer2.drm.FrameworkMediaDrm; -import com.google.android.exoplayer2.drm.HttpMediaDrmCallback; -import com.google.android.exoplayer2.drm.UnsupportedDrmException; -import com.google.android.exoplayer2.mediacodec.MediaCodecInfo; -import com.google.android.exoplayer2.mediacodec.MediaCodecUtil; -import com.google.android.exoplayer2.metadata.Metadata; -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.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; -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.MappingTrackSelector; -import com.google.android.exoplayer2.trackselection.ExoTrackSelection; -import com.google.android.exoplayer2.trackselection.TrackSelectionArray; -import com.google.android.exoplayer2.trackselection.TrackSelectionOverride; -import com.google.android.exoplayer2.ui.PlayerControlView; -import com.google.android.exoplayer2.upstream.BandwidthMeter; -import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DataSpec; -import com.google.android.exoplayer2.upstream.DefaultAllocator; -import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; -import com.google.android.exoplayer2.upstream.HttpDataSource; -import com.google.android.exoplayer2.util.Util; -import com.google.android.exoplayer2.trackselection.TrackSelection; -import com.google.android.exoplayer2.source.dash.DashUtil; -import com.google.android.exoplayer2.source.dash.manifest.DashManifest; -import com.google.android.exoplayer2.source.dash.manifest.Period; -import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet; -import com.google.android.exoplayer2.source.dash.manifest.Representation; -import com.google.android.exoplayer2.metadata.Metadata; -import com.google.android.exoplayer2.metadata.emsg.EventMessage; -import com.google.android.exoplayer2.metadata.id3.Id3Frame; -import com.google.android.exoplayer2.metadata.id3.TextInformationFrame; - import com.google.android.exoplayer2.ext.ima.ImaAdsLoader; -import com.google.android.exoplayer2.source.ads.AdsMediaSource; -import com.google.android.exoplayer2.source.DefaultMediaSourceFactory; -import com.google.android.exoplayer2.source.ClippingMediaSource; - import com.google.common.collect.ImmutableList; + import java.net.CookieHandler; import java.net.CookieManager; import java.net.CookiePolicy; -import java.util.ArrayList; +import java.lang.Math; import java.util.List; +import java.util.Map; +import java.util.ArrayList; import java.util.Locale; import java.util.UUID; -import java.util.Map; -import java.lang.Thread; +import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import java.lang.Integer; @SuppressLint("ViewConstructor") public class ReactExoplayerView extends FrameLayout implements @@ -149,7 +147,7 @@ public class ReactExoplayerView extends FrameLayout implements private final VideoEventEmitter eventEmitter; private final ReactExoplayerConfig config; private final DefaultBandwidthMeter bandwidthMeter; - private PlayerControlView playerControlView; + private LegacyPlayerControlView playerControlView; private View playPauseControlContainer; private Player.Listener eventListener; @@ -189,6 +187,7 @@ public class ReactExoplayerView extends FrameLayout implements private double minBackBufferMemoryReservePercent = ReactExoplayerView.DEFAULT_MIN_BACK_BUFFER_MEMORY_RESERVE; private double minBufferMemoryReservePercent = ReactExoplayerView.DEFAULT_MIN_BUFFER_MEMORY_RESERVE; private Handler mainHandler; + private Runnable mainRunnable; // Props from React private int backBufferDurationMs = DefaultLoadControl.DEFAULT_BACK_BUFFER_DURATION_MS; @@ -330,7 +329,6 @@ public class ReactExoplayerView extends FrameLayout implements } // LifecycleEventListener implementation - @Override public void onHostResume() { if (!playInBackground || !isInBackground) { @@ -379,7 +377,7 @@ public class ReactExoplayerView extends FrameLayout implements * Toggling the visibility of the player control view */ private void togglePlayerControlVisibility() { - if(player == null) return; + if (player == null) return; reLayout(playerControlView); if (playerControlView.isVisible()) { playerControlView.hide(); @@ -393,7 +391,7 @@ public class ReactExoplayerView extends FrameLayout implements */ private void initializePlayerControl() { if (playerControlView == null) { - playerControlView = new PlayerControlView(getContext()); + playerControlView = new LegacyPlayerControlView(getContext()); } if (fullScreenPlayerView == null) { @@ -410,34 +408,25 @@ public class ReactExoplayerView extends FrameLayout implements playPauseControlContainer = playerControlView.findViewById(R.id.exo_play_pause_container); // Invoking onClick event for exoplayerView - exoPlayerView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (!isPlayingAd()) { - togglePlayerControlVisibility(); - } + exoPlayerView.setOnClickListener((View v) -> { + if (!isPlayingAd()) { + togglePlayerControlVisibility(); } }); //Handling the playButton click event ImageButton playButton = playerControlView.findViewById(R.id.exo_play); - playButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (player != null && player.getPlaybackState() == Player.STATE_ENDED) { - player.seekTo(0); - } - setPausedModifier(false); + playButton.setOnClickListener((View v) -> { + if (player != null && player.getPlaybackState() == Player.STATE_ENDED) { + player.seekTo(0); } + setPausedModifier(false); }); //Handling the pauseButton click event ImageButton pauseButton = playerControlView.findViewById(R.id.exo_pause); - pauseButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - setPausedModifier(true); - } + pauseButton.setOnClickListener((View v) -> { + setPausedModifier(true); }); //Handling the fullScreenButton click event @@ -476,7 +465,7 @@ public class ReactExoplayerView extends FrameLayout implements * Adding Player control to the frame layout */ private void addPlayerControl() { - if(playerControlView == null) return; + if (playerControlView == null) return; LayoutParams layoutParams = new LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); @@ -551,64 +540,56 @@ public class ReactExoplayerView extends FrameLayout implements ReactExoplayerView self = this; Activity activity = themedReactContext.getCurrentActivity(); // This ensures all props have been settled, to avoid async racing conditions. - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - try { - if (player == null) { - // Initialize core configuration and listeners - initializePlayerCore(self); - } - if (playerNeedsSource && srcUri != null) { - exoPlayerView.invalidateAspectRatio(); - // DRM session manager creation must be done on a different thread to prevent crashes so we start a new thread - ExecutorService es = Executors.newSingleThreadExecutor(); - es.execute(new Runnable() { - @Override - public void run() { - // DRM initialization must run on a different thread - DrmSessionManager drmSessionManager = initializePlayerDrm(self); - if (drmSessionManager == null && self.drmUUID != null) { - // Failed to intialize DRM session manager - cannot continue - DebugLog.e("ExoPlayer Exception", "Failed to initialize DRM Session Manager Framework!"); - eventEmitter.error("Failed to initialize DRM Session Manager Framework!", new Exception("DRM Session Manager Framework failure!"), "3003"); - return; - } - - if (activity == null) { - DebugLog.e("ExoPlayer Exception", "Failed to initialize Player!"); - eventEmitter.error("Failed to initialize Player!", new Exception("Current Activity is null!"), "1001"); - return; - } + mainRunnable = () -> { + try { + if (player == null) { + // Initialize core configuration and listeners + initializePlayerCore(self); + } + if (playerNeedsSource && srcUri != null) { + exoPlayerView.invalidateAspectRatio(); + // DRM session manager creation must be done on a different thread to prevent crashes so we start a new thread + ExecutorService es = Executors.newSingleThreadExecutor(); + es.execute(() -> { + // DRM initialization must run on a different thread + DrmSessionManager drmSessionManager = initializePlayerDrm(self); + if (drmSessionManager == null && self.drmUUID != null) { + // Failed to intialize DRM session manager - cannot continue + DebugLog.e("ExoPlayer Exception", "Failed to initialize DRM Session Manager Framework!"); + eventEmitter.error("Failed to initialize DRM Session Manager Framework!", new Exception("DRM Session Manager Framework failure!"), "3003"); + return; + } - // Initialize handler to run on the main thread - activity.runOnUiThread(new Runnable() { - public void run() { - try { - // Source initialization must run on the main thread - initializePlayerSource(self, drmSessionManager); - } catch (Exception ex) { - self.playerNeedsSource = true; - DebugLog.e("ExoPlayer Exception", "Failed to initialize Player!"); - DebugLog.e("ExoPlayer Exception", ex.toString()); - self.eventEmitter.error(ex.toString(), ex, "1001"); - } - } - }); + if (activity == null) { + DebugLog.e("ExoPlayer Exception", "Failed to initialize Player!"); + eventEmitter.error("Failed to initialize Player!", new Exception("Current Activity is null!"), "1001"); + return; + } + + // Initialize handler to run on the main thread + activity.runOnUiThread(() -> { + try { + // Source initialization must run on the main thread + initializePlayerSource(self, drmSessionManager); + } catch (Exception ex) { + self.playerNeedsSource = true; + DebugLog.e("ExoPlayer Exception", "Failed to initialize Player!"); + DebugLog.e("ExoPlayer Exception", ex.toString()); + self.eventEmitter.error(ex.toString(), ex, "1001"); } }); - } else if (srcUri != null) { - initializePlayerSource(self, null); - } - } catch (Exception ex) { - self.playerNeedsSource = true; - DebugLog.e("ExoPlayer Exception", "Failed to initialize Player!"); - DebugLog.e("ExoPlayer Exception", ex.toString()); - eventEmitter.error(ex.toString(), ex, "1001"); + }); + } else if (srcUri != null) { + initializePlayerSource(self, null); } + } catch (Exception ex) { + self.playerNeedsSource = true; + DebugLog.e("ExoPlayer Exception", "Failed to initialize Player!"); + DebugLog.e("ExoPlayer Exception", ex.toString()); + eventEmitter.error(ex.toString(), ex, "1001"); } - }, 1); - + }; + mainHandler.postDelayed(mainRunnable, 1); } private void initializePlayerCore(ReactExoplayerView self) { @@ -637,14 +618,14 @@ public class ReactExoplayerView extends FrameLayout implements adsLoader = new ImaAdsLoader.Builder(themedReactContext).setAdEventListener(this).build(); MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory) - .setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView); + .setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView); player = new ExoPlayer.Builder(getContext(), renderersFactory) - .setTrackSelector(self.trackSelector) - .setBandwidthMeter(bandwidthMeter) - .setLoadControl(loadControl) - .setMediaSourceFactory(mediaSourceFactory) - .build(); + .setTrackSelector(self.trackSelector) + .setBandwidthMeter(bandwidthMeter) + .setLoadControl(loadControl) + .setMediaSourceFactory(mediaSourceFactory) + .build(); player.addListener(self); player.setVolume(muted ? 0.f : audioVolume * 1); exoPlayerView.setPlayer(player); @@ -759,8 +740,13 @@ public class ReactExoplayerView extends FrameLayout implements // When DRM fails using L1 we want to switch to L3 mediaDrm.setPropertyString("securityLevel", "L3"); } - return new DefaultDrmSessionManager(uuid, mediaDrm, drmCallback, null, false, 3); - } catch(UnsupportedDrmException ex) { + DefaultDrmSessionManager drmSessionManager = new DefaultDrmSessionManager.Builder() + .setUuidAndExoMediaDrmProvider(uuid, (_uuid) -> mediaDrm) + .setKeyRequestParameters(null) + .setMultiSession(false) + .build(drmCallback); + return drmSessionManager; + } catch (UnsupportedDrmException ex) { // Unsupported DRM exceptions are handled by the calling method throw ex; } catch (Exception ex) { @@ -790,61 +776,52 @@ public class ReactExoplayerView extends FrameLayout implements ); } - MediaItem mediaItem = mediaItemBuilder.build(); - MediaSource mediaSource; + MediaSource.Factory mediaSourceFactory; DrmSessionManagerProvider drmProvider; + List streamKeys = new ArrayList(); if (drmSessionManager != null) { - drmProvider = new DrmSessionManagerProvider() { - @Override - public DrmSessionManager get(MediaItem mediaItem) { - return drmSessionManager; - } - }; + drmProvider = ((_mediaItem) -> drmSessionManager); } else { drmProvider = new DefaultDrmSessionManagerProvider(); } + switch (type) { case CONTENT_TYPE_SS: - mediaSource = new SsMediaSource.Factory( + mediaSourceFactory = new SsMediaSource.Factory( new DefaultSsChunkSource.Factory(mediaDataSourceFactory), buildDataSourceFactory(false) - ).setDrmSessionManagerProvider(drmProvider) - .setLoadErrorHandlingPolicy( - config.buildLoadErrorHandlingPolicy(minLoadRetryCount) - ).createMediaSource(mediaItem); + ); break; case CONTENT_TYPE_DASH: - mediaSource = new DashMediaSource.Factory( + mediaSourceFactory = new DashMediaSource.Factory( new DefaultDashChunkSource.Factory(mediaDataSourceFactory), buildDataSourceFactory(false) - ).setDrmSessionManagerProvider(drmProvider) - .setLoadErrorHandlingPolicy( - config.buildLoadErrorHandlingPolicy(minLoadRetryCount) - ).createMediaSource(mediaItem); + ); break; case CONTENT_TYPE_HLS: - mediaSource = new HlsMediaSource.Factory( + mediaSourceFactory = new HlsMediaSource.Factory( mediaDataSourceFactory - ).setDrmSessionManagerProvider(drmProvider) - .setLoadErrorHandlingPolicy( - config.buildLoadErrorHandlingPolicy(minLoadRetryCount) - ).createMediaSource(mediaItem); + ); break; case CONTENT_TYPE_OTHER: - mediaSource = new ProgressiveMediaSource.Factory( + mediaSourceFactory = new ProgressiveMediaSource.Factory( mediaDataSourceFactory - ).setDrmSessionManagerProvider(drmProvider) - .setLoadErrorHandlingPolicy( - config.buildLoadErrorHandlingPolicy(minLoadRetryCount) - ).createMediaSource(mediaItem); + ); break; default: { throw new IllegalStateException("Unsupported type: " + type); } } - if (startTimeMs >= 0 && endTimeMs >= 0) - { + MediaItem mediaItem = mediaItemBuilder.setStreamKeys(streamKeys).build(); + MediaSource mediaSource = mediaSourceFactory + .setDrmSessionManagerProvider(drmProvider) + .setLoadErrorHandlingPolicy( + config.buildLoadErrorHandlingPolicy(minLoadRetryCount) + ) + .createMediaSource(mediaItem); + + if (startTimeMs >= 0 && endTimeMs >= 0) { return new ClippingMediaSource(mediaSource, startTimeMs * 1000, endTimeMs * 1000); } else if (startTimeMs >= 0) { return new ClippingMediaSource(mediaSource, startTimeMs * 1000, TIME_END_OF_SOURCE); @@ -869,7 +846,9 @@ public class ReactExoplayerView extends FrameLayout implements Uri uri = Uri.parse(textTrack.getString("uri")); MediaSource textSource = buildTextSource(title, uri, textTrack.getString("type"), language); - textSources.add(textSource); + if (textSource != null) { + textSources.add(textSource); + } } return textSources; } @@ -905,6 +884,11 @@ public class ReactExoplayerView extends FrameLayout implements themedReactContext.removeLifecycleEventListener(this); audioBecomingNoisyReceiver.removeListener(); bandwidthMeter.removeEventListener(this); + + if (mainHandler != null && mainRunnable != null) { + mainHandler.removeCallbacks(mainRunnable); + mainRunnable = null; + } } private static class OnAudioFocusChangedListener implements AudioManager.OnAudioFocusChangeListener { @@ -1065,16 +1049,13 @@ public class ReactExoplayerView extends FrameLayout implements return DataSourceUtil.getDefaultHttpDataSourceFactory(this.themedReactContext, useBandwidthMeter ? bandwidthMeter : null, requestHeaders); } - // AudioBecomingNoisyListener implementation - @Override public void onAudioBecomingNoisy() { eventEmitter.audioBecomingNoisy(); } // Player.Listener implementation - @Override public void onIsLoadingChanged(boolean isLoading) { // Do nothing. @@ -1096,38 +1077,38 @@ public class ReactExoplayerView extends FrameLayout implements setKeepScreenOn(false); } break; - case Player.STATE_BUFFERING: - text += "buffering"; - onBuffering(true); - clearProgressMessageHandler(); - setKeepScreenOn(preventsDisplaySleepDuringVideoPlayback); - break; - case Player.STATE_READY: - text += "ready"; - eventEmitter.ready(); - onBuffering(false); - clearProgressMessageHandler(); // ensure there is no other message - startProgressHandler(); - videoLoaded(); - if (selectTrackWhenReady && isUsingContentResolution) { - selectTrackWhenReady = false; - setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue); - } - // Setting the visibility for the playerControlView - if (playerControlView != null) { - playerControlView.show(); - } - setKeepScreenOn(preventsDisplaySleepDuringVideoPlayback); - break; - case Player.STATE_ENDED: - text += "ended"; - eventEmitter.end(); - onStopPlayback(); - setKeepScreenOn(false); - break; - default: - text += "unknown"; - break; + case Player.STATE_BUFFERING: + text += "buffering"; + onBuffering(true); + clearProgressMessageHandler(); + setKeepScreenOn(preventsDisplaySleepDuringVideoPlayback); + break; + case Player.STATE_READY: + text += "ready"; + eventEmitter.ready(); + onBuffering(false); + clearProgressMessageHandler(); // ensure there is no other message + startProgressHandler(); + videoLoaded(); + if (selectTrackWhenReady && isUsingContentResolution) { + selectTrackWhenReady = false; + setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue); + } + // Setting the visibility for the playerControlView + if (playerControlView != null) { + playerControlView.show(); + } + setKeepScreenOn(preventsDisplaySleepDuringVideoPlayback); + break; + case Player.STATE_ENDED: + text += "ended"; + eventEmitter.end(); + onStopPlayback(); + setKeepScreenOn(false); + break; + default: + text += "unknown"; + break; } DebugLog.d(TAG, text); } @@ -1137,13 +1118,13 @@ public class ReactExoplayerView extends FrameLayout implements progressHandler.sendEmptyMessage(SHOW_PROGRESS); } - /* - The progress message handler will duplicate recursions of the onProgressMessage handler - on change of player state from any state to STATE_READY with playWhenReady is true (when - the video is not paused). This clears all existing messages. + /** + * The progress message handler will duplicate recursions of the onProgressMessage handler + * on change of player state from any state to STATE_READY with playWhenReady is true (when + * the video is not paused). This clears all existing messages. */ private void clearProgressMessageHandler() { - progressHandler.removeMessages(SHOW_PROGRESS); + progressHandler.removeMessages(SHOW_PROGRESS); } private void videoLoaded() { @@ -1171,18 +1152,15 @@ public class ReactExoplayerView extends FrameLayout implements if (this.contentStartTime != -1L) { ExecutorService es = Executors.newSingleThreadExecutor(); - es.execute(new Runnable() { - @Override - public void run() { - // To prevent ANRs caused by getVideoTrackInfo we run this on a different thread and notify the player only when we're done - ArrayList videoTracks = getVideoTrackInfoFromManifest(); - if (videoTracks != null) { - isUsingContentResolution = true; - } - eventEmitter.load(duration, currentPosition, width, height, - audioTracks, textTracks, videoTracks, trackId ); - + es.execute(() -> { + // To prevent ANRs caused by getVideoTrackInfo we run this on a different thread and notify the player only when we're done + ArrayList videoTracks = getVideoTrackInfoFromManifest(); + if (videoTracks != null) { + isUsingContentResolution = true; } + eventEmitter.load(duration, currentPosition, width, height, + audioTracks, textTracks, videoTracks, trackId ); + }); return; } @@ -1275,7 +1253,7 @@ public class ReactExoplayerView extends FrameLayout implements final Uri sourceUri = this.srcUri; final long startTime = this.contentStartTime * 1000 - 100; // s -> ms with 100ms offset - Future> result = es.submit(new Callable<>() { + Future> result = es.submit(new Callable() { final DataSource ds = dataSource; final Uri uri = sourceUri; final long startTimeUs = startTime * 1000; // ms -> us @@ -1331,7 +1309,6 @@ public class ReactExoplayerView extends FrameLayout implements return null; } - private Track exoplayerTrackToGenericTrack(Format format, int trackIndex, TrackSelection selection, TrackGroup group) { Track track = new Track(); track.setIndex(trackIndex); @@ -1393,7 +1370,6 @@ public class ReactExoplayerView extends FrameLayout implements && player.getRepeatMode() == Player.REPEAT_MODE_ONE) { eventEmitter.end(); } - } @Override @@ -1770,11 +1746,11 @@ public class ReactExoplayerView extends FrameLayout implements TrackSelectionOverride selectionOverride = new TrackSelectionOverride(groups.get(groupIndex), tracks); DefaultTrackSelector.Parameters selectionParameters = trackSelector.getParameters() - .buildUpon() - .setRendererDisabled(rendererIndex, false) - .clearOverridesOfType(selectionOverride.getType()) - .addOverride(selectionOverride) - .build(); + .buildUpon() + .setRendererDisabled(rendererIndex, false) + .clearOverridesOfType(selectionOverride.getType()) + .addOverride(selectionOverride) + .build(); trackSelector.setParameters(selectionParameters); } @@ -2053,12 +2029,21 @@ public class ReactExoplayerView extends FrameLayout implements this.drmLicenseHeader = header; } - @Override public void onDrmKeysLoaded(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) { DebugLog.d("DRM Info", "onDrmKeysLoaded"); } + @Override + public void onDrmSessionAcquired(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId, int state) { + DebugLog.d("DRM Info", "onDrmSessionAcquired"); + } + + @Override + public void onDrmSessionReleased(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) { + DebugLog.d("DRM Info", "onDrmSessionReleased"); + } + @Override public void onDrmSessionManagerError(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId, Exception e) { DebugLog.d("DRM Info", "onDrmSessionManagerError"); diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java index b4aa18e7..7da5c19c 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -1,11 +1,15 @@ package com.brentvatne.exoplayer; -import android.graphics.Color; import android.content.Context; +import android.graphics.Color; import android.net.Uri; import android.text.TextUtils; import android.util.Log; +import androidx.media3.common.util.Util; +import androidx.media3.datasource.RawResourceDataSource; +import androidx.media3.exoplayer.DefaultLoadControl; + import com.brentvatne.common.API.ResizeMode; import com.brentvatne.common.API.SubtitleStyle; import com.brentvatne.common.react.VideoEventEmitter; @@ -19,9 +23,6 @@ import com.facebook.react.common.MapBuilder; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ViewGroupManager; import com.facebook.react.uimanager.annotations.ReactProp; -import com.google.android.exoplayer2.util.Util; -import com.google.android.exoplayer2.DefaultLoadControl; -import com.google.android.exoplayer2.upstream.RawResourceDataSource; import java.util.HashMap; import java.util.ArrayList; @@ -202,7 +203,6 @@ public class ReactExoplayerViewManager extends ViewGroupManager -