Skip to content

Commit

Permalink
feat: add listenable events for playback stall detection and gap jumping
Browse files Browse the repository at this point in the history
An event stalldetected can be dispatched when Shaka Player detects a
stall based on the value of stallThreshold through
StreamingConfiguration.

A second event gapjumped could also be dispatched when Shaka performs a
jump in a media gap.

Related to issue shaka-project#4227.
  • Loading branch information
JulianDomingo committed May 23, 2022
1 parent 60af9ad commit 70ef1c5
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 36 deletions.
1 change: 1 addition & 0 deletions CONTRIBUTORS
Expand Up @@ -66,6 +66,7 @@ John Bowers <john.bowers@verizondigitalmedia.com>
Jonas Birmé <jonas.birme@eyevinn.se>
Jono Ward <jonoward@gmail.com>
Jozef Chúťka <jozefchutka@gmail.com>
Julian Domingo <juliandomingo@google.com>
Jun Hong Chong <chongjunhong@gmail.com>
Leandro Ribeiro Moreira <leandro.ribeiro.moreira@gmail.com>
Lucas Gabriel Sánchez <unkiwii@gmail.com>
Expand Down
10 changes: 9 additions & 1 deletion lib/media/gap_jumping_controller.js
Expand Up @@ -11,6 +11,7 @@ goog.require('shaka.media.PresentationTimeline');
goog.require('shaka.media.StallDetector');
goog.require('shaka.media.TimeRangesUtils');
goog.require('shaka.util.EventManager');
goog.require('shaka.util.FakeEvent');
goog.require('shaka.util.IReleasable');
goog.require('shaka.util.Timer');

Expand All @@ -32,8 +33,13 @@ shaka.media.GapJumpingController = class {
* playable region. The gap jumping controller takes ownership over the
* stall detector.
* If no stall detection logic is desired, |null| may be provided.
* @param {function(!Event)} onEvent
* Called when an event is raised to be sent to the application.
*/
constructor(video, timeline, config, stallDetector) {
constructor(video, timeline, config, stallDetector, onEvent) {
/** @private {?function(!Event)} */
this.onEvent_ = onEvent;

/** @private {HTMLMediaElement} */
this.video_ = video;

Expand Down Expand Up @@ -98,6 +104,7 @@ shaka.media.GapJumpingController = class {
this.stallDetector_ = null;
}

this.onEvent_ = null;
this.timeline_ = null;
this.video_ = null;
}
Expand Down Expand Up @@ -209,6 +216,7 @@ shaka.media.GapJumpingController = class {
}

this.video_.currentTime = jumpTo;
this.onEvent_(new shaka.util.FakeEvent('gapjumped'));
}
};

