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. 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. 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. 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. 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, maxBufferMs: PropTypes.number,
bufferForPlaybackMs: PropTypes.number, bufferForPlaybackMs: PropTypes.number,
bufferForPlaybackAfterRebufferMs: PropTypes.number, bufferForPlaybackAfterRebufferMs: PropTypes.number,
maxHeapAllocationPercent: PropTypes.number,
}), }),
stereoPan: PropTypes.number, stereoPan: PropTypes.number,
rate: PropTypes.number, rate: PropTypes.number,

View File

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