From c3fbe31fd4d7f92cdcddeca4e2348850680b0b02 Mon Sep 17 00:00:00 2001 From: tapframe Date: Mon, 15 Dec 2025 00:49:00 +0530 Subject: [PATCH] dependency update --- .../exoplayer/ReactExoplayerView.java | 584 ++++++++++-------- 1 file changed, 334 insertions(+), 250 deletions(-) diff --git a/node_modules/react-native-video/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/node_modules/react-native-video/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 87e436a..0284f8e 100644 --- a/node_modules/react-native-video/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/node_modules/react-native-video/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -222,11 +222,13 @@ public class ReactExoplayerView extends FrameLayout implements private ArrayList rootViewChildrenOriginalVisibility = new ArrayList(); /* - * 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 */ private boolean isSeeking = false; private long seekPosition = -1; + private boolean hasVideoEnded = false; // Props from React private Source source = new Source(); @@ -291,7 +293,8 @@ public class ReactExoplayerView extends FrameLayout implements lastPos = pos; lastBufferDuration = bufferedDuration; lastDuration = duration; - eventEmitter.onVideoProgress.invoke(pos, bufferedDuration, player.getDuration(), getPositionInFirstPeriodMsForCurrentWindow(pos)); + eventEmitter.onVideoProgress.invoke(pos, bufferedDuration, player.getDuration(), + getPositionInFirstPeriodMsForCurrentWindow(pos)); } } } @@ -309,7 +312,7 @@ public class ReactExoplayerView extends FrameLayout implements public double getPositionInFirstPeriodMsForCurrentWindow(long currentPosition) { Timeline.Window window = new Timeline.Window(); - if(!player.getCurrentTimeline().isEmpty()) { + if (!player.getCurrentTimeline().isEmpty()) { player.getCurrentTimeline().getWindow(player.getCurrentMediaItemIndex(), window); } return window.windowStartTimeMs + currentPosition; @@ -348,9 +351,9 @@ public class ReactExoplayerView extends FrameLayout implements LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); exoPlayerView = new ExoPlayerView(getContext()); - exoPlayerView.addOnLayoutChangeListener( (View v, int l, int t, int r, int b, int ol, int ot, int or, int ob) -> - PictureInPictureUtil.applySourceRectHint(themedReactContext, pictureInPictureParamsBuilder, exoPlayerView) - ); + exoPlayerView.addOnLayoutChangeListener( + (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); addView(exoPlayerView, 0, layoutParams); @@ -376,8 +379,10 @@ public class ReactExoplayerView extends FrameLayout implements public void onHostPause() { isInBackground = true; Activity activity = themedReactContext.getCurrentActivity(); - boolean isInPictureInPicture = Util.SDK_INT >= Build.VERSION_CODES.N && activity != null && activity.isInPictureInPictureMode(); - boolean isInMultiWindowMode = Util.SDK_INT >= Build.VERSION_CODES.N && activity != null && activity.isInMultiWindowMode(); + boolean isInPictureInPicture = Util.SDK_INT >= Build.VERSION_CODES.N && activity != null + && activity.isInPictureInPictureMode(); + boolean isInMultiWindowMode = Util.SDK_INT >= Build.VERSION_CODES.N && activity != null + && activity.isInMultiWindowMode(); if (playInBackground || isInPictureInPicture || isInMultiWindowMode) { return; } @@ -396,7 +401,7 @@ public class ReactExoplayerView extends FrameLayout implements viewHasDropped = true; } - //BandwidthMeter.EventListener implementation + // BandwidthMeter.EventListener implementation @Override public void onBandwidthSample(int elapsedMs, long bytes, long bitrate) { if (mReportBandwidth) { @@ -404,7 +409,8 @@ public class ReactExoplayerView extends FrameLayout implements eventEmitter.onVideoBandwidthUpdate.invoke(bitrate, 0, 0, null); } else { 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 height = videoFormat != null ? (isRotatedContent ? videoFormat.width : videoFormat.height) : 0; 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 */ private void togglePlayerControlVisibility() { - if (player == null) return; + if (player == null) + return; if (exoPlayerView.isControllerVisible()) { exoPlayerView.hideController(); } else { @@ -429,7 +436,7 @@ public class ReactExoplayerView extends FrameLayout implements private void initializePlayerControl() { exoPlayerView.setPlayer(player); - + exoPlayerView.setControllerVisibilityListener(visibility -> { boolean isVisible = visibility == View.VISIBLE; eventEmitter.onControlsVisibilityChange.invoke(isVisible); @@ -443,26 +450,28 @@ public class ReactExoplayerView extends FrameLayout implements } private void updateControllerConfig() { - if (exoPlayerView == null) return; - + if (exoPlayerView == null) + return; + exoPlayerView.setControllerShowTimeoutMs(5000); - + exoPlayerView.setControllerAutoShow(true); exoPlayerView.setControllerHideOnTouch(true); - + updateControllerVisibility(); } private void updateControllerVisibility() { - if (exoPlayerView == null) return; - + if (exoPlayerView == null) + return; + exoPlayerView.setUseController(controls && !controlsConfig.getHideFullscreen()); } private void openSettings() { AlertDialog.Builder builder = new AlertDialog.Builder(themedReactContext); builder.setTitle(R.string.settings); - String[] settingsOptions = {themedReactContext.getString(R.string.playback_speed)}; + String[] settingsOptions = { themedReactContext.getString(R.string.playback_speed) }; builder.setItems(settingsOptions, (dialog, which) -> { if (which == 0) { showPlaybackSpeedOptions(); @@ -472,7 +481,7 @@ public class ReactExoplayerView extends FrameLayout implements } private void showPlaybackSpeedOptions() { - String[] speedOptions = {"0.5x", "1.0x", "1.5x", "2.0x"}; + String[] speedOptions = { "0.5x", "1.0x", "1.5x", "2.0x" }; AlertDialog.Builder builder = new AlertDialog.Builder(themedReactContext); builder.setTitle(R.string.select_playback_speed); @@ -490,8 +499,10 @@ public class ReactExoplayerView extends FrameLayout implements speed = 2.0f; break; default: - speed = 1.0f;; - }; + speed = 1.0f; + ; + } + ; setRateModifier(speed); }); builder.show(); @@ -503,24 +514,30 @@ public class ReactExoplayerView extends FrameLayout implements /** * 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: ... + * This is a workaround for the open bug in react-native: ... */ private void reLayout(View view) { - if (view == null) return; + if (view == null) + return; view.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); view.layout(view.getLeft(), view.getTop(), view.getMeasuredWidth(), view.getMeasuredHeight()); } private void refreshControlsStyles() { - if (exoPlayerView == null || player == null || !controls) return; + if (exoPlayerView == null || player == null || !controls) + return; updateControllerVisibility(); } - // Note: The following methods for live content and button visibility are no longer needed - // as PlayerView handles controls automatically. Some functionality may need to be + // Note: The following methods for live content and button visibility are no + // longer needed + // as PlayerView handles controls automatically. Some functionality may need to + // be // reimplemented using PlayerView's APIs if custom behavior is required. private void reLayoutControls() { @@ -557,6 +574,7 @@ public class ReactExoplayerView extends FrameLayout implements private class RNVLoadControl extends DefaultLoadControl { private final int availableHeapInBytes; private final Runtime runtime; + public RNVLoadControl(DefaultAllocator allocator, BufferConfig config) { super(allocator, config.getMinBufferMs() != BufferConfig.Companion.getBufferConfigPropUnsetInt() @@ -567,7 +585,7 @@ public class ReactExoplayerView extends FrameLayout implements : DefaultLoadControl.DEFAULT_MAX_BUFFER_MS, config.getBufferForPlaybackMs() != BufferConfig.Companion.getBufferConfigPropUnsetInt() ? config.getBufferForPlaybackMs() - : DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS , + : DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS, config.getBufferForPlaybackAfterRebufferMs() != BufferConfig.Companion.getBufferConfigPropUnsetInt() ? config.getBufferForPlaybackAfterRebufferMs() : DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS, @@ -578,10 +596,12 @@ public class ReactExoplayerView extends FrameLayout implements : DefaultLoadControl.DEFAULT_BACK_BUFFER_DURATION_MS, DefaultLoadControl.DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME); runtime = Runtime.getRuntime(); - ActivityManager activityManager = (ActivityManager) themedReactContext.getSystemService(ThemedReactContext.ACTIVITY_SERVICE); - double maxHeap = config.getMaxHeapAllocationPercent() != BufferConfig.Companion.getBufferConfigPropUnsetDouble() - ? config.getMaxHeapAllocationPercent() - : DEFAULT_MAX_HEAP_ALLOCATION_PERCENT; + ActivityManager activityManager = (ActivityManager) themedReactContext + .getSystemService(ThemedReactContext.ACTIVITY_SERVICE); + double maxHeap = config.getMaxHeapAllocationPercent() != BufferConfig.Companion + .getBufferConfigPropUnsetDouble() + ? config.getMaxHeapAllocationPercent() + : DEFAULT_MAX_HEAP_ALLOCATION_PERCENT; 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 freeMemory = runtime.maxMemory() - usedMemory; - double minBufferMemoryReservePercent = source.getBufferConfig().getMinBufferMemoryReservePercent() != BufferConfig.Companion.getBufferConfigPropUnsetDouble() - ? source.getBufferConfig().getMinBufferMemoryReservePercent() - : ReactExoplayerView.DEFAULT_MIN_BUFFER_MEMORY_RESERVE; + double minBufferMemoryReservePercent = source.getBufferConfig() + .getMinBufferMemoryReservePercent() != BufferConfig.Companion.getBufferConfigPropUnsetDouble() + ? source.getBufferConfig().getMinBufferMemoryReservePercent() + : ReactExoplayerView.DEFAULT_MIN_BUFFER_MEMORY_RESERVE; long reserveMemory = (long) minBufferMemoryReservePercent * runtime.maxMemory(); long bufferedMs = bufferedDurationUs / (long) 1000; 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; } if (runtime.freeMemory() == 0) { @@ -639,13 +661,13 @@ public class ReactExoplayerView extends FrameLayout implements // Initialize core configuration and listeners initializePlayerCore(self); 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) { RNVSimpleCache.INSTANCE.setSimpleCache( this.getContext(), - source.getBufferConfig().getCacheSize() - ); + source.getBufferConfig().getCacheSize()); useCache = true; } else { useCache = false; @@ -653,7 +675,8 @@ public class ReactExoplayerView extends FrameLayout implements if (playerNeedsSource) { // Will force display of shutter view if needed 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(); es.execute(() -> { // DRM initialization must run on a different thread @@ -662,7 +685,8 @@ public class ReactExoplayerView extends FrameLayout implements } if (activity == null) { 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; } @@ -715,8 +739,7 @@ public class ReactExoplayerView extends FrameLayout implements DefaultAllocator allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE); RNVLoadControl loadControl = new RNVLoadControl( allocator, - source.getBufferConfig() - ); + source.getBufferConfig()); long initialBitrate = source.getBufferConfig().getInitialBitrate(); if (initialBitrate > 0) { @@ -724,15 +747,15 @@ public class ReactExoplayerView extends FrameLayout implements this.bandwidthMeter = config.getBandwidthMeter(); } - DefaultRenderersFactory renderersFactory = - new DefaultRenderersFactory(getContext()) - .setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER) - .setEnableDecoderFallback(true) - .forceEnableMediaCodecAsynchronousQueueing(); + DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(getContext()) + .setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER) + .setEnableDecoderFallback(true) + .forceEnableMediaCodecAsynchronousQueueing(); DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory); if (useCache && !disableCache) { - mediaSourceFactory.setDataSourceFactory(RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true))); + mediaSourceFactory + .setDataSourceFactory(RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true))); } mediaSourceFactory.setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView.getPlayerView()); @@ -759,7 +782,7 @@ public class ReactExoplayerView extends FrameLayout implements player.setPlaybackParameters(params); changeAudioOutput(this.audioOutput); - if(showNotificationControls) { + if (showNotificationControls) { setupPlaybackService(); } } @@ -771,8 +794,7 @@ public class ReactExoplayerView extends FrameLayout implements Uri adTagUrl = adProps.getAdTagUrl(); if (adTagUrl != null) { // Create an AdsLoader. - ImaAdsLoader.Builder imaLoaderBuilder = new ImaAdsLoader - .Builder(themedReactContext) + ImaAdsLoader.Builder imaLoaderBuilder = new ImaAdsLoader.Builder(themedReactContext) .setAdEventListener(this) .setAdErrorListener(this); @@ -804,7 +826,8 @@ public class ReactExoplayerView extends FrameLayout implements } 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(); if (drmManager == null) { // 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); 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 - DrmSessionManager overriddenManager = ReactNativeVideoManager.Companion.getInstance().overrideDrmSessionManager(source, drmSessionManager); + DrmSessionManager overriddenManager = ReactNativeVideoManager.Companion.getInstance() + .overrideDrmSessionManager(source, drmSessionManager); return overriddenManager != null ? overriddenManager : drmSessionManager; } catch (UnsupportedDrmException ex) { // Unsupported DRM exceptions are handled by the calling method @@ -835,7 +860,8 @@ public class ReactExoplayerView extends FrameLayout implements } /// init DRM 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 DebugLog.e(TAG, "Failed to initialize DRM Session Manager Framework!"); return; @@ -892,7 +918,8 @@ public class ReactExoplayerView extends FrameLayout implements } catch (UnsupportedDrmException e) { int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported : (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME - ? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown); + ? R.string.error_drm_unsupported_scheme + : R.string.error_drm_unknown); eventEmitter.onVideoError.invoke(getResources().getString(errorStringId), e, "3003"); } } @@ -937,7 +964,8 @@ public class ReactExoplayerView extends FrameLayout implements if (playbackServiceBinder != null) { playbackServiceBinder.getService().unregisterPlayer(player); } - } catch (Exception ignored) {} + } catch (Exception ignored) { + } playbackServiceBinder = null; } @@ -969,21 +997,22 @@ public class ReactExoplayerView extends FrameLayout implements private void cleanupPlaybackService() { try { - if(player != null && playbackServiceBinder != null) { + if (player != null && playbackServiceBinder != null) { playbackServiceBinder.getService().unregisterPlayer(player); } playbackServiceBinder = null; - if(playbackServiceConnection != null) { + if (playbackServiceConnection != null) { themedReactContext.unbindService(playbackServiceConnection); } - } catch(Exception e) { + } catch (Exception e) { DebugLog.w(TAG, "Cloud not cleanup playback service"); } } - 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) { throw new IllegalStateException("Invalid video uri"); } @@ -1004,23 +1033,23 @@ public class ReactExoplayerView extends FrameLayout implements if (customMetadata != null) { mediaItemBuilder.setMediaMetadata(customMetadata); } - + // Add external subtitles to MediaItem List subtitleConfigurations = buildSubtitleConfigurations(); if (subtitleConfigurations != null) { mediaItemBuilder.setSubtitleConfigurations(subtitleConfigurations); } - + if (source.getAdsProps() != null) { Uri adTagUrl = source.getAdsProps().getAdTagUrl(); if (adTagUrl != null) { 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()); MediaSource.Factory mediaSourceFactory; @@ -1032,29 +1061,26 @@ public class ReactExoplayerView extends FrameLayout implements drmProvider = new DefaultDrmSessionManagerProvider(); } - switch (type) { case CONTENT_TYPE_SS: - if(!BuildConfig.USE_EXOPLAYER_SMOOTH_STREAMING) { + if (!BuildConfig.USE_EXOPLAYER_SMOOTH_STREAMING) { DebugLog.e("Exo Player Exception", "Smooth Streaming is not enabled!"); throw new IllegalStateException("Smooth Streaming is not enabled!"); } mediaSourceFactory = new SsMediaSource.Factory( new DefaultSsChunkSource.Factory(mediaDataSourceFactory), - buildDataSourceFactory(false) - ); + buildDataSourceFactory(false)); break; case CONTENT_TYPE_DASH: - if(!BuildConfig.USE_EXOPLAYER_DASH) { + if (!BuildConfig.USE_EXOPLAYER_DASH) { DebugLog.e("Exo Player Exception", "DASH is not enabled!"); throw new IllegalStateException("DASH is not enabled!"); } mediaSourceFactory = new DashMediaSource.Factory( new DefaultDashChunkSource.Factory(mediaDataSourceFactory), - buildDataSourceFactory(false) - ); + buildDataSourceFactory(false)); break; case CONTENT_TYPE_HLS: if (!BuildConfig.USE_EXOPLAYER_HLS) { @@ -1069,13 +1095,14 @@ public class ReactExoplayerView extends FrameLayout implements } mediaSourceFactory = new HlsMediaSource.Factory( - dataSourceFactory - ).setAllowChunklessPreparation(source.getTextTracksAllowChunklessPreparation()); + dataSourceFactory) + .setAllowChunklessPreparation(source.getTextTracksAllowChunklessPreparation()); break; case CONTENT_TYPE_OTHER: if ("asset".equals(uri.getScheme())) { try { - DataSource.Factory assetDataSourceFactory = DataSourceUtil.buildAssetDataSourceFactory(themedReactContext, uri); + DataSource.Factory assetDataSourceFactory = DataSourceUtil + .buildAssetDataSourceFactory(themedReactContext, uri); mediaSourceFactory = new ProgressiveMediaSource.Factory(assetDataSourceFactory); } catch (Exception e) { throw new IllegalStateException("cannot open input file:" + uri); @@ -1083,12 +1110,10 @@ public class ReactExoplayerView extends FrameLayout implements } else if ("file".equals(uri.getScheme()) || !useCache) { mediaSourceFactory = new ProgressiveMediaSource.Factory( - mediaDataSourceFactory - ); + mediaDataSourceFactory); } else { mediaSourceFactory = new ProgressiveMediaSource.Factory( - RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true)) - ); + RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true))); } break; @@ -1107,20 +1132,19 @@ public class ReactExoplayerView extends FrameLayout implements if (cmcdConfigurationFactory != null) { mediaSourceFactory = mediaSourceFactory.setCmcdConfigurationFactory( - cmcdConfigurationFactory::createCmcdConfiguration - ); + cmcdConfigurationFactory::createCmcdConfiguration); } mediaSourceFactory = Objects.requireNonNullElse( ReactNativeVideoManager.Companion.getInstance() .overrideMediaSourceFactory(source, mediaSourceFactory, mediaDataSourceFactory), - mediaSourceFactory - ); + mediaSourceFactory); mediaItemBuilder.setStreamKeys(streamKeys); @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 ? overridenMediaItemBuilder.build() @@ -1129,8 +1153,7 @@ public class ReactExoplayerView extends FrameLayout implements MediaSource mediaSource = mediaSourceFactory .setDrmSessionManagerProvider(drmProvider) .setLoadErrorHandlingPolicy( - config.buildLoadErrorHandlingPolicy(source.getMinLoadRetryCount()) - ) + config.buildLoadErrorHandlingPolicy(source.getMinLoadRetryCount())) .createMediaSource(mediaItem); if (cropStartMs >= 0 && cropEndMs >= 0) { @@ -1164,32 +1187,36 @@ public class ReactExoplayerView extends FrameLayout implements label += " (" + track.getLanguage() + ")"; } } - - MediaItem.SubtitleConfiguration.Builder configBuilder = new MediaItem.SubtitleConfiguration.Builder(track.getUri()) + + MediaItem.SubtitleConfiguration.Builder configBuilder = new MediaItem.SubtitleConfiguration.Builder( + track.getUri()) .setId(trackId) .setMimeType(track.getType()) .setLabel(label) .setRoleFlags(C.ROLE_FLAG_SUBTITLE); - + // Set language if available if (track.getLanguage() != null && !track.getLanguage().isEmpty()) { 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))) { configBuilder.setSelectionFlags(C.SELECTION_FLAG_DEFAULT); } else { configBuilder.setSelectionFlags(0); } - + MediaItem.SubtitleConfiguration subtitleConfiguration = configBuilder.build(); subtitleConfigurations.add(subtitleConfiguration); - - DebugLog.d(TAG, "Created subtitle configuration: " + trackId + " - " + label + " (" + track.getType() + ")"); + + DebugLog.d(TAG, + "Created subtitle configuration: " + trackId + " - " + label + " (" + track.getType() + ")"); trackIndex++; } 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()); } } @@ -1202,7 +1229,7 @@ public class ReactExoplayerView extends FrameLayout implements private void releasePlayer() { if (player != null) { - if(playbackServiceBinder != null) { + if (playbackServiceBinder != null) { playbackServiceBinder.getService().unregisterPlayer(player); themedReactContext.unbindService(playbackServiceConnection); } @@ -1252,7 +1279,8 @@ public class ReactExoplayerView extends FrameLayout implements case AudioManager.AUDIOFOCUS_LOSS: view.hasAudioFocus = 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) { activity.runOnUiThread(view::pausePlayback); } @@ -1273,16 +1301,12 @@ public class ReactExoplayerView extends FrameLayout implements if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) { // Lower the volume if (!view.muted) { - activity.runOnUiThread(() -> - view.player.setVolume(view.audioVolume * 0.8f) - ); + activity.runOnUiThread(() -> view.player.setVolume(view.audioVolume * 0.8f)); } } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { // Raise it back to normal if (!view.muted) { - activity.runOnUiThread(() -> - view.player.setVolume(view.audioVolume * 1) - ); + activity.runOnUiThread(() -> view.player.setVolume(view.audioVolume * 1)); } } } @@ -1355,7 +1379,8 @@ public class ReactExoplayerView extends FrameLayout implements /** * 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. * @return A new DataSource factory. */ @@ -1367,12 +1392,14 @@ public class ReactExoplayerView extends FrameLayout implements /** * Returns a new HttpDataSource factory. * - * @param useBandwidthMeter Whether to set {@link #bandwidthMeter} as a listener to the new - * DataSource factory. + * @param useBandwidthMeter Whether to set {@link #bandwidthMeter} as a listener + * to the new + * DataSource factory. * @return A new HttpDataSource factory. */ 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 @@ -1389,11 +1416,13 @@ public class ReactExoplayerView extends FrameLayout implements @Override 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(); boolean playWhenReady = player.getPlayWhenReady(); 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) { case Player.STATE_IDLE: text += "idle"; @@ -1411,6 +1440,7 @@ public class ReactExoplayerView extends FrameLayout implements break; case Player.STATE_READY: text += "ready"; + hasVideoEnded = false; eventEmitter.onReadyForDisplay.invoke(); onBuffering(false); clearProgressMessageHandler(); // ensure there is no other message @@ -1429,7 +1459,10 @@ public class ReactExoplayerView extends FrameLayout implements case Player.STATE_ENDED: text += "ended"; updateProgress(); - eventEmitter.onVideoEnd.invoke(); + if (!hasVideoEnded) { + hasVideoEnded = true; + eventEmitter.onVideoEnd.invoke(); + } onStopPlayback(); setKeepScreenOn(false); break; @@ -1446,9 +1479,11 @@ public class ReactExoplayerView extends FrameLayout implements } /** - * The progress message handler will duplicate recursions of the onProgressMessage handler - * on change of player state from any state to STATE_READY with playWhenReady is true (when - * the video is not paused). This clears all existing messages. + * The progress message handler will duplicate recursions of the + * onProgressMessage handler + * on change of player state from any state to STATE_READY with playWhenReady is + * true (when + * the video is not paused). This clears all existing messages. */ private void clearProgressMessageHandler() { progressHandler.removeMessages(SHOW_PROGRESS); @@ -1467,7 +1502,8 @@ public class ReactExoplayerView extends FrameLayout implements setSelectedTextTrack(textTrackType, textTrackValue); } 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 height = videoFormat != null ? (isRotatedContent ? videoFormat.width : videoFormat.height) : 0; String trackId = videoFormat != null ? videoFormat.id : null; @@ -1476,19 +1512,20 @@ public class ReactExoplayerView extends FrameLayout implements long duration = player.getDuration(); long currentPosition = player.getCurrentPosition(); ArrayList audioTracks = getAudioTrackInfo(); - ArrayList textTracks = getTextTrackInfo(); + ArrayList textTracks = getTextTrackInfo(); if (source.getContentStartTime() != -1) { ExecutorService es = Executors.newSingleThreadExecutor(); 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 videoTracks = getVideoTrackInfoFromManifest(); if (videoTracks != null) { isUsingContentResolution = true; } eventEmitter.onVideoLoad.invoke(duration, currentPosition, width, height, - audioTracks, textTracks, videoTracks, trackId ); - + audioTracks, textTracks, videoTracks, trackId); + updateSubtitleButtonVisibility(); }); return; @@ -1505,9 +1542,9 @@ public class ReactExoplayerView extends FrameLayout implements } private static boolean isTrackSelected(TrackSelection selection, TrackGroup group, - int trackIndex){ + int trackIndex) { return selection != null && selection.getTrackGroup() == group - && selection.indexOf( trackIndex ) != C.INDEX_UNSET; + && selection.indexOf(trackIndex) != C.INDEX_UNSET; } private ArrayList getAudioTrackInfo() { @@ -1525,23 +1562,22 @@ public class ReactExoplayerView extends FrameLayout implements TrackSelectionArray selectionArray = player.getCurrentTrackSelections(); TrackSelection selection = selectionArray.get(C.TRACK_TYPE_AUDIO); - for (int groupIndex = 0; groupIndex < groups.length; ++groupIndex) { TrackGroup group = groups.get(groupIndex); Format format = group.getFormat(0); - + // Check if this specific group is the currently selected one boolean isSelected = false; if (selection != null && selection.getTrackGroup() == group) { isSelected = true; } - + Track audioTrack = exoplayerTrackToGenericTrack(format, groupIndex, selection, group); audioTrack.setBitrate(format.bitrate == Format.NO_VALUE ? 0 : format.bitrate); audioTrack.setSelected(isSelected); audioTracks.add(audioTrack); } - + return audioTracks; } @@ -1551,7 +1587,8 @@ public class ReactExoplayerView extends FrameLayout implements videoTrack.setHeight(format.height == Format.NO_VALUE ? 0 : format.height); videoTrack.setBitrate(format.bitrate == Format.NO_VALUE ? 0 : format.bitrate); 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.setIndex(trackIndex); return videoTrack; @@ -1588,7 +1625,8 @@ public class ReactExoplayerView extends FrameLayout implements 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 private ArrayList getVideoTrackInfoFromManifest(int retryCount) { ExecutorService es = Executors.newSingleThreadExecutor(); @@ -1603,18 +1641,20 @@ public class ReactExoplayerView extends FrameLayout implements public ArrayList call() { ArrayList videoTracks = new ArrayList<>(); - try { + try { DashManifest manifest = DashUtil.loadManifest(this.ds, this.uri); int periodCount = manifest.getPeriodCount(); for (int i = 0; i < periodCount; 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); if (adaptation.type != C.TRACK_TYPE_VIDEO) { continue; } 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); Format format = representation.format; if (isFormatSupported(format)) { @@ -1622,7 +1662,8 @@ public class ReactExoplayerView extends FrameLayout implements break; } hasFoundContentPeriod = true; - VideoTrack videoTrack = exoplayerVideoTrackToGenericVideoTrack(format, representationIndex); + VideoTrack videoTrack = exoplayerVideoTrackToGenericVideoTrack(format, + representationIndex); videoTracks.add(videoTrack); } } @@ -1652,12 +1693,16 @@ public class ReactExoplayerView extends FrameLayout implements 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.setIndex(trackIndex); - if (format.sampleMimeType != null) track.setMimeType(format.sampleMimeType); - if (format.language != null) track.setLanguage(format.language); - if (format.label != null) track.setTitle(format.label); + if (format.sampleMimeType != null) + track.setMimeType(format.sampleMimeType); + if (format.language != null) + track.setLanguage(format.language); + if (format.label != null) + track.setTitle(format.label); track.setSelected(isTrackSelected(selection, group, trackIndex)); return track; } @@ -1667,13 +1712,13 @@ public class ReactExoplayerView extends FrameLayout implements if (trackSelector == null) { return textTracks; } - + MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo(); int index = getTrackRendererIndex(C.TRACK_TYPE_TEXT); if (info == null || index == C.INDEX_UNSET) { return textTracks; } - + TrackSelectionArray selectionArray = player.getCurrentTrackSelections(); TrackSelection selection = selectionArray.get(C.TRACK_TYPE_TEXT); TrackGroupArray groups = info.getTrackGroups(index); @@ -1683,12 +1728,12 @@ public class ReactExoplayerView extends FrameLayout implements for (int trackIndex = 0; trackIndex < group.length; trackIndex++) { Format format = group.getFormat(trackIndex); Track textTrack = exoplayerTrackToGenericTrack(format, trackIndex, selection, group); - + boolean isExternal = format.id != null && format.id.startsWith("external-subtitle-"); boolean isSelected = isTrackSelected(selection, group, trackIndex); - + textTrack.setIndex(textTracks.size()); - + if (textTrack.getTitle() == null || textTrack.getTitle().isEmpty()) { if (isExternal) { textTrack.setTitle("External " + (trackIndex + 1)); @@ -1696,7 +1741,7 @@ public class ReactExoplayerView extends FrameLayout implements textTrack.setTitle("Track " + (textTracks.size() + 1)); } } - + textTracks.add(textTrack); } } @@ -1716,23 +1761,24 @@ public class ReactExoplayerView extends FrameLayout implements } TrackGroupArray groups = info.getTrackGroups(index); - + for (int groupIndex = 0; groupIndex < groups.length; ++groupIndex) { TrackGroup group = groups.get(groupIndex); Format format = group.getFormat(0); - + // Create track without trying to determine selection status Track track = new Track(); track.setIndex(groupIndex); track.setLanguage(format.language != null ? format.language : "unknown"); track.setTitle(format.label != null ? format.label : "Track " + (groupIndex + 1)); 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); - + tracks.add(track); } - + DebugLog.d(TAG, "getBasicAudioTrackInfo: returning " + tracks.size() + " audio tracks (no selection status)"); return tracks; } @@ -1742,27 +1788,29 @@ public class ReactExoplayerView extends FrameLayout implements if (trackSelector == null) { return textTracks; } - + MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo(); int index = getTrackRendererIndex(C.TRACK_TYPE_TEXT); if (info == null || index == C.INDEX_UNSET) { return textTracks; } - + TrackGroupArray groups = info.getTrackGroups(index); for (int groupIndex = 0; groupIndex < groups.length; ++groupIndex) { TrackGroup group = groups.get(groupIndex); for (int trackIndex = 0; trackIndex < group.length; trackIndex++) { Format format = group.getFormat(trackIndex); - + Track textTrack = new Track(); textTrack.setIndex(textTracks.size()); - if (format.sampleMimeType != null) textTrack.setMimeType(format.sampleMimeType); - if (format.language != null) textTrack.setLanguage(format.language); - + if (format.sampleMimeType != null) + textTrack.setMimeType(format.sampleMimeType); + if (format.language != null) + textTrack.setLanguage(format.language); + boolean isExternal = format.id != null && format.id.startsWith("external-subtitle-"); - + if (format.label != null && !format.label.isEmpty()) { textTrack.setTitle(format.label); } else if (isExternal) { @@ -1770,7 +1818,7 @@ public class ReactExoplayerView extends FrameLayout implements } else { textTrack.setTitle("Track " + (textTracks.size() + 1)); } - + textTrack.setSelected(false); // Don't report selection status - let PlayerView handle it textTracks.add(textTrack); } @@ -1793,33 +1841,42 @@ public class ReactExoplayerView extends FrameLayout implements } @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) { isSeeking = true; seekPosition = newPosition.positionMs; 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); } } if (playerNeedsSource) { - // This will only occur if the user has performed a seek whilst in the error state. Update the - // resume position so that if the user then retries, playback will resume from the position to + // This will only occur if the user has performed a seek whilst in the error + // state. Update the + // resume position so that if the user then retries, playback will resume from + // the position to // which they seeked. updateResumePosition(); } 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); 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. if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION && player.getRepeatMode() == Player.REPEAT_MODE_ONE) { updateProgress(); - eventEmitter.onVideoEnd.invoke(); + if (!hasVideoEnded) { + hasVideoEnded = true; + eventEmitter.onVideoEnd.invoke(); + } } } @@ -1831,12 +1888,12 @@ public class ReactExoplayerView extends FrameLayout implements @Override public void onTracksChanged(@NonNull Tracks tracks) { DebugLog.d(TAG, "onTracksChanged called - updating track information, controls=" + controls); - + if (controls) { ArrayList textTracks = getBasicTextTrackInfo(); - ArrayList audioTracks = getBasicAudioTrackInfo(); + ArrayList audioTracks = getBasicAudioTrackInfo(); ArrayList videoTracks = getVideoTrackInfo(); - + eventEmitter.onTextTracks.invoke(textTracks); eventEmitter.onAudioTracks.invoke(audioTracks); eventEmitter.onVideoTracks.invoke(videoTracks); @@ -1844,7 +1901,7 @@ public class ReactExoplayerView extends FrameLayout implements ArrayList textTracks = getTextTrackInfo(); ArrayList audioTracks = getAudioTrackInfo(); ArrayList videoTracks = getVideoTrackInfo(); - + eventEmitter.onTextTracks.invoke(textTracks); eventEmitter.onAudioTracks.invoke(audioTracks); eventEmitter.onVideoTracks.invoke(videoTracks); @@ -1855,22 +1912,24 @@ public class ReactExoplayerView extends FrameLayout implements } } } - + updateSubtitleButtonVisibility(); } - - + private boolean hasBuiltInTextTracks() { - if (player == null || trackSelector == null) return false; - + if (player == null || trackSelector == null) + return false; + MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo(); - if (info == null) return false; - + if (info == null) + return false; + 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); - + // Check if any groups have tracks that are NOT external subtitles for (int i = 0; i < groups.length; i++) { TrackGroup group = groups.get(i); @@ -1882,17 +1941,18 @@ public class ReactExoplayerView extends FrameLayout implements } } } - + return false; } private void updateSubtitleButtonVisibility() { - if (exoPlayerView == null) return; - - boolean hasTextTracks = (source.getSideLoadedTextTracks() != null && - !source.getSideLoadedTextTracks().getTracks().isEmpty()) || - hasBuiltInTextTracks(); - + if (exoPlayerView == null) + return; + + boolean hasTextTracks = (source.getSideLoadedTextTracks() != null && + !source.getSideLoadedTextTracks().getTracks().isEmpty()) || + hasBuiltInTextTracks(); + exoPlayerView.setShowSubtitleButton(hasTextTracks); } @@ -1911,7 +1971,8 @@ public class ReactExoplayerView extends FrameLayout implements if (isPlaying && isSeeking) { eventEmitter.onVideoSeek.invoke(player.getCurrentPosition(), seekPosition); } - PictureInPictureUtil.applyPlayingStatus(themedReactContext, pictureInPictureParamsBuilder, pictureInPictureReceiver, !isPlaying); + PictureInPictureUtil.applyPlayingStatus(themedReactContext, pictureInPictureParamsBuilder, + pictureInPictureReceiver, !isPlaying); eventEmitter.onVideoPlaybackStateChanged.invoke(isPlaying, isSeeking); if (isPlaying) { @@ -1923,14 +1984,15 @@ public class ReactExoplayerView extends FrameLayout implements public void onPlayerError(@NonNull PlaybackException e) { String errorString = "ExoPlaybackException: " + PlaybackException.getErrorCodeName(e.errorCode); String errorCode = "2" + e.errorCode; - switch(e.errorCode) { + switch (e.errorCode) { case PlaybackException.ERROR_CODE_DRM_DEVICE_REVOKED: case PlaybackException.ERROR_CODE_DRM_LICENSE_ACQUISITION_FAILED: case PlaybackException.ERROR_CODE_DRM_PROVISIONING_FAILED: case PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR: case PlaybackException.ERROR_CODE_DRM_UNSPECIFIED: 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; playerNeedsSource = true; updateResumePosition(); @@ -2012,14 +2074,16 @@ public class ReactExoplayerView extends FrameLayout implements boolean isSourceEqual = source.isEquals(this.source); hasDrmFailed = false; this.source = source; - final DataSource.Factory tmpMediaDataSourceFactory = - DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, bandwidthMeter, - source.getHeaders()); + final DataSource.Factory tmpMediaDataSourceFactory = DataSourceUtil.getDefaultDataSourceFactory( + this.themedReactContext, bandwidthMeter, + source.getHeaders()); @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) { CMCDConfig cmcdConfig = new CMCDConfig(source.getCmcdProps()); @@ -2030,6 +2094,7 @@ public class ReactExoplayerView extends FrameLayout implements } if (!isSourceEqual) { + hasVideoEnded = false; playerNeedsSource = true; initializePlayer(); } @@ -2037,6 +2102,7 @@ public class ReactExoplayerView extends FrameLayout implements clearSrc(); } } + public void clearSrc() { if (source.getUri() != null) { if (player != null) { @@ -2085,8 +2151,9 @@ public class ReactExoplayerView extends FrameLayout implements } public void disableTrack(int rendererIndex) { - if (trackSelector == null) return; - + if (trackSelector == null) + return; + DefaultTrackSelector.Parameters disableParameters = trackSelector.getParameters() .buildUpon() .setRendererDisabled(rendererIndex, true) @@ -2095,31 +2162,32 @@ public class ReactExoplayerView extends FrameLayout implements } 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); - + DefaultTrackSelector.Parameters.Builder parametersBuilder = trackSelector.getParameters().buildUpon(); - + if ("disabled".equals(type) || value == null) { parametersBuilder.setTrackTypeDisabled(C.TRACK_TYPE_TEXT, true); } else { parametersBuilder.setTrackTypeDisabled(C.TRACK_TYPE_TEXT, false); - + parametersBuilder.clearOverridesOfType(C.TRACK_TYPE_TEXT); - + MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo(); if (info != null) { int textRendererIndex = getTrackRendererIndex(C.TRACK_TYPE_TEXT); if (textRendererIndex != C.INDEX_UNSET) { TrackGroupArray groups = info.getTrackGroups(textRendererIndex); boolean trackFound = false; - + for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) { TrackGroup group = groups.get(groupIndex); for (int trackIndex = 0; trackIndex < group.length; trackIndex++) { Format format = group.getFormat(trackIndex); - + boolean isMatch = false; if ("language".equals(type) && format.language != null && format.language.equals(value)) { isMatch = true; @@ -2131,29 +2199,30 @@ public class ReactExoplayerView extends FrameLayout implements isMatch = true; } } - + if (isMatch) { - TrackSelectionOverride override = new TrackSelectionOverride(group, - java.util.Arrays.asList(trackIndex)); + TrackSelectionOverride override = new TrackSelectionOverride(group, + java.util.Arrays.asList(trackIndex)); parametersBuilder.addOverride(override); trackFound = true; break; } } - if (trackFound) break; + if (trackFound) + break; } - + if (!trackFound) { - DebugLog.w(TAG, "Text track not found for type=" + type + ", value=" + value + - ". Keeping current selection."); + DebugLog.w(TAG, "Text track not found for type=" + type + ", value=" + value + + ". Keeping current selection."); } } } } - + try { trackSelector.setParameters(parametersBuilder.build()); - + // Give PlayerView time to update its controls mainHandler.postDelayed(() -> { if (exoPlayerView != null) { @@ -2166,17 +2235,18 @@ public class ReactExoplayerView extends FrameLayout implements } public void setSelectedTrack(int trackType, String type, String value) { - if (player == null || trackSelector == null) return; - + if (player == null || trackSelector == null) + return; + if (controls) { return; } - + int rendererIndex = getTrackRendererIndex(trackType); if (rendererIndex == C.INDEX_UNSET) { return; } - + MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo(); if (info == null) { return; @@ -2240,9 +2310,11 @@ public class ReactExoplayerView extends FrameLayout implements usingExactMatch = true; break; } 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 ((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 closestFormat = format; 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) { // No close match found - so we pick the lowest quality 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 // Use system settings if possible - CaptioningManager captioningManager - = (CaptioningManager)themedReactContext.getSystemService(Context.CAPTIONING_SERVICE); + CaptioningManager captioningManager = (CaptioningManager) themedReactContext + .getSystemService(Context.CAPTIONING_SERVICE); if (captioningManager != null && captioningManager.isEnabled()) { groupIndex = getGroupIndexForDefaultLocale(groups); } @@ -2306,7 +2379,7 @@ public class ReactExoplayerView extends FrameLayout implements // With only one tracks we can't remove any tracks so attempt to play it anyway tracks = allTracks; } else { - tracks = new ArrayList<>(supportedFormatLength + 1); + tracks = new ArrayList<>(supportedFormatLength + 1); for (int k = 0; k < allTracks.size(); k++) { Format format = group.getFormat(k); if (isFormatSupported(format)) { @@ -2331,8 +2404,9 @@ public class ReactExoplayerView extends FrameLayout implements .setExceedVideoConstraintsIfNecessary(true) .setRendererDisabled(rendererIndex, false); - // 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 + // 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 if (trackType != C.TRACK_TYPE_AUDIO || !type.equals("default")) { selectionParameters.clearOverridesOfType(selectionOverride.getType()); } @@ -2347,8 +2421,8 @@ public class ReactExoplayerView extends FrameLayout implements if (trackType == C.TRACK_TYPE_AUDIO) { selectionParameters.setForceHighestSupportedBitrate(false); selectionParameters.setForceLowestBitrate(false); - DebugLog.d(TAG, "Audio track selection: group=" + groupIndex + ", tracks=" + tracks + - ", override=" + selectionOverride); + DebugLog.d(TAG, "Audio track selection: group=" + groupIndex + ", tracks=" + tracks + + ", override=" + selectionOverride); } trackSelector.setParameters(selectionParameters.build()); @@ -2379,7 +2453,7 @@ public class ReactExoplayerView extends FrameLayout implements } private int getGroupIndexForDefaultLocale(TrackGroupArray groups) { - if (groups.length == 0){ + if (groups.length == 0) { return C.INDEX_UNSET; } @@ -2400,13 +2474,14 @@ public class ReactExoplayerView extends FrameLayout implements public void setSelectedVideoTrack(String type, String value) { videoTrackType = type; 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) { audioTrackType = type; audioTrackValue = value; - + if (!controls && player != null && trackSelector != null) { setSelectedTrack(C.TRACK_TYPE_AUDIO, audioTrackType, audioTrackValue); } @@ -2415,7 +2490,7 @@ public class ReactExoplayerView extends FrameLayout implements public void setSelectedTextTrack(String type, String value) { textTrackType = type; textTrackValue = value; - + selectTextTrackInternal(type, value); } @@ -2431,9 +2506,11 @@ public class ReactExoplayerView extends FrameLayout implements } 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) { - 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); if (fullScreenPlayerView != null && fullScreenPlayerView.isShowing()) { - if (isInPictureInPicture) fullScreenPlayerView.hideWithoutPlayer(); + if (isInPictureInPicture) + fullScreenPlayerView.hideWithoutPlayer(); return; } Activity currentActivity = themedReactContext.getCurrentActivity(); - if (currentActivity == null) return; + if (currentActivity == null) + return; View decorView = currentActivity.getWindow().getDecorView(); ViewGroup rootView = decorView.findViewById(android.R.id.content); @@ -2456,7 +2535,7 @@ public class ReactExoplayerView extends FrameLayout implements LayoutParams.MATCH_PARENT); if (isInPictureInPicture) { - ViewGroup parent = (ViewGroup)exoPlayerView.getParent(); + ViewGroup parent = (ViewGroup) exoPlayerView.getParent(); if (parent != null) { parent.removeView(exoPlayerView); } @@ -2482,10 +2561,12 @@ public class ReactExoplayerView extends FrameLayout implements public void enterPictureInPictureMode() { PictureInPictureParams _pipParams = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - ArrayList actions = PictureInPictureUtil.getPictureInPictureActions(themedReactContext, isPaused, pictureInPictureReceiver); + ArrayList actions = PictureInPictureUtil.getPictureInPictureActions(themedReactContext, + isPaused, pictureInPictureReceiver); pictureInPictureParamsBuilder.setActions(actions); if (player.getPlaybackState() == Player.STATE_READY) { - pictureInPictureParamsBuilder.setAspectRatio(PictureInPictureUtil.calcPictureInPictureAspectRatio(player)); + pictureInPictureParamsBuilder + .setAspectRatio(PictureInPictureUtil.calcPictureInPictureAspectRatio(player)); } _pipParams = pictureInPictureParamsBuilder.build(); } @@ -2494,13 +2575,15 @@ public class ReactExoplayerView extends FrameLayout implements public void exitPictureInPictureMode() { Activity currentActivity = themedReactContext.getCurrentActivity(); - if (currentActivity == null) return; + if (currentActivity == null) + return; View decorView = currentActivity.getWindow().getDecorView(); ViewGroup rootView = decorView.findViewById(android.R.id.content); 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++) { rootView.getChildAt(i).setVisibility(rootViewChildrenOriginalVisibility.get(i)); } @@ -2598,7 +2681,7 @@ public class ReactExoplayerView extends FrameLayout implements if (playbackServiceConnection == null && showNotificationControls) { setupPlaybackService(); - } else if(!showNotificationControls && playbackServiceConnection != null) { + } else if (!showNotificationControls && playbackServiceConnection != null) { cleanupPlaybackService(); } } @@ -2627,12 +2710,13 @@ public class ReactExoplayerView extends FrameLayout implements } if (isFullscreen) { - fullScreenPlayerView = new FullScreenPlayerView(getContext(), exoPlayerView, this, null, new OnBackPressedCallback(true) { - @Override - public void handleOnBackPressed() { - setFullscreen(false); - } - }, controlsConfig); + fullScreenPlayerView = new FullScreenPlayerView(getContext(), exoPlayerView, this, null, + new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + setFullscreen(false); + } + }, controlsConfig); eventEmitter.onVideoFullscreenPlayerWillPresent.invoke(); if (fullScreenPlayerView != null) { fullScreenPlayerView.show(); @@ -2669,7 +2753,8 @@ public class ReactExoplayerView extends FrameLayout implements } @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"); eventEmitter.onVideoError.invoke("onDrmSessionManagerError", e, "3002"); } @@ -2687,7 +2772,7 @@ public class ReactExoplayerView extends FrameLayout implements /** * Handling controls prop * - * @param controls Controls prop, if true enable controls, if false disable them + * @param controls Controls prop, if true enable controls, if false disable them */ public void setControls(boolean controls) { this.controls = controls; @@ -2696,7 +2781,7 @@ public class ReactExoplayerView extends FrameLayout implements // Additional configuration for proper touch handling if (controls) { exoPlayerView.setControllerAutoShow(true); - exoPlayerView.setControllerHideOnTouch(true); // Show controls on touch, don't hide + exoPlayerView.setControllerHideOnTouch(true); // Show controls on touch, don't hide exoPlayerView.setControllerShowTimeoutMs(5000); } } @@ -2729,8 +2814,7 @@ public class ReactExoplayerView extends FrameLayout implements Map errMap = Map.of( "message", error.getMessage(), "code", String.valueOf(error.getErrorCode()), - "type", String.valueOf(error.getErrorType()) - ); + "type", String.valueOf(error.getErrorType())); eventEmitter.onReceiveAdEvent.invoke("ERROR", errMap); }