From 9c42c4d0c0313bba718ae4cb97831f58e64d5e16 Mon Sep 17 00:00:00 2001 From: Paul Berberian Date: Tue, 29 Oct 2024 12:15:53 +0100 Subject: [PATCH 1/4] [Proposal] Add PlaybackRate-based rebuffering avoidance mechanism While admittedly checking what other media players were doing that we don't do yet, I saw an interesting opportunity. Some players have a mechanism where they lower very slightly the playback rate (the speed at which we play) - for example at `0.95` (meaning we play 0.95 seconds of content every second) - if the current buffer size is low, until it gains back an acceptable size again. I'm not sure of the actual impact this mechanism has in terms of rebuffering avoidance but it seems that it can help. Because it has potential but has high uncertainties, I propose adding this mechanism as an experimental `loadVideo` option for now, meaning that we reserve the right to update this API or even remove it at any time. I re-use the `experimentalOptions` concept I already proposed in several of the currently-open PRs for this. Because we might want to check the buffer size often when it's close to the potentially-very-low value where this mechanism activates, I reduced our PlaybackObserver's regular interval from 1s to 500ms. This is the same interval we already tested on low-latency contents so it shouldn't be an issue. I also for now only enable this mechanism if the application plays at x1, not if it is already updating the playbackRate as I found the wanted behavior in thoses cases harder to define. Some players also only enable this mechanism on VoD contents, not on live contents. I think that this is a mistake as there's no reason timeshifted live shouldn't be treated the same. I didn't do the API documentation yet nor link it to the demo because I'm somewhat unhappy for that mechanism's name, for now called `playbackRateBasedRebufferingAvoidance`. Even if it describes well what it does, I found that name too long even in the code. I think we may prefer finding it a nicer-sounding name to make the code more readable and the API more approachable, like we did for our "fast-switching concept" (which sadly has now a new meaning for DASH low-latency contents different than ours or dash.js's usage, but that's always a risk and I'm willing to take it). --- src/default_config.ts | 2 +- src/main_thread/api/option_utils.ts | 5 ++ src/main_thread/api/public_api.ts | 8 +- .../init/directfile_content_initializer.ts | 13 ++- .../init/media_source_content_initializer.ts | 8 ++ .../init/utils/rebuffering_controller.ts | 89 +++++++++++++++++-- src/public_types.ts | 29 ++++++ .../src/main_thread/api/option_utils.test.ts | 1 + 8 files changed, 147 insertions(+), 8 deletions(-) diff --git a/src/default_config.ts b/src/default_config.ts index 134a8ec562..2366325433 100644 --- a/src/default_config.ts +++ b/src/default_config.ts @@ -346,7 +346,7 @@ const DEFAULT_CONFIG = { * triggered when various events of the media element are received. * @type {Number} */ - SAMPLING_INTERVAL_MEDIASOURCE: 1000, + SAMPLING_INTERVAL_MEDIASOURCE: 500, /** * Same than SAMPLING_INTERVAL_MEDIASOURCE but for lowLatency mode. diff --git a/src/main_thread/api/option_utils.ts b/src/main_thread/api/option_utils.ts index 827f116ddd..f57870f9b9 100644 --- a/src/main_thread/api/option_utils.ts +++ b/src/main_thread/api/option_utils.ts @@ -35,6 +35,7 @@ import type { IServerSyncInfos, IRxPlayerMode, ICmcdOptions, + IPlaybackRateBasedRebufferingAvoidanceSettings, } from "../../public_types.ts"; import arrayIncludes from "../../utils/array_includes.ts"; import isNullOrUndefined from "../../utils/is_null_or_undefined.ts"; @@ -133,6 +134,7 @@ interface IParsedLoadVideoOptionsBase { /** @see ILoadVideoOptions.experimentalOptions */ experimentalOptions: { enableRepresentationAvoidance: boolean; + playbackRateBasedRebufferingAvoidanceSettings: IPlaybackRateBasedRebufferingAvoidanceSettings | null; }; __priv_manifestUpdateUrl?: string | undefined; __priv_patchLastSegmentInSidx?: boolean | undefined; @@ -577,6 +579,9 @@ function parseLoadVideoOptions(options: ILoadVideoOptions): IParsedLoadVideoOpti experimentalOptions: { enableRepresentationAvoidance: options.experimentalOptions?.enableRepresentationAvoidance === true, + playbackRateBasedRebufferingAvoidanceSettings: + options.experimentalOptions?.playbackRateBasedRebufferingAvoidanceSettings ?? + null, }, }; } diff --git a/src/main_thread/api/public_api.ts b/src/main_thread/api/public_api.ts index 23fd021a9c..2b14bd2bff 100644 --- a/src/main_thread/api/public_api.ts +++ b/src/main_thread/api/public_api.ts @@ -1018,12 +1018,12 @@ class Player extends EventEmitter { referenceDateTime, segmentLoader, serverSyncInfos, - experimentalOptions, __priv_manifestUpdateUrl, __priv_patchLastSegmentInSidx, url, onAudioTracksNotPlayable, onVideoTracksNotPlayable, + experimentalOptions, } = options; // Perform multiple checks on the given options @@ -1231,6 +1231,8 @@ class Player extends EventEmitter { startAt, textTrackOptions, url, + playbackRateBasedRebufferingAvoidanceSettings: + experimentalOptions.playbackRateBasedRebufferingAvoidanceSettings, playbackSupport: { mseInWorker: false, videoTrack: isVideoHandled, @@ -1316,6 +1318,8 @@ class Player extends EventEmitter { startAt, textTrackOptions, url, + playbackRateBasedRebufferingAvoidanceSettings: + experimentalOptions.playbackRateBasedRebufferingAvoidanceSettings, MediaSourceClass, playbackSupport: { mseInWorker: hasMseInWorker, @@ -1346,6 +1350,8 @@ class Player extends EventEmitter { speed: this._priv_speed, startAt, url, + playbackRateBasedRebufferingAvoidanceSettings: + experimentalOptions.playbackRateBasedRebufferingAvoidanceSettings, }); } diff --git a/src/main_thread/init/directfile_content_initializer.ts b/src/main_thread/init/directfile_content_initializer.ts index da2e7ecafe..67531feaff 100644 --- a/src/main_thread/init/directfile_content_initializer.ts +++ b/src/main_thread/init/directfile_content_initializer.ts @@ -25,7 +25,11 @@ import getStartDate from "../../compat/get_start_date.ts"; import type { MediaError } from "../../errors/index.ts"; import log from "../../log.ts"; import type { IMediaElementPlaybackObserver } from "../../playback_observer/index.ts"; -import type { IKeySystemOption, IPlayerError } from "../../public_types.ts"; +import type { + IKeySystemOption, + IPlaybackRateBasedRebufferingAvoidanceSettings, + IPlayerError, +} from "../../public_types.ts"; import assert from "../../utils/assert.ts"; import isNullOrUndefined from "../../utils/is_null_or_undefined.ts"; import noop from "../../utils/noop.ts"; @@ -127,6 +131,7 @@ export default class DirectFileContentInitializer extends ContentInitializer { playbackObserver, null, speed, + this._settings.playbackRateBasedRebufferingAvoidanceSettings, ); rebufferingController.addEventListener("stalled", (evt) => this.trigger("stalled", evt), @@ -413,4 +418,10 @@ export interface IDirectFileOptions { startAt?: IInitialTimeOptions | undefined; /** URL that should be played. */ url: string; + /** + * Configuration on a rebuffering avoidance mechanism where the playback rate + * (i.e. the speed at which the content plays) is lowered when there's not a lot + * of data in the buffer. + */ + playbackRateBasedRebufferingAvoidanceSettings: IPlaybackRateBasedRebufferingAvoidanceSettings | null; } diff --git a/src/main_thread/init/media_source_content_initializer.ts b/src/main_thread/init/media_source_content_initializer.ts index 44d1f42aa2..9c08aacb7f 100644 --- a/src/main_thread/init/media_source_content_initializer.ts +++ b/src/main_thread/init/media_source_content_initializer.ts @@ -44,6 +44,7 @@ import type { ICmcdOptions, IInitialManifest, IKeySystemOption, + IPlaybackRateBasedRebufferingAvoidanceSettings, IPlayerError, IRepresentationFilter, IManifestLoader, @@ -1740,6 +1741,7 @@ export default class MediaSourceContentInitializer extends ContentInitializer { playbackObserver, manifest, speed, + this._settings.playbackRateBasedRebufferingAvoidanceSettings, ); rebufferingController.addEventListener("stalled", (evt) => this.trigger("stalled", evt), @@ -2256,6 +2258,12 @@ export interface IInitializeArguments { url: string | undefined; /** `MediaSource` implementation that is wanted for that content. */ MediaSourceClass: IMediaSourceClass; + /** + * Configuration on a rebuffering avoidance mechanism where the playback rate + * (i.e. the speed at which the content plays) is lowered when there's not a lot + * of data in the buffer. + */ + playbackRateBasedRebufferingAvoidanceSettings: IPlaybackRateBasedRebufferingAvoidanceSettings | null; } function bindNumberReferencesToCore( diff --git a/src/main_thread/init/utils/rebuffering_controller.ts b/src/main_thread/init/utils/rebuffering_controller.ts index 4c08f27937..0ff6932433 100644 --- a/src/main_thread/init/utils/rebuffering_controller.ts +++ b/src/main_thread/init/utils/rebuffering_controller.ts @@ -20,13 +20,16 @@ import type { IBufferType } from "../../../core/types.ts"; import { MediaError } from "../../../errors/index.ts"; import log from "../../../log.ts"; import type { IManifestMetadata, IPeriodMetadata } from "../../../manifest/index.ts"; -import { getPeriodAfter } from "../../../manifest/index.ts"; +import { getLivePosition, getPeriodAfter } from "../../../manifest/index.ts"; import { SeekingState } from "../../../playback_observer/index.ts"; import type { IMediaElementPlaybackObserver, IPlaybackObservation, } from "../../../playback_observer/index.ts"; -import type { IPlayerError } from "../../../public_types.ts"; +import type { + IPlaybackRateBasedRebufferingAvoidanceSettings, + IPlayerError, +} from "../../../public_types.ts"; import EventEmitter from "../../../utils/event_emitter.ts"; import getMonotonicTimeStamp from "../../../utils/monotonic_timestamp.ts"; import { getNextBufferedTimeRangeGap } from "../../../utils/ranges.ts"; @@ -60,7 +63,7 @@ export default class RebufferingController extends EventEmitter, + playbackRateBasedRebufferingAvoidanceSettings: IPlaybackRateBasedRebufferingAvoidanceSettings | null, ) { super(); this._playbackObserver = playbackObserver; @@ -79,6 +83,8 @@ export default class RebufferingController extends EventEmitter + (this._playbackRateBasedRebufferingAvoidanceSettings?.onBufferGapSize ?? 0) + ) { + log.info("Init", "stopping lower playback rate due to low buffer gap", { + bufferGap: observation.bufferGap, + onBufferGapSize: + this._playbackRateBasedRebufferingAvoidanceSettings?.onBufferGapSize, + minPlaybackRate: + this._playbackRateBasedRebufferingAvoidanceSettings?.minPlaybackRate, + }); + playbackRateUpdater.stopLowerRegularPlaybackRate(); + } + if (freezing !== null) { const now = getMonotonicTimeStamp(); if (now - freezing.timestamp > FREEZING_STALLED_DELAY) { @@ -134,6 +156,34 @@ export default class RebufferingController extends EventEmitter { - log.info("Init", "Resume playback speed", { newSpeed: lastSpeed }); - this._playbackObserver.setPlaybackRate(lastSpeed); + const wantedSpeed = + lastSpeed === 1 ? lastSpeed * this._playbackRateRatio : lastSpeed; + log.info("Init", "Resume playback speed", { + lastSpeed, + actualSpeed: wantedSpeed, + }); + this._playbackObserver.setPlaybackRate(wantedSpeed); }, { clearSignal: this._speedUpdateCanceller.signal, diff --git a/src/public_types.ts b/src/public_types.ts index b717b793e9..a6129e210e 100644 --- a/src/public_types.ts +++ b/src/public_types.ts @@ -242,6 +242,12 @@ export interface ILoadVideoOptions { * have issues being decoded on the current device. */ enableRepresentationAvoidance: boolean | undefined; + /** + * Update playback rate (speed) when the buffer is close to empty, to avoid + * rebuffering. + */ + playbackRateBasedRebufferingAvoidanceSettings?: + IPlaybackRateBasedRebufferingAvoidanceSettings | null | undefined; } | undefined; } @@ -1456,3 +1462,26 @@ export interface IThumbnailRenderingOptions { */ thumbnailTrackId?: string | undefined; } + +/** + * Configuration on a rebuffering avoidance mechanism where the playback rate + * (i.e. the speed at which the content plays) is lowered when there's not a lot + * of data in the buffer. + */ +export interface IPlaybackRateBasedRebufferingAvoidanceSettings { + /** + * Once the player is below this amount of seconds in the buffer before + * rebuffering, it will start updating the "regular" `playbackRate` + * ("regular" here meaning: the `playbackRate` is currently set to the + * default value `1`) to the `minPlaybackRate` value below. + */ + onBufferGapSize: number; + + /** + * The playbackRate that should be set if there's less than + * `onBufferGapSize` seconds in the buffer and if the player is currently + * doing a regular `playbackRate` ("regular" here meaning: the + * `playbackRate` is currently set to the default value `1`) + */ + minPlaybackRate: number; +} diff --git a/tests/unit/src/main_thread/api/option_utils.test.ts b/tests/unit/src/main_thread/api/option_utils.test.ts index 299f9fbecb..481bfd240f 100644 --- a/tests/unit/src/main_thread/api/option_utils.test.ts +++ b/tests/unit/src/main_thread/api/option_utils.test.ts @@ -281,6 +281,7 @@ describe("API - parseLoadVideoOptions", () => { url: undefined, experimentalOptions: { enableRepresentationAvoidance: false, + playbackRateBasedRebufferingAvoidanceSettings: null, }, }; From 83dd01832e83652d1ecd42b1cebed1c8c8d1329c Mon Sep 17 00:00:00 2001 From: Paul Berberian Date: Fri, 8 Nov 2024 15:08:00 +0100 Subject: [PATCH 2/4] Add security in rebuffering controller for when rebuffering avoidance buffer gap is set to 0 --- src/main_thread/init/utils/rebuffering_controller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main_thread/init/utils/rebuffering_controller.ts b/src/main_thread/init/utils/rebuffering_controller.ts index 0ff6932433..5ad66a1bd8 100644 --- a/src/main_thread/init/utils/rebuffering_controller.ts +++ b/src/main_thread/init/utils/rebuffering_controller.ts @@ -160,6 +160,7 @@ export default class RebufferingController extends EventEmitter 0 && observation.bufferGap < this._playbackRateBasedRebufferingAvoidanceSettings.onBufferGapSize ) { From 7e3eb02ba9b074abd143de08e5fbd5385b5c899d Mon Sep 17 00:00:00 2001 From: Paul Berberian Date: Fri, 8 Nov 2024 15:10:42 +0100 Subject: [PATCH 3/4] Add rebuffering avoidance to the demo page --- .../components/Options/BufferOptions.tsx | 104 ++++++++++++++++++ demo/scripts/controllers/Settings.tsx | 67 +++++++++++ demo/scripts/lib/defaultOptionsValues.ts | 8 ++ demo/scripts/modules/player/index.ts | 6 + 4 files changed, 185 insertions(+) diff --git a/demo/scripts/components/Options/BufferOptions.tsx b/demo/scripts/components/Options/BufferOptions.tsx index fc8782c302..cd6fc37e9c 100644 --- a/demo/scripts/components/Options/BufferOptions.tsx +++ b/demo/scripts/components/Options/BufferOptions.tsx @@ -9,6 +9,12 @@ const DEFAULT_MAX_BUFFER_AHEAD = DEFAULT_VALUES.player.maxBufferAhead; const DEFAULT_MAX_BUFFER_BEHIND = DEFAULT_VALUES.player.maxBufferBehind; const DEFAULT_MAX_VIDEO_BUFFER_SIZE = DEFAULT_VALUES.player.maxVideoBufferSize; const DEFAULT_WANTED_BUFFER_AHEAD = DEFAULT_VALUES.player.wantedBufferAhead; +const DEFAULT_REBUFFERING_AVOIDANCE_BUFFER_GAP_SIZE = + DEFAULT_VALUES.loadVideo.experimentalOptions + .playbackRateBasedRebufferingAvoidanceSettings.onBufferGapSize; +const DEFAULT_REBUFFERING_AVOIDANCE_MIN_PLAYBACK_RATE = + DEFAULT_VALUES.loadVideo.experimentalOptions + .playbackRateBasedRebufferingAvoidanceSettings.minPlaybackRate; /** * @param {Object} props @@ -19,19 +25,27 @@ function BufferOptions({ maxVideoBufferSize, maxBufferAhead, maxBufferBehind, + rebufferingAvoidanceBufferGapSize, + rebufferingAvoidanceMinPlaybackRate, onWantedBufferAheadChange, onMaxVideoBufferSizeChange, onMaxBufferAheadChange, onMaxBufferBehindChange, + onRebufferingAvoidanceBufferGapSizeChange, + onRebufferingAvoidanceMinPlaybackRateChange, }: { wantedBufferAhead: number; maxVideoBufferSize: number; maxBufferAhead: number; maxBufferBehind: number; + rebufferingAvoidanceBufferGapSize: number; + rebufferingAvoidanceMinPlaybackRate: number; onWantedBufferAheadChange: (newVal: number) => void; onMaxVideoBufferSizeChange: (newVal: number) => void; onMaxBufferBehindChange: (newVal: number) => void; onMaxBufferAheadChange: (newVal: number) => void; + onRebufferingAvoidanceBufferGapSizeChange: (newVal: number) => void; + onRebufferingAvoidanceMinPlaybackRateChange: (newVal: number) => void; }): React.JSX.Element { /* Value of the `wantedBufferAhead` input */ const [wantedBufferAheadStr, setWantedBufferAheadStr] = useState( @@ -45,6 +59,14 @@ function BufferOptions({ const [maxBufferBehindStr, setMaxBufferBehindStr] = useState(String(maxBufferBehind)); /* Value of the `maxBufferAhead` input */ const [maxBufferAheadStr, setMaxBufferAheadStr] = useState(String(maxBufferAhead)); + /* Value of the `rebufferingAvoidanceBufferGapSize` input */ + const [rebufferingAvoidanceBufferGapSizeStr, setRebufferingAvoidanceBufferGapSizeStr] = + useState(String(rebufferingAvoidanceBufferGapSize)); + /* Value of the `rebufferingAvoidanceMinPlaybackRate` input */ + const [ + rebufferingAvoidanceMinPlaybackRateStr, + setRebufferingAvoidanceMinPlaybackRateStr, + ] = useState(String(rebufferingAvoidanceMinPlaybackRate)); /* * Keep track of the "limit maxBufferAhead" toggle: * `false` == checkbox enabled @@ -66,6 +88,7 @@ function BufferOptions({ const [isMaxVideoBufferSizeLimited, setMaxVideoBufferSizeLimit] = useState( maxVideoBufferSize !== Infinity, ); + const isRebufferingAvoidanceEnabled = rebufferingAvoidanceBufferGapSize !== 0; // Update `wantedBufferAhead` when its linked text change useEffect(() => { @@ -98,6 +121,26 @@ function BufferOptions({ onMaxBufferBehindChange(newVal); }, [maxBufferBehindStr]); + // Update `rebufferingAvoidanceBufferGapSize` when its linked text change + useEffect(() => { + // Note that this unnecessarily also run on first render - there seem to be + // no quick and easy way to disable this in react. + // This is not too problematic so I put up with it. + let newVal = parseFloat(rebufferingAvoidanceBufferGapSizeStr); + newVal = isNaN(newVal) ? DEFAULT_REBUFFERING_AVOIDANCE_BUFFER_GAP_SIZE : newVal; + onRebufferingAvoidanceBufferGapSizeChange(newVal); + }, [rebufferingAvoidanceBufferGapSizeStr]); + + // Update `rebufferingAvoidanceMinPlaybackRate` when its linked text change + useEffect(() => { + // Note that this unnecessarily also run on first render - there seem to be + // no quick and easy way to disable this in react. + // This is not too problematic so I put up with it. + let newVal = parseFloat(rebufferingAvoidanceMinPlaybackRateStr); + newVal = isNaN(newVal) ? DEFAULT_REBUFFERING_AVOIDANCE_MIN_PLAYBACK_RATE : newVal; + onRebufferingAvoidanceMinPlaybackRateChange(newVal); + }, [rebufferingAvoidanceMinPlaybackRateStr]); + const onChangeLimitMaxBufferAhead = useCallback((isNotLimited: boolean) => { if (isNotLimited) { setMaxBufferAheadLimit(false); @@ -128,6 +171,14 @@ function BufferOptions({ } }, []); + const onRebufferingAvoidanceEnableClick = useCallback((isEnabled: boolean) => { + if (isEnabled) { + setRebufferingAvoidanceBufferGapSizeStr("0"); + } else { + setRebufferingAvoidanceBufferGapSizeStr("1.5"); + } + }, []); + const onWantedBufferAheadResetClick = React.useCallback(() => { setWantedBufferAheadStr(String(DEFAULT_WANTED_BUFFER_AHEAD)); }, []); @@ -147,6 +198,18 @@ function BufferOptions({ setMaxBufferBehindLimit(DEFAULT_MAX_BUFFER_BEHIND !== Infinity); }, []); + const onRebufferingAvoidanceBufferGapSizeResetClick = React.useCallback(() => { + setRebufferingAvoidanceBufferGapSizeStr( + String(DEFAULT_REBUFFERING_AVOIDANCE_BUFFER_GAP_SIZE), + ); + }, []); + + const onRebufferingAvoidanceMinPlaybackRateResetClick = React.useCallback(() => { + setRebufferingAvoidanceMinPlaybackRateStr( + String(DEFAULT_REBUFFERING_AVOIDANCE_MIN_PLAYBACK_RATE), + ); + }, []); + return (
  • @@ -242,6 +305,47 @@ function BufferOptions({ : `Manually cleaning data ${maxBufferBehind} second(s) behind the current position`}
  • +
  • + + + Disable + + + {rebufferingAvoidanceBufferGapSize <= 0 || !isRebufferingAvoidanceEnabled + ? "No triggering Playback-Rate-based rebuffering Avoidance" + : `Starting rebuffering avoidance strategy once ${rebufferingAvoidanceBufferGapSize} second(s) is left in the buffer`} + + + + {rebufferingAvoidanceBufferGapSize <= 0 || !isRebufferingAvoidanceEnabled + ? "No triggering Playback-Rate-based rebuffering Avoidance" + : `Rebuffering avoidance strategies will play at ${rebufferingAvoidanceMinPlaybackRate}x the speed once ${rebufferingAvoidanceBufferGapSize} second(s) is left in the buffer`} + +
  • ); } diff --git a/demo/scripts/controllers/Settings.tsx b/demo/scripts/controllers/Settings.tsx index 5bcb6a08ab..957f84275b 100644 --- a/demo/scripts/controllers/Settings.tsx +++ b/demo/scripts/controllers/Settings.tsx @@ -75,6 +75,7 @@ function Settings({ onCodecSwitch, onAudioTracksNotPlayable, onVideoTracksNotPlayable, + experimentalOptions, } = loadVideoOptions; const cmcdCommunicationMethod = cmcd?.communicationType ?? "disabled"; const { manifest: manifestRequestConfig, segment: segmentRequestConfig } = @@ -350,6 +351,58 @@ function Settings({ [playerOptions], ); + const onRebufferingAvoidanceBufferGapSizeChange = useCallback( + (rebufferingAvoidanceBufferGapSize: number) => { + updateLoadVideoOptions((prevOptions) => { + if ( + rebufferingAvoidanceBufferGapSize === + prevOptions.experimentalOptions.playbackRateBasedRebufferingAvoidanceSettings + .onBufferGapSize + ) { + return prevOptions; + } + return Object.assign({}, prevOptions, { + ...prevOptions.experimentalOptions, + experimentalOptions: { + playbackRateBasedRebufferingAvoidanceSettings: { + onBufferGapSize: rebufferingAvoidanceBufferGapSize, + minPlaybackRate: + prevOptions.experimentalOptions + .playbackRateBasedRebufferingAvoidanceSettings.minPlaybackRate, + }, + }, + }); + }); + }, + [playerOptions], + ); + + const onRebufferingAvoidanceMinPlaybackRateChange = useCallback( + (rebufferingAvoidanceMinPlaybackRate: number) => { + updateLoadVideoOptions((prevOptions) => { + if ( + rebufferingAvoidanceMinPlaybackRate === + prevOptions.experimentalOptions.playbackRateBasedRebufferingAvoidanceSettings + .minPlaybackRate + ) { + return prevOptions; + } + return Object.assign({}, prevOptions, { + experimentalOptions: { + ...prevOptions.experimentalOptions, + playbackRateBasedRebufferingAvoidanceSettings: { + onBufferGapSize: + prevOptions.experimentalOptions + .playbackRateBasedRebufferingAvoidanceSettings.onBufferGapSize, + minPlaybackRate: rebufferingAvoidanceMinPlaybackRate, + }, + }, + }); + }); + }, + [playerOptions], + ); + const onMaxVideoBufferSizeChange = useCallback( (maxVideoBufferSize: number) => { updatePlayerOptions((prevOptions) => { @@ -475,10 +528,24 @@ function Settings({ maxVideoBufferSize={maxVideoBufferSize} maxBufferAhead={maxBufferAhead} maxBufferBehind={maxBufferBehind} + rebufferingAvoidanceBufferGapSize={ + experimentalOptions.playbackRateBasedRebufferingAvoidanceSettings + .onBufferGapSize + } + rebufferingAvoidanceMinPlaybackRate={ + experimentalOptions.playbackRateBasedRebufferingAvoidanceSettings + .minPlaybackRate + } onWantedBufferAheadChange={onWantedBufferAheadChange} onMaxBufferAheadChange={onMaxBufferAheadChange} onMaxBufferBehindChange={onMaxBufferBehindChange} onMaxVideoBufferSizeChange={onMaxVideoBufferSizeChange} + onRebufferingAvoidanceBufferGapSizeChange={ + onRebufferingAvoidanceBufferGapSizeChange + } + onRebufferingAvoidanceMinPlaybackRateChange={ + onRebufferingAvoidanceMinPlaybackRateChange + } /> diff --git a/demo/scripts/lib/defaultOptionsValues.ts b/demo/scripts/lib/defaultOptionsValues.ts index b1854375b9..102b9389ab 100644 --- a/demo/scripts/lib/defaultOptionsValues.ts +++ b/demo/scripts/lib/defaultOptionsValues.ts @@ -2,6 +2,7 @@ import type { ICmcdOptions, IConstructorOptions, ILoadVideoOptions, + IPlaybackRateBasedRebufferingAvoidanceSettings, } from "../../../src/public_types.ts"; const defaultOptionsValues = { @@ -33,6 +34,13 @@ const defaultOptionsValues = { onCodecSwitch: "continue", onAudioTracksNotPlayable: "error", onVideoTracksNotPlayable: "error", + experimentalOptions: { + enableRepresentationAvoidance: true, + playbackRateBasedRebufferingAvoidanceSettings: { + onBufferGapSize: 0, + minPlaybackRate: 0.95, + } as IPlaybackRateBasedRebufferingAvoidanceSettings, + }, }, } satisfies { player: IConstructorOptions; diff --git a/demo/scripts/modules/player/index.ts b/demo/scripts/modules/player/index.ts index db5d0f0880..7c0b9111be 100644 --- a/demo/scripts/modules/player/index.ts +++ b/demo/scripts/modules/player/index.ts @@ -306,6 +306,12 @@ const PlayerModule = declareModule( textTrackElement, onAudioTracksNotPlayable: state.get("onAudioTracksNotPlayable"), onVideoTracksNotPlayable: state.get("onVideoTracksNotPlayable"), + experimentalOptions: { + playbackRateBasedRebufferingAvoidanceSettings: { + onBufferGapSize: 1.5, + minPlaybackRate: 0.95, + }, + }, }, arg, ) as ILoadVideoOptions, From 8b987e1930164d7ea78a00ef300ce1c2373739e1 Mon Sep 17 00:00:00 2001 From: Paul Berberian Date: Fri, 8 Nov 2024 19:53:58 +0100 Subject: [PATCH 4/4] demo: Add separator between rebuffering avoidance options --- demo/scripts/components/Options/BufferOptions.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/demo/scripts/components/Options/BufferOptions.tsx b/demo/scripts/components/Options/BufferOptions.tsx index cd6fc37e9c..40eb07ad1f 100644 --- a/demo/scripts/components/Options/BufferOptions.tsx +++ b/demo/scripts/components/Options/BufferOptions.tsx @@ -330,6 +330,13 @@ function BufferOptions({ ? "No triggering Playback-Rate-based rebuffering Avoidance" : `Starting rebuffering avoidance strategy once ${rebufferingAvoidanceBufferGapSize} second(s) is left in the buffer`} +