dependency update

This commit is contained in:
tapframe 2025-12-15 00:49:00 +05:30
parent f05366ae45
commit c3fbe31fd4

View file

@ -222,11 +222,13 @@ public class ReactExoplayerView extends FrameLayout implements
private ArrayList<Integer> rootViewChildrenOriginalVisibility = new ArrayList<Integer>(); private ArrayList<Integer> rootViewChildrenOriginalVisibility = new ArrayList<Integer>();
/* /*
* When user is seeking first called is on onPositionDiscontinuity -> DISCONTINUITY_REASON_SEEK * When user is seeking first called is on onPositionDiscontinuity ->
* DISCONTINUITY_REASON_SEEK
* Then we set if to false when playback is back in onIsPlayingChanged -> true * Then we set if to false when playback is back in onIsPlayingChanged -> true
*/ */
private boolean isSeeking = false; private boolean isSeeking = false;
private long seekPosition = -1; private long seekPosition = -1;
private boolean hasVideoEnded = false;
// Props from React // Props from React
private Source source = new Source(); private Source source = new Source();
@ -291,7 +293,8 @@ public class ReactExoplayerView extends FrameLayout implements
lastPos = pos; lastPos = pos;
lastBufferDuration = bufferedDuration; lastBufferDuration = bufferedDuration;
lastDuration = duration; lastDuration = duration;
eventEmitter.onVideoProgress.invoke(pos, bufferedDuration, player.getDuration(), getPositionInFirstPeriodMsForCurrentWindow(pos)); eventEmitter.onVideoProgress.invoke(pos, bufferedDuration, player.getDuration(),
getPositionInFirstPeriodMsForCurrentWindow(pos));
} }
} }
} }
@ -348,9 +351,9 @@ public class ReactExoplayerView extends FrameLayout implements
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT); LayoutParams.MATCH_PARENT);
exoPlayerView = new ExoPlayerView(getContext()); exoPlayerView = new ExoPlayerView(getContext());
exoPlayerView.addOnLayoutChangeListener( (View v, int l, int t, int r, int b, int ol, int ot, int or, int ob) -> exoPlayerView.addOnLayoutChangeListener(
PictureInPictureUtil.applySourceRectHint(themedReactContext, pictureInPictureParamsBuilder, exoPlayerView) (View v, int l, int t, int r, int b, int ol, int ot, int or, int ob) -> PictureInPictureUtil
); .applySourceRectHint(themedReactContext, pictureInPictureParamsBuilder, exoPlayerView));
exoPlayerView.setLayoutParams(layoutParams); exoPlayerView.setLayoutParams(layoutParams);
addView(exoPlayerView, 0, layoutParams); addView(exoPlayerView, 0, layoutParams);
@ -376,8 +379,10 @@ public class ReactExoplayerView extends FrameLayout implements
public void onHostPause() { public void onHostPause() {
isInBackground = true; isInBackground = true;
Activity activity = themedReactContext.getCurrentActivity(); Activity activity = themedReactContext.getCurrentActivity();
boolean isInPictureInPicture = Util.SDK_INT >= Build.VERSION_CODES.N && activity != null && activity.isInPictureInPictureMode(); boolean isInPictureInPicture = Util.SDK_INT >= Build.VERSION_CODES.N && activity != null
boolean isInMultiWindowMode = Util.SDK_INT >= Build.VERSION_CODES.N && activity != null && activity.isInMultiWindowMode(); && activity.isInPictureInPictureMode();
boolean isInMultiWindowMode = Util.SDK_INT >= Build.VERSION_CODES.N && activity != null
&& activity.isInMultiWindowMode();
if (playInBackground || isInPictureInPicture || isInMultiWindowMode) { if (playInBackground || isInPictureInPicture || isInMultiWindowMode) {
return; return;
} }
@ -404,7 +409,8 @@ public class ReactExoplayerView extends FrameLayout implements
eventEmitter.onVideoBandwidthUpdate.invoke(bitrate, 0, 0, null); eventEmitter.onVideoBandwidthUpdate.invoke(bitrate, 0, 0, null);
} else { } else {
Format videoFormat = player.getVideoFormat(); Format videoFormat = player.getVideoFormat();
boolean isRotatedContent = videoFormat != null && (videoFormat.rotationDegrees == 90 || videoFormat.rotationDegrees == 270); boolean isRotatedContent = videoFormat != null
&& (videoFormat.rotationDegrees == 90 || videoFormat.rotationDegrees == 270);
int width = videoFormat != null ? (isRotatedContent ? videoFormat.height : videoFormat.width) : 0; int width = videoFormat != null ? (isRotatedContent ? videoFormat.height : videoFormat.width) : 0;
int height = videoFormat != null ? (isRotatedContent ? videoFormat.width : videoFormat.height) : 0; int height = videoFormat != null ? (isRotatedContent ? videoFormat.width : videoFormat.height) : 0;
String trackId = videoFormat != null ? videoFormat.id : null; String trackId = videoFormat != null ? videoFormat.id : null;
@ -419,7 +425,8 @@ 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;
if (exoPlayerView.isControllerVisible()) { if (exoPlayerView.isControllerVisible()) {
exoPlayerView.hideController(); exoPlayerView.hideController();
} else { } else {
@ -443,7 +450,8 @@ public class ReactExoplayerView extends FrameLayout implements
} }
private void updateControllerConfig() { private void updateControllerConfig() {
if (exoPlayerView == null) return; if (exoPlayerView == null)
return;
exoPlayerView.setControllerShowTimeoutMs(5000); exoPlayerView.setControllerShowTimeoutMs(5000);
@ -454,7 +462,8 @@ public class ReactExoplayerView extends FrameLayout implements
} }
private void updateControllerVisibility() { private void updateControllerVisibility() {
if (exoPlayerView == null) return; if (exoPlayerView == null)
return;
exoPlayerView.setUseController(controls && !controlsConfig.getHideFullscreen()); exoPlayerView.setUseController(controls && !controlsConfig.getHideFullscreen());
} }
@ -490,8 +499,10 @@ public class ReactExoplayerView extends FrameLayout implements
speed = 2.0f; speed = 2.0f;
break; break;
default: default:
speed = 1.0f;; speed = 1.0f;
}; ;
}
;
setRateModifier(speed); setRateModifier(speed);
}); });
builder.show(); builder.show();
@ -503,24 +514,30 @@ public class ReactExoplayerView extends FrameLayout implements
/** /**
* Update the layout * Update the layout
*
* @param view view needs to update layout * @param view view needs to update layout
* *
* This is a workaround for the open bug in react-native: <a href="https://github.com/facebook/react-native/issues/17968">...</a> * This is a workaround for the open bug in react-native: <a href=
* "https://github.com/facebook/react-native/issues/17968">...</a>
*/ */
private void reLayout(View view) { private void reLayout(View view) {
if (view == null) return; if (view == null)
return;
view.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), view.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
view.layout(view.getLeft(), view.getTop(), view.getMeasuredWidth(), view.getMeasuredHeight()); view.layout(view.getLeft(), view.getTop(), view.getMeasuredWidth(), view.getMeasuredHeight());
} }
private void refreshControlsStyles() { private void refreshControlsStyles() {
if (exoPlayerView == null || player == null || !controls) return; if (exoPlayerView == null || player == null || !controls)
return;
updateControllerVisibility(); updateControllerVisibility();
} }
// Note: The following methods for live content and button visibility are no longer needed // Note: The following methods for live content and button visibility are no
// as PlayerView handles controls automatically. Some functionality may need to be // longer needed
// as PlayerView handles controls automatically. Some functionality may need to
// be
// reimplemented using PlayerView's APIs if custom behavior is required. // reimplemented using PlayerView's APIs if custom behavior is required.
private void reLayoutControls() { private void reLayoutControls() {
@ -557,6 +574,7 @@ public class ReactExoplayerView extends FrameLayout implements
private class RNVLoadControl extends DefaultLoadControl { private class RNVLoadControl extends DefaultLoadControl {
private final int availableHeapInBytes; private final int availableHeapInBytes;
private final Runtime runtime; private final Runtime runtime;
public RNVLoadControl(DefaultAllocator allocator, BufferConfig config) { public RNVLoadControl(DefaultAllocator allocator, BufferConfig config) {
super(allocator, super(allocator,
config.getMinBufferMs() != BufferConfig.Companion.getBufferConfigPropUnsetInt() config.getMinBufferMs() != BufferConfig.Companion.getBufferConfigPropUnsetInt()
@ -578,8 +596,10 @@ public class ReactExoplayerView extends FrameLayout implements
: DefaultLoadControl.DEFAULT_BACK_BUFFER_DURATION_MS, : DefaultLoadControl.DEFAULT_BACK_BUFFER_DURATION_MS,
DefaultLoadControl.DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME); DefaultLoadControl.DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME);
runtime = Runtime.getRuntime(); runtime = Runtime.getRuntime();
ActivityManager activityManager = (ActivityManager) themedReactContext.getSystemService(ThemedReactContext.ACTIVITY_SERVICE); ActivityManager activityManager = (ActivityManager) themedReactContext
double maxHeap = config.getMaxHeapAllocationPercent() != BufferConfig.Companion.getBufferConfigPropUnsetDouble() .getSystemService(ThemedReactContext.ACTIVITY_SERVICE);
double maxHeap = config.getMaxHeapAllocationPercent() != BufferConfig.Companion
.getBufferConfigPropUnsetDouble()
? config.getMaxHeapAllocationPercent() ? config.getMaxHeapAllocationPercent()
: DEFAULT_MAX_HEAP_ALLOCATION_PERCENT; : DEFAULT_MAX_HEAP_ALLOCATION_PERCENT;
availableHeapInBytes = (int) Math.floor(activityManager.getMemoryClass() * maxHeap * 1024 * 1024); availableHeapInBytes = (int) Math.floor(activityManager.getMemoryClass() * maxHeap * 1024 * 1024);
@ -599,13 +619,15 @@ public class ReactExoplayerView extends FrameLayout implements
} }
long usedMemory = runtime.totalMemory() - runtime.freeMemory(); long usedMemory = runtime.totalMemory() - runtime.freeMemory();
long freeMemory = runtime.maxMemory() - usedMemory; long freeMemory = runtime.maxMemory() - usedMemory;
double minBufferMemoryReservePercent = source.getBufferConfig().getMinBufferMemoryReservePercent() != BufferConfig.Companion.getBufferConfigPropUnsetDouble() double minBufferMemoryReservePercent = source.getBufferConfig()
.getMinBufferMemoryReservePercent() != BufferConfig.Companion.getBufferConfigPropUnsetDouble()
? source.getBufferConfig().getMinBufferMemoryReservePercent() ? source.getBufferConfig().getMinBufferMemoryReservePercent()
: ReactExoplayerView.DEFAULT_MIN_BUFFER_MEMORY_RESERVE; : ReactExoplayerView.DEFAULT_MIN_BUFFER_MEMORY_RESERVE;
long reserveMemory = (long) minBufferMemoryReservePercent * runtime.maxMemory(); long reserveMemory = (long) minBufferMemoryReservePercent * runtime.maxMemory();
long bufferedMs = bufferedDurationUs / (long) 1000; long bufferedMs = bufferedDurationUs / (long) 1000;
if (reserveMemory > freeMemory && bufferedMs > 2000) { if (reserveMemory > freeMemory && bufferedMs > 2000) {
// We don't have enough memory in reserve so we stop buffering to allow other components to use it instead // We don't have enough memory in reserve so we stop buffering to allow other
// components to use it instead
return false; return false;
} }
if (runtime.freeMemory() == 0) { if (runtime.freeMemory() == 0) {
@ -639,13 +661,13 @@ public class ReactExoplayerView extends FrameLayout implements
// Initialize core configuration and listeners // Initialize core configuration and listeners
initializePlayerCore(self); initializePlayerCore(self);
pipListenerUnsubscribe = PictureInPictureUtil.addLifecycleEventListener(themedReactContext, this); pipListenerUnsubscribe = PictureInPictureUtil.addLifecycleEventListener(themedReactContext, this);
PictureInPictureUtil.applyAutoEnterEnabled(themedReactContext, pictureInPictureParamsBuilder, this.enterPictureInPictureOnLeave); PictureInPictureUtil.applyAutoEnterEnabled(themedReactContext, pictureInPictureParamsBuilder,
this.enterPictureInPictureOnLeave);
} }
if (!source.isLocalAssetFile() && !source.isAsset() && source.getBufferConfig().getCacheSize() > 0) { if (!source.isLocalAssetFile() && !source.isAsset() && source.getBufferConfig().getCacheSize() > 0) {
RNVSimpleCache.INSTANCE.setSimpleCache( RNVSimpleCache.INSTANCE.setSimpleCache(
this.getContext(), this.getContext(),
source.getBufferConfig().getCacheSize() source.getBufferConfig().getCacheSize());
);
useCache = true; useCache = true;
} else { } else {
useCache = false; useCache = false;
@ -653,7 +675,8 @@ public class ReactExoplayerView extends FrameLayout implements
if (playerNeedsSource) { if (playerNeedsSource) {
// Will force display of shutter view if needed // Will force display of shutter view if needed
exoPlayerView.invalidateAspectRatio(); exoPlayerView.invalidateAspectRatio();
// DRM session manager creation must be done on a different thread to prevent crashes so we start a new thread // DRM session manager creation must be done on a different thread to prevent
// crashes so we start a new thread
ExecutorService es = Executors.newSingleThreadExecutor(); ExecutorService es = Executors.newSingleThreadExecutor();
es.execute(() -> { es.execute(() -> {
// DRM initialization must run on a different thread // DRM initialization must run on a different thread
@ -662,7 +685,8 @@ public class ReactExoplayerView extends FrameLayout implements
} }
if (activity == null) { if (activity == null) {
DebugLog.e(TAG, "Failed to initialize Player!, null activity"); DebugLog.e(TAG, "Failed to initialize Player!, null activity");
eventEmitter.onVideoError.invoke("Failed to initialize Player!", new Exception("Current Activity is null!"), "1001"); eventEmitter.onVideoError.invoke("Failed to initialize Player!",
new Exception("Current Activity is null!"), "1001");
return; return;
} }
@ -715,8 +739,7 @@ public class ReactExoplayerView extends FrameLayout implements
DefaultAllocator allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE); DefaultAllocator allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE);
RNVLoadControl loadControl = new RNVLoadControl( RNVLoadControl loadControl = new RNVLoadControl(
allocator, allocator,
source.getBufferConfig() source.getBufferConfig());
);
long initialBitrate = source.getBufferConfig().getInitialBitrate(); long initialBitrate = source.getBufferConfig().getInitialBitrate();
if (initialBitrate > 0) { if (initialBitrate > 0) {
@ -724,15 +747,15 @@ public class ReactExoplayerView extends FrameLayout implements
this.bandwidthMeter = config.getBandwidthMeter(); this.bandwidthMeter = config.getBandwidthMeter();
} }
DefaultRenderersFactory renderersFactory = DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(getContext())
new DefaultRenderersFactory(getContext())
.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER) .setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER)
.setEnableDecoderFallback(true) .setEnableDecoderFallback(true)
.forceEnableMediaCodecAsynchronousQueueing(); .forceEnableMediaCodecAsynchronousQueueing();
DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory); DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory);
if (useCache && !disableCache) { if (useCache && !disableCache) {
mediaSourceFactory.setDataSourceFactory(RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true))); mediaSourceFactory
.setDataSourceFactory(RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true)));
} }
mediaSourceFactory.setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView.getPlayerView()); mediaSourceFactory.setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView.getPlayerView());
@ -771,8 +794,7 @@ public class ReactExoplayerView extends FrameLayout implements
Uri adTagUrl = adProps.getAdTagUrl(); Uri adTagUrl = adProps.getAdTagUrl();
if (adTagUrl != null) { if (adTagUrl != null) {
// Create an AdsLoader. // Create an AdsLoader.
ImaAdsLoader.Builder imaLoaderBuilder = new ImaAdsLoader ImaAdsLoader.Builder imaLoaderBuilder = new ImaAdsLoader.Builder(themedReactContext)
.Builder(themedReactContext)
.setAdEventListener(this) .setAdEventListener(this)
.setAdErrorListener(this); .setAdErrorListener(this);
@ -804,7 +826,8 @@ public class ReactExoplayerView extends FrameLayout implements
} }
try { try {
// First check if there's a custom DRM manager registered through the plugin system // First check if there's a custom DRM manager registered through the plugin
// system
DRMManagerSpec drmManager = ReactNativeVideoManager.Companion.getInstance().getDRMManager(); DRMManagerSpec drmManager = ReactNativeVideoManager.Companion.getInstance().getDRMManager();
if (drmManager == null) { if (drmManager == null) {
// If no custom manager is registered, use the default implementation // If no custom manager is registered, use the default implementation
@ -813,11 +836,13 @@ public class ReactExoplayerView extends FrameLayout implements
DrmSessionManager drmSessionManager = drmManager.buildDrmSessionManager(uuid, drmProps); DrmSessionManager drmSessionManager = drmManager.buildDrmSessionManager(uuid, drmProps);
if (drmSessionManager == null) { if (drmSessionManager == null) {
eventEmitter.onVideoError.invoke("Failed to build DRM session manager", new Exception("DRM session manager is null"), "3007"); eventEmitter.onVideoError.invoke("Failed to build DRM session manager",
new Exception("DRM session manager is null"), "3007");
} }
// Allow plugins to override the DrmSessionManager // Allow plugins to override the DrmSessionManager
DrmSessionManager overriddenManager = ReactNativeVideoManager.Companion.getInstance().overrideDrmSessionManager(source, drmSessionManager); DrmSessionManager overriddenManager = ReactNativeVideoManager.Companion.getInstance()
.overrideDrmSessionManager(source, drmSessionManager);
return overriddenManager != null ? overriddenManager : drmSessionManager; return overriddenManager != null ? overriddenManager : drmSessionManager;
} catch (UnsupportedDrmException ex) { } catch (UnsupportedDrmException ex) {
// Unsupported DRM exceptions are handled by the calling method // Unsupported DRM exceptions are handled by the calling method
@ -835,7 +860,8 @@ public class ReactExoplayerView extends FrameLayout implements
} }
/// init DRM /// init DRM
DrmSessionManager drmSessionManager = initializePlayerDrm(); DrmSessionManager drmSessionManager = initializePlayerDrm();
if (drmSessionManager == null && runningSource.getDrmProps() != null && runningSource.getDrmProps().getDrmType() != null) { if (drmSessionManager == null && runningSource.getDrmProps() != null
&& runningSource.getDrmProps().getDrmType() != null) {
// Failed to initialize DRM session manager - cannot continue // Failed to initialize DRM session manager - cannot continue
DebugLog.e(TAG, "Failed to initialize DRM Session Manager Framework!"); DebugLog.e(TAG, "Failed to initialize DRM Session Manager Framework!");
return; return;
@ -892,7 +918,8 @@ public class ReactExoplayerView extends FrameLayout implements
} catch (UnsupportedDrmException e) { } catch (UnsupportedDrmException e) {
int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported
: (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME : (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME
? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown); ? R.string.error_drm_unsupported_scheme
: R.string.error_drm_unknown);
eventEmitter.onVideoError.invoke(getResources().getString(errorStringId), e, "3003"); eventEmitter.onVideoError.invoke(getResources().getString(errorStringId), e, "3003");
} }
} }
@ -937,7 +964,8 @@ public class ReactExoplayerView extends FrameLayout implements
if (playbackServiceBinder != null) { if (playbackServiceBinder != null) {
playbackServiceBinder.getService().unregisterPlayer(player); playbackServiceBinder.getService().unregisterPlayer(player);
} }
} catch (Exception ignored) {} } catch (Exception ignored) {
}
playbackServiceBinder = null; playbackServiceBinder = null;
} }
@ -983,7 +1011,8 @@ public class ReactExoplayerView extends FrameLayout implements
} }
} }
private MediaSource buildMediaSource(Uri uri, String overrideExtension, DrmSessionManager drmSessionManager, long cropStartMs, long cropEndMs) { private MediaSource buildMediaSource(Uri uri, String overrideExtension, DrmSessionManager drmSessionManager,
long cropStartMs, long cropEndMs) {
if (uri == null) { if (uri == null) {
throw new IllegalStateException("Invalid video uri"); throw new IllegalStateException("Invalid video uri");
} }
@ -1015,12 +1044,12 @@ public class ReactExoplayerView extends FrameLayout implements
Uri adTagUrl = source.getAdsProps().getAdTagUrl(); Uri adTagUrl = source.getAdsProps().getAdTagUrl();
if (adTagUrl != null) { if (adTagUrl != null) {
mediaItemBuilder.setAdsConfiguration( mediaItemBuilder.setAdsConfiguration(
new MediaItem.AdsConfiguration.Builder(adTagUrl).build() new MediaItem.AdsConfiguration.Builder(adTagUrl).build());
);
} }
} }
MediaItem.LiveConfiguration.Builder liveConfiguration = ConfigurationUtils.getLiveConfiguration(source.getBufferConfig()); MediaItem.LiveConfiguration.Builder liveConfiguration = ConfigurationUtils
.getLiveConfiguration(source.getBufferConfig());
mediaItemBuilder.setLiveConfiguration(liveConfiguration.build()); mediaItemBuilder.setLiveConfiguration(liveConfiguration.build());
MediaSource.Factory mediaSourceFactory; MediaSource.Factory mediaSourceFactory;
@ -1032,7 +1061,6 @@ public class ReactExoplayerView extends FrameLayout implements
drmProvider = new DefaultDrmSessionManagerProvider(); drmProvider = new DefaultDrmSessionManagerProvider();
} }
switch (type) { switch (type) {
case CONTENT_TYPE_SS: case CONTENT_TYPE_SS:
if (!BuildConfig.USE_EXOPLAYER_SMOOTH_STREAMING) { if (!BuildConfig.USE_EXOPLAYER_SMOOTH_STREAMING) {
@ -1042,8 +1070,7 @@ public class ReactExoplayerView extends FrameLayout implements
mediaSourceFactory = new SsMediaSource.Factory( mediaSourceFactory = new SsMediaSource.Factory(
new DefaultSsChunkSource.Factory(mediaDataSourceFactory), new DefaultSsChunkSource.Factory(mediaDataSourceFactory),
buildDataSourceFactory(false) buildDataSourceFactory(false));
);
break; break;
case CONTENT_TYPE_DASH: case CONTENT_TYPE_DASH:
if (!BuildConfig.USE_EXOPLAYER_DASH) { if (!BuildConfig.USE_EXOPLAYER_DASH) {
@ -1053,8 +1080,7 @@ public class ReactExoplayerView extends FrameLayout implements
mediaSourceFactory = new DashMediaSource.Factory( mediaSourceFactory = new DashMediaSource.Factory(
new DefaultDashChunkSource.Factory(mediaDataSourceFactory), new DefaultDashChunkSource.Factory(mediaDataSourceFactory),
buildDataSourceFactory(false) buildDataSourceFactory(false));
);
break; break;
case CONTENT_TYPE_HLS: case CONTENT_TYPE_HLS:
if (!BuildConfig.USE_EXOPLAYER_HLS) { if (!BuildConfig.USE_EXOPLAYER_HLS) {
@ -1069,13 +1095,14 @@ public class ReactExoplayerView extends FrameLayout implements
} }
mediaSourceFactory = new HlsMediaSource.Factory( mediaSourceFactory = new HlsMediaSource.Factory(
dataSourceFactory dataSourceFactory)
).setAllowChunklessPreparation(source.getTextTracksAllowChunklessPreparation()); .setAllowChunklessPreparation(source.getTextTracksAllowChunklessPreparation());
break; break;
case CONTENT_TYPE_OTHER: case CONTENT_TYPE_OTHER:
if ("asset".equals(uri.getScheme())) { if ("asset".equals(uri.getScheme())) {
try { try {
DataSource.Factory assetDataSourceFactory = DataSourceUtil.buildAssetDataSourceFactory(themedReactContext, uri); DataSource.Factory assetDataSourceFactory = DataSourceUtil
.buildAssetDataSourceFactory(themedReactContext, uri);
mediaSourceFactory = new ProgressiveMediaSource.Factory(assetDataSourceFactory); mediaSourceFactory = new ProgressiveMediaSource.Factory(assetDataSourceFactory);
} catch (Exception e) { } catch (Exception e) {
throw new IllegalStateException("cannot open input file:" + uri); throw new IllegalStateException("cannot open input file:" + uri);
@ -1083,12 +1110,10 @@ public class ReactExoplayerView extends FrameLayout implements
} else if ("file".equals(uri.getScheme()) || } else if ("file".equals(uri.getScheme()) ||
!useCache) { !useCache) {
mediaSourceFactory = new ProgressiveMediaSource.Factory( mediaSourceFactory = new ProgressiveMediaSource.Factory(
mediaDataSourceFactory mediaDataSourceFactory);
);
} else { } else {
mediaSourceFactory = new ProgressiveMediaSource.Factory( mediaSourceFactory = new ProgressiveMediaSource.Factory(
RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true)) RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true)));
);
} }
break; break;
@ -1107,20 +1132,19 @@ public class ReactExoplayerView extends FrameLayout implements
if (cmcdConfigurationFactory != null) { if (cmcdConfigurationFactory != null) {
mediaSourceFactory = mediaSourceFactory.setCmcdConfigurationFactory( mediaSourceFactory = mediaSourceFactory.setCmcdConfigurationFactory(
cmcdConfigurationFactory::createCmcdConfiguration cmcdConfigurationFactory::createCmcdConfiguration);
);
} }
mediaSourceFactory = Objects.requireNonNullElse( mediaSourceFactory = Objects.requireNonNullElse(
ReactNativeVideoManager.Companion.getInstance() ReactNativeVideoManager.Companion.getInstance()
.overrideMediaSourceFactory(source, mediaSourceFactory, mediaDataSourceFactory), .overrideMediaSourceFactory(source, mediaSourceFactory, mediaDataSourceFactory),
mediaSourceFactory mediaSourceFactory);
);
mediaItemBuilder.setStreamKeys(streamKeys); mediaItemBuilder.setStreamKeys(streamKeys);
@Nullable @Nullable
final MediaItem.Builder overridenMediaItemBuilder = ReactNativeVideoManager.Companion.getInstance().overrideMediaItemBuilder(source, mediaItemBuilder); final MediaItem.Builder overridenMediaItemBuilder = ReactNativeVideoManager.Companion.getInstance()
.overrideMediaItemBuilder(source, mediaItemBuilder);
MediaItem mediaItem = overridenMediaItemBuilder != null MediaItem mediaItem = overridenMediaItemBuilder != null
? overridenMediaItemBuilder.build() ? overridenMediaItemBuilder.build()
@ -1129,8 +1153,7 @@ public class ReactExoplayerView extends FrameLayout implements
MediaSource mediaSource = mediaSourceFactory MediaSource mediaSource = mediaSourceFactory
.setDrmSessionManagerProvider(drmProvider) .setDrmSessionManagerProvider(drmProvider)
.setLoadErrorHandlingPolicy( .setLoadErrorHandlingPolicy(
config.buildLoadErrorHandlingPolicy(source.getMinLoadRetryCount()) config.buildLoadErrorHandlingPolicy(source.getMinLoadRetryCount()))
)
.createMediaSource(mediaItem); .createMediaSource(mediaItem);
if (cropStartMs >= 0 && cropEndMs >= 0) { if (cropStartMs >= 0 && cropEndMs >= 0) {
@ -1165,7 +1188,8 @@ public class ReactExoplayerView extends FrameLayout implements
} }
} }
MediaItem.SubtitleConfiguration.Builder configBuilder = new MediaItem.SubtitleConfiguration.Builder(track.getUri()) MediaItem.SubtitleConfiguration.Builder configBuilder = new MediaItem.SubtitleConfiguration.Builder(
track.getUri())
.setId(trackId) .setId(trackId)
.setMimeType(track.getType()) .setMimeType(track.getType())
.setLabel(label) .setLabel(label)
@ -1176,7 +1200,8 @@ public class ReactExoplayerView extends FrameLayout implements
configBuilder.setLanguage(track.getLanguage()); configBuilder.setLanguage(track.getLanguage());
} }
// Set selection flags - make first track default if no specific track is selected // Set selection flags - make first track default if no specific track is
// selected
if (trackIndex == 0 && (textTrackType == null || "disabled".equals(textTrackType))) { if (trackIndex == 0 && (textTrackType == null || "disabled".equals(textTrackType))) {
configBuilder.setSelectionFlags(C.SELECTION_FLAG_DEFAULT); configBuilder.setSelectionFlags(C.SELECTION_FLAG_DEFAULT);
} else { } else {
@ -1186,10 +1211,12 @@ public class ReactExoplayerView extends FrameLayout implements
MediaItem.SubtitleConfiguration subtitleConfiguration = configBuilder.build(); MediaItem.SubtitleConfiguration subtitleConfiguration = configBuilder.build();
subtitleConfigurations.add(subtitleConfiguration); subtitleConfigurations.add(subtitleConfiguration);
DebugLog.d(TAG, "Created subtitle configuration: " + trackId + " - " + label + " (" + track.getType() + ")"); DebugLog.d(TAG,
"Created subtitle configuration: " + trackId + " - " + label + " (" + track.getType() + ")");
trackIndex++; trackIndex++;
} catch (Exception e) { } catch (Exception e) {
DebugLog.e(TAG, "Error creating SubtitleConfiguration for URI " + track.getUri() + ": " + e.getMessage()); DebugLog.e(TAG,
"Error creating SubtitleConfiguration for URI " + track.getUri() + ": " + e.getMessage());
} }
} }
@ -1252,7 +1279,8 @@ public class ReactExoplayerView extends FrameLayout implements
case AudioManager.AUDIOFOCUS_LOSS: case AudioManager.AUDIOFOCUS_LOSS:
view.hasAudioFocus = false; view.hasAudioFocus = false;
view.eventEmitter.onAudioFocusChanged.invoke(false); view.eventEmitter.onAudioFocusChanged.invoke(false);
// FIXME this pause can cause issue if content doesn't have pause capability (can happen on live channel) // FIXME this pause can cause issue if content doesn't have pause capability
// (can happen on live channel)
if (activity != null) { if (activity != null) {
activity.runOnUiThread(view::pausePlayback); activity.runOnUiThread(view::pausePlayback);
} }
@ -1273,16 +1301,12 @@ public class ReactExoplayerView extends FrameLayout implements
if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) { if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
// Lower the volume // Lower the volume
if (!view.muted) { if (!view.muted) {
activity.runOnUiThread(() -> activity.runOnUiThread(() -> view.player.setVolume(view.audioVolume * 0.8f));
view.player.setVolume(view.audioVolume * 0.8f)
);
} }
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// Raise it back to normal // Raise it back to normal
if (!view.muted) { if (!view.muted) {
activity.runOnUiThread(() -> activity.runOnUiThread(() -> view.player.setVolume(view.audioVolume * 1));
view.player.setVolume(view.audioVolume * 1)
);
} }
} }
} }
@ -1355,7 +1379,8 @@ public class ReactExoplayerView extends FrameLayout implements
/** /**
* Returns a new DataSource factory. * Returns a new DataSource factory.
* *
* @param useBandwidthMeter Whether to set {@link #bandwidthMeter} as a listener to the new * @param useBandwidthMeter Whether to set {@link #bandwidthMeter} as a listener
* to the new
* DataSource factory. * DataSource factory.
* @return A new DataSource factory. * @return A new DataSource factory.
*/ */
@ -1367,12 +1392,14 @@ public class ReactExoplayerView extends FrameLayout implements
/** /**
* Returns a new HttpDataSource factory. * Returns a new HttpDataSource factory.
* *
* @param useBandwidthMeter Whether to set {@link #bandwidthMeter} as a listener to the new * @param useBandwidthMeter Whether to set {@link #bandwidthMeter} as a listener
* to the new
* DataSource factory. * DataSource factory.
* @return A new HttpDataSource factory. * @return A new HttpDataSource factory.
*/ */
private HttpDataSource.Factory buildHttpDataSourceFactory(boolean useBandwidthMeter) { private HttpDataSource.Factory buildHttpDataSourceFactory(boolean useBandwidthMeter) {
return DataSourceUtil.getDefaultHttpDataSourceFactory(this.themedReactContext, useBandwidthMeter ? bandwidthMeter : null, source.getHeaders()); return DataSourceUtil.getDefaultHttpDataSourceFactory(this.themedReactContext,
useBandwidthMeter ? bandwidthMeter : null, source.getHeaders());
} }
// AudioBecomingNoisyListener implementation // AudioBecomingNoisyListener implementation
@ -1389,11 +1416,13 @@ public class ReactExoplayerView extends FrameLayout implements
@Override @Override
public void onEvents(@NonNull Player player, Player.Events events) { public void onEvents(@NonNull Player player, Player.Events events) {
if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED) || events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)) { if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)
|| events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)) {
int playbackState = player.getPlaybackState(); int playbackState = player.getPlaybackState();
boolean playWhenReady = player.getPlayWhenReady(); boolean playWhenReady = player.getPlayWhenReady();
String text = "onStateChanged: playWhenReady=" + playWhenReady + ", playbackState="; String text = "onStateChanged: playWhenReady=" + playWhenReady + ", playbackState=";
eventEmitter.onPlaybackRateChange.invoke(playWhenReady && playbackState == ExoPlayer.STATE_READY ? 1.0f : 0.0f); eventEmitter.onPlaybackRateChange
.invoke(playWhenReady && playbackState == ExoPlayer.STATE_READY ? 1.0f : 0.0f);
switch (playbackState) { switch (playbackState) {
case Player.STATE_IDLE: case Player.STATE_IDLE:
text += "idle"; text += "idle";
@ -1411,6 +1440,7 @@ public class ReactExoplayerView extends FrameLayout implements
break; break;
case Player.STATE_READY: case Player.STATE_READY:
text += "ready"; text += "ready";
hasVideoEnded = false;
eventEmitter.onReadyForDisplay.invoke(); eventEmitter.onReadyForDisplay.invoke();
onBuffering(false); onBuffering(false);
clearProgressMessageHandler(); // ensure there is no other message clearProgressMessageHandler(); // ensure there is no other message
@ -1429,7 +1459,10 @@ public class ReactExoplayerView extends FrameLayout implements
case Player.STATE_ENDED: case Player.STATE_ENDED:
text += "ended"; text += "ended";
updateProgress(); updateProgress();
if (!hasVideoEnded) {
hasVideoEnded = true;
eventEmitter.onVideoEnd.invoke(); eventEmitter.onVideoEnd.invoke();
}
onStopPlayback(); onStopPlayback();
setKeepScreenOn(false); setKeepScreenOn(false);
break; break;
@ -1446,8 +1479,10 @@ public class ReactExoplayerView extends FrameLayout implements
} }
/** /**
* The progress message handler will duplicate recursions of the onProgressMessage handler * The progress message handler will duplicate recursions of the
* on change of player state from any state to STATE_READY with playWhenReady is true (when * 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 video is not paused). This clears all existing messages.
*/ */
private void clearProgressMessageHandler() { private void clearProgressMessageHandler() {
@ -1467,7 +1502,8 @@ public class ReactExoplayerView extends FrameLayout implements
setSelectedTextTrack(textTrackType, textTrackValue); setSelectedTextTrack(textTrackType, textTrackValue);
} }
Format videoFormat = player.getVideoFormat(); Format videoFormat = player.getVideoFormat();
boolean isRotatedContent = videoFormat != null && (videoFormat.rotationDegrees == 90 || videoFormat.rotationDegrees == 270); boolean isRotatedContent = videoFormat != null
&& (videoFormat.rotationDegrees == 90 || videoFormat.rotationDegrees == 270);
int width = videoFormat != null ? (isRotatedContent ? videoFormat.height : videoFormat.width) : 0; int width = videoFormat != null ? (isRotatedContent ? videoFormat.height : videoFormat.width) : 0;
int height = videoFormat != null ? (isRotatedContent ? videoFormat.width : videoFormat.height) : 0; int height = videoFormat != null ? (isRotatedContent ? videoFormat.width : videoFormat.height) : 0;
String trackId = videoFormat != null ? videoFormat.id : null; String trackId = videoFormat != null ? videoFormat.id : null;
@ -1481,7 +1517,8 @@ public class ReactExoplayerView extends FrameLayout implements
if (source.getContentStartTime() != -1) { if (source.getContentStartTime() != -1) {
ExecutorService es = Executors.newSingleThreadExecutor(); ExecutorService es = Executors.newSingleThreadExecutor();
es.execute(() -> { es.execute(() -> {
// To prevent ANRs caused by getVideoTrackInfo we run this on a different thread and notify the player only when we're done // To prevent ANRs caused by getVideoTrackInfo we run this on a different thread
// and notify the player only when we're done
ArrayList<VideoTrack> videoTracks = getVideoTrackInfoFromManifest(); ArrayList<VideoTrack> videoTracks = getVideoTrackInfoFromManifest();
if (videoTracks != null) { if (videoTracks != null) {
isUsingContentResolution = true; isUsingContentResolution = true;
@ -1525,7 +1562,6 @@ public class ReactExoplayerView extends FrameLayout implements
TrackSelectionArray selectionArray = player.getCurrentTrackSelections(); TrackSelectionArray selectionArray = player.getCurrentTrackSelections();
TrackSelection selection = selectionArray.get(C.TRACK_TYPE_AUDIO); TrackSelection selection = selectionArray.get(C.TRACK_TYPE_AUDIO);
for (int groupIndex = 0; groupIndex < groups.length; ++groupIndex) { for (int groupIndex = 0; groupIndex < groups.length; ++groupIndex) {
TrackGroup group = groups.get(groupIndex); TrackGroup group = groups.get(groupIndex);
Format format = group.getFormat(0); Format format = group.getFormat(0);
@ -1551,7 +1587,8 @@ public class ReactExoplayerView extends FrameLayout implements
videoTrack.setHeight(format.height == Format.NO_VALUE ? 0 : format.height); videoTrack.setHeight(format.height == Format.NO_VALUE ? 0 : format.height);
videoTrack.setBitrate(format.bitrate == Format.NO_VALUE ? 0 : format.bitrate); videoTrack.setBitrate(format.bitrate == Format.NO_VALUE ? 0 : format.bitrate);
videoTrack.setRotation(format.rotationDegrees); videoTrack.setRotation(format.rotationDegrees);
if (format.codecs != null) videoTrack.setCodecs(format.codecs); if (format.codecs != null)
videoTrack.setCodecs(format.codecs);
videoTrack.setTrackId(format.id == null ? String.valueOf(trackIndex) : format.id); videoTrack.setTrackId(format.id == null ? String.valueOf(trackIndex) : format.id);
videoTrack.setIndex(trackIndex); videoTrack.setIndex(trackIndex);
return videoTrack; return videoTrack;
@ -1588,7 +1625,8 @@ public class ReactExoplayerView extends FrameLayout implements
return this.getVideoTrackInfoFromManifest(0); return this.getVideoTrackInfoFromManifest(0);
} }
// We need retry count to in case where minefest request fails from poor network conditions // We need retry count to in case where minefest request fails from poor network
// conditions
@WorkerThread @WorkerThread
private ArrayList<VideoTrack> getVideoTrackInfoFromManifest(int retryCount) { private ArrayList<VideoTrack> getVideoTrackInfoFromManifest(int retryCount) {
ExecutorService es = Executors.newSingleThreadExecutor(); ExecutorService es = Executors.newSingleThreadExecutor();
@ -1608,13 +1646,15 @@ public class ReactExoplayerView extends FrameLayout implements
int periodCount = manifest.getPeriodCount(); int periodCount = manifest.getPeriodCount();
for (int i = 0; i < periodCount; i++) { for (int i = 0; i < periodCount; i++) {
Period period = manifest.getPeriod(i); Period period = manifest.getPeriod(i);
for (int adaptationIndex = 0; adaptationIndex < period.adaptationSets.size(); adaptationIndex++) { for (int adaptationIndex = 0; adaptationIndex < period.adaptationSets
.size(); adaptationIndex++) {
AdaptationSet adaptation = period.adaptationSets.get(adaptationIndex); AdaptationSet adaptation = period.adaptationSets.get(adaptationIndex);
if (adaptation.type != C.TRACK_TYPE_VIDEO) { if (adaptation.type != C.TRACK_TYPE_VIDEO) {
continue; continue;
} }
boolean hasFoundContentPeriod = false; boolean hasFoundContentPeriod = false;
for (int representationIndex = 0; representationIndex < adaptation.representations.size(); representationIndex++) { for (int representationIndex = 0; representationIndex < adaptation.representations
.size(); representationIndex++) {
Representation representation = adaptation.representations.get(representationIndex); Representation representation = adaptation.representations.get(representationIndex);
Format format = representation.format; Format format = representation.format;
if (isFormatSupported(format)) { if (isFormatSupported(format)) {
@ -1622,7 +1662,8 @@ public class ReactExoplayerView extends FrameLayout implements
break; break;
} }
hasFoundContentPeriod = true; hasFoundContentPeriod = true;
VideoTrack videoTrack = exoplayerVideoTrackToGenericVideoTrack(format, representationIndex); VideoTrack videoTrack = exoplayerVideoTrackToGenericVideoTrack(format,
representationIndex);
videoTracks.add(videoTrack); videoTracks.add(videoTrack);
} }
} }
@ -1652,12 +1693,16 @@ 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);
if (format.sampleMimeType != null) track.setMimeType(format.sampleMimeType); if (format.sampleMimeType != null)
if (format.language != null) track.setLanguage(format.language); track.setMimeType(format.sampleMimeType);
if (format.label != null) track.setTitle(format.label); if (format.language != null)
track.setLanguage(format.language);
if (format.label != null)
track.setTitle(format.label);
track.setSelected(isTrackSelected(selection, group, trackIndex)); track.setSelected(isTrackSelected(selection, group, trackIndex));
return track; return track;
} }
@ -1727,7 +1772,8 @@ public class ReactExoplayerView extends FrameLayout implements
track.setLanguage(format.language != null ? format.language : "unknown"); track.setLanguage(format.language != null ? format.language : "unknown");
track.setTitle(format.label != null ? format.label : "Track " + (groupIndex + 1)); track.setTitle(format.label != null ? format.label : "Track " + (groupIndex + 1));
track.setSelected(false); // Don't report selection status - let PlayerView handle it track.setSelected(false); // Don't report selection status - let PlayerView handle it
if (format.sampleMimeType != null) track.setMimeType(format.sampleMimeType); if (format.sampleMimeType != null)
track.setMimeType(format.sampleMimeType);
track.setBitrate(format.bitrate == Format.NO_VALUE ? 0 : format.bitrate); track.setBitrate(format.bitrate == Format.NO_VALUE ? 0 : format.bitrate);
tracks.add(track); tracks.add(track);
@ -1758,8 +1804,10 @@ public class ReactExoplayerView extends FrameLayout implements
Track textTrack = new Track(); Track textTrack = new Track();
textTrack.setIndex(textTracks.size()); textTrack.setIndex(textTracks.size());
if (format.sampleMimeType != null) textTrack.setMimeType(format.sampleMimeType); if (format.sampleMimeType != null)
if (format.language != null) textTrack.setLanguage(format.language); textTrack.setMimeType(format.sampleMimeType);
if (format.language != null)
textTrack.setLanguage(format.language);
boolean isExternal = format.id != null && format.id.startsWith("external-subtitle-"); boolean isExternal = format.id != null && format.id.startsWith("external-subtitle-");
@ -1793,35 +1841,44 @@ public class ReactExoplayerView extends FrameLayout implements
} }
@Override @Override
public void onPositionDiscontinuity(@NonNull Player.PositionInfo oldPosition, @NonNull Player.PositionInfo newPosition, @Player.DiscontinuityReason int reason) { public void onPositionDiscontinuity(@NonNull Player.PositionInfo oldPosition,
@NonNull Player.PositionInfo newPosition, @Player.DiscontinuityReason int reason) {
if (reason == Player.DISCONTINUITY_REASON_SEEK) { if (reason == Player.DISCONTINUITY_REASON_SEEK) {
isSeeking = true; isSeeking = true;
seekPosition = newPosition.positionMs; seekPosition = newPosition.positionMs;
if (isUsingContentResolution) { if (isUsingContentResolution) {
// We need to update the selected track to make sure that it still matches user selection if track list has changed in this period // We need to update the selected track to make sure that it still matches user
// selection if track list has changed in this period
setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue); setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue);
} }
} }
if (playerNeedsSource) { if (playerNeedsSource) {
// This will only occur if the user has performed a seek whilst in the error state. Update the // This will only occur if the user has performed a seek whilst in the error
// resume position so that if the user then retries, playback will resume from the position to // state. Update the
// resume position so that if the user then retries, playback will resume from
// the position to
// which they seeked. // which they seeked.
updateResumePosition(); updateResumePosition();
} }
if (isUsingContentResolution) { if (isUsingContentResolution) {
// Discontinuity events might have a different track list so we update the selected track // Discontinuity events might have a different track list so we update the
// selected track
setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue); setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue);
selectTrackWhenReady = true; selectTrackWhenReady = true;
} }
// When repeat is turned on, reaching the end of the video will not cause a state change // When repeat is turned on, reaching the end of the video will not cause a
// state change
// so we need to explicitly detect it. // so we need to explicitly detect it.
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION
&& player.getRepeatMode() == Player.REPEAT_MODE_ONE) { && player.getRepeatMode() == Player.REPEAT_MODE_ONE) {
updateProgress(); updateProgress();
if (!hasVideoEnded) {
hasVideoEnded = true;
eventEmitter.onVideoEnd.invoke(); eventEmitter.onVideoEnd.invoke();
} }
} }
}
@Override @Override
public void onTimelineChanged(@NonNull Timeline timeline, int reason) { public void onTimelineChanged(@NonNull Timeline timeline, int reason) {
@ -1859,15 +1916,17 @@ public class ReactExoplayerView extends FrameLayout implements
updateSubtitleButtonVisibility(); updateSubtitleButtonVisibility();
} }
private boolean hasBuiltInTextTracks() { private boolean hasBuiltInTextTracks() {
if (player == null || trackSelector == null) return false; if (player == null || trackSelector == null)
return false;
MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo(); MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo();
if (info == null) return false; if (info == null)
return false;
int textRendererIndex = getTrackRendererIndex(C.TRACK_TYPE_TEXT); int textRendererIndex = getTrackRendererIndex(C.TRACK_TYPE_TEXT);
if (textRendererIndex == C.INDEX_UNSET) return false; if (textRendererIndex == C.INDEX_UNSET)
return false;
TrackGroupArray groups = info.getTrackGroups(textRendererIndex); TrackGroupArray groups = info.getTrackGroups(textRendererIndex);
@ -1887,7 +1946,8 @@ public class ReactExoplayerView extends FrameLayout implements
} }
private void updateSubtitleButtonVisibility() { private void updateSubtitleButtonVisibility() {
if (exoPlayerView == null) return; if (exoPlayerView == null)
return;
boolean hasTextTracks = (source.getSideLoadedTextTracks() != null && boolean hasTextTracks = (source.getSideLoadedTextTracks() != null &&
!source.getSideLoadedTextTracks().getTracks().isEmpty()) || !source.getSideLoadedTextTracks().getTracks().isEmpty()) ||
@ -1911,7 +1971,8 @@ public class ReactExoplayerView extends FrameLayout implements
if (isPlaying && isSeeking) { if (isPlaying && isSeeking) {
eventEmitter.onVideoSeek.invoke(player.getCurrentPosition(), seekPosition); eventEmitter.onVideoSeek.invoke(player.getCurrentPosition(), seekPosition);
} }
PictureInPictureUtil.applyPlayingStatus(themedReactContext, pictureInPictureParamsBuilder, pictureInPictureReceiver, !isPlaying); PictureInPictureUtil.applyPlayingStatus(themedReactContext, pictureInPictureParamsBuilder,
pictureInPictureReceiver, !isPlaying);
eventEmitter.onVideoPlaybackStateChanged.invoke(isPlaying, isSeeking); eventEmitter.onVideoPlaybackStateChanged.invoke(isPlaying, isSeeking);
if (isPlaying) { if (isPlaying) {
@ -1930,7 +1991,8 @@ public class ReactExoplayerView extends FrameLayout implements
case PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR: case PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR:
case PlaybackException.ERROR_CODE_DRM_UNSPECIFIED: case PlaybackException.ERROR_CODE_DRM_UNSPECIFIED:
if (!hasDrmFailed) { if (!hasDrmFailed) {
// When DRM fails to reach the app level certificate server it will fail with a source error so we assume that it is DRM related and try one more time // When DRM fails to reach the app level certificate server it will fail with a
// source error so we assume that it is DRM related and try one more time
hasDrmFailed = true; hasDrmFailed = true;
playerNeedsSource = true; playerNeedsSource = true;
updateResumePosition(); updateResumePosition();
@ -2012,14 +2074,16 @@ public class ReactExoplayerView extends FrameLayout implements
boolean isSourceEqual = source.isEquals(this.source); boolean isSourceEqual = source.isEquals(this.source);
hasDrmFailed = false; hasDrmFailed = false;
this.source = source; this.source = source;
final DataSource.Factory tmpMediaDataSourceFactory = final DataSource.Factory tmpMediaDataSourceFactory = DataSourceUtil.getDefaultDataSourceFactory(
DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, bandwidthMeter, this.themedReactContext, bandwidthMeter,
source.getHeaders()); source.getHeaders());
@Nullable @Nullable
final DataSource.Factory overriddenMediaDataSourceFactory = ReactNativeVideoManager.Companion.getInstance().overrideMediaDataSourceFactory(source, tmpMediaDataSourceFactory); final DataSource.Factory overriddenMediaDataSourceFactory = ReactNativeVideoManager.Companion.getInstance()
.overrideMediaDataSourceFactory(source, tmpMediaDataSourceFactory);
this.mediaDataSourceFactory = Objects.requireNonNullElse(overriddenMediaDataSourceFactory, tmpMediaDataSourceFactory); this.mediaDataSourceFactory = Objects.requireNonNullElse(overriddenMediaDataSourceFactory,
tmpMediaDataSourceFactory);
if (source.getCmcdProps() != null) { if (source.getCmcdProps() != null) {
CMCDConfig cmcdConfig = new CMCDConfig(source.getCmcdProps()); CMCDConfig cmcdConfig = new CMCDConfig(source.getCmcdProps());
@ -2030,6 +2094,7 @@ public class ReactExoplayerView extends FrameLayout implements
} }
if (!isSourceEqual) { if (!isSourceEqual) {
hasVideoEnded = false;
playerNeedsSource = true; playerNeedsSource = true;
initializePlayer(); initializePlayer();
} }
@ -2037,6 +2102,7 @@ public class ReactExoplayerView extends FrameLayout implements
clearSrc(); clearSrc();
} }
} }
public void clearSrc() { public void clearSrc() {
if (source.getUri() != null) { if (source.getUri() != null) {
if (player != null) { if (player != null) {
@ -2085,7 +2151,8 @@ public class ReactExoplayerView extends FrameLayout implements
} }
public void disableTrack(int rendererIndex) { public void disableTrack(int rendererIndex) {
if (trackSelector == null) return; if (trackSelector == null)
return;
DefaultTrackSelector.Parameters disableParameters = trackSelector.getParameters() DefaultTrackSelector.Parameters disableParameters = trackSelector.getParameters()
.buildUpon() .buildUpon()
@ -2095,7 +2162,8 @@ public class ReactExoplayerView extends FrameLayout implements
} }
private void selectTextTrackInternal(String type, String value) { private void selectTextTrackInternal(String type, String value) {
if (player == null || trackSelector == null) return; if (player == null || trackSelector == null)
return;
DebugLog.d(TAG, "selectTextTrackInternal: type=" + type + ", value=" + value); DebugLog.d(TAG, "selectTextTrackInternal: type=" + type + ", value=" + value);
@ -2140,7 +2208,8 @@ public class ReactExoplayerView extends FrameLayout implements
break; break;
} }
} }
if (trackFound) break; if (trackFound)
break;
} }
if (!trackFound) { if (!trackFound) {
@ -2166,7 +2235,8 @@ public class ReactExoplayerView extends FrameLayout implements
} }
public void setSelectedTrack(int trackType, String type, String value) { public void setSelectedTrack(int trackType, String type, String value) {
if (player == null || trackSelector == null) return; if (player == null || trackSelector == null)
return;
if (controls) { if (controls) {
return; return;
@ -2240,9 +2310,11 @@ public class ReactExoplayerView extends FrameLayout implements
usingExactMatch = true; usingExactMatch = true;
break; break;
} else if (isUsingContentResolution) { } else if (isUsingContentResolution) {
// When using content resolution rather than ads, we need to try and find the closest match if there is no exact match // When using content resolution rather than ads, we need to try and find the
// closest match if there is no exact match
if (closestFormat != null) { if (closestFormat != null) {
if ((format.bitrate > closestFormat.bitrate || format.height > closestFormat.height) && format.height < height) { if ((format.bitrate > closestFormat.bitrate || format.height > closestFormat.height)
&& format.height < height) {
// Higher quality match // Higher quality match
closestFormat = format; closestFormat = format;
closestTrackIndex = j; closestTrackIndex = j;
@ -2253,7 +2325,8 @@ public class ReactExoplayerView extends FrameLayout implements
} }
} }
} }
// This is a fallback if the new period contains only higher resolutions than the user has selected // This is a fallback if the new period contains only higher resolutions than
// the user has selected
if (closestFormat == null && isUsingContentResolution && !usingExactMatch) { if (closestFormat == null && isUsingContentResolution && !usingExactMatch) {
// No close match found - so we pick the lowest quality // No close match found - so we pick the lowest quality
int minHeight = Integer.MAX_VALUE; int minHeight = Integer.MAX_VALUE;
@ -2276,8 +2349,8 @@ public class ReactExoplayerView extends FrameLayout implements
} }
} else if (trackType == C.TRACK_TYPE_TEXT && Util.SDK_INT > 18) { // Text default } else if (trackType == C.TRACK_TYPE_TEXT && Util.SDK_INT > 18) { // Text default
// Use system settings if possible // Use system settings if possible
CaptioningManager captioningManager CaptioningManager captioningManager = (CaptioningManager) themedReactContext
= (CaptioningManager)themedReactContext.getSystemService(Context.CAPTIONING_SERVICE); .getSystemService(Context.CAPTIONING_SERVICE);
if (captioningManager != null && captioningManager.isEnabled()) { if (captioningManager != null && captioningManager.isEnabled()) {
groupIndex = getGroupIndexForDefaultLocale(groups); groupIndex = getGroupIndexForDefaultLocale(groups);
} }
@ -2332,7 +2405,8 @@ public class ReactExoplayerView extends FrameLayout implements
.setRendererDisabled(rendererIndex, false); .setRendererDisabled(rendererIndex, false);
// Clear existing overrides for this track type to avoid conflicts // Clear existing overrides for this track type to avoid conflicts
// But be careful with audio tracks - don't clear unless explicitly selecting a different track // But be careful with audio tracks - don't clear unless explicitly selecting a
// different track
if (trackType != C.TRACK_TYPE_AUDIO || !type.equals("default")) { if (trackType != C.TRACK_TYPE_AUDIO || !type.equals("default")) {
selectionParameters.clearOverridesOfType(selectionOverride.getType()); selectionParameters.clearOverridesOfType(selectionOverride.getType());
} }
@ -2400,7 +2474,8 @@ public class ReactExoplayerView extends FrameLayout implements
public void setSelectedVideoTrack(String type, String value) { public void setSelectedVideoTrack(String type, String value) {
videoTrackType = type; videoTrackType = type;
videoTrackValue = value; videoTrackValue = value;
if (!loadVideoStarted) setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue); if (!loadVideoStarted)
setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue);
} }
public void setSelectedAudioTrack(String type, String value) { public void setSelectedAudioTrack(String type, String value) {
@ -2431,9 +2506,11 @@ public class ReactExoplayerView extends FrameLayout implements
} }
public void setEnterPictureInPictureOnLeave(boolean enterPictureInPictureOnLeave) { public void setEnterPictureInPictureOnLeave(boolean enterPictureInPictureOnLeave) {
this.enterPictureInPictureOnLeave = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && enterPictureInPictureOnLeave; this.enterPictureInPictureOnLeave = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
&& enterPictureInPictureOnLeave;
if (player != null) { if (player != null) {
PictureInPictureUtil.applyAutoEnterEnabled(themedReactContext, pictureInPictureParamsBuilder, this.enterPictureInPictureOnLeave); PictureInPictureUtil.applyAutoEnterEnabled(themedReactContext, pictureInPictureParamsBuilder,
this.enterPictureInPictureOnLeave);
} }
} }
@ -2441,12 +2518,14 @@ public class ReactExoplayerView extends FrameLayout implements
eventEmitter.onPictureInPictureStatusChanged.invoke(isInPictureInPicture); eventEmitter.onPictureInPictureStatusChanged.invoke(isInPictureInPicture);
if (fullScreenPlayerView != null && fullScreenPlayerView.isShowing()) { if (fullScreenPlayerView != null && fullScreenPlayerView.isShowing()) {
if (isInPictureInPicture) fullScreenPlayerView.hideWithoutPlayer(); if (isInPictureInPicture)
fullScreenPlayerView.hideWithoutPlayer();
return; return;
} }
Activity currentActivity = themedReactContext.getCurrentActivity(); Activity currentActivity = themedReactContext.getCurrentActivity();
if (currentActivity == null) return; if (currentActivity == null)
return;
View decorView = currentActivity.getWindow().getDecorView(); View decorView = currentActivity.getWindow().getDecorView();
ViewGroup rootView = decorView.findViewById(android.R.id.content); ViewGroup rootView = decorView.findViewById(android.R.id.content);
@ -2482,10 +2561,12 @@ public class ReactExoplayerView extends FrameLayout implements
public void enterPictureInPictureMode() { public void enterPictureInPictureMode() {
PictureInPictureParams _pipParams = null; PictureInPictureParams _pipParams = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ArrayList<RemoteAction> actions = PictureInPictureUtil.getPictureInPictureActions(themedReactContext, isPaused, pictureInPictureReceiver); ArrayList<RemoteAction> actions = PictureInPictureUtil.getPictureInPictureActions(themedReactContext,
isPaused, pictureInPictureReceiver);
pictureInPictureParamsBuilder.setActions(actions); pictureInPictureParamsBuilder.setActions(actions);
if (player.getPlaybackState() == Player.STATE_READY) { if (player.getPlaybackState() == Player.STATE_READY) {
pictureInPictureParamsBuilder.setAspectRatio(PictureInPictureUtil.calcPictureInPictureAspectRatio(player)); pictureInPictureParamsBuilder
.setAspectRatio(PictureInPictureUtil.calcPictureInPictureAspectRatio(player));
} }
_pipParams = pictureInPictureParamsBuilder.build(); _pipParams = pictureInPictureParamsBuilder.build();
} }
@ -2494,13 +2575,15 @@ public class ReactExoplayerView extends FrameLayout implements
public void exitPictureInPictureMode() { public void exitPictureInPictureMode() {
Activity currentActivity = themedReactContext.getCurrentActivity(); Activity currentActivity = themedReactContext.getCurrentActivity();
if (currentActivity == null) return; if (currentActivity == null)
return;
View decorView = currentActivity.getWindow().getDecorView(); View decorView = currentActivity.getWindow().getDecorView();
ViewGroup rootView = decorView.findViewById(android.R.id.content); ViewGroup rootView = decorView.findViewById(android.R.id.content);
if (!rootViewChildrenOriginalVisibility.isEmpty()) { if (!rootViewChildrenOriginalVisibility.isEmpty()) {
if (exoPlayerView.getParent().equals(rootView)) rootView.removeView(exoPlayerView); if (exoPlayerView.getParent().equals(rootView))
rootView.removeView(exoPlayerView);
for (int i = 0; i < rootView.getChildCount(); i++) { for (int i = 0; i < rootView.getChildCount(); i++) {
rootView.getChildAt(i).setVisibility(rootViewChildrenOriginalVisibility.get(i)); rootView.getChildAt(i).setVisibility(rootViewChildrenOriginalVisibility.get(i));
} }
@ -2627,7 +2710,8 @@ public class ReactExoplayerView extends FrameLayout implements
} }
if (isFullscreen) { if (isFullscreen) {
fullScreenPlayerView = new FullScreenPlayerView(getContext(), exoPlayerView, this, null, new OnBackPressedCallback(true) { fullScreenPlayerView = new FullScreenPlayerView(getContext(), exoPlayerView, this, null,
new OnBackPressedCallback(true) {
@Override @Override
public void handleOnBackPressed() { public void handleOnBackPressed() {
setFullscreen(false); setFullscreen(false);
@ -2669,7 +2753,8 @@ public class ReactExoplayerView extends FrameLayout implements
} }
@Override @Override
public void onDrmSessionManagerError(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId, @NonNull Exception e) { public void onDrmSessionManagerError(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId,
@NonNull Exception e) {
DebugLog.d("DRM Info", "onDrmSessionManagerError"); DebugLog.d("DRM Info", "onDrmSessionManagerError");
eventEmitter.onVideoError.invoke("onDrmSessionManagerError", e, "3002"); eventEmitter.onVideoError.invoke("onDrmSessionManagerError", e, "3002");
} }
@ -2729,8 +2814,7 @@ public class ReactExoplayerView extends FrameLayout implements
Map<String, String> errMap = Map.of( Map<String, String> errMap = Map.of(
"message", error.getMessage(), "message", error.getMessage(),
"code", String.valueOf(error.getErrorCode()), "code", String.valueOf(error.getErrorCode()),
"type", String.valueOf(error.getErrorType()) "type", String.valueOf(error.getErrorType()));
);
eventEmitter.onReceiveAdEvent.invoke("ERROR", errMap); eventEmitter.onReceiveAdEvent.invoke("ERROR", errMap);
} }