Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Proposal] Add PlaybackRate-based rebuffering avoidance mechanism #1588

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions demo/scripts/components/Options/BufferOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand All @@ -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
Expand All @@ -66,6 +88,7 @@ function BufferOptions({
const [isMaxVideoBufferSizeLimited, setMaxVideoBufferSizeLimit] = useState(
maxVideoBufferSize !== Infinity,
);
const isRebufferingAvoidanceEnabled = rebufferingAvoidanceBufferGapSize !== 0;

// Update `wantedBufferAhead` when its linked text change
useEffect(() => {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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));
}, []);
Expand All @@ -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 (
<Fragment>
<li>
Expand Down Expand Up @@ -242,6 +305,54 @@ function BufferOptions({
: `Manually cleaning data ${maxBufferBehind} second(s) behind the current position`}
</span>
</li>
<li>
<PlayerOptionNumberInput
ariaLabel="Playback Rate Based Rebuffering Avoidance Buffer Size option"
label="playbackRateBasedRebufferingAvoidanceBufferSize"
title="Rebuffering Avoidance: Buffer Size"
valueAsString={rebufferingAvoidanceBufferGapSizeStr}
defaultValueAsNumber={DEFAULT_REBUFFERING_AVOIDANCE_BUFFER_GAP_SIZE}
isDisabled={!isRebufferingAvoidanceEnabled}
onUpdateValue={setRebufferingAvoidanceBufferGapSizeStr}
onResetClick={onRebufferingAvoidanceBufferGapSizeResetClick}
/>
<Checkbox
className="playerOptionsCheckBox"
ariaLabel="Do not enable Playback Rate-Based Rebuffering Avoidance"
name="rebufferingAvoidanceEnable"
checked={!isRebufferingAvoidanceEnabled}
onChange={onRebufferingAvoidanceEnableClick}
>
Disable
</Checkbox>
<span className="option-desc">
{rebufferingAvoidanceBufferGapSize <= 0 || !isRebufferingAvoidanceEnabled
? "No triggering Playback-Rate-based rebuffering Avoidance"
: `Starting rebuffering avoidance strategy once ${rebufferingAvoidanceBufferGapSize} second(s) is left in the buffer`}
</span>
<span
style={{
marginTop: "5px",
borderBottom: "1px solid #999",
marginBottom: "5px",
}}
></span>
<PlayerOptionNumberInput
ariaLabel="Playback Rate Based Rebuffering Avoidance Min Playback Rate option"
label="playbackRateBasedRebufferingAvoidanceMinPlaybackRate"
title="Rebuffering Avoidance: Playback Rate"
valueAsString={rebufferingAvoidanceMinPlaybackRateStr}
defaultValueAsNumber={DEFAULT_REBUFFERING_AVOIDANCE_MIN_PLAYBACK_RATE}
isDisabled={!isRebufferingAvoidanceEnabled}
onUpdateValue={setRebufferingAvoidanceMinPlaybackRateStr}
onResetClick={onRebufferingAvoidanceMinPlaybackRateResetClick}
/>
<span className="option-desc">
{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`}
</span>
</li>
</Fragment>
);
}
Expand Down
67 changes: 67 additions & 0 deletions demo/scripts/controllers/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ function Settings({
checkManifestIntegrity,
requestConfig,
onCodecSwitch,
experimentalOptions,
} = loadVideoOptions;
const cmcdCommunicationMethod = cmcd?.communicationType ?? "disabled";
const { manifest: manifestRequestConfig, segment: segmentRequestConfig } =
Expand Down Expand Up @@ -313,6 +314,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) => {
Expand Down Expand Up @@ -432,10 +485,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
}
/>
</Option>
</div>
Expand Down
7 changes: 7 additions & 0 deletions demo/scripts/lib/defaultOptionsValues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
ICmcdOptions,
IConstructorOptions,
ILoadVideoOptions,
IPlaybackRateBasedRebufferingAvoidanceSettings,
} from "../../../src/public_types";

const defaultOptionsValues = {
Expand Down Expand Up @@ -31,6 +32,12 @@ const defaultOptionsValues = {
},
},
onCodecSwitch: "continue",
experimentalOptions: {
playbackRateBasedRebufferingAvoidanceSettings: {
onBufferGapSize: 0,
minPlaybackRate: 0.95,
} as IPlaybackRateBasedRebufferingAvoidanceSettings,
},
},
} satisfies {
player: IConstructorOptions;
Expand Down
6 changes: 6 additions & 0 deletions demo/scripts/modules/player/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,12 @@ const PlayerModule = declareModule(
{
mode: state.get("relyOnWorker") ? "auto" : "main",
textTrackElement,
experimentalOptions: {
playbackRateBasedRebufferingAvoidanceSettings: {
onBufferGapSize: 1.5,
minPlaybackRate: 0.95,
},
},
},
arg,
) as ILoadVideoOptions,
Expand Down
2 changes: 1 addition & 1 deletion src/default_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,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.
Expand Down
3 changes: 3 additions & 0 deletions src/main_thread/api/__tests__/option_utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,9 @@ describe("API - parseLoadVideoOptions", () => {
textTrackElement: undefined,
textTrackMode: "native",
url: undefined,
experimentalOptions: {
playbackRateBasedRebufferingAvoidanceSettings: null,
},
};

it("should throw if no option is given", () => {
Expand Down
10 changes: 10 additions & 0 deletions src/main_thread/api/option_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import type {
IServerSyncInfos,
IRxPlayerMode,
ICmcdOptions,
IPlaybackRateBasedRebufferingAvoidanceSettings,
} from "../../public_types";
import arrayIncludes from "../../utils/array_includes";
import isNullOrUndefined from "../../utils/is_null_or_undefined";
Expand Down Expand Up @@ -65,6 +66,7 @@ export interface IParsedConstructorOptions {
/**
* Base type which the types for the parsed options of the RxPlayer's
* `loadVideo` method exend.
* @see ILoadVideoOptions
*/
interface IParsedLoadVideoOptionsBase {
url: string | undefined;
Expand All @@ -88,6 +90,9 @@ interface IParsedLoadVideoOptionsBase {
serverSyncInfos?: IServerSyncInfos | undefined;
mode: IRxPlayerMode;
cmcd: ICmcdOptions | undefined;
experimentalOptions: {
playbackRateBasedRebufferingAvoidanceSettings: IPlaybackRateBasedRebufferingAvoidanceSettings | null;
};
__priv_manifestUpdateUrl?: string | undefined;
__priv_patchLastSegmentInSidx?: boolean | undefined;
}
Expand Down Expand Up @@ -461,6 +466,11 @@ function parseLoadVideoOptions(options: ILoadVideoOptions): IParsedLoadVideoOpti
mode,
url,
cmcd: options.cmcd,
experimentalOptions: {
playbackRateBasedRebufferingAvoidanceSettings:
options.experimentalOptions?.playbackRateBasedRebufferingAvoidanceSettings ??
null,
},
};
}

Expand Down
7 changes: 7 additions & 0 deletions src/main_thread/api/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
__priv_manifestUpdateUrl,
__priv_patchLastSegmentInSidx,
url,
experimentalOptions,
} = options;

// Perform multiple checks on the given options
Expand Down Expand Up @@ -944,6 +945,8 @@ class Player extends EventEmitter<IPublicAPIEvent> {
startAt,
textTrackOptions,
url,
playbackRateBasedRebufferingAvoidanceSettings:
experimentalOptions.playbackRateBasedRebufferingAvoidanceSettings,
});
} else {
if (features.multithread === null) {
Expand Down Expand Up @@ -987,6 +990,8 @@ class Player extends EventEmitter<IPublicAPIEvent> {
textTrackOptions,
worker: this._priv_worker,
url,
playbackRateBasedRebufferingAvoidanceSettings:
experimentalOptions.playbackRateBasedRebufferingAvoidanceSettings,
});
}
} else {
Expand All @@ -1011,6 +1016,8 @@ class Player extends EventEmitter<IPublicAPIEvent> {
speed: this._priv_speed,
startAt,
url,
playbackRateBasedRebufferingAvoidanceSettings:
experimentalOptions.playbackRateBasedRebufferingAvoidanceSettings,
});
}

Expand Down
Loading
Loading