From cdbd192b381912836f8705509e725947919030b8 Mon Sep 17 00:00:00 2001 From: Olivier Bouillet Date: Sat, 23 Nov 2024 23:32:02 +0100 Subject: [PATCH] fix: more bufferConfig inside source - restart issue on react 0.76 - fix constness - deprecate bufferConfig in root props - update documentation --- .../com/brentvatne/common/api/BufferConfig.kt | 30 +++++------ .../java/com/brentvatne/common/api/Source.kt | 11 +++- .../exoplayer/ReactExoplayerView.java | 36 ++++++------- .../exoplayer/ReactExoplayerViewManager.kt | 8 --- docs/pages/component/props.mdx | 53 +++++++++++++++++++ examples/expo/src/BasicExample.tsx | 3 +- src/Video.tsx | 5 ++ src/specs/VideoNativeComponent.ts | 2 +- src/types/video.ts | 2 + 9 files changed, 102 insertions(+), 48 deletions(-) diff --git a/android/src/main/java/com/brentvatne/common/api/BufferConfig.kt b/android/src/main/java/com/brentvatne/common/api/BufferConfig.kt index b3889888bd..4730e9f15a 100644 --- a/android/src/main/java/com/brentvatne/common/api/BufferConfig.kt +++ b/android/src/main/java/com/brentvatne/common/api/BufferConfig.kt @@ -31,11 +31,11 @@ class BufferConfig { var targetOffsetMs: Long = BufferConfigPropUnsetInt.toLong() companion object { - private val PROP_BUFFER_CONFIG_LIVE_MAX_PLAYBACK_SPEED = "maxPlaybackSpeed" - private val PROP_BUFFER_CONFIG_LIVE_MIN_PLAYBACK_SPEED = "minPlaybackSpeed" - private val PROP_BUFFER_CONFIG_LIVE_MAX_OFFSET_MS = "maxOffsetMs" - private val PROP_BUFFER_CONFIG_LIVE_MIN_OFFSET_MS = "minOffsetMs" - private val PROP_BUFFER_CONFIG_LIVE_TARGET_OFFSET_MS = "targetOffsetMs" + private const val PROP_BUFFER_CONFIG_LIVE_MAX_PLAYBACK_SPEED = "maxPlaybackSpeed" + private const val PROP_BUFFER_CONFIG_LIVE_MIN_PLAYBACK_SPEED = "minPlaybackSpeed" + private const val PROP_BUFFER_CONFIG_LIVE_MAX_OFFSET_MS = "maxOffsetMs" + private const val PROP_BUFFER_CONFIG_LIVE_MIN_OFFSET_MS = "minOffsetMs" + private const val PROP_BUFFER_CONFIG_LIVE_TARGET_OFFSET_MS = "targetOffsetMs" @JvmStatic fun parse(src: ReadableMap?): Live { @@ -54,16 +54,16 @@ class BufferConfig { val BufferConfigPropUnsetInt = -1 val BufferConfigPropUnsetDouble = -1.0 - private val PROP_BUFFER_CONFIG_CACHE_SIZE = "cacheSizeMB" - private val PROP_BUFFER_CONFIG_MIN_BUFFER_MS = "minBufferMs" - private val PROP_BUFFER_CONFIG_MAX_BUFFER_MS = "maxBufferMs" - private val PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_MS = "bufferForPlaybackMs" - private val PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = "bufferForPlaybackAfterRebufferMs" - private val PROP_BUFFER_CONFIG_MAX_HEAP_ALLOCATION_PERCENT = "maxHeapAllocationPercent" - private val PROP_BUFFER_CONFIG_MIN_BACK_BUFFER_MEMORY_RESERVE_PERCENT = "minBackBufferMemoryReservePercent" - private val PROP_BUFFER_CONFIG_MIN_BUFFER_MEMORY_RESERVE_PERCENT = "minBufferMemoryReservePercent" - private val PROP_BUFFER_CONFIG_BACK_BUFFER_DURATION_MS = "backBufferDurationMs" - private val PROP_BUFFER_CONFIG_LIVE = "live" + private const val PROP_BUFFER_CONFIG_CACHE_SIZE = "cacheSizeMB" + private const val PROP_BUFFER_CONFIG_MIN_BUFFER_MS = "minBufferMs" + private const val PROP_BUFFER_CONFIG_MAX_BUFFER_MS = "maxBufferMs" + private const val PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_MS = "bufferForPlaybackMs" + private const val PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = "bufferForPlaybackAfterRebufferMs" + private const val PROP_BUFFER_CONFIG_MAX_HEAP_ALLOCATION_PERCENT = "maxHeapAllocationPercent" + private const val PROP_BUFFER_CONFIG_MIN_BACK_BUFFER_MEMORY_RESERVE_PERCENT = "minBackBufferMemoryReservePercent" + private const val PROP_BUFFER_CONFIG_MIN_BUFFER_MEMORY_RESERVE_PERCENT = "minBufferMemoryReservePercent" + private const val PROP_BUFFER_CONFIG_BACK_BUFFER_DURATION_MS = "backBufferDurationMs" + private const val PROP_BUFFER_CONFIG_LIVE = "live" @JvmStatic fun parse(src: ReadableMap?): BufferConfig { diff --git a/android/src/main/java/com/brentvatne/common/api/Source.kt b/android/src/main/java/com/brentvatne/common/api/Source.kt index 0f6a349103..2d5ede07d3 100644 --- a/android/src/main/java/com/brentvatne/common/api/Source.kt +++ b/android/src/main/java/com/brentvatne/common/api/Source.kt @@ -74,6 +74,11 @@ class Source { */ var adsProps: AdsProps? = null + /* + * buffering configuration + */ + var bufferConfig = BufferConfig() + /** * The list of sideLoaded text tracks */ @@ -95,7 +100,8 @@ class Source { cmcdProps == other.cmcdProps && sideLoadedTextTracks == other.sideLoadedTextTracks && adsProps == other.adsProps && - minLoadRetryCount == other.minLoadRetryCount + minLoadRetryCount == other.minLoadRetryCount && + bufferConfig == other.bufferConfig ) } @@ -164,6 +170,7 @@ class Source { private const val PROP_SRC_TEXT_TRACKS_ALLOW_CHUNKLESS_PREPARATION = "textTracksAllowChunklessPreparation" private const val PROP_SRC_TEXT_TRACKS = "textTracks" private const val PROP_SRC_MIN_LOAD_RETRY_COUNT = "minLoadRetryCount" + private const val PROP_SRC_BUFFER_CONFIG = "bufferConfig" @SuppressLint("DiscouragedApi") private fun getUriFromAssetId(context: Context, uriString: String): Uri? { @@ -229,6 +236,8 @@ class Source { source.textTracksAllowChunklessPreparation = safeGetBool(src, PROP_SRC_TEXT_TRACKS_ALLOW_CHUNKLESS_PREPARATION, true) source.sideLoadedTextTracks = SideLoadedTextTrackList.parse(safeGetArray(src, PROP_SRC_TEXT_TRACKS)) source.minLoadRetryCount = safeGetInt(src, PROP_SRC_MIN_LOAD_RETRY_COUNT, 3) + source.bufferConfig = BufferConfig.parse(safeGetMap(src, PROP_SRC_BUFFER_CONFIG)) + val propSrcHeadersArray = safeGetArray(src, PROP_SRC_HEADERS) if (propSrcHeadersArray != null) { if (propSrcHeadersArray.size() > 0) { diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 06b1658c5f..c6d52c7c73 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -206,7 +206,6 @@ public class ReactExoplayerView extends FrameLayout implements private float rate = 1f; private AudioOutput audioOutput = AudioOutput.SPEAKER; private float audioVolume = 1f; - private BufferConfig bufferConfig = new BufferConfig(); private int maxBitRate = 0; private boolean hasDrmFailed = false; private boolean isUsingContentResolution = false; @@ -691,7 +690,7 @@ public RNVLoadControl(DefaultAllocator allocator, BufferConfig config) { runtime = Runtime.getRuntime(); ActivityManager activityManager = (ActivityManager) themedReactContext.getSystemService(ThemedReactContext.ACTIVITY_SERVICE); double maxHeap = config.getMaxHeapAllocationPercent() != BufferConfig.Companion.getBufferConfigPropUnsetDouble() - ? bufferConfig.getMaxHeapAllocationPercent() + ? config.getMaxHeapAllocationPercent() : DEFAULT_MAX_HEAP_ALLOCATION_PERCENT; availableHeapInBytes = (int) Math.floor(activityManager.getMemoryClass() * maxHeap * 1024 * 1024); } @@ -710,8 +709,8 @@ public boolean shouldContinueLoading(long playbackPositionUs, long bufferedDurat } long usedMemory = runtime.totalMemory() - runtime.freeMemory(); long freeMemory = runtime.maxMemory() - usedMemory; - double minBufferMemoryReservePercent = bufferConfig.getMinBufferMemoryReservePercent() != BufferConfig.Companion.getBufferConfigPropUnsetDouble() - ? bufferConfig.getMinBufferMemoryReservePercent() + 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; @@ -743,10 +742,20 @@ private void initializePlayer() { if (runningSource.getUri() == null) { return; } + if (player == null) { // Initialize core configuration and listeners initializePlayerCore(self); } + if (source.getBufferConfig().getCacheSize() > 0) { + RNVSimpleCache.INSTANCE.setSimpleCache( + this.getContext(), + source.getBufferConfig().getCacheSize() + ); + useCache = true; + } else { + useCache = false; + } if (playerNeedsSource) { // Will force display of shutter view if needed exoPlayerView.updateShutterViewVisibility(); @@ -813,7 +822,7 @@ private void initializePlayerCore(ReactExoplayerView self) { DefaultAllocator allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE); RNVLoadControl loadControl = new RNVLoadControl( allocator, - bufferConfig + source.getBufferConfig() ); DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(getContext()) @@ -1119,7 +1128,7 @@ private MediaSource buildMediaSource(Uri uri, String overrideExtension, DrmSessi } } - MediaItem.LiveConfiguration.Builder liveConfiguration = ConfigurationUtils.getLiveConfiguration(bufferConfig); + MediaItem.LiveConfiguration.Builder liveConfiguration = ConfigurationUtils.getLiveConfiguration(source.getBufferConfig()); mediaItemBuilder.setLiveConfiguration(liveConfiguration.build()); MediaSource.Factory mediaSourceFactory; @@ -2368,21 +2377,6 @@ public void setHideShutterView(boolean hideShutterView) { exoPlayerView.setHideShutterView(hideShutterView); } - public void setBufferConfig(BufferConfig config) { - bufferConfig = config; - if (bufferConfig.getCacheSize() > 0) { - RNVSimpleCache.INSTANCE.setSimpleCache( - this.getContext(), - bufferConfig.getCacheSize() - ); - useCache = true; - } else { - useCache = false; - } - releasePlayer(); - initializePlayer(); - } - @Override public void onDrmKeysLoaded(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) { DebugLog.d("DRM Info", "onDrmKeysLoaded"); diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt index a2bebb8b01..2ac8dd1665 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt @@ -2,7 +2,6 @@ package com.brentvatne.exoplayer import android.graphics.Color import android.util.Log -import com.brentvatne.common.api.BufferConfig import com.brentvatne.common.api.BufferingStrategy import com.brentvatne.common.api.ControlsConfig import com.brentvatne.common.api.ResizeMode @@ -36,7 +35,6 @@ class ReactExoplayerViewManager(private val config: ReactExoplayerConfig) : View private const val PROP_MUTED = "muted" private const val PROP_AUDIO_OUTPUT = "audioOutput" private const val PROP_VOLUME = "volume" - private const val PROP_BUFFER_CONFIG = "bufferConfig" private const val PROP_PREVENTS_DISPLAY_SLEEP_DURING_VIDEO_PLAYBACK = "preventsDisplaySleepDuringVideoPlayback" private const val PROP_PROGRESS_UPDATE_INTERVAL = "progressUpdateInterval" @@ -242,12 +240,6 @@ class ReactExoplayerViewManager(private val config: ReactExoplayerConfig) : View videoView.setShutterColor(color) } - @ReactProp(name = PROP_BUFFER_CONFIG) - fun setBufferConfig(videoView: ReactExoplayerView, bufferConfig: ReadableMap?) { - val config = BufferConfig.parse(bufferConfig) - videoView.setBufferConfig(config) - } - @ReactProp(name = PROP_SHOW_NOTIFICATION_CONTROLS) fun setShowNotificationControls(videoView: ReactExoplayerView, showNotificationControls: Boolean) { videoView.setShowNotificationControls(showNotificationControls) diff --git a/docs/pages/component/props.mdx b/docs/pages/component/props.mdx index e205e6bad0..9854ed768b 100644 --- a/docs/pages/component/props.mdx +++ b/docs/pages/component/props.mdx @@ -52,6 +52,9 @@ A Boolean value that indicates whether the player should automatically delay pla ### `bufferConfig` +> [!WARNING] +> Deprecated, use source.bufferConfig instead + Adjust the buffer settings. This prop takes an object with one or more of the properties listed below. @@ -907,6 +910,56 @@ source={{ }} ``` +### `bufferConfig` + + + +Adjust the buffer settings. This prop takes an object with one or more of the properties listed below. + +| Property | Type | Description | +| --------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| minBufferMs | number | The default minimum duration of media that the player will attempt to ensure is buffered at all times, 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. | +| 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. | +| backBufferDurationMs | number | The number of milliseconds of buffer to keep before the current position. This allows rewinding without rebuffering within that duration. | +| maxHeapAllocationPercent | number | The percentage of available heap that the video can use to buffer, between 0 and 1 | +| minBackBufferMemoryReservePercent | number | The percentage of available app memory at which during startup the back buffer will be disabled, between 0 and 1 | +| minBufferMemoryReservePercent | number | The percentage of available app memory to keep in reserve that prevents buffer from using it, between 0 and 1 | +| cacheSizeMB | number | Cache size in MB, enabling this to prevent new src requests and save bandwidth while repeating videos, or 0 to disable. Android only. | +| live | object | Object containing another config set for live playback configuration, see next table | + + +Description of live object: + +| Property | Type | Description | +| --------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| maxPlaybackSpeed | number | The maximum playback speed the player can use to catch up when trying to reach the target live offset. | +| minPlaybackSpeed | number | The minimum playback speed the player can use to fall back when trying to reach the target live offset. | +| maxOffsetMs | number | The maximum allowed live offset. Even when adjusting the offset to current network conditions, the player will not attempt to get above this offset during playback. | +| minOffsetMs | number | The minimum allowed live offset. Even when adjusting the offset to current network conditions, the player will not attempt to get below this offset during playback. | +| targetOffsetMs | number | The target live offset. The player will attempt to get close to this live offset during playback if possible. | + +For android, more informations about live configuration can be find [here](https://developer.android.com/media/media3/exoplayer/live-streaming?hl=en) + +Example with default values: + +```javascript +bufferConfig={{ + minBufferMs: 15000, + maxBufferMs: 50000, + bufferForPlaybackMs: 2500, + bufferForPlaybackAfterRebufferMs: 5000, + backBufferDurationMs: 120000, + cacheSizeMB: 0, + live: { + targetOffsetMs: 500, + }, +}} +``` + +Please note that the Android cache is a global cache that is shared among all components; individual components can still opt out of caching behavior by setting cacheSizeMB to 0, but multiple components with a positive cacheSizeMB will be sharing the same one, and the cache size will always be the first value set; it will not change during the app's lifecycle. + #### `minLoadRetryCount` diff --git a/examples/expo/src/BasicExample.tsx b/examples/expo/src/BasicExample.tsx index 2997cd58b2..af1aeede92 100644 --- a/examples/expo/src/BasicExample.tsx +++ b/examples/expo/src/BasicExample.tsx @@ -240,7 +240,7 @@ const BasicExample = () => { }; useEffect(() => { - videoRef.current?.setSource(currentSrc); + videoRef.current?.setSource({...currentSrc, bufferConfig: _bufferConfig }); }, [currentSrc]); return ( @@ -284,7 +284,6 @@ const BasicExample = () => { selectedAudioTrack={selectedAudioTrack} selectedVideoTrack={selectedVideoTrack} playInBackground={false} - bufferConfig={_bufferConfig} preventsDisplaySleepDuringVideoPlayback={true} renderLoader={_renderLoader} onPlaybackRateChange={onPlaybackRateChange} diff --git a/src/Video.tsx b/src/Video.tsx index 50cc352498..0aaa119904 100644 --- a/src/Video.tsx +++ b/src/Video.tsx @@ -108,6 +108,7 @@ const Video = forwardRef( onAspectRatio, localSourceEncryptionKeyScheme, minLoadRetryCount, + bufferConfig, ...rest }, ref, @@ -219,6 +220,8 @@ const Video = forwardRef( const _minLoadRetryCount = _source.minLoadRetryCount || minLoadRetryCount; + + const _bufferConfig = _source.bufferConfig || bufferConfig; return { uri, isNetwork, @@ -240,6 +243,7 @@ const Video = forwardRef( textTracksAllowChunklessPreparation: resolvedSource.textTracksAllowChunklessPreparation, minLoadRetryCount: _minLoadRetryCount, + bufferConfig: _bufferConfig, }; }, [ @@ -251,6 +255,7 @@ const Video = forwardRef( minLoadRetryCount, source?.cmcd, textTracks, + bufferConfig, ], ); diff --git a/src/specs/VideoNativeComponent.ts b/src/specs/VideoNativeComponent.ts index 907bfcb76c..d434d58534 100644 --- a/src/specs/VideoNativeComponent.ts +++ b/src/specs/VideoNativeComponent.ts @@ -51,6 +51,7 @@ export type VideoSrc = Readonly<{ textTracks?: TextTracks; ad?: AdsConfig; minLoadRetryCount?: Int32; // Android + bufferConfig?: BufferConfig; // Android }>; type DRMType = WithDefault; @@ -358,7 +359,6 @@ export interface VideoNativeProps extends ViewProps { restoreUserInterfaceForPIPStopCompletionHandler?: boolean; debug?: DebugConfig; showNotificationControls?: WithDefault; // Android, iOS - bufferConfig?: BufferConfig; // Android currentPlaybackTime?: Double; // Android disableDisconnectError?: boolean; // Android focusable?: boolean; // Android diff --git a/src/types/video.ts b/src/types/video.ts index f3118cfa71..6a213d1d4a 100644 --- a/src/types/video.ts +++ b/src/types/video.ts @@ -40,6 +40,7 @@ export type ReactVideoSourceProperties = { textTracks?: TextTracks; ad?: AdConfig; minLoadRetryCount?: number; // Android + bufferConfig?: BufferConfig; }; export type ReactVideoSource = Readonly< @@ -289,6 +290,7 @@ export interface ReactVideoProps extends ReactVideoEvents, ViewProps { adLanguage?: ISO639_1; audioOutput?: AudioOutput; // Mobile automaticallyWaitsToMinimizeStalling?: boolean; // iOS + /** @deprecated Use source.bufferConfig */ bufferConfig?: BufferConfig; // Android bufferingStrategy?: BufferingStrategyType; chapters?: Chapters[]; // iOS