Expand Down
15 changes: 11 additions & 4 deletions lib/media/playhead.js
Expand Up @@ -160,8 +160,10 @@ shaka.media.MediaSourcePlayhead = class {
* @param {function()} onSeek
* Called when the user agent seeks to a time within the presentation
* timeline.
* @param {function(!Event)} onEvent
* Called when an event is raised to be sent to the application.
*/
constructor(mediaElement, manifest, config, startTime, onSeek) {
constructor(mediaElement, manifest, config, startTime, onSeek, onEvent) {
/**
* The seek range must be at least this number of seconds long. If it is
* smaller than this, change it to be this big so we don't repeatedly seek
Expand Down Expand Up @@ -192,12 +194,15 @@ shaka.media.MediaSourcePlayhead = class {
/** @private {?number} */
this.lastCorrectiveSeek_ = null;

const stallDetector =
this.createStallDetector_(mediaElement, config, onEvent);
/** @private {shaka.media.GapJumpingController} */
this.gapController_ = new shaka.media.GapJumpingController(
mediaElement,
manifest.presentationTimeline,
config,
this.createStallDetector_(mediaElement, config));
stallDetector,
onEvent);

/** @private {shaka.media.VideoWrapper} */
this.videoWrapper_ = new shaka.media.VideoWrapper(
Expand Down Expand Up @@ -475,10 +480,12 @@ shaka.media.MediaSourcePlayhead = class {
*
* @param {!HTMLMediaElement} mediaElement
* @param {shaka.extern.StreamingConfiguration} config
* @param {function(!Event)} onEvent
* Called when an event is raised to be sent to the application.
* @return {shaka.media.StallDetector}
* @private
*/
createStallDetector_(mediaElement, config) {
createStallDetector_(mediaElement, config, onEvent) {
if (!config.stallEnabled) {
return null;
}
Expand All @@ -492,7 +499,7 @@ shaka.media.MediaSourcePlayhead = class {
// playhead forward.
const detector = new shaka.media.StallDetector(
new shaka.media.StallDetector.MediaElementImplementation(mediaElement),
threshold);
threshold, onEvent);

detector.onStall((at, duration) => {
shaka.log.debug(`Stall detected at ${at} for ${duration} seconds.`);
Expand Down
10 changes: 8 additions & 2 deletions lib/media/stall_detector.js
Expand Up @@ -9,6 +9,7 @@ goog.provide('shaka.media.StallDetector.Implementation');
goog.provide('shaka.media.StallDetector.MediaElementImplementation');

goog.require('shaka.media.TimeRangesUtils');
goog.require('shaka.util.FakeEvent');
goog.require('shaka.util.IReleasable');

/**
Expand All @@ -23,11 +24,14 @@ shaka.media.StallDetector = class {
/**
* @param {shaka.media.StallDetector.Implementation} implementation
* @param {number} stallThresholdSeconds
* @param {function(!Event)} onEvent
* Called when an event is raised to be sent to the application.
*/
constructor(implementation, stallThresholdSeconds) {
constructor(implementation, stallThresholdSeconds, onEvent) {
/** @private {?function(!Event)} */
this.onEvent_ = onEvent;
/** @private {shaka.media.StallDetector.Implementation} */
this.implementation_ = implementation;

/** @private {boolean} */
this.wasMakingProgress_ = implementation.shouldBeMakingProgress();
/** @private {number} */
Expand All @@ -53,6 +57,7 @@ shaka.media.StallDetector = class {
release() {
// Drop external references to make things easier on the GC.
this.implementation_ = null;
this.onEvent_ = null;
this.onStall_ = () => {};
}

Expand Down Expand Up @@ -100,6 +105,7 @@ shaka.media.StallDetector = class {
// If the onStall_ method updated the current time, update our stored
// value so we don't think that was an update.
this.value_ = impl.getPresentationSeconds();
this.onEvent_(new shaka.util.FakeEvent('stalldetected'));
}

return triggerCallback;
Expand Down
34 changes: 33 additions & 1 deletion lib/player.js
Expand Up @@ -385,6 +385,26 @@ goog.requireType('shaka.routing.Payload');
*/


/**
* @event shaka.Player.StallDetectedEvent
* @description Fired when a stall in playback is detected by the StallDetector.
* Not all stalls can be caused by gaps in the buffered ranges.
* @property {string} type
* 'stalldetected'
* @exportDoc
*/


/**
* @event shaka.Player.GapJumpedEvent
* @description Fired when the GapJumpingController jumps over a gap in the
* buffered ranges.
* @property {string} type
* 'gapjumped'
* @exportDoc
*/


/**
* @summary The main player object for Shaka Player.
*
Expand Down Expand Up @@ -2701,7 +2721,17 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
this.manifest_,
this.config_.streaming,
startTime,
() => this.onSeek_());
() => this.onSeek_(),
(event) => {
if (event.type === 'stalldetected') {
this.dispatchEvent(
this.makeEvent_(shaka.Player.EventName.StallDetected));
}
if (event.type === 'gapjumped') {
this.dispatchEvent(
this.makeEvent_(shaka.Player.EventName.GapJumped));
}
});
}

/**
Expand Down Expand Up @@ -6491,6 +6521,7 @@ shaka.Player.EventName = {
Emsg: 'emsg',
Error: 'error',
ExpirationUpdated: 'expirationupdated',
GapJumped: 'gapjumped',
Loaded: 'loaded',
Loading: 'loading',
ManifestParsed: 'manifestparsed',
Expand All @@ -6500,6 +6531,7 @@ shaka.Player.EventName = {
OnStateIdle: 'onstateidle',
RateChange: 'ratechange',
SessionDataEvent: 'sessiondata',
StallDetected: 'stalldetected',
Streaming: 'streaming',
TextChanged: 'textchanged',
TextTrackVisibility: 'texttrackvisibility',
Expand Down

0 comments on commit 70ef1c5

Please sign in to comment.