Merge pull request #361 from chrisk325/patch-8

Several optimizations for exoplayer for preventing crashes with heavy file sizes
This commit is contained in:
Nayif 2026-01-06 00:17:12 +05:30 committed by GitHub
commit 20601cd7ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 51 additions and 30 deletions

View file

@ -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";
@ -244,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;
@ -270,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;
@ -642,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;
@ -664,10 +667,14 @@ public class ReactExoplayerView extends FrameLayout implements
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 {
useCache = false;
@ -677,8 +684,7 @@ public class ReactExoplayerView extends FrameLayout implements
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(() -> {
SHARED_EXECUTOR.execute(() -> {
// DRM initialization must run on a different thread
if (viewHasDropped && runningSource == source) {
return;
@ -876,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;
@ -1515,8 +1518,7 @@ public class ReactExoplayerView extends FrameLayout implements
ArrayList<Track> textTracks = getTextTrackInfo();
if (source.getContentStartTime() != -1) {
ExecutorService es = Executors.newSingleThreadExecutor();
es.execute(() -> {
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<VideoTrack> videoTracks = getVideoTrackInfoFromManifest();
@ -1629,12 +1631,11 @@ public class ReactExoplayerView extends FrameLayout implements
// conditions
@WorkerThread
private ArrayList<VideoTrack> 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<ArrayList<VideoTrack>> result = es.submit(new Callable() {
Future<ArrayList<VideoTrack>> result = SHARED_EXECUTOR.submit(new Callable<ArrayList<VideoTrack>>() {
final DataSource ds = dataSource;
final Uri uri = sourceUri;
final long startTimeUs = startTime * 1000; // ms -> us
@ -1684,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());
@ -1993,13 +1993,15 @@ public class ReactExoplayerView extends FrameLayout implements
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;
}
if (drmRetryCount < 1) {
drmRetryCount++;
hasDrmFailed = true;
playerNeedsSource = true;
updateResumePosition();
initializePlayer();
setPlayWhenReady(true);
return;
}
break;
default:
break;

View file

@ -115,6 +115,13 @@ export const PlayerControls: React.FC<PlayerControlsProps> = ({
/* Animations - State & Refs */
const [showBackwardSign, setShowBackwardSign] = React.useState(false);
const [showForwardSign, setShowForwardSign] = React.useState(false);
const [previewTime, setPreviewTime] = React.useState(currentTime);
const isSlidingRef = React.useRef(false);
React.useEffect(() => {
if (!isSlidingRef.current) {
setPreviewTime(currentTime);
}
}, [currentTime]);
/* Separate Animations for Each Button */
const backwardPressAnim = React.useRef(new Animated.Value(0)).current;
@ -280,10 +287,22 @@ export const PlayerControls: React.FC<PlayerControlsProps> = ({
}}
minimumValue={0}
maximumValue={duration || 1}
value={currentTime}
onValueChange={onSliderValueChange}
onSlidingStart={onSlidingStart}
onSlidingComplete={onSlidingComplete}
value={previewTime}
onValueChange={(v) => setPreviewTime(v)}
onSlidingStart={() => {
isSlidingRef.current = true;
onSlidingStart();
}}
onSlidingComplete={(v) => {
isSlidingRef.current = false;
setPreviewTime(v);
onSlidingComplete(v);
}}
minimumTrackTintColor={currentTheme.colors.primary}
maximumTrackTintColor={currentTheme.colors.mediumEmphasis}
thumbTintColor={Platform.OS === 'android' ? currentTheme.colors.white : undefined}
@ -608,4 +627,4 @@ export const PlayerControls: React.FC<PlayerControlsProps> = ({
);
};
export default PlayerControls;
export default PlayerControls;