From 41ddc5c27a9c180a6d0364b3f3286211f7a10e68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Moska=C5=82a?= <91079590+moskalakamil@users.noreply.github.com> Date: Sun, 6 Apr 2025 16:16:58 +0200 Subject: [PATCH] feat(android): initial bitrate (#4480) * feat(android): implement initialBitrate * refactor: reorder type * docs: add initialBitrate section * fix: lint * refactor: move `initialBitrate` into `source.bufferConfig` --- .../com/brentvatne/common/api/BufferConfig.kt | 4 +++ .../exoplayer/DefaultReactExoplayerConfig.kt | 17 ++++++++++-- .../exoplayer/ReactExoplayerConfig.kt | 2 ++ .../exoplayer/ReactExoplayerView.java | 9 ++++++- docs/bun.lockb | Bin 155781 -> 155837 bytes docs/pages/component/props.mdx | 25 +++++++++--------- src/types/video.ts | 1 + 7 files changed, 43 insertions(+), 15 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 4cb33ea4..17f54c62 100644 --- a/android/src/main/java/com/brentvatne/common/api/BufferConfig.kt +++ b/android/src/main/java/com/brentvatne/common/api/BufferConfig.kt @@ -20,6 +20,7 @@ class BufferConfig { var maxHeapAllocationPercent = BufferConfigPropUnsetDouble var minBackBufferMemoryReservePercent = BufferConfigPropUnsetDouble var minBufferMemoryReservePercent = BufferConfigPropUnsetDouble + var initialBitrate = BufferConfigPropUnsetInt var live: Live = Live() @@ -36,6 +37,7 @@ class BufferConfig { maxHeapAllocationPercent == other.maxHeapAllocationPercent && minBackBufferMemoryReservePercent == other.minBackBufferMemoryReservePercent && minBufferMemoryReservePercent == other.minBufferMemoryReservePercent && + initialBitrate == other.initialBitrate && live == other.live ) } @@ -91,6 +93,7 @@ class BufferConfig { 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_INITIAL_BITRATE = "initialBitrate" private const val PROP_BUFFER_CONFIG_LIVE = "live" @JvmStatic @@ -118,6 +121,7 @@ class BufferConfig { BufferConfigPropUnsetDouble ) bufferConfig.backBufferDurationMs = safeGetInt(src, PROP_BUFFER_CONFIG_BACK_BUFFER_DURATION_MS, BufferConfigPropUnsetInt) + bufferConfig.initialBitrate = safeGetInt(src, PROP_BUFFER_CONFIG_INITIAL_BITRATE, BufferConfigPropUnsetInt) bufferConfig.live = Live.parse(src.getMap(PROP_BUFFER_CONFIG_LIVE)) } return bufferConfig diff --git a/android/src/main/java/com/brentvatne/exoplayer/DefaultReactExoplayerConfig.kt b/android/src/main/java/com/brentvatne/exoplayer/DefaultReactExoplayerConfig.kt index 12c0ddf8..435e2fc2 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/DefaultReactExoplayerConfig.kt +++ b/android/src/main/java/com/brentvatne/exoplayer/DefaultReactExoplayerConfig.kt @@ -5,13 +5,26 @@ import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter import androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy -class DefaultReactExoplayerConfig(context: Context) : ReactExoplayerConfig { +class DefaultReactExoplayerConfig(private val context: Context, override var initialBitrate: Long? = null) : ReactExoplayerConfig { + + private var bandWidthMeter: DefaultBandwidthMeter = createBandwidthMeter(initialBitrate) - private var bandWidthMeter: DefaultBandwidthMeter = DefaultBandwidthMeter.Builder(context).build() override var disableDisconnectError: Boolean = false + override val bandwidthMeter: DefaultBandwidthMeter get() = bandWidthMeter + private fun createBandwidthMeter(bitrate: Long?): DefaultBandwidthMeter = + DefaultBandwidthMeter.Builder(context) + .setInitialBitrateEstimate(bitrate ?: DefaultBandwidthMeter.DEFAULT_INITIAL_BITRATE_ESTIMATE) + .build() + + override fun setInitialBitrate(bitrate: Long) { + if (initialBitrate == bitrate) return + initialBitrate = bitrate + bandWidthMeter = createBandwidthMeter(bitrate) + } + override fun buildLoadErrorHandlingPolicy(minLoadRetryCount: Int): LoadErrorHandlingPolicy = if (disableDisconnectError) { ReactExoplayerLoadErrorHandlingPolicy(minLoadRetryCount) diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerConfig.kt b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerConfig.kt index 6f5e65ba..4cc47d5f 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerConfig.kt +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerConfig.kt @@ -7,4 +7,6 @@ interface ReactExoplayerConfig { fun buildLoadErrorHandlingPolicy(minLoadRetryCount: Int): LoadErrorHandlingPolicy var disableDisconnectError: Boolean val bandwidthMeter: DefaultBandwidthMeter + var initialBitrate: Long? + fun setInitialBitrate(bitrate: Long) } diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index c3bbc95e..7760d1bb 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -177,7 +177,7 @@ public class ReactExoplayerView extends FrameLayout implements protected final VideoEventEmitter eventEmitter; private final ReactExoplayerConfig config; - private final DefaultBandwidthMeter bandwidthMeter; + private DefaultBandwidthMeter bandwidthMeter; private LegacyPlayerControlView playerControlView; private View playPauseControlContainer; private Player.Listener eventListener; @@ -849,6 +849,13 @@ public class ReactExoplayerView extends FrameLayout implements allocator, source.getBufferConfig() ); + + long initialBitrate = source.getBufferConfig().getInitialBitrate(); + if (initialBitrate > 0) { + config.setInitialBitrate(initialBitrate); + this.bandwidthMeter = config.getBandwidthMeter(); + } + DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(getContext()) .setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF) diff --git a/docs/bun.lockb b/docs/bun.lockb index 4c9665a25655a915bf35835a3e4e5ce7609b2e6c..0536e71efb7208a714fb206aed745f7c9e5386dc 100755 GIT binary patch delta 5330 zcmYk836xXi8HRINB8(VOYXp=O1_>$<6*RbD926qjnu2Wuo}*J-h=>x9B~GPe03}^c z8wZetxgzk4+*7ES(li-}}>|oz2=VjMYCK0(HyO%ME%0=0;oJmYB$=-XI z#O1Q=TfrnDS7d*RNm8!Lfi#npT$6(~` za(E4sob1f9ZtQAlxgfjOGKtDX+4BgKm|T**k1~nNW!blmNkXp3{`E|fa#ap& zV3Lw+axljvBiH57V@$GgLk>UABquvJvTv31vTGBQh+L4}PcVtfMcK2NNlY%u-Y1#F z<+AL{GfBu5+26?|DOcsd7A7gVCI|n`cPqS~8^RjCjlZaf9 z-32C5xhQ*{VG@%|vUfX^xLlTfJD4QoitOLXBq>+rz_UzJa!n5IVv>>Ta_BiGS-ByH zi%fE|b2s}|IWN1OXA+SMvit8$qHRVGonD0}uYiOD6|TV@iM z%d+n^CJDJB`(J00l&f;!4JIkMCI|n`BqP`5(0`a@<%S%7lSxi?{+E5LoR?h{CK0(H zyZ^@|Di>wXJ|;1_BzyNWiOXf#cYsMkuE_p_Op|5o$?E0KZL@vngFPKE-qU@v#yCWty*9?xHXMV zH@8_M(i7UOQ^zps0)}x9wkZghbp<0CV7ty_-OziLI2_lTtb2NOn{`6F6Y;Nk9JU)# zdCH@}Jd;Jt@E*u!n(QW%^+Xmo*))?iA^VNVZZ_G`$YQ3Sj>=2y1)`Rt9mV08VRjya z{#x|huv7QrLVqT%iWkNU;O=9=STsD*ER*#{zm>%_-ll{?*I~5tv z$B%(Cgg?h%um%}7%uu`Tgx}3av6oZXVFnICpGMEATxzn@(DyJ0b1M0foDR02=M1ed zlj{)h`{g(vSZ3gQGw=drJJ53~H=uI& zg@6~xyL`-y8;$-NWW3AAO?DCbJ;->M8%-8K--(Pjw8>-_I~>y;TTJ+b30p9Lch9@z zuK<6JF`y5|@#>#MfYxydIKZ9QI#JnP3V4CMD1IlHy~5ubw$14o3#=jOjs?~!?Z;rc zdjXE;c`gH&gRx*7xB`p^LBN}70lfJ!02i(E^M-iSoJoFVcw_t&IP3gM@GHZ~8vyvJ z@aw`K1lH?zeboHY^GnVzCU15iXa<7-XO6SXpDx!N{s{PM<2uE4iE9ZLGA@Mtb@c^J zfJ=r8H#QM<0+;3~f6qQ?b|D<>B$PEZ@*3du(S7dEb-v%ws|M*&w}u1{Rd zxH8`YE&=C*^MD_W0T+V{z=hx<5CEe<3m6T~1yk_;j>+Iwd{i?)6wCytW9PMCAA4{H z7z%~~AGj3z#sMz#T*7Y$ZQxg63g8Mp3|s*&1DAvGU_bV8)qV>c1Oq1^oCpr%MORE$ zH}D;LKBy)kzbk$9LThL{mzd>X2zI@M`Z9O{a3S6ac7Y;z9y|xQ7|#H6aGKk|JTM*1 z1~&jM%(K8$a4U#_X>`otUw=Ntp9qmnE8i;`FK^=Sv4uL9YfbT(fOvefn~K@Td1Qcmr8Ey?2o{zkMD~F&m5pR{}16SAmUyYuO`U6%JX6+5whc<(4Wt> zlM#H59iM?u!53fy;3_{ATo1Uu{RDL< z>Mp>CF0&-YpM%^7&g4)0EHqzX2bcadxCh*UK|Gj?BOiv-z+k{<+YMj>#!mzz!DR3v z;B$&^!0Z8x;bVs9Aia>e03RuQy&dqWv4(eWcO?8$iD2kiercH|tdAs>l+EzkRd{qLZ6anQcI&|w>}G#y=FHFvJP%WCP>a{~XH z!1i(HCmq)ApABi@xv}lzM~JOy^qi?PVpC`P#_SLdLOih4xZAv>43Fo!T$l&Yh9cG delta 5228 zcmYk836xXi8HRHQ2+RlpY7HouvPcxsxJM9-pcv7%sr6{+qJwNHDntbdBWeZ^((N>| zXd*7bjR>d_DrRsYf=i%PqZWwTC3R3DqQsRTF0t?X-k)=N&pglf<;|aa|GD?(zuCOC zdCAu1rJ<$+lQvx1?GDSaB7Db0I43)ovTv31vTGTWh+L4}%b7&wqU>40Bqo<+@10EI za#{A>#UvqDWPgfDQm)E@G?SEElY@6N$;fp%w310yZph(#nB-*Vz3f}%yzIJ<*O zNy#-im}8QW>vHHxCRw>5hu1U7$<7VzTjjj$`YV%&T#(&QF^S4W*|U*JOfJdZrAp;wq><%S$C zGReu#ZR}g+yzJV}BqA4N_urUA<)ZBQJCm4PlD)4oiOXf#_ZpLgT#@~+GfB!-IZ$Gf zl52AC4JH}6E{EP^l9d~B_$?+m+4&Flt#V#=?O+m-3$lABlc-#jJ-e91zz zl52ACGbS0iE{8s6l9d~B_zNaE+4&{=Ryi-bzG4!Q3$ptFlc-#jJ#{8Axg>iJGKtG& z+4nV*gj|vRhnOVgsvP)+NlLEC!Ec#lpqCvxLOoO>{76c`v`?myA>8UVgmy%t)y%fgnQnriF zr7&Taie3D@6(;RcwM(Fn!jxTVb_w>iCaxIQzh8R&F;>@(O?|B^Bd%$;Sq^S3W9H1B z>Pvgttkct%w^@VQn-G#y_%61|7{{Sqz!?m%O)*(l^k*q?IHsDc8(#0`4zNu#nG=1? zWYbO79ofw$iy&j$!{KOmM9qL1CTzwVbJ26V)?_W{V`kh;R8H;)aI+bAy~%nayVYc~ zQQ7taag)t4nG4yK=$mkS$4zP+j@}5bG6N4EtPisB$auhcsC>IG7-7b>ne0c%PDal& z#7S}dk>Ex1A9GUpbNm>bW-?w83&(dH1y-4mGvz=xSZ%U)1Z?|(`6gRv#`Q;bi^&$5 z>}X^!q30>T-DJn0--w=Pcd^L^pl{Eca0x1BJrMi_J-0uYfybgxn(U7z^C07W!L#-! zll=t!b%5KSk#SywK*>yy`(-;Av;l1G4*YmmI0UaYnt{tqHWb+^^gJ6YOm-al2hel7 z3zh9KkilnphEgW;qF;%OXDDs5vuc$ws0-3K>uDlO`L5zJZ?GdXt@l-ZG!vfDCQBV>H4VUhpzJWd@Ex zUqR2yz`GWIj&s3QG(5deqjEn1&>sya&|$Lk&>uw42|R;}KgU?Gn+-NzLbgl!{o@z* z*2U?yA;XiBFox(eVHv%s_FElSKFC;GHa(SE&ZUI~tc^TqhCO$ih zkE1BK9!vsPgA>6Ba1!u=>oE8#@Egqdx8QR07k~@FS-=m@1|z{Ja1Iy^#(;A{0GtQL zf^pz{!1eqjaK+Ww;jcg!oT{$idmQjPz^6?e^&r>-s-OT~09(LTz{UP0@G{s2wgWEq ztI~%SSfdBOi2c6|UI9h09SpgO@jf8^E<-3W$K;ff*nG&I4n? z*1mi>%k4Y18+n#8_WT7K@4=m zsc7IpKY(w*A@CvC3qAt-z{lVdupe9j!r<3{tN$C|GSG&7-2&oZ0ayqYfem<%i~rgt z+`kWC=6B=8Qm_mxZ%RiNTAseoBi{_T>d!?!J3V=kb!2+uLaS}myVyw?G-0A#nZL#R zpMuZ8=b#2AfSq6|hAad3fF?~}a{F?KTCn?p;An6Rcn8@5eC$i`75D<| zLblV9Zn@p^v>%Hx1He$^QB?kRxDt#9Cxfq%Co!In$X%c}II0Ot2A6Lx*Z=>Y+=~ NKdEEiZfnPF{{!}@UEu%# diff --git a/docs/pages/component/props.mdx b/docs/pages/component/props.mdx index 10efba93..e85d38c8 100644 --- a/docs/pages/component/props.mdx +++ b/docs/pages/component/props.mdx @@ -985,18 +985,19 @@ source={{ Adjust the buffer settings. This prop takes an object with one or more of the properties listed below. -| Property | Type | Description | -|-----------------------------------|--------|---------------------------------------------------------------------------------------------------------------------------| -| minBufferMs | number | Minimum duration of media that the player will attempt to buffer at all times, in milliseconds. | -| maxBufferMs | number | Maximum duration of media that the player will attempt to buffer, in milliseconds. | -| bufferForPlaybackMs | number | Duration of media that must be buffered for playback to start or resume following a user action, in milliseconds. | -| bufferForPlaybackAfterRebufferMs | number | Duration of media that must be buffered for playback to resume after a rebuffer, in milliseconds. | -| backBufferDurationMs | number | Duration of buffer to keep before the current position, allowing rewinding without rebuffering. | -| maxHeapAllocationPercent | number | Percentage of available heap that the video can use to buffer, between 0 and 1. | -| minBackBufferMemoryReservePercent | number | Percentage of available app memory at which during startup the back buffer will be disabled, between 0 and 1. | -| minBufferMemoryReservePercent | number | Percentage of available app memory to keep in reserve, preventing buffer usage, between 0 and 1. | -| cacheSizeMB | number | Cache size in MB, preventing new src requests and saving bandwidth while repeating videos, or 0 to disable. Android only. | -| live | object | Object containing another config set for live playback configuration. | +| Property | Type | Description | +|----------------------------------|--------|------------------------------------------------------------------------------------------------------------------------------------------------| +| minBufferMs | number | Minimum duration of media that the player will attempt to buffer at all times, in milliseconds. | +| maxBufferMs | number | Maximum duration of media that the player will attempt to buffer, in milliseconds. | +| bufferForPlaybackMs | number | Duration of media that must be buffered for playback to start or resume following a user action, in milliseconds. | +| bufferForPlaybackAfterRebufferMs | number | Duration of media that must be buffered for playback to resume after a rebuffer, in milliseconds. | +| backBufferDurationMs | number | Duration of buffer to keep before the current position, allowing rewinding without rebuffering. | +| maxHeapAllocationPercent | number | Percentage of available heap that the video can use to buffer, between 0 and 1. | +| minBackBufferMemoryReservePercent| number | Percentage of available app memory at which during startup the back buffer will be disabled, between 0 and 1. | +| minBufferMemoryReservePercent | number | Percentage of available app memory to keep in reserve, preventing buffer usage, between 0 and 1. | +| initialBitrate | number | Initial bitrate in bits per second (Android only). Defaults to 1_000_000. Used only at start, then ABR (Adaptive Bitrate Streaming) takes over.| +| cacheSizeMB | number | Cache size in MB, preventing new src requests and saving bandwidth while repeating videos, or 0 to disable. Android only. | +| live | object | Object containing another config set for live playback configuration. | #### `minLoadRetryCount` diff --git a/src/types/video.ts b/src/types/video.ts index d14fe7e2..1be6775e 100644 --- a/src/types/video.ts +++ b/src/types/video.ts @@ -145,6 +145,7 @@ export type BufferConfig = { maxHeapAllocationPercent?: number; minBackBufferMemoryReservePercent?: number; minBufferMemoryReservePercent?: number; + initialBitrate?: number; // Android cacheSizeMB?: number; live?: BufferConfigLive; };