feat(android): replace deprecated ExoPlayer2 with AndroidX media3 (#3337)

* feat(android): implement AndroidX media3 dependencies
* refactor(android): remove duplicate code
* refactor(android): remove unused codes
* feat(android): replace ExoPlayer2 with AndroidX media3
* fix(android): move default properties to gradle.properties
* revert(android): prevent security exception
* chore: align indent
* chore: remove redundant comments
* chore: reorder import
* fix: apply media3's legacy player control view
This commit is contained in:
YangJH 2023-11-18 22:13:54 +09:00 committed by GitHub
parent 1ba93f9e9d
commit f2e80e9f2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 446 additions and 481 deletions

View File

@ -19,28 +19,24 @@ def safeExtGet(prop) {
return rootProject.ext.has(prop) ? rootProject.ext.get(prop) : project.properties["RNVideo_" + 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() { def isNewArchitectureEnabled() {
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true" return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
} }
def supportsNamespace() { def supportsNamespace() {
def parsed = Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.') def parsed = Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
def major = parsed[0].toInteger() def major = parsed[0].toInteger()
def minor = parsed[1].toInteger() def minor = parsed[1].toInteger()
// Namespace support was added in 7.3.0 // Namespace support was added in 7.3.0
if (major == 7 && minor >= 3) { if (major == 7 && minor >= 3) {
return true return true
} }
return major >= 8 return major >= 8
} }
def useExoplayerIMA = getExtOrDefault("RNVUseExoplayerIMA", false) def useExoplayerIMA = safeExtGet("RNVUseExoplayerIMA")?.toBoolean() ?: false
println "useExoplayerIMA:" + useExoplayerIMA println "useExoplayerIMA:" + useExoplayerIMA
@ -58,13 +54,13 @@ if (isNewArchitectureEnabled()) {
android { android {
if (supportsNamespace()) { if (supportsNamespace()) {
namespace 'com.brentvatne.react' namespace 'com.brentvatne.react'
sourceSets { sourceSets {
main { main {
manifest.srcFile "src/main/AndroidManifestNew.xml" manifest.srcFile "src/main/AndroidManifestNew.xml"
}
} }
}
} }
compileSdkVersion safeExtGet('compileSdkVersion') compileSdkVersion safeExtGet('compileSdkVersion')
@ -72,14 +68,14 @@ android {
def agpVersion = Version.ANDROID_GRADLE_PLUGIN_VERSION def agpVersion = Version.ANDROID_GRADLE_PLUGIN_VERSION
if (agpVersion.tokenize('.')[0].toInteger() < 8) { if (agpVersion.tokenize('.')[0].toInteger() < 8) {
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_11 sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11
} }
kotlinOptions { kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.majorVersion jvmTarget = JavaVersion.VERSION_11.majorVersion
} }
} }
defaultConfig { defaultConfig {
@ -119,12 +115,12 @@ android {
java { java {
if (isNewArchitectureEnabled()) { if (isNewArchitectureEnabled()) {
srcDirs += [ srcDirs += [
"src/fabric/java", "src/fabric/java",
"${project.buildDir}/generated/source/codegen/java" "${project.buildDir}/generated/source/codegen/java"
] ]
} else { } else {
srcDirs += [ srcDirs += [
"src/oldarch/java" "src/oldarch/java"
] ]
} }
} }
@ -145,6 +141,7 @@ repositories {
mavenCentral() mavenCentral()
} }
def media3_version = safeExtGet('media3Version')
def kotlin_version = safeExtGet('kotlinVersion') def kotlin_version = safeExtGet('kotlinVersion')
dependencies { dependencies {
@ -152,22 +149,36 @@ dependencies {
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
//noinspection GradleDynamicVersion //noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+" 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.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') { // For media playback using ExoPlayer
exclude group: 'com.squareup.okhttp3', module: 'okhttp' 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) { 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" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
} }

View File

@ -4,3 +4,5 @@ RNVideo_targetSdkVersion=31
RNVideo_compileSdkVersion=31 RNVideo_compileSdkVersion=31
RNVideo_ndkversion=21.4.7075529 RNVideo_ndkversion=21.4.7075529
RNVideo_buildToolsVersion=30.0.2 RNVideo_buildToolsVersion=30.0.2
RNVideo_media3Version=1.1.1
RNVideo_RNVUseExoplayerIMA=false

View File

@ -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;
}
}

View File

@ -1,6 +1,6 @@
package com.brentvatne.common.API package com.brentvatne.common.API
import com.brentvatne.ReactBridgeUtils import com.brentvatne.common.toolbox.ReactBridgeUtils
import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.ReadableMap
/** /**

View File

@ -1,6 +1,7 @@
package com.brentvatne.common.react; package com.brentvatne.common.react;
import androidx.annotation.StringDef; import androidx.annotation.StringDef;
import android.view.View; import android.view.View;
import com.brentvatne.common.API.TimedMetadata; 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.bridge.WritableMap;
import com.facebook.react.uimanager.events.RCTEventEmitter; import com.facebook.react.uimanager.events.RCTEventEmitter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.io.StringWriter;
import java.io.PrintWriter;
import java.util.ArrayList; import java.util.ArrayList;
public class VideoEventEmitter { 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_FORWARD = "canStepForward";
private static final String EVENT_PROP_STEP_BACKWARD = "canStepBackward"; 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_DURATION = "duration";
private static final String EVENT_PROP_PLAYABLE_DURATION = "playableDuration"; private static final String EVENT_PROP_PLAYABLE_DURATION = "playableDuration";
private static final String EVENT_PROP_SEEKABLE_DURATION = "seekableDuration"; 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); load( duration, currentPosition, videoWidth, videoHeight, waAudioTracks, waTextTracks, waVideoTracks, trackId);
} }
void load(double duration, double currentPosition, int videoWidth, int videoHeight,
public void load(double duration, double currentPosition, int videoWidth, int videoHeight,
WritableArray audioTracks, WritableArray textTracks, WritableArray videoTracks, String trackId) { WritableArray audioTracks, WritableArray textTracks, WritableArray videoTracks, String trackId) {
WritableMap event = Arguments.createMap(); WritableMap event = Arguments.createMap();
event.putDouble(EVENT_PROP_DURATION, duration / 1000D); event.putDouble(EVENT_PROP_DURATION, duration / 1000D);
@ -267,8 +265,6 @@ public class VideoEventEmitter {
receiveEvent(EVENT_LOAD, event); receiveEvent(EVENT_LOAD, event);
} }
WritableMap arrayToObject(String field, WritableArray array) { WritableMap arrayToObject(String field, WritableArray array) {
WritableMap event = Arguments.createMap(); WritableMap event = Arguments.createMap();
event.putArray(field, array); event.putArray(field, array);

View File

@ -1,17 +1,18 @@
package com.brentvatne.exoplayer; package com.brentvatne.exoplayer;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import com.google.android.exoplayer2.C;
import androidx.media3.common.C;
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
public enum AudioOutput { public enum AudioOutput {
SPEAKER("speaker", C.STREAM_TYPE_MUSIC), SPEAKER("speaker", C.STREAM_TYPE_MUSIC),
EARPIECE("earpiece", C.STREAM_TYPE_VOICE_CALL); EARPIECE("earpiece", C.STREAM_TYPE_VOICE_CALL);
private final int streamType; private final @C.StreamType int streamType;
private final String mName; private final String mName;
AudioOutput(final String name, int stream) { AudioOutput(final String name, @C.StreamType int stream) {
mName = name; mName = name;
streamType = stream; streamType = stream;
} }

View File

@ -1,20 +1,22 @@
package com.brentvatne.exoplayer; 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.bridge.ReactContext;
import com.facebook.react.modules.network.CookieJarContainer; import com.facebook.react.modules.network.CookieJarContainer;
import com.facebook.react.modules.network.ForwardingCookieHandler; import com.facebook.react.modules.network.ForwardingCookieHandler;
import com.facebook.react.modules.network.OkHttpClientProvider; import com.facebook.react.modules.network.OkHttpClientProvider;
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource;
import com.google.android.exoplayer2.upstream.DataSource; import java.util.Map;
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 okhttp3.Call; import okhttp3.Call;
import okhttp3.JavaNetCookieJar; import okhttp3.JavaNetCookieJar;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import java.util.Map;
public class DataSourceUtil { public class DataSourceUtil {
@ -76,8 +78,7 @@ public class DataSourceUtil {
} }
private static DataSource.Factory buildDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter, Map<String, String> requestHeaders) { private static DataSource.Factory buildDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter, Map<String, String> requestHeaders) {
return new DefaultDataSource.Factory(context, return new DefaultDataSource.Factory(context, buildHttpDataSourceFactory(context, bandwidthMeter, requestHeaders));
buildHttpDataSourceFactory(context, bandwidthMeter, requestHeaders));
} }
private static HttpDataSource.Factory buildHttpDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter, Map<String, String> requestHeaders) { private static HttpDataSource.Factory buildHttpDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter, Map<String, String> requestHeaders) {

View File

@ -2,9 +2,9 @@ package com.brentvatne.exoplayer;
import android.content.Context; import android.content.Context;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; import androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
public class DefaultReactExoplayerConfig implements ReactExoplayerConfig { public class DefaultReactExoplayerConfig implements ReactExoplayerConfig {

View File

@ -2,6 +2,20 @@ package com.brentvatne.exoplayer;
import android.content.Context; import android.content.Context;
import androidx.core.content.ContextCompat; 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.AttributeSet;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.Gravity; import android.view.Gravity;
@ -13,19 +27,6 @@ import android.widget.FrameLayout;
import com.brentvatne.common.API.ResizeMode; import com.brentvatne.common.API.ResizeMode;
import com.brentvatne.common.API.SubtitleStyle; 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; import java.util.List;
@ -106,6 +107,7 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider {
player.setVideoSurfaceView((SurfaceView) surfaceView); player.setVideoSurfaceView((SurfaceView) surfaceView);
} }
} }
public void setSubtitleStyle(SubtitleStyle style) { public void setSubtitleStyle(SubtitleStyle style) {
// ensure we reset subtile style before reapplying it // ensure we reset subtile style before reapplying it
subtitleLayout.setUserDefaultStyle(); subtitleLayout.setUserDefaultStyle();
@ -154,7 +156,7 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider {
post(measureAndLayout); post(measureAndLayout);
} }
// AdsLoader.AdViewProvider implementation. // AdsLoader.AdViewProvider implementation.
@Override @Override
public ViewGroup getAdViewGroup() { public ViewGroup getAdViewGroup() {
@ -194,7 +196,6 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider {
layout.setResizeMode(resizeMode); layout.setResizeMode(resizeMode);
post(measureAndLayout); post(measureAndLayout);
} }
} }
/** /**
@ -226,14 +227,11 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider {
updateShutterViewVisibility(); updateShutterViewVisibility();
} }
private final Runnable measureAndLayout = new Runnable() { private final Runnable measureAndLayout = () -> {
@Override measure(
public void run() { MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
measure( MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY), layout(getLeft(), getTop(), getRight(), getBottom());
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
layout(getLeft(), getTop(), getRight(), getBottom());
}
}; };
private void updateForCurrentTrackSelections() { private void updateForCurrentTrackSelections() {
@ -259,15 +257,11 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider {
private final class ComponentListener implements Player.Listener { private final class ComponentListener implements Player.Listener {
// TextRenderer.Output implementation
@Override @Override
public void onCues(List<Cue> cues) { public void onCues(List<Cue> cues) {
subtitleLayout.setCues(cues); subtitleLayout.setCues(cues);
} }
// ExoPlayer.VideoListener implementation
@Override @Override
public void onVideoSizeChanged(VideoSize videoSize) { public void onVideoSizeChanged(VideoSize videoSize) {
boolean isInitialRatio = layout.getAspectRatio() == 0; boolean isInitialRatio = layout.getAspectRatio() == 0;
@ -284,8 +278,6 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider {
shutterView.setVisibility(INVISIBLE); shutterView.setVisibility(INVISIBLE);
} }
// ExoPlayer.EventListener implementation
@Override @Override
public void onIsLoadingChanged(boolean isLoading) { public void onIsLoadingChanged(boolean isLoading) {
// Do nothing. // Do nothing.

View File

@ -7,74 +7,73 @@ import android.widget.FrameLayout;
import android.widget.ImageButton; import android.widget.ImageButton;
import androidx.activity.OnBackPressedCallback; import androidx.activity.OnBackPressedCallback;
import androidx.media3.ui.LegacyPlayerControlView;
import com.google.android.exoplayer2.ui.PlayerControlView;
public class FullScreenPlayerView extends Dialog { public class FullScreenPlayerView extends Dialog {
private final PlayerControlView playerControlView; private final LegacyPlayerControlView playerControlView;
private final ExoPlayerView exoPlayerView; private final ExoPlayerView exoPlayerView;
private ViewGroup parent; private ViewGroup parent;
private final FrameLayout containerView; private final FrameLayout containerView;
private final OnBackPressedCallback onBackPressedCallback; private final OnBackPressedCallback onBackPressedCallback;
public FullScreenPlayerView(Context context, ExoPlayerView exoPlayerView, PlayerControlView playerControlView, OnBackPressedCallback onBackPressedCallback) { public FullScreenPlayerView(Context context, ExoPlayerView exoPlayerView, LegacyPlayerControlView playerControlView, OnBackPressedCallback onBackPressedCallback) {
super(context, android.R.style.Theme_Black_NoTitleBar_Fullscreen); super(context, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
this.playerControlView = playerControlView; this.playerControlView = playerControlView;
this.exoPlayerView = exoPlayerView; this.exoPlayerView = exoPlayerView;
this.onBackPressedCallback = onBackPressedCallback; this.onBackPressedCallback = onBackPressedCallback;
containerView = new FrameLayout(context); containerView = new FrameLayout(context);
setContentView(containerView, generateDefaultLayoutParams()); 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());
} }
super.onStart(); @Override
} public void onBackPressed() {
super.onBackPressed();
@Override onBackPressedCallback.handleOnBackPressed();
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());
} }
parent.requestLayout(); @Override
parent = null; protected void onStart() {
parent = (FrameLayout)(exoPlayerView.getParent());
super.onStop(); parent.removeView(exoPlayerView);
} containerView.addView(exoPlayerView, generateDefaultLayoutParams());
private FrameLayout.LayoutParams generateDefaultLayoutParams() { if (playerControlView != null) {
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams( ImageButton imageButton = playerControlView.findViewById(com.brentvatne.react.R.id.exo_fullscreen);
FrameLayout.LayoutParams.MATCH_PARENT, imageButton.setImageResource(androidx.media3.ui.R.drawable.exo_icon_fullscreen_exit);
FrameLayout.LayoutParams.MATCH_PARENT imageButton.setContentDescription(getContext().getString(androidx.media3.ui.R.string.exo_controls_fullscreen_exit_description));
); parent.removeView(playerControlView);
layoutParams.setMargins(0, 0, 0, 0); containerView.addView(playerControlView, generateDefaultLayoutParams());
return layoutParams; }
}
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;
}
} }

View File

@ -2,8 +2,8 @@ package com.brentvatne.exoplayer;
import android.content.Context; import android.content.Context;
import com.google.android.exoplayer2.upstream.DataSource; import androidx.media3.datasource.DataSource;
import com.google.android.exoplayer2.upstream.RawResourceDataSource; import androidx.media3.datasource.RawResourceDataSource;
class RawResourceDataSourceFactory implements DataSource.Factory { class RawResourceDataSourceFactory implements DataSource.Factory {

View File

@ -1,7 +1,7 @@
package com.brentvatne.exoplayer; package com.brentvatne.exoplayer;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
/** /**
* Extension points to configure the Exoplayer instance * Extension points to configure the Exoplayer instance

View File

@ -1,36 +1,36 @@
package com.brentvatne.exoplayer; package com.brentvatne.exoplayer;
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; import androidx.media3.common.C;
import com.google.android.exoplayer2.upstream.HttpDataSource.HttpDataSourceException; import androidx.media3.datasource.HttpDataSource.HttpDataSourceException;
import com.google.android.exoplayer2.C; import androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy;
public final class ReactExoplayerLoadErrorHandlingPolicy extends DefaultLoadErrorHandlingPolicy { public final class ReactExoplayerLoadErrorHandlingPolicy extends DefaultLoadErrorHandlingPolicy {
private final int minLoadRetryCount; private final int minLoadRetryCount;
public ReactExoplayerLoadErrorHandlingPolicy(int minLoadRetryCount) { public ReactExoplayerLoadErrorHandlingPolicy(int minLoadRetryCount) {
super(minLoadRetryCount); super(minLoadRetryCount);
this.minLoadRetryCount = 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
} }
}
@Override @Override
public int getMinimumLoadableRetryCount(int dataType) { public long getRetryDelayMsFor(LoadErrorInfo loadErrorInfo) {
return Integer.MAX_VALUE; 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;
}
} }

View File

@ -1,10 +1,10 @@
package com.brentvatne.exoplayer; package com.brentvatne.exoplayer;
import static com.google.android.exoplayer2.C.CONTENT_TYPE_DASH; import static androidx.media3.common.C.CONTENT_TYPE_DASH;
import static com.google.android.exoplayer2.C.CONTENT_TYPE_HLS; import static androidx.media3.common.C.CONTENT_TYPE_HLS;
import static com.google.android.exoplayer2.C.CONTENT_TYPE_OTHER; import static androidx.media3.common.C.CONTENT_TYPE_OTHER;
import static com.google.android.exoplayer2.C.CONTENT_TYPE_SS; import static androidx.media3.common.C.CONTENT_TYPE_SS;
import static com.google.android.exoplayer2.C.TIME_END_OF_SOURCE; import static androidx.media3.common.C.TIME_END_OF_SOURCE;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
@ -22,9 +22,70 @@ import android.view.accessibility.CaptioningManager;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.ImageButton; import android.widget.ImageButton;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread; 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.ResizeMode;
import com.brentvatne.common.API.SubtitleStyle; 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.bridge.ReadableMap;
import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ThemedReactContext;
import com.google.ads.interactivemedia.v3.api.AdEvent; 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.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 com.google.common.collect.ImmutableList;
import java.net.CookieHandler; import java.net.CookieHandler;
import java.net.CookieManager; import java.net.CookieManager;
import java.net.CookiePolicy; import java.net.CookiePolicy;
import java.util.ArrayList; import java.lang.Math;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.util.Locale; import java.util.Locale;
import java.util.UUID; import java.util.UUID;
import java.util.Map; import java.util.concurrent.Callable;
import java.lang.Thread;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Callable;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.lang.Integer;
@SuppressLint("ViewConstructor") @SuppressLint("ViewConstructor")
public class ReactExoplayerView extends FrameLayout implements public class ReactExoplayerView extends FrameLayout implements
@ -149,7 +147,7 @@ public class ReactExoplayerView extends FrameLayout implements
private final VideoEventEmitter eventEmitter; private final VideoEventEmitter eventEmitter;
private final ReactExoplayerConfig config; private final ReactExoplayerConfig config;
private final DefaultBandwidthMeter bandwidthMeter; private final DefaultBandwidthMeter bandwidthMeter;
private PlayerControlView playerControlView; private LegacyPlayerControlView playerControlView;
private View playPauseControlContainer; private View playPauseControlContainer;
private Player.Listener eventListener; 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 minBackBufferMemoryReservePercent = ReactExoplayerView.DEFAULT_MIN_BACK_BUFFER_MEMORY_RESERVE;
private double minBufferMemoryReservePercent = ReactExoplayerView.DEFAULT_MIN_BUFFER_MEMORY_RESERVE; private double minBufferMemoryReservePercent = ReactExoplayerView.DEFAULT_MIN_BUFFER_MEMORY_RESERVE;
private Handler mainHandler; private Handler mainHandler;
private Runnable mainRunnable;
// Props from React // Props from React
private int backBufferDurationMs = DefaultLoadControl.DEFAULT_BACK_BUFFER_DURATION_MS; private int backBufferDurationMs = DefaultLoadControl.DEFAULT_BACK_BUFFER_DURATION_MS;
@ -330,7 +329,6 @@ public class ReactExoplayerView extends FrameLayout implements
} }
// LifecycleEventListener implementation // LifecycleEventListener implementation
@Override @Override
public void onHostResume() { public void onHostResume() {
if (!playInBackground || !isInBackground) { if (!playInBackground || !isInBackground) {
@ -379,7 +377,7 @@ public class ReactExoplayerView extends FrameLayout implements
* Toggling the visibility of the player control view * Toggling the visibility of the player control view
*/ */
private void togglePlayerControlVisibility() { private void togglePlayerControlVisibility() {
if(player == null) return; if (player == null) return;
reLayout(playerControlView); reLayout(playerControlView);
if (playerControlView.isVisible()) { if (playerControlView.isVisible()) {
playerControlView.hide(); playerControlView.hide();
@ -393,7 +391,7 @@ public class ReactExoplayerView extends FrameLayout implements
*/ */
private void initializePlayerControl() { private void initializePlayerControl() {
if (playerControlView == null) { if (playerControlView == null) {
playerControlView = new PlayerControlView(getContext()); playerControlView = new LegacyPlayerControlView(getContext());
} }
if (fullScreenPlayerView == null) { if (fullScreenPlayerView == null) {
@ -410,34 +408,25 @@ public class ReactExoplayerView extends FrameLayout implements
playPauseControlContainer = playerControlView.findViewById(R.id.exo_play_pause_container); playPauseControlContainer = playerControlView.findViewById(R.id.exo_play_pause_container);
// Invoking onClick event for exoplayerView // Invoking onClick event for exoplayerView
exoPlayerView.setOnClickListener(new OnClickListener() { exoPlayerView.setOnClickListener((View v) -> {
@Override if (!isPlayingAd()) {
public void onClick(View v) { togglePlayerControlVisibility();
if (!isPlayingAd()) {
togglePlayerControlVisibility();
}
} }
}); });
//Handling the playButton click event //Handling the playButton click event
ImageButton playButton = playerControlView.findViewById(R.id.exo_play); ImageButton playButton = playerControlView.findViewById(R.id.exo_play);
playButton.setOnClickListener(new View.OnClickListener() { playButton.setOnClickListener((View v) -> {
@Override if (player != null && player.getPlaybackState() == Player.STATE_ENDED) {
public void onClick(View v) { player.seekTo(0);
if (player != null && player.getPlaybackState() == Player.STATE_ENDED) {
player.seekTo(0);
}
setPausedModifier(false);
} }
setPausedModifier(false);
}); });
//Handling the pauseButton click event //Handling the pauseButton click event
ImageButton pauseButton = playerControlView.findViewById(R.id.exo_pause); ImageButton pauseButton = playerControlView.findViewById(R.id.exo_pause);
pauseButton.setOnClickListener(new View.OnClickListener() { pauseButton.setOnClickListener((View v) -> {
@Override setPausedModifier(true);
public void onClick(View v) {
setPausedModifier(true);
}
}); });
//Handling the fullScreenButton click event //Handling the fullScreenButton click event
@ -476,7 +465,7 @@ public class ReactExoplayerView extends FrameLayout implements
* Adding Player control to the frame layout * Adding Player control to the frame layout
*/ */
private void addPlayerControl() { private void addPlayerControl() {
if(playerControlView == null) return; if (playerControlView == null) return;
LayoutParams layoutParams = new LayoutParams( LayoutParams layoutParams = new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT); LayoutParams.MATCH_PARENT);
@ -551,64 +540,56 @@ public class ReactExoplayerView extends FrameLayout implements
ReactExoplayerView self = this; ReactExoplayerView self = this;
Activity activity = themedReactContext.getCurrentActivity(); Activity activity = themedReactContext.getCurrentActivity();
// This ensures all props have been settled, to avoid async racing conditions. // This ensures all props have been settled, to avoid async racing conditions.
new Handler().postDelayed(new Runnable() { mainRunnable = () -> {
@Override try {
public void run() { if (player == null) {
try { // Initialize core configuration and listeners
if (player == null) { initializePlayerCore(self);
// Initialize core configuration and listeners }
initializePlayerCore(self); if (playerNeedsSource && srcUri != null) {
} exoPlayerView.invalidateAspectRatio();
if (playerNeedsSource && srcUri != null) { // DRM session manager creation must be done on a different thread to prevent crashes so we start a new thread
exoPlayerView.invalidateAspectRatio(); ExecutorService es = Executors.newSingleThreadExecutor();
// DRM session manager creation must be done on a different thread to prevent crashes so we start a new thread es.execute(() -> {
ExecutorService es = Executors.newSingleThreadExecutor(); // DRM initialization must run on a different thread
es.execute(new Runnable() { DrmSessionManager drmSessionManager = initializePlayerDrm(self);
@Override if (drmSessionManager == null && self.drmUUID != null) {
public void run() { // Failed to intialize DRM session manager - cannot continue
// DRM initialization must run on a different thread DebugLog.e("ExoPlayer Exception", "Failed to initialize DRM Session Manager Framework!");
DrmSessionManager drmSessionManager = initializePlayerDrm(self); eventEmitter.error("Failed to initialize DRM Session Manager Framework!", new Exception("DRM Session Manager Framework failure!"), "3003");
if (drmSessionManager == null && self.drmUUID != null) { return;
// 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) { if (activity == null) {
DebugLog.e("ExoPlayer Exception", "Failed to initialize Player!"); DebugLog.e("ExoPlayer Exception", "Failed to initialize Player!");
eventEmitter.error("Failed to initialize Player!", new Exception("Current Activity is null!"), "1001"); eventEmitter.error("Failed to initialize Player!", new Exception("Current Activity is null!"), "1001");
return; return;
} }
// Initialize handler to run on the main thread // Initialize handler to run on the main thread
activity.runOnUiThread(new Runnable() { activity.runOnUiThread(() -> {
public void run() { try {
try { // Source initialization must run on the main thread
// Source initialization must run on the main thread initializePlayerSource(self, drmSessionManager);
initializePlayerSource(self, drmSessionManager); } catch (Exception ex) {
} catch (Exception ex) { self.playerNeedsSource = true;
self.playerNeedsSource = true; DebugLog.e("ExoPlayer Exception", "Failed to initialize Player!");
DebugLog.e("ExoPlayer Exception", "Failed to initialize Player!"); DebugLog.e("ExoPlayer Exception", ex.toString());
DebugLog.e("ExoPlayer Exception", ex.toString()); self.eventEmitter.error(ex.toString(), ex, "1001");
self.eventEmitter.error(ex.toString(), ex, "1001");
}
}
});
} }
}); });
} else if (srcUri != null) { });
initializePlayerSource(self, null); } 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");
} }
} 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) { private void initializePlayerCore(ReactExoplayerView self) {
@ -637,14 +618,14 @@ public class ReactExoplayerView extends FrameLayout implements
adsLoader = new ImaAdsLoader.Builder(themedReactContext).setAdEventListener(this).build(); adsLoader = new ImaAdsLoader.Builder(themedReactContext).setAdEventListener(this).build();
MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory) MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory)
.setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView); .setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView);
player = new ExoPlayer.Builder(getContext(), renderersFactory) player = new ExoPlayer.Builder(getContext(), renderersFactory)
.setTrackSelector(self.trackSelector) .setTrackSelector(self.trackSelector)
.setBandwidthMeter(bandwidthMeter) .setBandwidthMeter(bandwidthMeter)
.setLoadControl(loadControl) .setLoadControl(loadControl)
.setMediaSourceFactory(mediaSourceFactory) .setMediaSourceFactory(mediaSourceFactory)
.build(); .build();
player.addListener(self); player.addListener(self);
player.setVolume(muted ? 0.f : audioVolume * 1); player.setVolume(muted ? 0.f : audioVolume * 1);
exoPlayerView.setPlayer(player); exoPlayerView.setPlayer(player);
@ -759,8 +740,13 @@ public class ReactExoplayerView extends FrameLayout implements
// When DRM fails using L1 we want to switch to L3 // When DRM fails using L1 we want to switch to L3
mediaDrm.setPropertyString("securityLevel", "L3"); mediaDrm.setPropertyString("securityLevel", "L3");
} }
return new DefaultDrmSessionManager(uuid, mediaDrm, drmCallback, null, false, 3); DefaultDrmSessionManager drmSessionManager = new DefaultDrmSessionManager.Builder()
} catch(UnsupportedDrmException ex) { .setUuidAndExoMediaDrmProvider(uuid, (_uuid) -> mediaDrm)
.setKeyRequestParameters(null)
.setMultiSession(false)
.build(drmCallback);
return drmSessionManager;
} catch (UnsupportedDrmException ex) {
// Unsupported DRM exceptions are handled by the calling method // Unsupported DRM exceptions are handled by the calling method
throw ex; throw ex;
} catch (Exception ex) { } catch (Exception ex) {
@ -790,61 +776,52 @@ public class ReactExoplayerView extends FrameLayout implements
); );
} }
MediaItem mediaItem = mediaItemBuilder.build(); MediaSource.Factory mediaSourceFactory;
MediaSource mediaSource;
DrmSessionManagerProvider drmProvider; DrmSessionManagerProvider drmProvider;
List<StreamKey> streamKeys = new ArrayList();
if (drmSessionManager != null) { if (drmSessionManager != null) {
drmProvider = new DrmSessionManagerProvider() { drmProvider = ((_mediaItem) -> drmSessionManager);
@Override
public DrmSessionManager get(MediaItem mediaItem) {
return drmSessionManager;
}
};
} else { } else {
drmProvider = new DefaultDrmSessionManagerProvider(); drmProvider = new DefaultDrmSessionManagerProvider();
} }
switch (type) { switch (type) {
case CONTENT_TYPE_SS: case CONTENT_TYPE_SS:
mediaSource = new SsMediaSource.Factory( mediaSourceFactory = new SsMediaSource.Factory(
new DefaultSsChunkSource.Factory(mediaDataSourceFactory), new DefaultSsChunkSource.Factory(mediaDataSourceFactory),
buildDataSourceFactory(false) buildDataSourceFactory(false)
).setDrmSessionManagerProvider(drmProvider) );
.setLoadErrorHandlingPolicy(
config.buildLoadErrorHandlingPolicy(minLoadRetryCount)
).createMediaSource(mediaItem);
break; break;
case CONTENT_TYPE_DASH: case CONTENT_TYPE_DASH:
mediaSource = new DashMediaSource.Factory( mediaSourceFactory = new DashMediaSource.Factory(
new DefaultDashChunkSource.Factory(mediaDataSourceFactory), new DefaultDashChunkSource.Factory(mediaDataSourceFactory),
buildDataSourceFactory(false) buildDataSourceFactory(false)
).setDrmSessionManagerProvider(drmProvider) );
.setLoadErrorHandlingPolicy(
config.buildLoadErrorHandlingPolicy(minLoadRetryCount)
).createMediaSource(mediaItem);
break; break;
case CONTENT_TYPE_HLS: case CONTENT_TYPE_HLS:
mediaSource = new HlsMediaSource.Factory( mediaSourceFactory = new HlsMediaSource.Factory(
mediaDataSourceFactory mediaDataSourceFactory
).setDrmSessionManagerProvider(drmProvider) );
.setLoadErrorHandlingPolicy(
config.buildLoadErrorHandlingPolicy(minLoadRetryCount)
).createMediaSource(mediaItem);
break; break;
case CONTENT_TYPE_OTHER: case CONTENT_TYPE_OTHER:
mediaSource = new ProgressiveMediaSource.Factory( mediaSourceFactory = new ProgressiveMediaSource.Factory(
mediaDataSourceFactory mediaDataSourceFactory
).setDrmSessionManagerProvider(drmProvider) );
.setLoadErrorHandlingPolicy(
config.buildLoadErrorHandlingPolicy(minLoadRetryCount)
).createMediaSource(mediaItem);
break; break;
default: { default: {
throw new IllegalStateException("Unsupported type: " + type); 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); return new ClippingMediaSource(mediaSource, startTimeMs * 1000, endTimeMs * 1000);
} else if (startTimeMs >= 0) { } else if (startTimeMs >= 0) {
return new ClippingMediaSource(mediaSource, startTimeMs * 1000, TIME_END_OF_SOURCE); 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")); Uri uri = Uri.parse(textTrack.getString("uri"));
MediaSource textSource = buildTextSource(title, uri, textTrack.getString("type"), MediaSource textSource = buildTextSource(title, uri, textTrack.getString("type"),
language); language);
textSources.add(textSource); if (textSource != null) {
textSources.add(textSource);
}
} }
return textSources; return textSources;
} }
@ -905,6 +884,11 @@ public class ReactExoplayerView extends FrameLayout implements
themedReactContext.removeLifecycleEventListener(this); themedReactContext.removeLifecycleEventListener(this);
audioBecomingNoisyReceiver.removeListener(); audioBecomingNoisyReceiver.removeListener();
bandwidthMeter.removeEventListener(this); bandwidthMeter.removeEventListener(this);
if (mainHandler != null && mainRunnable != null) {
mainHandler.removeCallbacks(mainRunnable);
mainRunnable = null;
}
} }
private static class OnAudioFocusChangedListener implements AudioManager.OnAudioFocusChangeListener { 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); return DataSourceUtil.getDefaultHttpDataSourceFactory(this.themedReactContext, useBandwidthMeter ? bandwidthMeter : null, requestHeaders);
} }
// AudioBecomingNoisyListener implementation // AudioBecomingNoisyListener implementation
@Override @Override
public void onAudioBecomingNoisy() { public void onAudioBecomingNoisy() {
eventEmitter.audioBecomingNoisy(); eventEmitter.audioBecomingNoisy();
} }
// Player.Listener implementation // Player.Listener implementation
@Override @Override
public void onIsLoadingChanged(boolean isLoading) { public void onIsLoadingChanged(boolean isLoading) {
// Do nothing. // Do nothing.
@ -1096,38 +1077,38 @@ public class ReactExoplayerView extends FrameLayout implements
setKeepScreenOn(false); setKeepScreenOn(false);
} }
break; break;
case Player.STATE_BUFFERING: case Player.STATE_BUFFERING:
text += "buffering"; text += "buffering";
onBuffering(true); onBuffering(true);
clearProgressMessageHandler(); clearProgressMessageHandler();
setKeepScreenOn(preventsDisplaySleepDuringVideoPlayback); setKeepScreenOn(preventsDisplaySleepDuringVideoPlayback);
break; break;
case Player.STATE_READY: case Player.STATE_READY:
text += "ready"; text += "ready";
eventEmitter.ready(); eventEmitter.ready();
onBuffering(false); onBuffering(false);
clearProgressMessageHandler(); // ensure there is no other message clearProgressMessageHandler(); // ensure there is no other message
startProgressHandler(); startProgressHandler();
videoLoaded(); videoLoaded();
if (selectTrackWhenReady && isUsingContentResolution) { if (selectTrackWhenReady && isUsingContentResolution) {
selectTrackWhenReady = false; selectTrackWhenReady = false;
setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue); setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue);
} }
// Setting the visibility for the playerControlView // Setting the visibility for the playerControlView
if (playerControlView != null) { if (playerControlView != null) {
playerControlView.show(); playerControlView.show();
} }
setKeepScreenOn(preventsDisplaySleepDuringVideoPlayback); setKeepScreenOn(preventsDisplaySleepDuringVideoPlayback);
break; break;
case Player.STATE_ENDED: case Player.STATE_ENDED:
text += "ended"; text += "ended";
eventEmitter.end(); eventEmitter.end();
onStopPlayback(); onStopPlayback();
setKeepScreenOn(false); setKeepScreenOn(false);
break; break;
default: default:
text += "unknown"; text += "unknown";
break; break;
} }
DebugLog.d(TAG, text); DebugLog.d(TAG, text);
} }
@ -1137,13 +1118,13 @@ public class ReactExoplayerView extends FrameLayout implements
progressHandler.sendEmptyMessage(SHOW_PROGRESS); progressHandler.sendEmptyMessage(SHOW_PROGRESS);
} }
/* /**
The progress message handler will duplicate recursions of the onProgressMessage handler * 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 * 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 video is not paused). This clears all existing messages.
*/ */
private void clearProgressMessageHandler() { private void clearProgressMessageHandler() {
progressHandler.removeMessages(SHOW_PROGRESS); progressHandler.removeMessages(SHOW_PROGRESS);
} }
private void videoLoaded() { private void videoLoaded() {
@ -1171,18 +1152,15 @@ public class ReactExoplayerView extends FrameLayout implements
if (this.contentStartTime != -1L) { if (this.contentStartTime != -1L) {
ExecutorService es = Executors.newSingleThreadExecutor(); ExecutorService es = Executors.newSingleThreadExecutor();
es.execute(new Runnable() { es.execute(() -> {
@Override // To prevent ANRs caused by getVideoTrackInfo we run this on a different thread and notify the player only when we're done
public void run() { ArrayList<VideoTrack> videoTracks = getVideoTrackInfoFromManifest();
// To prevent ANRs caused by getVideoTrackInfo we run this on a different thread and notify the player only when we're done if (videoTracks != null) {
ArrayList<VideoTrack> videoTracks = getVideoTrackInfoFromManifest(); isUsingContentResolution = true;
if (videoTracks != null) {
isUsingContentResolution = true;
}
eventEmitter.load(duration, currentPosition, width, height,
audioTracks, textTracks, videoTracks, trackId );
} }
eventEmitter.load(duration, currentPosition, width, height,
audioTracks, textTracks, videoTracks, trackId );
}); });
return; return;
} }
@ -1275,7 +1253,7 @@ public class ReactExoplayerView extends FrameLayout implements
final Uri sourceUri = this.srcUri; final Uri sourceUri = this.srcUri;
final long startTime = this.contentStartTime * 1000 - 100; // s -> ms with 100ms offset final long startTime = this.contentStartTime * 1000 - 100; // s -> ms with 100ms offset
Future<ArrayList<VideoTrack>> result = es.submit(new Callable<>() { Future<ArrayList<VideoTrack>> result = es.submit(new Callable() {
final DataSource ds = dataSource; final DataSource ds = dataSource;
final Uri uri = sourceUri; final Uri uri = sourceUri;
final long startTimeUs = startTime * 1000; // ms -> us final long startTimeUs = startTime * 1000; // ms -> us
@ -1331,7 +1309,6 @@ public class ReactExoplayerView extends FrameLayout implements
return null; return null;
} }
private Track exoplayerTrackToGenericTrack(Format format, int trackIndex, TrackSelection selection, TrackGroup group) { private Track exoplayerTrackToGenericTrack(Format format, int trackIndex, TrackSelection selection, TrackGroup group) {
Track track = new Track(); Track track = new Track();
track.setIndex(trackIndex); track.setIndex(trackIndex);
@ -1393,7 +1370,6 @@ public class ReactExoplayerView extends FrameLayout implements
&& player.getRepeatMode() == Player.REPEAT_MODE_ONE) { && player.getRepeatMode() == Player.REPEAT_MODE_ONE) {
eventEmitter.end(); eventEmitter.end();
} }
} }
@Override @Override
@ -1770,11 +1746,11 @@ public class ReactExoplayerView extends FrameLayout implements
TrackSelectionOverride selectionOverride = new TrackSelectionOverride(groups.get(groupIndex), tracks); TrackSelectionOverride selectionOverride = new TrackSelectionOverride(groups.get(groupIndex), tracks);
DefaultTrackSelector.Parameters selectionParameters = trackSelector.getParameters() DefaultTrackSelector.Parameters selectionParameters = trackSelector.getParameters()
.buildUpon() .buildUpon()
.setRendererDisabled(rendererIndex, false) .setRendererDisabled(rendererIndex, false)
.clearOverridesOfType(selectionOverride.getType()) .clearOverridesOfType(selectionOverride.getType())
.addOverride(selectionOverride) .addOverride(selectionOverride)
.build(); .build();
trackSelector.setParameters(selectionParameters); trackSelector.setParameters(selectionParameters);
} }
@ -2053,12 +2029,21 @@ public class ReactExoplayerView extends FrameLayout implements
this.drmLicenseHeader = header; this.drmLicenseHeader = header;
} }
@Override @Override
public void onDrmKeysLoaded(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) { public void onDrmKeysLoaded(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) {
DebugLog.d("DRM Info", "onDrmKeysLoaded"); 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 @Override
public void onDrmSessionManagerError(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId, Exception e) { public void onDrmSessionManagerError(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId, Exception e) {
DebugLog.d("DRM Info", "onDrmSessionManagerError"); DebugLog.d("DRM Info", "onDrmSessionManagerError");

View File

@ -1,11 +1,15 @@
package com.brentvatne.exoplayer; package com.brentvatne.exoplayer;
import android.graphics.Color;
import android.content.Context; import android.content.Context;
import android.graphics.Color;
import android.net.Uri; import android.net.Uri;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; 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.ResizeMode;
import com.brentvatne.common.API.SubtitleStyle; import com.brentvatne.common.API.SubtitleStyle;
import com.brentvatne.common.react.VideoEventEmitter; 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.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager; import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp; 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.HashMap;
import java.util.ArrayList; import java.util.ArrayList;
@ -202,7 +203,6 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
videoView.setAdTagUrl(adTagUrl); videoView.setAdTagUrl(adTagUrl);
} }
@ReactProp(name = PROP_RESIZE_MODE) @ReactProp(name = PROP_RESIZE_MODE)
public void setResizeMode(final ReactExoplayerView videoView, final String resizeMode) { public void setResizeMode(final ReactExoplayerView videoView, final String resizeMode) {
switch (resizeMode) { switch (resizeMode) {

View File

@ -11,12 +11,16 @@ import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.UIManagerModule;
public class VideoManagerModule extends ReactContextBaseJavaModule { public class VideoManagerModule extends ReactContextBaseJavaModule {
ReactApplicationContext reactContext; private static final String REACT_CLASS = "VideoManager";
public VideoManagerModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@NonNull @NonNull
@Override @Override
public String getName() { public String getName() {
return "VideoManager"; return REACT_CLASS;
} }
@ReactMethod @ReactMethod
@ -31,9 +35,4 @@ public class VideoManagerModule extends ReactContextBaseJavaModule {
} }
}); });
} }
public VideoManagerModule(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
}
} }

