diff --git a/android/src/main/java/com/brentvatne/common/api/BufferingStrategy.kt b/android/src/main/java/com/brentvatne/common/api/BufferingStrategy.kt
new file mode 100644
index 00000000..9c48474c
--- /dev/null
+++ b/android/src/main/java/com/brentvatne/common/api/BufferingStrategy.kt
@@ -0,0 +1,47 @@
+package com.brentvatne.common.api
+
+import com.brentvatne.common.toolbox.DebugLog
+
+/**
+ * Define how exoplayer with load data and parsing helper
+ */
+
+class BufferingStrategy {
+
+ /**
+ * Define how exoplayer with load data
+ */
+ enum class BufferingStrategyEnum {
+ /**
+ * default exoplayer strategy
+ */
+ Default,
+
+ /**
+ * never load more than needed
+ */
+ DisableBuffering,
+
+ /**
+ * use default strategy but pause loading when available memory is low
+ */
+ DependingOnMemory
+ }
+
+ companion object {
+ private const val TAG = "BufferingStrategy"
+
+ /**
+ * companion function to transform input string to enum
+ */
+ fun parse(src: String?): BufferingStrategyEnum {
+ if (src == null) return BufferingStrategyEnum.Default
+ return try {
+ BufferingStrategyEnum.valueOf(src)
+ } catch (e: Exception) {
+ DebugLog.e(TAG, "cannot parse buffering strategy " + src)
+ BufferingStrategyEnum.Default
+ }
+ }
+ }
+}
diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java
index 6dccaee0..9eb60e22 100644
--- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java
+++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java
@@ -32,7 +32,6 @@ import android.widget.ImageButton;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
@@ -105,6 +104,7 @@ import androidx.media3.session.MediaSessionService;
import androidx.media3.ui.LegacyPlayerControlView;
import com.brentvatne.common.api.BufferConfig;
+import com.brentvatne.common.api.BufferingStrategy;
import com.brentvatne.common.api.ResizeMode;
import com.brentvatne.common.api.SideLoadedTextTrack;
import com.brentvatne.common.api.SideLoadedTextTrackList;
@@ -223,7 +223,7 @@ public class ReactExoplayerView extends FrameLayout implements
private SideLoadedTextTrackList textTracks;
private boolean disableFocus;
private boolean focusable = true;
- private boolean disableBuffering;
+ private BufferingStrategy.BufferingStrategyEnum bufferingStrategy;
private long contentStartTime = -1L;
private boolean disableDisconnectError;
private boolean preventsDisplaySleepDuringVideoPlayback = true;
@@ -541,30 +541,34 @@ public class ReactExoplayerView extends FrameLayout implements
@Override
public boolean shouldContinueLoading(long playbackPositionUs, long bufferedDurationUs, float playbackSpeed) {
- if (ReactExoplayerView.this.disableBuffering) {
- return false;
- }
- int loadedBytes = getAllocator().getTotalBytesAllocated();
- boolean isHeapReached = availableHeapInBytes > 0 && loadedBytes >= availableHeapInBytes;
- if (isHeapReached) {
- return false;
- }
- long usedMemory = runtime.totalMemory() - runtime.freeMemory();
- long freeMemory = runtime.maxMemory() - usedMemory;
- double minBufferMemoryReservePercent = bufferConfig.getMinBufferMemoryReservePercent() != BufferConfig.Companion.getBufferConfigPropUnsetDouble()
- ? bufferConfig.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
- return false;
- }
- if (runtime.freeMemory() == 0) {
- DebugLog.w("ExoPlayer Warning", "Free memory reached 0, forcing garbage collection");
- runtime.gc();
+ if (bufferingStrategy == BufferingStrategy.BufferingStrategyEnum.DisableBuffering) {
return false;
+ } else if (bufferingStrategy == BufferingStrategy.BufferingStrategyEnum.DependingOnMemory) {
+ // The goal of this algorithm is to pause video loading (increasing the buffer)
+ // when available memory on device become low.
+ int loadedBytes = getAllocator().getTotalBytesAllocated();
+ boolean isHeapReached = availableHeapInBytes > 0 && loadedBytes >= availableHeapInBytes;
+ if (isHeapReached) {
+ return false;
+ }
+ long usedMemory = runtime.totalMemory() - runtime.freeMemory();
+ long freeMemory = runtime.maxMemory() - usedMemory;
+ double minBufferMemoryReservePercent = bufferConfig.getMinBufferMemoryReservePercent() != BufferConfig.Companion.getBufferConfigPropUnsetDouble()
+ ? bufferConfig.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
+ return false;
+ }
+ if (runtime.freeMemory() == 0) {
+ DebugLog.w(TAG, "Free memory reached 0, forcing garbage collection");
+ runtime.gc();
+ return false;
+ }
}
+ // "default" case or normal case for "DependingOnMemory"
return super.shouldContinueLoading(playbackPositionUs, bufferedDurationUs, playbackSpeed);
}
}
@@ -2077,8 +2081,8 @@ public class ReactExoplayerView extends FrameLayout implements
}
}
- public void setDisableBuffering(boolean disableBuffering) {
- this.disableBuffering = disableBuffering;
+ public void setBufferingStrategy(BufferingStrategy.BufferingStrategyEnum _bufferingStrategy) {
+ bufferingStrategy = _bufferingStrategy;
}
public boolean getPreventsDisplaySleepDuringVideoPlayback() {
diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java
index 24e9a004..997a137e 100644
--- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java
+++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java
@@ -10,9 +10,9 @@ import androidx.annotation.NonNull;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.RawResourceDataSource;
-import androidx.media3.exoplayer.DefaultLoadControl;
import com.brentvatne.common.api.BufferConfig;
+import com.brentvatne.common.api.BufferingStrategy;
import com.brentvatne.common.api.ResizeMode;
import com.brentvatne.common.api.SideLoadedTextTrackList;
import com.brentvatne.common.api.SubtitleStyle;
@@ -73,7 +73,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager
+
+Configure buffering / data loading strategy.
+
+ - **Default (default)**: use exoplayer default loading strategy
+ - **DisableBuffering**: never try to buffer more than needed. Be carefull using this value will stop playback. To be used with care.
+ - **DependingOnMemory**: use exoplayer default strategy, but stop buffering and starts gc if available memory is low |
+
### `chapters`
diff --git a/examples/basic/src/VideoPlayer.tsx b/examples/basic/src/VideoPlayer.tsx
index d94baba5..663806f5 100644
--- a/examples/basic/src/VideoPlayer.tsx
+++ b/examples/basic/src/VideoPlayer.tsx
@@ -39,6 +39,7 @@ import Video, {
OnSeekData,
OnPlaybackStateChangedData,
OnPlaybackRateChangeData,
+ BufferingStrategyType,
} from 'react-native-video';
import ToggleControl from './ToggleControl';
import MultiValueControl, {
@@ -934,6 +935,7 @@ class VideoPlayer extends Component {
poster={this.state.poster}
onPlaybackRateChange={this.onPlaybackRateChange}
onPlaybackStateChanged={this.onPlaybackStateChanged}
+ bufferingStrategy={BufferingStrategyType.DEFAULT}
/>
);
diff --git a/src/specs/VideoNativeComponent.ts b/src/specs/VideoNativeComponent.ts
index d5dc5079..1b383eae 100644
--- a/src/specs/VideoNativeComponent.ts
+++ b/src/specs/VideoNativeComponent.ts
@@ -93,6 +93,8 @@ export type Seek = Readonly<{
tolerance?: Float;
}>;
+type BufferingStrategyType = WithDefault;
+
type BufferConfig = Readonly<{
minBufferMs?: Float;
maxBufferMs?: Float;
@@ -317,6 +319,7 @@ export interface VideoNativeProps extends ViewProps {
subtitleStyle?: SubtitleStyle; // android
useTextureView?: boolean; // Android
useSecureView?: boolean; // Android
+ bufferingStrategy?: BufferingStrategyType; // Android
onVideoLoad?: DirectEventHandler;
onVideoLoadStart?: DirectEventHandler;
onVideoAspectRatio?: DirectEventHandler;
diff --git a/src/types/video.ts b/src/types/video.ts
index f57e2b0e..e174bbbf 100644
--- a/src/types/video.ts
+++ b/src/types/video.ts
@@ -68,6 +68,12 @@ export type Drm = Readonly<{
/* eslint-enable @typescript-eslint/no-unused-vars */
}>;
+export enum BufferingStrategyType {
+ DEFAULT = 'Default',
+ DISABLE_BUFFERING = 'DisableBuffering',
+ DEPENDING_ON_MEMORY = 'DependingOnMemory',
+}
+
export type BufferConfig = {
minBufferMs?: number;
maxBufferMs?: number;
@@ -195,6 +201,7 @@ export interface ReactVideoProps extends ReactVideoEvents, ViewProps {
audioOutput?: AudioOutput; // Mobile
automaticallyWaitsToMinimizeStalling?: boolean; // iOS
bufferConfig?: BufferConfig; // Android
+ bufferingStrategy?: BufferingStrategyType;
chapters?: Chapters[]; // iOS
contentStartTime?: number; // Android
controls?: boolean;