From e16730de11d50b8a85cd09fa2b102fdbf777d8ad Mon Sep 17 00:00:00 2001 From: Olivier Bouillet <62574056+freeboub@users.noreply.github.com> Date: Mon, 20 May 2024 16:18:20 +0200 Subject: [PATCH] fix(android): implement live configuration management (#3792) * perf: ensure we do not provide callback to native if no callback provided from app * chore: rework bufferConfig to make it more generic and reduce ReactExoplayerView code size * chore: improve issue template * fix(android): avoid video view flickering at playback startup * feat(android): implement live buffer configuration * chore: fix linter --- .../com/brentvatne/common/api/BufferConfig.kt | 32 +++++++++++++++++ .../exoplayer/ConfigurationUtils.kt | 36 +++++++++++++++++++ .../exoplayer/ReactExoplayerView.java | 3 ++ docs/pages/component/props.mdx | 19 +++++++++- examples/basic/src/VideoPlayer.tsx | 3 ++ src/specs/VideoNativeComponent.ts | 9 +++++ src/types/video.ts | 9 +++++ 7 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 android/src/main/java/com/brentvatne/exoplayer/ConfigurationUtils.kt 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 297a1bf6..b3889888 100644 --- a/android/src/main/java/com/brentvatne/common/api/BufferConfig.kt +++ b/android/src/main/java/com/brentvatne/common/api/BufferConfig.kt @@ -1,6 +1,7 @@ package com.brentvatne.common.api import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetDouble +import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetFloat import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetInt import com.facebook.react.bridge.ReadableMap @@ -20,6 +21,35 @@ class BufferConfig { var minBackBufferMemoryReservePercent = BufferConfigPropUnsetDouble var minBufferMemoryReservePercent = BufferConfigPropUnsetDouble + var live: Live = Live() + + class Live { + var maxPlaybackSpeed: Float = BufferConfigPropUnsetDouble.toFloat() + var minPlaybackSpeed: Float = BufferConfigPropUnsetDouble.toFloat() + var maxOffsetMs: Long = BufferConfigPropUnsetInt.toLong() + var minOffsetMs: Long = BufferConfigPropUnsetInt.toLong() + 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" + + @JvmStatic + fun parse(src: ReadableMap?): Live { + val live = Live() + live.maxPlaybackSpeed = safeGetFloat(src, PROP_BUFFER_CONFIG_LIVE_MAX_PLAYBACK_SPEED, BufferConfigPropUnsetDouble.toFloat()) + live.minPlaybackSpeed = safeGetFloat(src, PROP_BUFFER_CONFIG_LIVE_MIN_PLAYBACK_SPEED, BufferConfigPropUnsetDouble.toFloat()) + live.maxOffsetMs = safeGetInt(src, PROP_BUFFER_CONFIG_LIVE_MAX_OFFSET_MS, BufferConfigPropUnsetInt).toLong() + live.minOffsetMs = safeGetInt(src, PROP_BUFFER_CONFIG_LIVE_MIN_OFFSET_MS, BufferConfigPropUnsetInt).toLong() + live.targetOffsetMs = safeGetInt(src, PROP_BUFFER_CONFIG_LIVE_TARGET_OFFSET_MS, BufferConfigPropUnsetInt).toLong() + return live + } + } + } + companion object { val BufferConfigPropUnsetInt = -1 val BufferConfigPropUnsetDouble = -1.0 @@ -33,6 +63,7 @@ class BufferConfig { 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" @JvmStatic fun parse(src: ReadableMap?): BufferConfig { @@ -59,6 +90,7 @@ class BufferConfig { BufferConfigPropUnsetDouble ) bufferConfig.backBufferDurationMs = safeGetInt(src, PROP_BUFFER_CONFIG_BACK_BUFFER_DURATION_MS, BufferConfigPropUnsetInt) + bufferConfig.live = Live.parse(src.getMap(PROP_BUFFER_CONFIG_LIVE)) } return bufferConfig } diff --git a/android/src/main/java/com/brentvatne/exoplayer/ConfigurationUtils.kt b/android/src/main/java/com/brentvatne/exoplayer/ConfigurationUtils.kt new file mode 100644 index 00000000..6900a037 --- /dev/null +++ b/android/src/main/java/com/brentvatne/exoplayer/ConfigurationUtils.kt @@ -0,0 +1,36 @@ +package com.brentvatne.exoplayer + +import androidx.media3.common.MediaItem.LiveConfiguration +import com.brentvatne.common.api.BufferConfig +import com.brentvatne.common.api.BufferConfig.Live + +/** + * Helper functions to create exoplayer configuration + */ +object ConfigurationUtils { + + /** + * Create a media3.LiveConfiguration.Builder from parsed BufferConfig + */ + @JvmStatic + fun getLiveConfiguration(bufferConfig: BufferConfig): LiveConfiguration.Builder { + val liveConfiguration = LiveConfiguration.Builder() + val live: Live = bufferConfig.live + if (bufferConfig.live.maxOffsetMs >= 0) { + liveConfiguration.setMaxOffsetMs(live.maxOffsetMs) + } + if (bufferConfig.live.maxPlaybackSpeed >= 0) { + liveConfiguration.setMaxPlaybackSpeed(live.maxPlaybackSpeed) + } + if (bufferConfig.live.targetOffsetMs >= 0) { + liveConfiguration.setTargetOffsetMs(live.targetOffsetMs) + } + if (bufferConfig.live.minOffsetMs >= 0) { + liveConfiguration.setMinOffsetMs(live.minOffsetMs) + } + if (bufferConfig.live.minPlaybackSpeed >= 0) { + liveConfiguration.setMinPlaybackSpeed(live.minPlaybackSpeed) + } + return liveConfiguration + } +} diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 64de3f15..13f25366 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -965,6 +965,9 @@ public class ReactExoplayerView extends FrameLayout implements ); } + MediaItem.LiveConfiguration.Builder liveConfiguration = ConfigurationUtils.getLiveConfiguration(bufferConfig); + mediaItemBuilder.setLiveConfiguration(liveConfiguration.build()); + MediaSource.Factory mediaSourceFactory; DrmSessionManagerProvider drmProvider; List streamKeys = new ArrayList(); diff --git a/docs/pages/component/props.mdx b/docs/pages/component/props.mdx index 5bbbb643..f6d7fcaf 100644 --- a/docs/pages/component/props.mdx +++ b/docs/pages/component/props.mdx @@ -83,6 +83,20 @@ Adjust the buffer settings. This prop takes an object with one or more of the pr | 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: @@ -93,7 +107,10 @@ bufferConfig={{ bufferForPlaybackMs: 2500, bufferForPlaybackAfterRebufferMs: 5000, backBufferDurationMs: 120000, - cacheSizeMB: 0 + cacheSizeMB: 0, + live: { + targetOffsetMs: 500, + }, }} ``` diff --git a/examples/basic/src/VideoPlayer.tsx b/examples/basic/src/VideoPlayer.tsx index d9a6a863..37378bae 100644 --- a/examples/basic/src/VideoPlayer.tsx +++ b/examples/basic/src/VideoPlayer.tsx @@ -930,6 +930,9 @@ class VideoPlayer extends Component { bufferForPlaybackMs: 2500, bufferForPlaybackAfterRebufferMs: 5000, cacheSizeMB: this.state.useCache ? 200 : 0, + live: { + targetOffsetMs: 500, + }, }} preventsDisplaySleepDuringVideoPlayback={true} poster={this.state.poster} diff --git a/src/specs/VideoNativeComponent.ts b/src/specs/VideoNativeComponent.ts index 53cec03b..dac5b5fd 100644 --- a/src/specs/VideoNativeComponent.ts +++ b/src/specs/VideoNativeComponent.ts @@ -93,6 +93,14 @@ export type Seek = Readonly<{ tolerance?: Float; }>; +type BufferConfigLive = Readonly<{ + maxPlaybackSpeed?: Float; + minPlaybackSpeed?: Float; + maxOffsetMs?: Int32; + minOffsetMs?: Int32; + targetOffsetMs?: Int32; +}>; + type BufferingStrategyType = WithDefault; type BufferConfig = Readonly<{ @@ -105,6 +113,7 @@ type BufferConfig = Readonly<{ minBackBufferMemoryReservePercent?: Float; minBufferMemoryReservePercent?: Float; cacheSizeMB?: Float; + live?: BufferConfigLive; }>; type SubtitleStyle = Readonly<{ diff --git a/src/types/video.ts b/src/types/video.ts index 56694ecd..3bff6735 100644 --- a/src/types/video.ts +++ b/src/types/video.ts @@ -74,6 +74,14 @@ export enum BufferingStrategyType { DEPENDING_ON_MEMORY = 'DependingOnMemory', } +export type BufferConfigLive = { + maxPlaybackSpeed?: number; + minPlaybackSpeed?: number; + maxOffsetMs?: number; + minOffsetMs?: number; + targetOffsetMs?: number; +}; + export type BufferConfig = { minBufferMs?: number; maxBufferMs?: number; @@ -84,6 +92,7 @@ export type BufferConfig = { minBackBufferMemoryReservePercent?: number; minBufferMemoryReservePercent?: number; cacheSizeMB?: number; + live?: BufferConfigLive; }; export enum SelectedTrackType {