Skip to content

Commit aaf36bf

Browse files
feat: Add option to ignore ready state check for synthetic stalls (#91)
* feat: remove ready state check * feat: default emitSyntheticStallEvents to false * wip: synthetic stall changes * add option to check or ignore ready state * fix: waitForReadyState not returning func * style: remove redundant check * fix: pass ignore ready state to playback rate setter * fix: name --------- Co-authored-by: Jo Monaghan <[email protected]>
1 parent d2b8eea commit aaf36bf

File tree

2 files changed

+77
-34
lines changed

2 files changed

+77
-34
lines changed

src/core/Settings.js

+23-5
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,15 @@ import Events from './events/Events';
107107
* stallThreshold: 0.3,
108108
* useAppendWindow: true,
109109
* setStallState: true,
110-
* emitSyntheticStallEvents: true,
111110
* avoidCurrentTimeRangePruning: false,
112111
* useChangeTypeForTrackSwitch: true,
113112
* mediaSourceDurationInfinity: true,
114-
* resetSourceBuffersForTrackSwitch: false
113+
* resetSourceBuffersForTrackSwitch: false,
114+
* syntheticStallEvents: {
115+
* enabled: false,
116+
* ignoreReadyState: false
117+
* }
118+
*
115119
* },
116120
* gaps: {
117121
* jumpGaps: true,
@@ -337,7 +341,7 @@ import Events from './events/Events';
337341
* Specifies if the appendWindow attributes of the MSE SourceBuffers should be set according to content duration from manifest.
338342
* @property {boolean} [setStallState=true]
339343
* Specifies if we record stalled streams once the stall threshold is reached
340-
* @property {boolean} [emitSyntheticStallEvents=true]
344+
* @property {module:Settings~SyntheticStallSettings} [syntheticStallEvents]
341345
* Specified if we fire manual stall events once the stall threshold is reached
342346
* @property {boolean} [avoidCurrentTimeRangePruning=false]
343347
* Avoids pruning of the buffered range that contains the current playback time.
@@ -362,6 +366,17 @@ import Events from './events/Events';
362366
* Configuration for video media type of tracks.
363367
*/
364368

369+
/**
370+
* @typedef {Object} module:Settings~SyntheticStallSettings
371+
* @property {boolean} [enabled]
372+
* Fire manual stall events once the stall threshold is reached
373+
* @property {boolean} [ignoreReadyState]
374+
* Ignore the media element's ready state when entering and exiting a stall
375+
* Enable this when either of these scenarios still occur with synthetic stalls enabled:
376+
* - If the buffer is empty, but playback is not stalled.
377+
* - If playback resumes, but a playing event isn't reported.
378+
*/
379+
365380
/**
366381
* @typedef {Object} DebugSettings
367382
* @property {number} [logLevel=dashjs.Debug.LOG_LEVEL_WARNING]
@@ -920,11 +935,14 @@ function Settings() {
920935
stallThreshold: 0.3,
921936
useAppendWindow: true,
922937
setStallState: true,
923-
emitSyntheticStallEvents: true,
924938
avoidCurrentTimeRangePruning: false,
925939
useChangeTypeForTrackSwitch: true,
926940
mediaSourceDurationInfinity: true,
927-
resetSourceBuffersForTrackSwitch: false
941+
resetSourceBuffersForTrackSwitch: false,
942+
syntheticStallEvents: {
943+
enabled: false,
944+
ignoreReadyState: false
945+
}
928946
},
929947
gaps: {
930948
jumpGaps: true,

src/streaming/models/VideoModel.js

+54-29
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ function VideoModel() {
5454
element,
5555
_currentTime,
5656
setCurrentTimeReadyStateFunction,
57+
resumeReadyStateFunction,
5758
TTMLRenderingDiv,
5859
vttRenderingDiv,
5960
previousPlaybackRate,
@@ -80,21 +81,20 @@ function VideoModel() {
8081
eventBus.off(Events.PLAYBACK_PLAYING, onPlaying, this);
8182
}
8283

83-
function onPlaybackCanPlay() {
84-
if (element) {
85-
element.playbackRate = previousPlaybackRate || 1;
86-
element.removeEventListener('canplay', onPlaybackCanPlay);
84+
function setPlaybackRate(value, ignoreReadyState = false) {
85+
if (!element) {
86+
return;
8787
}
88-
}
8988

90-
function setPlaybackRate(value, ignoreReadyState = false) {
91-
if (!element) return;
92-
if (!ignoreReadyState && element.readyState <= 2 && value > 0) {
93-
// If media element hasn't loaded enough data to play yet, wait until it has
94-
element.addEventListener('canplay', onPlaybackCanPlay);
95-
} else {
89+
if (ignoreReadyState) {
9690
element.playbackRate = value;
91+
return;
9792
}
93+
94+
// If media element hasn't loaded enough data to play yet, wait until it has
95+
waitForReadyState(Constants.VIDEO_ELEMENT_READY_STATES.HAVE_FUTURE_DATA, () => {
96+
element.playbackRate = value;
97+
});
9898
}
9999

100100
//TODO Move the DVR window calculations from MediaPlayer to Here.
@@ -238,19 +238,27 @@ function VideoModel() {
238238
}
239239

240240
function addStalledStream(type) {
241-
242241
if (type === null || !element || element.seeking || stalledStreams.indexOf(type) !== -1) {
243242
return;
244243
}
245244

246245
stalledStreams.push(type);
247-
if (settings.get().streaming.buffer.emitSyntheticStallEvents && element && stalledStreams.length === 1 && element.readyState >= Constants.VIDEO_ELEMENT_READY_STATES.HAVE_FUTURE_DATA) {
246+
247+
if (
248+
settings.get().streaming.buffer.syntheticStallEvents.enabled &&
249+
element &&
250+
stalledStreams.length === 1 &&
251+
(settings.get().streaming.buffer.syntheticStallEvents.ignoreReadyState || getReadyState() >= Constants.VIDEO_ELEMENT_READY_STATES.HAVE_FUTURE_DATA)
252+
) {
248253
logger.debug(`emitting synthetic waiting event and halting playback with playback rate 0`);
254+
255+
previousPlaybackRate = element.playbackRate;
256+
257+
setPlaybackRate(0, true);
258+
249259
// Halt playback until nothing is stalled.
250260
const event = document.createEvent('Event');
251261
event.initEvent('waiting', true, false);
252-
previousPlaybackRate = element.playbackRate;
253-
setPlaybackRate(0);
254262
element.dispatchEvent(event);
255263
}
256264
}
@@ -265,14 +273,27 @@ function VideoModel() {
265273
stalledStreams.splice(index, 1);
266274
}
267275

268-
// If nothing is stalled resume playback.
269-
if (settings.get().streaming.buffer.emitSyntheticStallEvents && element && isStalled() === false && element.playbackRate === 0 && element.readyState >= Constants.VIDEO_ELEMENT_READY_STATES.HAVE_FUTURE_DATA) {
270-
logger.debug(`emitting synthetic playing event (if not paused) and resuming playback with playback rate: ${previousPlaybackRate || 1}`);
271-
setPlaybackRate(previousPlaybackRate || 1);
272-
if (!element.paused) {
273-
const event = document.createEvent('Event');
274-
event.initEvent('playing', true, false);
275-
element.dispatchEvent(event);
276+
277+
if (settings.get().streaming.buffer.syntheticStallEvents.enabled && element && !isStalled() && element.playbackRate === 0) {
278+
const resume = () => {
279+
logger.debug(`emitting synthetic playing event (if not paused) and resuming playback with playback rate: ${previousPlaybackRate || 1}`);
280+
281+
setPlaybackRate(previousPlaybackRate || 1, settings.get().streaming.buffer.syntheticStallEvents.ignoreReadyState);
282+
283+
if (!element.paused) {
284+
const event = document.createEvent('Event');
285+
event.initEvent('playing', true, false);
286+
element.dispatchEvent(event);
287+
}
288+
}
289+
290+
if (settings.get().streaming.buffer.syntheticStallEvents.ignoreReadyState) {
291+
resume()
292+
} else {
293+
if (resumeReadyStateFunction && resumeReadyStateFunction.func && resumeReadyStateFunction.event) {
294+
removeEventListener(resumeReadyStateFunction.event, resumeReadyStateFunction.func);
295+
}
296+
resumeReadyStateFunction = waitForReadyState(Constants.VIDEO_ELEMENT_READY_STATES.HAVE_FUTURE_DATA, resume)
276297
}
277298
}
278299
}
@@ -465,15 +486,18 @@ function VideoModel() {
465486
}
466487

467488
function waitForReadyState(targetReadyState, callback) {
468-
if (targetReadyState === Constants.VIDEO_ELEMENT_READY_STATES.HAVE_NOTHING ||
469-
getReadyState() >= targetReadyState) {
489+
if (
490+
targetReadyState === Constants.VIDEO_ELEMENT_READY_STATES.HAVE_NOTHING ||
491+
getReadyState() >= targetReadyState
492+
) {
470493
callback();
471494
return null;
472-
} else {
473-
// wait for the appropriate callback before checking again
474-
const event = READY_STATES_TO_EVENT_NAMES[targetReadyState];
475-
_listenOnce(event, callback);
476495
}
496+
497+
// wait for the appropriate callback before checking again
498+
const event = READY_STATES_TO_EVENT_NAMES[targetReadyState];
499+
500+
return _listenOnce(event, callback);
477501
}
478502

479503
function _listenOnce(event, callback) {
@@ -483,6 +507,7 @@ function VideoModel() {
483507
// Call the original listener.
484508
callback(event);
485509
};
510+
486511
addEventListener(event, func);
487512

488513
return { func, event }

0 commit comments

Comments
 (0)