VEX-6030: Reduce buffer size based on heap (#13)

This PR changes the behavior on old devices that have poor memory management.

Jira: VEX-6030
https://jira.tenkasu.net/browse/VEX-6030

The solution implied customizing the method shouldContinueLoading from RNVLoadControl to use only the available heap, performing tests on an old Nexus 5 it was determined the ideal bytes allocation to half the reported heap, that provided some buffering during ads but smooth playback during the video with no crashes (23:39 of 23:39 at the moment of writing this, video kept playing as expected after 3 ad breaks)

The fix is only targeting Marshmallow as the reduction of buffer is substantial and other versions that work properly should not get affected.

Depending on the test result of VEX-5758, this same fix can be applied to Nougat

Reviews
Major reviewer (domain expert): @armadilio3
Minor reviewer: @nickfujita
This commit is contained in:
Gabriel Rivero 2021-11-09 14:37:52 -04:00 committed by GitHub
commit 292b53916a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 22 additions and 3 deletions

View File

@ -385,6 +385,7 @@ minBufferMs | number | The default minimum duration of media that the player wil
maxBufferMs | number | The default maximum duration of media that the player will attempt to buffer, in milliseconds.
bufferForPlaybackMs | number | The default duration of media that must be buffered for playback to start or resume following a user action such as a seek, in milliseconds.
bufferForPlaybackAfterRebufferMs | number | The default duration of media that must be buffered for playback to resume after a rebuffer, in milliseconds. A rebuffer is defined to be caused by buffer depletion rather than a user action.
maxHeapAllocationPercent | number | The percentage of available heap that the video can use to buffer, between 0 and 1
This prop should only be set when you are setting the source, changing it after the media is loaded will cause it to be reloaded.

View File

@ -468,6 +468,7 @@ Video.propTypes = {
maxBufferMs: PropTypes.number,
bufferForPlaybackMs: PropTypes.number,
bufferForPlaybackAfterRebufferMs: PropTypes.number,
maxHeapAllocationPercent: PropTypes.number,
}),
stereoPan: PropTypes.number,
rate: PropTypes.number,

View File

