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

feat: add listenable events for playback stall detection and gap jumping #4249

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTRIBUTORS
Original file line number Diff line number Diff line change
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
8 changes: 8 additions & 0 deletions externs/shaka/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ shaka.extern.StateChange;
*
* maxSegmentDuration: number,
*
* gapsJumped: number,
* stallsDetected: number,
*
* switchHistory: !Array.<shaka.extern.TrackChoice>,
* stateHistory: !Array.<shaka.extern.StateChange>
* }}
Expand Down Expand Up @@ -112,6 +115,11 @@ shaka.extern.StateChange;
* @property {number} estimatedBandwidth
* The current estimated network bandwidth (in bit/sec).
*
* @property {number} gapsJumped
* The total number of playback gaps jumped by the GapJumpingController.
* @property {number} stallsDetected
* The total number of playback stalls detected by the StallDetector.
*
* @property {number} completionPercent
* This is the greatest completion percent that the user has experienced in
* playback. Also known as the "high water mark". Is NaN when there is no
Expand Down
6 changes: 3 additions & 3 deletions lib/cast/cast_proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,8 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
(event) => this.videoProxyLocalEvent_(event));
}

for (const key in shaka.Player.EventName) {
const name = shaka.Player.EventName[key];
for (const key in shaka.util.FakeEvent.EventName) {
const name = shaka.util.FakeEvent.EventName[key];
this.eventManager_.listen(this.localPlayer_, name,
(event) => this.playerProxyLocalEvent_(event));
}
Expand Down Expand Up @@ -537,7 +537,7 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
// Pass any errors through to the app.
goog.asserts.assert(error instanceof shaka.util.Error,
'Wrong error type!');
const eventType = shaka.Player.EventName.Error;
const eventType = shaka.util.FakeEvent.EventName.Error;
const data = (new Map()).set('detail', error);
const event = new shaka.util.FakeEvent(eventType, data);
this.localPlayer_.dispatchEvent(event);
Expand Down
6 changes: 3 additions & 3 deletions lib/cast/cast_receiver.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,8 @@ shaka.cast.CastReceiver = class extends shaka.util.FakeEventTarget {
this.video_, name, (event) => this.proxyEvent_('video', event));
}

for (const key in shaka.Player.EventName) {
const name = shaka.Player.EventName[key];
for (const key in shaka.util.FakeEvent.EventName) {
const name = shaka.util.FakeEvent.EventName[key];
this.eventManager_.listen(
this.player_, name, (event) => this.proxyEvent_('player', event));
}
Expand Down Expand Up @@ -409,7 +409,7 @@ shaka.cast.CastReceiver = class extends shaka.util.FakeEventTarget {
// Pass any errors through to the app.
goog.asserts.assert(error instanceof shaka.util.Error,
'Wrong error type!');
const eventType = shaka.Player.EventName.Error;
const eventType = shaka.util.FakeEvent.EventName.Error;
const data = (new Map()).set('detail', error);
const event = new shaka.util.FakeEvent(eventType, data);
// Only dispatch the event if the player still exists.
Expand Down
24 changes: 23 additions & 1 deletion lib/media/gap_jumping_controller.js
Original file line number Diff line number Diff line change
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 All @@ -52,6 +58,9 @@ shaka.media.GapJumpingController = class {
/** @private {number} */
this.prevReadyState_ = video.readyState;

/** @private {number} */
this.gapsJumped_ = 0;

/**
* The stall detector tries to keep the playhead moving forward. It is
* managed by the gap-jumping controller to avoid conflicts. On some
Expand Down Expand Up @@ -98,6 +107,7 @@ shaka.media.GapJumpingController = class {
this.stallDetector_ = null;
}

this.onEvent_ = null;
this.timeline_ = null;
this.video_ = null;
}
Expand All @@ -121,6 +131,15 @@ shaka.media.GapJumpingController = class {
}


/**
* Returns the total number of playback gaps jumped.
* @return {number}
*/
getGapsJumped() {
return this.gapsJumped_;
}


/**
* Called on a recurring timer to check for gaps in the media. This is also
* called in a 'waiting' event.
Expand Down Expand Up @@ -209,6 +228,9 @@ shaka.media.GapJumpingController = class {
}

this.video_.currentTime = jumpTo;
this.gapsJumped_++;
this.onEvent_(
new shaka.util.FakeEvent(shaka.util.FakeEvent.EventName.GapJumped));
}
};

Expand Down
51 changes: 47 additions & 4 deletions lib/media/playhead.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ shaka.media.Playhead = class {
*/
setStartTime(startTime) {}

/**
* Get the number of playback stalls detected by the StallDetector.
*
* @return {number}
*/
getStallsDetected() {}

/**
* Get the number of playback gaps jumped by the GapJumpingController.
*
* @return {number}
*/
getGapsJumped() {}

/**
* Get the current playhead position. The position will be restricted to valid
* time ranges.
Expand Down Expand Up @@ -133,6 +147,16 @@ shaka.media.SrcEqualsPlayhead = class {
return time || 0;
}

/** @override */
getStallsDetected() {
return 0;
}

/** @override */
getGapsJumped() {
return 0;
}

/** @override */
notifyOfBufferingChange() {}
};
Expand Down Expand Up @@ -160,8 +184,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 +218,17 @@ shaka.media.MediaSourcePlayhead = class {
/** @private {?number} */
this.lastCorrectiveSeek_ = null;

/** @private {shaka.media.StallDetector} */
this.stallDetector_ =
this.createStallDetector_(mediaElement, config, onEvent);

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

/** @private {shaka.media.VideoWrapper} */
this.videoWrapper_ = new shaka.media.VideoWrapper(
Expand Down Expand Up @@ -261,6 +292,16 @@ shaka.media.MediaSourcePlayhead = class {
return time;
}

/** @override */
getStallsDetected() {
return this.stallDetector_.getStallsDetected();
}

/** @override */
getGapsJumped() {
return this.gapController_.getGapsJumped();
}

/**
* Gets the playhead's initial position in seconds.
*
Expand Down Expand Up @@ -475,10 +516,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 +535,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
21 changes: 19 additions & 2 deletions lib/media/stall_detector.js
Original file line number Diff line number Diff line change
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 @@ -36,6 +40,8 @@ shaka.media.StallDetector = class {
this.lastUpdateSeconds_ = implementation.getWallSeconds();
/** @private {boolean} */
this.didJump_ = false;
/** @private {number} */
this.stallsDetected_ = 0;

/**
* The amount of time in seconds that we must have the same value of
Expand All @@ -53,6 +59,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 All @@ -66,6 +73,13 @@ shaka.media.StallDetector = class {
this.onStall_ = doThis;
}

/**
* Returns the number of playback stalls detected.
*/
getStallsDetected() {
return this.stallsDetected_;
}

/**
* Have the detector update itself and fire the "on stall" callback if a stall
* was detected.
Expand Down Expand Up @@ -100,6 +114,9 @@ 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.stallsDetected_++;
this.onEvent_(new shaka.util.FakeEvent(
shaka.util.FakeEvent.EventName.StallDetected));
}

return triggerCallback;
Expand Down
2 changes: 1 addition & 1 deletion lib/media/streaming_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -1701,7 +1701,7 @@ shaka.media.StreamingEngine = class {
};

// Dispatch an event to notify the application about the emsg box.
const eventName = shaka.Player.EventName.Emsg;
const eventName = shaka.util.FakeEvent.EventName.Emsg;
const data = (new Map()).set('detail', emsg);
const event = new shaka.util.FakeEvent(eventName, data);
this.playerInterface_.onEvent(event);
Expand Down