From b10aab605742c2805e644227b17672f499a57077 Mon Sep 17 00:00:00 2001 From: tapframe Date: Tue, 6 Jan 2026 19:44:54 +0530 Subject: [PATCH] release: 1.3.4 --- android/app/build.gradle | 6 +- android/app/src/main/res/values/strings.xml | 2 +- app.json | 8 +- .../exoplayer/ReactExoplayerView.java | 591 ++++---- nuvio-source.json | 8 + package-lock.json | 15 +- package.json | 3 +- patches/react-native-video+6.18.0.patch | 1323 ----------------- src/utils/version.ts | 2 +- 9 files changed, 290 insertions(+), 1668 deletions(-) delete mode 100644 patches/react-native-video+6.18.0.patch diff --git a/android/app/build.gradle b/android/app/build.gradle index a2458cd..435a61a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -95,8 +95,8 @@ android { applicationId 'com.nuvio.app' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 31 - versionName "1.3.3" + versionCode 32 + versionName "1.3.4" buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\"" } @@ -118,7 +118,7 @@ android { def abiVersionCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4] applicationVariants.all { variant -> variant.outputs.each { output -> - def baseVersionCode = 31 // Current versionCode 31 from defaultConfig + def baseVersionCode = 32 // Current versionCode 32 from defaultConfig def abiName = output.getFilter(com.android.build.OutputFile.ABI) def versionCode = baseVersionCode * 100 // Base multiplier diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index f01e08f..2669e01 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -3,5 +3,5 @@ contain false dark - 1.3.3 + 1.3.4 \ No newline at end of file diff --git a/app.json b/app.json index 74d3950..4929ae9 100644 --- a/app.json +++ b/app.json @@ -2,7 +2,7 @@ "expo": { "name": "Nuvio", "slug": "nuvio", - "version": "1.3.3", + "version": "1.3.4", "orientation": "default", "backgroundColor": "#020404", "icon": "./assets/ios/AppIcon.appiconset/Icon-App-60x60@3x.png", @@ -17,7 +17,7 @@ "ios": { "supportsTablet": true, "icon": "./assets/ios/AppIcon.appiconset/Icon-App-60x60@3x.png", - "buildNumber": "31", + "buildNumber": "32", "infoPlist": { "NSAppTransportSecurity": { "NSAllowsArbitraryLoads": true @@ -51,7 +51,7 @@ "android.permission.WRITE_SETTINGS" ], "package": "com.nuvio.app", - "versionCode": 31, + "versionCode": 32, "architectures": [ "arm64-v8a", "armeabi-v7a", @@ -98,6 +98,6 @@ "fallbackToCacheTimeout": 30000, "url": "https://ota.nuvioapp.space/api/manifest" }, - "runtimeVersion": "1.3.3" + "runtimeVersion": "1.3.4" } } \ No newline at end of file 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 a939148..f6f2f3f 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 @@ -165,6 +165,7 @@ public class ReactExoplayerView extends FrameLayout implements public static final double DEFAULT_MIN_BUFFER_MEMORY_RESERVE = 0; private static final String TAG = "ReactExoplayerView"; + private static final ExecutorService SHARED_EXECUTOR = Executors.newSingleThreadExecutor(); private static final CookieManager DEFAULT_COOKIE_MANAGER; private static final int SHOW_PROGRESS = 1; @@ -211,6 +212,7 @@ public class ReactExoplayerView extends FrameLayout implements private float audioVolume = 1f; private int maxBitRate = 0; private boolean hasDrmFailed = false; + private int drmRetryCount = 0; private boolean isUsingContentResolution = false; private boolean selectTrackWhenReady = false; private final Handler mainHandler; @@ -222,8 +224,7 @@ 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; @@ -270,7 +271,6 @@ public class ReactExoplayerView extends FrameLayout implements private final String instanceId = String.valueOf(UUID.randomUUID()); private CmcdConfiguration.Factory cmcdConfigurationFactory; - private static final ExecutorService SHARED_EXECUTOR = Executors.newSingleThreadExecutor(); public void setCmcdConfigurationFactory(CmcdConfiguration.Factory factory) { this.cmcdConfigurationFactory = factory; @@ -294,8 +294,7 @@ 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)); } } } @@ -313,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; @@ -352,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); @@ -380,10 +379,8 @@ 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; } @@ -402,7 +399,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) { @@ -410,8 +407,7 @@ 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; @@ -426,8 +422,7 @@ public class ReactExoplayerView extends FrameLayout implements * Toggling the visibility of the player control view */ private void togglePlayerControlVisibility() { - if (player == null) - return; + if (player == null) return; if (exoPlayerView.isControllerVisible()) { exoPlayerView.hideController(); } else { @@ -437,7 +432,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); @@ -451,28 +446,26 @@ 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(); @@ -482,7 +475,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); @@ -500,10 +493,8 @@ public class ReactExoplayerView extends FrameLayout implements speed = 2.0f; break; default: - speed = 1.0f; - ; - } - ; + speed = 1.0f;; + }; setRateModifier(speed); }); builder.show(); @@ -515,30 +506,24 @@ 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() { @@ -575,7 +560,6 @@ 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() @@ -586,7 +570,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, @@ -597,12 +581,10 @@ 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); } @@ -620,15 +602,13 @@ 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) { @@ -643,8 +623,6 @@ public class ReactExoplayerView extends FrameLayout implements } private void initializePlayer() { - drmRetryCount = 0; - hasDrmFailed = false; disableCache = ReactNativeVideoManager.Companion.getInstance().shouldDisableCache(source); ReactExoplayerView self = this; @@ -664,16 +642,15 @@ 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); } - long requestedCacheSize = source.getBufferConfig().getCacheSize(); - long MAX_SAFE_CACHE_SIZE = 100L * 1024 * 1024; - long effectiveCacheSize = Math.min(requestedCacheSize, MAX_SAFE_CACHE_SIZE); - if (!source.isLocalAssetFile() && !source.isAsset() && effectiveCacheSize > 0) { + if (!source.isLocalAssetFile() && !source.isAsset() && source.getBufferConfig().getCacheSize() > 0) { + long requestedCacheSize = source.getBufferConfig().getCacheSize(); + long MAX_SAFE_CACHE_SIZE = 100L * 1024 * 1024; + long effectiveCacheSize = Math.min(requestedCacheSize, MAX_SAFE_CACHE_SIZE); RNVSimpleCache.INSTANCE.setSimpleCache( this.getContext(), - effectiveCacheSize + (int) effectiveCacheSize ); useCache = true; } else { @@ -682,8 +659,9 @@ 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 + drmRetryCount = 0; + hasDrmFailed = false; + // DRM session manager creation must be done on a different thread to prevent crashes so we start a new thread SHARED_EXECUTOR.execute(() -> { // DRM initialization must run on a different thread if (viewHasDropped && runningSource == source) { @@ -691,8 +669,7 @@ 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; } @@ -745,7 +722,8 @@ 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) { @@ -753,15 +731,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()); @@ -788,7 +766,7 @@ public class ReactExoplayerView extends FrameLayout implements player.setPlaybackParameters(params); changeAudioOutput(this.audioOutput); - if (showNotificationControls) { + if(showNotificationControls) { setupPlaybackService(); } } @@ -800,7 +778,8 @@ 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); @@ -832,8 +811,7 @@ 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 @@ -842,13 +820,11 @@ 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 @@ -866,8 +842,7 @@ 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; @@ -921,8 +896,7 @@ 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"); } } @@ -967,8 +941,7 @@ public class ReactExoplayerView extends FrameLayout implements if (playbackServiceBinder != null) { playbackServiceBinder.getService().unregisterPlayer(player); } - } catch (Exception ignored) { - } + } catch (Exception ignored) {} playbackServiceBinder = null; } @@ -1000,22 +973,21 @@ 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"); } @@ -1036,23 +1008,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; @@ -1064,26 +1036,29 @@ 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) { @@ -1098,14 +1073,13 @@ 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); @@ -1113,10 +1087,12 @@ 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; @@ -1135,19 +1111,20 @@ 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() @@ -1156,7 +1133,8 @@ 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) { @@ -1190,36 +1168,32 @@ 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()); } } @@ -1232,7 +1206,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); } @@ -1282,8 +1256,7 @@ 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); } @@ -1304,12 +1277,16 @@ 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) + ); } } } @@ -1382,8 +1359,7 @@ 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. */ @@ -1395,14 +1371,12 @@ 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 @@ -1419,13 +1393,11 @@ 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"; @@ -1482,11 +1454,9 @@ 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); @@ -1505,8 +1475,7 @@ 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; @@ -1515,19 +1484,18 @@ 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) { SHARED_EXECUTOR.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; @@ -1544,9 +1512,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() { @@ -1564,22 +1532,23 @@ 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; } @@ -1589,8 +1558,7 @@ 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; @@ -1627,8 +1595,7 @@ 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) { final DataSource dataSource = this.mediaDataSourceFactory.createDataSource(); @@ -1642,20 +1609,18 @@ 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)) { @@ -1663,8 +1628,7 @@ public class ReactExoplayerView extends FrameLayout implements break; } hasFoundContentPeriod = true; - VideoTrack videoTrack = exoplayerVideoTrackToGenericVideoTrack(format, - representationIndex); + VideoTrack videoTrack = exoplayerVideoTrackToGenericVideoTrack(format, representationIndex); videoTracks.add(videoTrack); } } @@ -1693,16 +1657,12 @@ 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; } @@ -1712,13 +1672,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); @@ -1728,12 +1688,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)); @@ -1741,7 +1701,7 @@ public class ReactExoplayerView extends FrameLayout implements textTrack.setTitle("Track " + (textTracks.size() + 1)); } } - + textTracks.add(textTrack); } } @@ -1761,24 +1721,23 @@ 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; } @@ -1788,29 +1747,27 @@ 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) { @@ -1818,7 +1775,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); } @@ -1841,34 +1798,28 @@ 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) { @@ -1888,12 +1839,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); @@ -1901,7 +1852,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); @@ -1912,24 +1863,22 @@ 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); @@ -1941,18 +1890,17 @@ 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); } @@ -1971,8 +1919,7 @@ 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) { @@ -1984,15 +1931,14 @@ 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 if (drmRetryCount < 1) { drmRetryCount++; hasDrmFailed = true; @@ -2002,6 +1948,7 @@ public class ReactExoplayerView extends FrameLayout implements setPlayWhenReady(true); return; } + } break; default: break; @@ -2076,16 +2023,14 @@ 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()); @@ -2104,7 +2049,6 @@ public class ReactExoplayerView extends FrameLayout implements clearSrc(); } } - public void clearSrc() { if (source.getUri() != null) { if (player != null) { @@ -2153,9 +2097,8 @@ 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) @@ -2164,33 +2107,31 @@ 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; - int cumulativeIndex = 0; // Track cumulative index across all groups - + 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; @@ -2198,36 +2139,33 @@ public class ReactExoplayerView extends FrameLayout implements isMatch = true; } else if ("index".equals(type)) { int targetIndex = ReactBridgeUtils.safeParseInt(value, -1); - // Use cumulative index to match getTextTrackInfo() behavior - if (targetIndex == cumulativeIndex) { + if (targetIndex == trackIndex) { 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; } - cumulativeIndex++; // Increment after each track } - 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) { @@ -2240,18 +2178,17 @@ 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; @@ -2315,11 +2252,9 @@ 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; @@ -2330,8 +2265,7 @@ 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; @@ -2354,8 +2288,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); } @@ -2384,7 +2318,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)) { @@ -2409,9 +2343,8 @@ 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()); } @@ -2426,8 +2359,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()); @@ -2458,7 +2391,7 @@ public class ReactExoplayerView extends FrameLayout implements } private int getGroupIndexForDefaultLocale(TrackGroupArray groups) { - if (groups.length == 0) { + if (groups.length == 0){ return C.INDEX_UNSET; } @@ -2479,14 +2412,13 @@ 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); } @@ -2495,7 +2427,7 @@ public class ReactExoplayerView extends FrameLayout implements public void setSelectedTextTrack(String type, String value) { textTrackType = type; textTrackValue = value; - + selectTextTrackInternal(type, value); } @@ -2511,11 +2443,9 @@ 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); } } @@ -2523,14 +2453,12 @@ 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); @@ -2540,7 +2468,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); } @@ -2566,12 +2494,10 @@ 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(); } @@ -2580,15 +2506,13 @@ 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)); } @@ -2686,7 +2610,7 @@ public class ReactExoplayerView extends FrameLayout implements if (playbackServiceConnection == null && showNotificationControls) { setupPlaybackService(); - } else if (!showNotificationControls && playbackServiceConnection != null) { + } else if(!showNotificationControls && playbackServiceConnection != null) { cleanupPlaybackService(); } } @@ -2715,13 +2639,12 @@ 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(); @@ -2758,8 +2681,7 @@ 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"); } @@ -2777,7 +2699,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; @@ -2786,7 +2708,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); } } @@ -2819,7 +2741,8 @@ 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); } diff --git a/nuvio-source.json b/nuvio-source.json index 4088e55..4ad1c7a 100644 --- a/nuvio-source.json +++ b/nuvio-source.json @@ -30,6 +30,14 @@ "https://github.com/tapframe/NuvioStreaming/blob/main/screenshots/search-portrait.png?raw=true" ], "versions": [ + { + "version": "1.3.4", + "buildVersion": "32", + "date": "2026-01-06", + "localizedDescription": "## Update Notes\n\n### Player & Playback\n- Fixed **Android player crashes with large files** when using ExoPlayer \n - Merged PR **#361** by **@chrisk325**\n\n### Trakt Improvements\n- Improved **Trakt Continue Watching** section for better accuracy and reliability\n\n### Internationalization\n- Added **multi-language support** across the app using **i18n** \n - More languages will be added through **community contributions** \n - ⚠️ **Arabic UI does not use RTL yet**. RTL support will be added in a future update\n\n### Stability & Fixes\n- Crash optimizations and internal stability improvements\n\nThis update focuses on improving playback stability, Trakt experience, and expanding language support.", + "downloadURL": "https://github.com/tapframe/NuvioStreaming/releases/download/v1.3.4/app-release.apk", + "size": 25700000 + }, { "version": "1.3.3", "buildVersion": "31", diff --git a/package-lock.json b/package-lock.json index f79cf16..23be36d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -70,6 +70,7 @@ "lottie-react-native": "~7.3.1", "posthog-react-native": "^4.4.0", "react": "19.1.0", + "react-dom": "19.1.0", "react-i18next": "^16.5.1", "react-native": "0.81.4", "react-native-boost": "^0.6.2", @@ -90,7 +91,7 @@ "react-native-svg": "^15.12.1", "react-native-url-polyfill": "^3.0.0", "react-native-vector-icons": "^10.3.0", - "react-native-video": "^6.17.0", + "react-native-video": "6.18.0", "react-native-web": "^0.21.0", "react-native-wheel-color-picker": "^1.3.1", "react-native-worklets": "^0.7.1" @@ -10574,6 +10575,18 @@ } } }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, "node_modules/react-freeze": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.4.tgz", diff --git a/package.json b/package.json index 1bc7b10..3da74a4 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "lottie-react-native": "~7.3.1", "posthog-react-native": "^4.4.0", "react": "19.1.0", + "react-dom": "19.1.0", "react-i18next": "^16.5.1", "react-native": "0.81.4", "react-native-boost": "^0.6.2", @@ -90,7 +91,7 @@ "react-native-svg": "^15.12.1", "react-native-url-polyfill": "^3.0.0", "react-native-vector-icons": "^10.3.0", - "react-native-video": "^6.17.0", + "react-native-video": "6.18.0", "react-native-web": "^0.21.0", "react-native-wheel-color-picker": "^1.3.1", "react-native-worklets": "^0.7.1" diff --git a/patches/react-native-video+6.18.0.patch b/patches/react-native-video+6.18.0.patch deleted file mode 100644 index de10c59..0000000 --- a/patches/react-native-video+6.18.0.patch +++ /dev/null @@ -1,1323 +0,0 @@ -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 539ecfd..a939148 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 -@@ -161,7 +161,7 @@ public class ReactExoplayerView extends FrameLayout implements - AdEvent.AdEventListener, - AdErrorEvent.AdErrorListener { - -- public static final double DEFAULT_MAX_HEAP_ALLOCATION_PERCENT = 1; -+ public static final double DEFAULT_MAX_HEAP_ALLOCATION_PERCENT = 0.5; - public static final double DEFAULT_MIN_BUFFER_MEMORY_RESERVE = 0; - - private static final String TAG = "ReactExoplayerView"; -@@ -222,7 +222,8 @@ 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; -@@ -243,7 +244,7 @@ public class ReactExoplayerView extends FrameLayout implements - private BufferingStrategy.BufferingStrategyEnum bufferingStrategy; - private boolean disableDisconnectError; - private boolean preventsDisplaySleepDuringVideoPlayback = true; -- private float mProgressUpdateInterval = 250.0f; -+ private float mProgressUpdateInterval = 1000.0f; - protected boolean playInBackground = false; - private boolean mReportBandwidth = false; - private boolean controls = false; -@@ -269,6 +270,7 @@ public class ReactExoplayerView extends FrameLayout implements - private final String instanceId = String.valueOf(UUID.randomUUID()); - - private CmcdConfiguration.Factory cmcdConfigurationFactory; -+ private static final ExecutorService SHARED_EXECUTOR = Executors.newSingleThreadExecutor(); - - public void setCmcdConfigurationFactory(CmcdConfiguration.Factory factory) { - this.cmcdConfigurationFactory = factory; -@@ -292,7 +294,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)); - } - } - } -@@ -310,7 +313,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; -@@ -349,9 +352,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); - -@@ -377,8 +380,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; - } -@@ -397,7 +402,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) { -@@ -405,7 +410,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; -@@ -420,7 +426,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 { -@@ -444,7 +451,8 @@ public class ReactExoplayerView extends FrameLayout implements - } - - private void updateControllerConfig() { -- if (exoPlayerView == null) return; -+ if (exoPlayerView == null) -+ return; - - exoPlayerView.setControllerShowTimeoutMs(5000); - -@@ -455,7 +463,8 @@ public class ReactExoplayerView extends FrameLayout implements - } - - private void updateControllerVisibility() { -- if (exoPlayerView == null) return; -+ if (exoPlayerView == null) -+ return; - - exoPlayerView.setUseController(controls && !controlsConfig.getHideFullscreen()); - } -@@ -463,7 +472,7 @@ public class ReactExoplayerView extends FrameLayout implements - 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(); -@@ -473,7 +482,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); - -@@ -491,8 +500,10 @@ public class ReactExoplayerView extends FrameLayout implements - speed = 2.0f; - break; - default: -- speed = 1.0f;; -- }; -+ speed = 1.0f; -+ ; -+ } -+ ; - setRateModifier(speed); - }); - builder.show(); -@@ -504,24 +515,30 @@ public class ReactExoplayerView extends FrameLayout implements - - /** - * Update the layout -- * @param view view needs to update layout - * -- * This is a workaround for the open bug in react-native: ... -+ * @param view view needs to update layout -+ * -+ * 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() { -@@ -558,6 +575,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() -@@ -568,7 +586,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, -@@ -579,10 +597,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); - } - -@@ -600,13 +620,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) { -@@ -621,6 +643,8 @@ public class ReactExoplayerView extends FrameLayout implements - } - - private void initializePlayer() { -+ drmRetryCount = 0; -+ hasDrmFailed = false; - disableCache = ReactNativeVideoManager.Companion.getInstance().shouldDisableCache(source); - - ReactExoplayerView self = this; -@@ -640,12 +664,16 @@ 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) { -+ long requestedCacheSize = source.getBufferConfig().getCacheSize(); -+ long MAX_SAFE_CACHE_SIZE = 100L * 1024 * 1024; -+ long effectiveCacheSize = Math.min(requestedCacheSize, MAX_SAFE_CACHE_SIZE); -+ if (!source.isLocalAssetFile() && !source.isAsset() && effectiveCacheSize > 0) { - RNVSimpleCache.INSTANCE.setSimpleCache( - this.getContext(), -- source.getBufferConfig().getCacheSize() -+ effectiveCacheSize - ); - useCache = true; - } else { -@@ -654,16 +682,17 @@ 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 -- ExecutorService es = Executors.newSingleThreadExecutor(); -- es.execute(() -> { -+ // DRM session manager creation must be done on a different thread to prevent -+ // crashes so we start a new thread -+ SHARED_EXECUTOR.execute(() -> { - // DRM initialization must run on a different thread - if (viewHasDropped && runningSource == source) { - return; - } - 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; - } - -@@ -716,8 +745,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) { -@@ -725,15 +753,15 @@ public class ReactExoplayerView extends FrameLayout implements - this.bandwidthMeter = config.getBandwidthMeter(); - } - -- DefaultRenderersFactory renderersFactory = -- new DefaultRenderersFactory(getContext()) -- .setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF) -- .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()); -@@ -760,7 +788,7 @@ public class ReactExoplayerView extends FrameLayout implements - player.setPlaybackParameters(params); - changeAudioOutput(this.audioOutput); - -- if(showNotificationControls) { -+ if (showNotificationControls) { - setupPlaybackService(); - } - } -@@ -772,8 +800,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); - -@@ -805,7 +832,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 -@@ -814,11 +842,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 -@@ -836,7 +866,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; -@@ -851,13 +882,10 @@ public class ReactExoplayerView extends FrameLayout implements - MediaSource mediaSource = Objects.requireNonNullElse(mediaSourceWithAds, videoSource); - - // wait for player to be set -- while (player == null) { -- try { -- wait(); -- } catch (InterruptedException ex) { -- Thread.currentThread().interrupt(); -- DebugLog.e(TAG, ex.toString()); -- } -+ if (player == null) { -+ DebugLog.w(TAG, "Player not ready yet, aborting source initialization"); -+ playerNeedsSource = true; -+ return; - } - - boolean haveResumePosition = resumeWindow != C.INDEX_UNSET; -@@ -893,7 +921,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"); - } - } -@@ -938,7 +967,8 @@ public class ReactExoplayerView extends FrameLayout implements - if (playbackServiceBinder != null) { - playbackServiceBinder.getService().unregisterPlayer(player); - } -- } catch (Exception ignored) {} -+ } catch (Exception ignored) { -+ } - - playbackServiceBinder = null; - } -@@ -970,21 +1000,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"); - } -@@ -1016,12 +1047,12 @@ public class ReactExoplayerView extends FrameLayout implements - 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; -@@ -1033,29 +1064,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) { -@@ -1070,13 +1098,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); -@@ -1084,12 +1113,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; -@@ -1108,20 +1135,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() -@@ -1130,8 +1156,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) { -@@ -1166,7 +1191,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) - .setMimeType(track.getType()) - .setLabel(label) -@@ -1177,7 +1203,8 @@ public class ReactExoplayerView extends FrameLayout implements - 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 { -@@ -1187,10 +1214,12 @@ public class ReactExoplayerView extends FrameLayout implements - 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()); - } - } - -@@ -1203,7 +1232,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); - } -@@ -1253,7 +1282,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); - } -@@ -1274,16 +1304,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)); - } - } - } -@@ -1356,7 +1382,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. - */ -@@ -1368,12 +1395,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 -@@ -1390,11 +1419,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"; -@@ -1451,9 +1482,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); -@@ -1472,7 +1505,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; -@@ -1481,18 +1515,18 @@ 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 -+ SHARED_EXECUTOR.execute(() -> { -+ // To prevent ANRs caused by getVideoTrackInfo we run this on a different thread -+ // and notify the player only when we're done - ArrayList videoTracks = getVideoTrackInfoFromManifest(); - if (videoTracks != null) { - isUsingContentResolution = true; - } - eventEmitter.onVideoLoad.invoke(duration, currentPosition, width, height, -- audioTracks, textTracks, videoTracks, trackId ); -+ audioTracks, textTracks, videoTracks, trackId); - - updateSubtitleButtonVisibility(); - }); -@@ -1510,9 +1544,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() { -@@ -1530,7 +1564,6 @@ 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); -@@ -1556,7 +1589,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; -@@ -1593,33 +1627,35 @@ 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(); - final DataSource dataSource = this.mediaDataSourceFactory.createDataSource(); - final Uri sourceUri = source.getUri(); - final long startTime = source.getContentStartTime() * 1000 - 100; // s -> ms with 100ms offset - -- Future> result = es.submit(new Callable() { -+ Future> result = SHARED_EXECUTOR.submit(new Callable>() { - final DataSource ds = dataSource; - final Uri uri = sourceUri; - final long startTimeUs = startTime * 1000; // ms -> us - - 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)) { -@@ -1627,7 +1663,8 @@ public class ReactExoplayerView extends FrameLayout implements - break; - } - hasFoundContentPeriod = true; -- VideoTrack videoTrack = exoplayerVideoTrackToGenericVideoTrack(format, representationIndex); -+ VideoTrack videoTrack = exoplayerVideoTrackToGenericVideoTrack(format, -+ representationIndex); - videoTracks.add(videoTrack); - } - } -@@ -1648,7 +1685,6 @@ public class ReactExoplayerView extends FrameLayout implements - if (results == null && retryCount < 1) { - return this.getVideoTrackInfoFromManifest(++retryCount); - } -- es.shutdown(); - return results; - } catch (Exception e) { - DebugLog.w(TAG, "error in getVideoTrackInfoFromManifest handling request:" + e.getMessage()); -@@ -1657,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; - } -@@ -1732,7 +1772,8 @@ public class ReactExoplayerView extends FrameLayout implements - 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); -@@ -1763,8 +1804,10 @@ public class ReactExoplayerView extends FrameLayout implements - - 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-"); - -@@ -1798,28 +1841,34 @@ 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) { -@@ -1867,15 +1916,17 @@ 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); - -@@ -1895,11 +1946,12 @@ public class ReactExoplayerView extends FrameLayout implements - } - - private void updateSubtitleButtonVisibility() { -- if (exoPlayerView == null) return; -+ if (exoPlayerView == null) -+ return; - - boolean hasTextTracks = (source.getSideLoadedTextTracks() != null && -- !source.getSideLoadedTextTracks().getTracks().isEmpty()) || -- hasBuiltInTextTracks(); -+ !source.getSideLoadedTextTracks().getTracks().isEmpty()) || -+ hasBuiltInTextTracks(); - - exoPlayerView.setShowSubtitleButton(hasTextTracks); - } -@@ -1919,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) { -@@ -1931,21 +1984,24 @@ 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 -- hasDrmFailed = true; -- playerNeedsSource = true; -- updateResumePosition(); -- initializePlayer(); -- setPlayWhenReady(true); -- return; -- } -+ // 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 -+ if (drmRetryCount < 1) { -+ drmRetryCount++; -+ hasDrmFailed = true; -+ playerNeedsSource = true; -+ updateResumePosition(); -+ initializePlayer(); -+ setPlayWhenReady(true); -+ return; -+ } - break; - default: - break; -@@ -2020,14 +2076,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()); -@@ -2046,6 +2104,7 @@ public class ReactExoplayerView extends FrameLayout implements - clearSrc(); - } - } -+ - public void clearSrc() { - if (source.getUri() != null) { - if (player != null) { -@@ -2094,7 +2153,8 @@ 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() -@@ -2104,7 +2164,8 @@ 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); - -@@ -2123,6 +2184,7 @@ public class ReactExoplayerView extends FrameLayout implements - if (textRendererIndex != C.INDEX_UNSET) { - TrackGroupArray groups = info.getTrackGroups(textRendererIndex); - boolean trackFound = false; -+ int cumulativeIndex = 0; // Track cumulative index across all groups - - for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) { - TrackGroup group = groups.get(groupIndex); -@@ -2136,25 +2198,28 @@ public class ReactExoplayerView extends FrameLayout implements - isMatch = true; - } else if ("index".equals(type)) { - int targetIndex = ReactBridgeUtils.safeParseInt(value, -1); -- if (targetIndex == trackIndex) { -+ // Use cumulative index to match getTextTrackInfo() behavior -+ if (targetIndex == cumulativeIndex) { - isMatch = true; - } - } - - if (isMatch) { - TrackSelectionOverride override = new TrackSelectionOverride(group, -- java.util.Arrays.asList(trackIndex)); -+ java.util.Arrays.asList(trackIndex)); - parametersBuilder.addOverride(override); - trackFound = true; - break; - } -+ cumulativeIndex++; // Increment after each track - } -- if (trackFound) break; -+ if (trackFound) -+ break; - } - - if (!trackFound) { - DebugLog.w(TAG, "Text track not found for type=" + type + ", value=" + value + -- ". Keeping current selection."); -+ ". Keeping current selection."); - } - } - } -@@ -2175,7 +2240,8 @@ 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; -@@ -2249,9 +2315,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; -@@ -2262,7 +2330,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; -@@ -2285,8 +2354,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); - } -@@ -2315,7 +2384,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)) { -@@ -2341,7 +2410,8 @@ public class ReactExoplayerView extends FrameLayout implements - .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 -+ // 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()); - } -@@ -2357,7 +2427,7 @@ public class ReactExoplayerView extends FrameLayout implements - selectionParameters.setForceHighestSupportedBitrate(false); - selectionParameters.setForceLowestBitrate(false); - DebugLog.d(TAG, "Audio track selection: group=" + groupIndex + ", tracks=" + tracks + -- ", override=" + selectionOverride); -+ ", override=" + selectionOverride); - } - - trackSelector.setParameters(selectionParameters.build()); -@@ -2388,7 +2458,7 @@ public class ReactExoplayerView extends FrameLayout implements - } - - private int getGroupIndexForDefaultLocale(TrackGroupArray groups) { -- if (groups.length == 0){ -+ if (groups.length == 0) { - return C.INDEX_UNSET; - } - -@@ -2409,7 +2479,8 @@ 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) { -@@ -2440,9 +2511,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); - } - } - -@@ -2450,12 +2523,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); -@@ -2465,7 +2540,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); - } -@@ -2491,10 +2566,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(); - } -@@ -2503,13 +2580,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)); - } -@@ -2607,7 +2686,7 @@ public class ReactExoplayerView extends FrameLayout implements - - if (playbackServiceConnection == null && showNotificationControls) { - setupPlaybackService(); -- } else if(!showNotificationControls && playbackServiceConnection != null) { -+ } else if (!showNotificationControls && playbackServiceConnection != null) { - cleanupPlaybackService(); - } - } -@@ -2636,12 +2715,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(); -@@ -2678,7 +2758,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"); - } -@@ -2696,7 +2777,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; -@@ -2705,7 +2786,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); - } - } -@@ -2738,8 +2819,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); - } - diff --git a/src/utils/version.ts b/src/utils/version.ts index a55f42e..3bab475 100644 --- a/src/utils/version.ts +++ b/src/utils/version.ts @@ -1,7 +1,7 @@ // Single source of truth for the app version displayed in Settings // Update this when bumping app version -export const APP_VERSION = '1.3.3'; +export const APP_VERSION = '1.3.4'; export function getDisplayedAppVersion(): string { return APP_VERSION;