@ -2,6 +2,7 @@ package com.brentvatne.exoplayer;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.media.AudioManager;
import android.net.Uri;
@ -111,6 +112,8 @@ class ReactExoplayerView extends FrameLayout implements
MetadataOutput,
DrmSessionEventListener {
public static final double DEFAULT_MAX_HEAP_ALLOCATION_PERCENT = 1;
private static final String TAG = "ReactExoplayerView";
private static final CookieManager DEFAULT_COOKIE_MANAGER;
@ -157,6 +160,7 @@ class ReactExoplayerView extends FrameLayout implements
private int maxBufferMs = DefaultLoadControl.DEFAULT_MAX_BUFFER_MS;
private int bufferForPlaybackMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS;
private int bufferForPlaybackAfterRebufferMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS;
private double maxHeapAllocationPercent = ReactExoplayerView.DEFAULT_MAX_HEAP_ALLOCATION_PERCENT;
private Handler mainHandler;
private Timer bufferCheckTimer;
@ -215,7 +219,7 @@ 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.getCurrentWindowIndex(), window);
}
return window.windowStartTimeMs + currentPosition;
@ -418,6 +422,7 @@ class ReactExoplayerView extends FrameLayout implements
}
private class RNVLoadControl extends DefaultLoadControl {
private int availableHeapInBytes = 0;
public RNVLoadControl(DefaultAllocator allocator, int minBufferMs, int maxBufferMs, int bufferForPlaybackMs, int bufferForPlaybackAfterRebufferMs, int targetBufferBytes, boolean prioritizeTimeOverSizeThresholds, int backBufferDurationMs, boolean retainBackBufferFromKeyframe) {
super(allocator,
minBufferMs,
@ -428,6 +433,8 @@ class ReactExoplayerView extends FrameLayout implements
prioritizeTimeOverSizeThresholds,
backBufferDurationMs,
retainBackBufferFromKeyframe);
ActivityManager activityManager = (ActivityManager) themedReactContext.getSystemService(themedReactContext.ACTIVITY_SERVICE);
availableHeapInBytes = (int) Math.floor(activityManager.getMemoryClass() * maxHeapAllocationPercent * 1024 * 1024);
}
@Override
@ -435,6 +442,11 @@ class ReactExoplayerView extends FrameLayout implements
if (ReactExoplayerView.this.disableBuffering) {
return false;
}
int loadedBytes = getAllocator().getTotalBytesAllocated();
boolean isHeapReached = availableHeapInBytes > 0 && loadedBytes >= availableHeapInBytes;
if (isHeapReached) {
return false;
}
return super.shouldContinueLoading(playbackPositionUs, bufferedDurationUs, playbackSpeed);
}
}
@ -1660,11 +1672,12 @@ class ReactExoplayerView extends FrameLayout implements
exoPlayerView.setHideShutterView(hideShutterView);
}
public void setBufferConfig(int newMinBufferMs, int newMaxBufferMs, int newBufferForPlaybackMs, int newBufferForPlaybackAfterRebufferMs) {
public void setBufferConfig(int newMinBufferMs, int newMaxBufferMs, int newBufferForPlaybackMs, int newBufferForPlaybackAfterRebufferMs, double newMaxHeapAllocationPercent) {
minBufferMs = newMinBufferMs;
maxBufferMs = newMaxBufferMs;
bufferForPlaybackMs = newBufferForPlaybackMs;
bufferForPlaybackAfterRebufferMs = newBufferForPlaybackAfterRebufferMs;
maxHeapAllocationPercent = newMaxHeapAllocationPercent;
releasePlayer();
initializePlayer();
}

View File

@ -55,6 +55,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
private static final String PROP_BUFFER_CONFIG_MAX_BUFFER_MS = "maxBufferMs";
private static final String PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_MS = "bufferForPlaybackMs";
private static final String PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = "bufferForPlaybackAfterRebufferMs";
private static final String PROP_BUFFER_CONFIG_MAX_HEAP_ALLOCATION_PERCENT = "maxHeapAllocationPercent";
private static final String PROP_PREVENTS_DISPLAY_SLEEP_DURING_VIDEO_PLAYBACK = "preventsDisplaySleepDuringVideoPlayback";
private static final String PROP_PROGRESS_UPDATE_INTERVAL = "progressUpdateInterval";
private static final String PROP_REPORT_BANDWIDTH = "reportBandwidth";
@ -344,6 +345,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
int maxBufferMs = DefaultLoadControl.DEFAULT_MAX_BUFFER_MS;
int bufferForPlaybackMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS;
int bufferForPlaybackAfterRebufferMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS;
double maxHeapAllocationPercent = ReactExoplayerView.DEFAULT_MAX_HEAP_ALLOCATION_PERCENT;
if (bufferConfig != null) {
minBufferMs = bufferConfig.hasKey(PROP_BUFFER_CONFIG_MIN_BUFFER_MS)
? bufferConfig.getInt(PROP_BUFFER_CONFIG_MIN_BUFFER_MS) : minBufferMs;
@ -353,7 +355,9 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
? bufferConfig.getInt(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_MS) : bufferForPlaybackMs;
bufferForPlaybackAfterRebufferMs = bufferConfig.hasKey(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS)
? bufferConfig.getInt(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS) : bufferForPlaybackAfterRebufferMs;
videoView.setBufferConfig(minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs);
maxHeapAllocationPercent = bufferConfig.hasKey(PROP_BUFFER_CONFIG_MAX_HEAP_ALLOCATION_PERCENT)
? bufferConfig.getDouble(PROP_BUFFER_CONFIG_MAX_HEAP_ALLOCATION_PERCENT) : maxHeapAllocationPercent;
videoView.setBufferConfig(minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs, maxHeapAllocationPercent);
}
}