View File

@ -2,11 +2,12 @@ package com.brentvatne.receiver;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import androidx.core.content.ContextCompat;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.media.AudioManager; import android.media.AudioManager;
import androidx.core.content.ContextCompat;
public class AudioBecomingNoisyReceiver extends BroadcastReceiver { public class AudioBecomingNoisyReceiver extends BroadcastReceiver {
private final Context context; private final Context context;

View File

@ -1,14 +1,14 @@
package com.google.android.exoplayer2.ext.ima; package com.google.android.exoplayer2.ext.ima;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.AdViewProvider;
import androidx.media3.common.Player;
import androidx.media3.datasource.DataSpec;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.source.ads.AdsLoader;
import androidx.media3.exoplayer.source.ads.AdsMediaSource;
import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ThemedReactContext;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.source.ads.AdsLoader;
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
import com.google.android.exoplayer2.ui.AdViewProvider;
import com.google.android.exoplayer2.upstream.DataSpec;
import java.io.IOException; import java.io.IOException;
@ -30,7 +30,7 @@ public class ImaAdsLoader implements AdsLoader {
} }
@Override @Override
public void start(AdsMediaSource adsMediaSource, DataSpec dataSpec, Object o, AdViewProvider adViewProvider, EventListener eventListener) { public void start(AdsMediaSource adsMediaSource, DataSpec dataSpec, Object adsId, AdViewProvider adViewProvider, EventListener eventListener) {
} }

View File

@ -56,7 +56,7 @@
android:includeFontPadding="false" android:includeFontPadding="false"
android:textColor="#FFBEBEBE"/> android:textColor="#FFBEBEBE"/>
<com.google.android.exoplayer2.ui.DefaultTimeBar <androidx.media3.ui.DefaultTimeBar
android:id="@+id/exo_progress" android:id="@+id/exo_progress"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_weight="1" android:layout_weight="1"