diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 26c5952226c..de3998609b8 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -66,6 +66,7 @@ John Bowers Jonas Birmé Jono Ward Jozef Chúťka +Julian Domingo Jun Hong Chong Leandro Ribeiro Moreira Lucas Gabriel Sánchez diff --git a/lib/media/gap_jumping_controller.js b/lib/media/gap_jumping_controller.js index 636fd245ace..6b9c1cc0d6d 100644 --- a/lib/media/gap_jumping_controller.js +++ b/lib/media/gap_jumping_controller.js @@ -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'); @@ -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; @@ -98,6 +104,7 @@ shaka.media.GapJumpingController = class { this.stallDetector_ = null; } + this.onEvent_ = null; this.timeline_ = null; this.video_ = null; } @@ -209,6 +216,7 @@ shaka.media.GapJumpingController = class { } this.video_.currentTime = jumpTo; + this.onEvent_(new shaka.util.FakeEvent('gapjumped')); } }; diff --git a/lib/media/playhead.js b/lib/media/playhead.js index 462af1907c0..28348505402 100644 --- a/lib/media/playhead.js +++ b/lib/media/playhead.js @@ -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 @@ -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( @@ -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; } @@ -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.`); diff --git a/lib/media/stall_detector.js b/lib/media/stall_detector.js index 08a96e0d69d..810bdad9ba3 100644 --- a/lib/media/stall_detector.js +++ b/lib/media/stall_detector.js @@ -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'); /** @@ -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} */ @@ -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_ = () => {}; } @@ -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; diff --git a/lib/player.js b/lib/player.js index 92a8c52a57c..6b0f53e102a 100644 --- a/lib/player.js +++ b/lib/player.js @@ -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. * @@ -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)); + } + }); } /** @@ -6491,6 +6521,7 @@ shaka.Player.EventName = { Emsg: 'emsg', Error: 'error', ExpirationUpdated: 'expirationupdated', + GapJumped: 'gapjumped', Loaded: 'loaded', Loading: 'loading', ManifestParsed: 'manifestparsed', @@ -6500,6 +6531,7 @@ shaka.Player.EventName = { OnStateIdle: 'onstateidle', RateChange: 'ratechange', SessionDataEvent: 'sessiondata', + StallDetected: 'stalldetected', Streaming: 'streaming', TextChanged: 'textchanged', TextTrackVisibility: 'texttrackvisibility', diff --git a/test/media/playhead_unit.js b/test/media/playhead_unit.js index 98dffc59503..67113d19842 100644 --- a/test/media/playhead_unit.js +++ b/test/media/playhead_unit.js @@ -21,12 +21,15 @@ let TimeRange; * start: number, * waitingAt: number, * expectedEndTime: number, + * expectEvent: boolean, * }} * * @description * Parameters for a test where we start playing inside a buffered range and play * until the end of the buffer. Then, if we expect it, Playhead should jump - * to the expected time. + * to the expected time. We should get a 'stalldetected' event when the Playhead + * detects a stall through the StallDetector, and a 'gapjumped' event when the + * Playhead jumps over a gap in the buffered range(s). * * @property {!Array.} buffered * The buffered ranges for the test. @@ -36,6 +39,9 @@ let TimeRange; * The time to pause at and fire a 'waiting' event. * @property {number} expectedEndTime * The expected time at the end of the test. + * @property {boolean} expectEvent + * If true, expect either the 'stalldetected' or 'gapjumped' event to be + * fired. */ let PlayingTestInfo; @@ -47,6 +53,7 @@ let PlayingTestInfo; * start: number, * seekTo: number, * expectedEndTime: number, + * expectEvent: boolean, * }} * * @description @@ -65,6 +72,9 @@ let PlayingTestInfo; * The time to seek to. * @property {number} expectedEndTime * The expected time at the end of the test. + * @property {boolean} expectEvent + * If true, expect either the 'stalldetected' or 'gapjumped' event to be + * fired. */ let SeekTestInfo; @@ -87,6 +97,10 @@ describe('Playhead', () => { /** @type {!jasmine.Spy} */ let onSeek; + // Callback to us from Playhead when an event should be sent to the app. + /** @type {!jasmine.Spy} */ + let onEvent; + beforeAll(() => { jasmine.clock().install(); }); @@ -100,6 +114,7 @@ describe('Playhead', () => { timeline = new shaka.test.FakePresentationTimeline(); onSeek = jasmine.createSpy('onSeek'); + onEvent = jasmine.createSpy('onEvent'); timeline.isLive.and.returnValue(false); timeline.getSeekRangeStart.and.returnValue(5); @@ -143,7 +158,8 @@ describe('Playhead', () => { manifest, config, /* startTime= */ 5, - Util.spyFunc(onSeek)); + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); expect(video.currentTime).toBe(5); expect(playhead.getTime()).toBe(5); @@ -163,7 +179,8 @@ describe('Playhead', () => { manifest, config, /* startTime= */ 5, - Util.spyFunc(onSeek)); + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); expect(video.addEventListener).toHaveBeenCalledWith( 'loadedmetadata', jasmine.any(Function), jasmine.anything()); @@ -203,7 +220,8 @@ describe('Playhead', () => { manifest, config, /* startTime= */ 5, - Util.spyFunc(onSeek)); + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); video.on['seeking'](); expect(playhead.getTime()).toBe(5); @@ -221,7 +239,12 @@ describe('Playhead', () => { timeline.getSeekRangeEnd.and.returnValue(60); playhead = new shaka.media.MediaSourcePlayhead( - video, manifest, config, /* startTime= */ 0, Util.spyFunc(onSeek)); + video, + manifest, + config, + /* startTime= */ 0, + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); expect(playhead.getTime()).toBe(0); }); @@ -234,7 +257,12 @@ describe('Playhead', () => { timeline.getDuration.and.returnValue(60); playhead = new shaka.media.MediaSourcePlayhead( - video, manifest, config, /* startTime= */ 60, Util.spyFunc(onSeek)); + video, + manifest, + config, + /* startTime= */ 60, + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); expect(playhead.getTime()).toBe(59); // duration - durationBackoff expect(video.currentTime).toBe(59); // duration - durationBackoff @@ -248,7 +276,12 @@ describe('Playhead', () => { timeline.getSeekRangeEnd.and.returnValue(60); playhead = new shaka.media.MediaSourcePlayhead( - video, manifest, config, /* startTime= */ -15, Util.spyFunc(onSeek)); + video, + manifest, + config, + /* startTime= */ -15, + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); expect(playhead.getTime()).toBe(45); }); @@ -262,7 +295,12 @@ describe('Playhead', () => { // If the live stream's playback offset time is not available, start // playing from the seek range start time. playhead = new shaka.media.MediaSourcePlayhead( - video, manifest, config, /* startTime= */ -40, Util.spyFunc(onSeek)); + video, + manifest, + config, + /* startTime= */ -40, + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); expect(playhead.getTime()).toBe(30); }); @@ -273,7 +311,8 @@ describe('Playhead', () => { manifest, config, /* startTime= */ 5, - Util.spyFunc(onSeek)); + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); expect(video.addEventListener).toHaveBeenCalledWith( 'loadedmetadata', jasmine.any(Function), jasmine.anything()); @@ -305,7 +344,8 @@ describe('Playhead', () => { manifest, config, /* startTime= */ null, - Util.spyFunc(onSeek)); + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); expect(video.addEventListener).toHaveBeenCalledWith( 'loadedmetadata', jasmine.any(Function), jasmine.anything()); @@ -337,7 +377,8 @@ describe('Playhead', () => { manifest, config, /* startTime= */ 5, - Util.spyFunc(onSeek)); + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); // This has to periodically increment the mock date to allow the onSeeking_ // handler to seek, if appropriate. @@ -496,7 +537,8 @@ describe('Playhead', () => { manifest, config, /* startTime= */ 5, - Util.spyFunc(onSeek)); + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); setMockDate(0); video.on['seeking'](); @@ -544,7 +586,8 @@ describe('Playhead', () => { manifest, config, /* startTime= */ 5, - Util.spyFunc(onSeek)); + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); // First, seek to start time. video.currentTime = 0; @@ -604,7 +647,8 @@ describe('Playhead', () => { manifest, config, /* startTime= */ 5, - Util.spyFunc(onSeek)); + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); expect(currentTime).toBe(1000); seekCount = 0; @@ -649,7 +693,8 @@ describe('Playhead', () => { manifest, config, /* startTime= */ 5, - Util.spyFunc(onSeek)); + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); video.on['seeking'](); expect(video.currentTime).toBe(5); @@ -680,7 +725,8 @@ describe('Playhead', () => { manifest, config, /* startTime= */ 5, - Util.spyFunc(onSeek)); + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); video.on['seeking'](); expect(video.currentTime).toBe(5); @@ -714,7 +760,8 @@ describe('Playhead', () => { manifest, config, /* startTime= */ 30, - Util.spyFunc(onSeek)); + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); video.currentTime = 0; video.seeking = true; @@ -759,7 +806,8 @@ describe('Playhead', () => { manifest, config, /* startTime= */ 30, - Util.spyFunc(onSeek)); + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); /** * Prevent retries on the initial start time seek. This will ensure that @@ -798,6 +846,7 @@ describe('Playhead', () => { buffered: [{start: 0, end: 10}], start: 3, waitingAt: 10, + expectEvent: false, expectedEndTime: 10, }); @@ -805,6 +854,7 @@ describe('Playhead', () => { buffered: [{start: 0, end: 10}, {start: 20, end: 30}], start: 24, waitingAt: 30, + expectEvent: false, expectedEndTime: 30, }); @@ -812,6 +862,7 @@ describe('Playhead', () => { buffered: [{start: 0, end: 10}, {start: 11, end: 20}], start: 5, waitingAt: 10, + expectEvent: true, expectedEndTime: 11, }); @@ -820,6 +871,7 @@ describe('Playhead', () => { [{start: 0, end: 10}, {start: 11, end: 20}, {start: 21, end: 30}], start: 5, waitingAt: 10, + expectEvent: true, expectedEndTime: 11, }); @@ -828,6 +880,7 @@ describe('Playhead', () => { [{start: 0, end: 10}, {start: 11, end: 20}, {start: 21, end: 30}], start: 15, waitingAt: 20, + expectEvent: true, expectedEndTime: 21, }); }); // with small gaps @@ -837,6 +890,7 @@ describe('Playhead', () => { buffered: [{start: 0, end: 10}, {start: 30, end: 40}], start: 5, waitingAt: 10, + expectEvent: true, expectedEndTime: 30, }); @@ -845,6 +899,7 @@ describe('Playhead', () => { [{start: 0, end: 10}, {start: 30, end: 40}, {start: 50, end: 60}], start: 5, waitingAt: 10, + expectEvent: true, expectedEndTime: 30, }); @@ -853,6 +908,7 @@ describe('Playhead', () => { [{start: 0, end: 10}, {start: 20, end: 30}, {start: 50, end: 60}], start: 24, waitingAt: 30, + expectEvent: true, expectedEndTime: 50, }); }); // with large gaps @@ -867,12 +923,15 @@ describe('Playhead', () => { video.currentTime = data.start; video.readyState = HTMLMediaElement.HAVE_ENOUGH_DATA; + onEvent.and.callFake((event) => {}); + playhead = new shaka.media.MediaSourcePlayhead( video, manifest, config, /* startTime= */ data.start, - Util.spyFunc(onSeek)); + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); jasmine.clock().tick(500); for (let time = data.start; time < data.waitingAt; time++) { @@ -893,11 +952,14 @@ describe('Playhead', () => { expect(video.currentTime).toBe(time + 0.4); } + expect(onEvent).not.toHaveBeenCalled(); + video.currentTime = data.waitingAt; video.readyState = HTMLMediaElement.HAVE_CURRENT_DATA; video.on['waiting'](); jasmine.clock().tick(500); + expect(onEvent).toHaveBeenCalledTimes(data.expectEvent ? 1 : 0); expect(video.currentTime).toBe(data.expectedEndTime); }); } @@ -909,6 +971,7 @@ describe('Playhead', () => { buffered: [{start: 0, end: 10}], start: 4, seekTo: 14, + expectEvent: false, expectedEndTime: 14, }); @@ -916,6 +979,7 @@ describe('Playhead', () => { buffered: [{start: 0, end: 10}, {start: 11, end: 20}], start: 3, seekTo: 10.4, + expectEvent: true, expectedEndTime: 11, }); @@ -924,6 +988,7 @@ describe('Playhead', () => { [{start: 0, end: 10}, {start: 11, end: 20}, {start: 21, end: 30}], start: 3, seekTo: 10.4, + expectEvent: true, expectedEndTime: 11, }); @@ -932,6 +997,7 @@ describe('Playhead', () => { [{start: 0, end: 10}, {start: 11, end: 20}, {start: 21, end: 30}], start: 3, seekTo: 20.5, + expectEvent: true, expectedEndTime: 21, }); @@ -939,6 +1005,7 @@ describe('Playhead', () => { buffered: [{start: 0, end: 10}, {start: 30, end: 40}], start: 3, seekTo: 29.2, + expectEvent: true, expectedEndTime: 30, }); }); // with small gaps @@ -948,6 +1015,7 @@ describe('Playhead', () => { buffered: [{start: 0, end: 10}, {start: 30, end: 40}], start: 5, seekTo: 12, + expectEvent: true, expectedEndTime: 30, }); }); // with large gaps @@ -961,6 +1029,7 @@ describe('Playhead', () => { newBuffered: [{start: 20, end: 30}, {start: 31, end: 40}], start: 3, seekTo: 22, + expectEvent: false, expectedEndTime: 22, }); @@ -971,6 +1040,7 @@ describe('Playhead', () => { newBuffered: [{start: 0.2, end: 10}], start: 4, seekTo: 0, + expectEvent: true, expectedEndTime: 0.2, }); @@ -980,6 +1050,7 @@ describe('Playhead', () => { newBuffered: [{start: 20, end: 30}, {start: 31, end: 40}], start: 3, seekTo: 30.2, + expectEvent: true, expectedEndTime: 31, }); @@ -989,6 +1060,7 @@ describe('Playhead', () => { newBuffered: [{start: 20, end: 30}, {start: 31, end: 40}], start: 3, seekTo: 30, + expectEvent: true, expectedEndTime: 31, }); @@ -998,6 +1070,7 @@ describe('Playhead', () => { newBuffered: [{start: 20, end: 30}], start: 3, seekTo: 34, + expectEvent: false, expectedEndTime: 34, }); @@ -1007,6 +1080,7 @@ describe('Playhead', () => { newBuffered: [{start: 0, end: 10}], start: 24, seekTo: 4, + expectEvent: false, expectedEndTime: 4, }); @@ -1017,6 +1091,7 @@ describe('Playhead', () => { // should still be waiting. start: 24, seekTo: 4, + expectEvent: false, expectedEndTime: 4, }); @@ -1026,6 +1101,7 @@ describe('Playhead', () => { newBuffered: [{start: 2, end: 10}], start: 24, seekTo: 1.6, + expectEvent: true, expectedEndTime: 2, }); }); // with small gaps @@ -1036,6 +1112,7 @@ describe('Playhead', () => { newBuffered: [{start: 20, end: 30}], start: 25, seekTo: 0, + expectEvent: true, expectedEndTime: 20, }); @@ -1045,6 +1122,7 @@ describe('Playhead', () => { newBuffered: [{start: 20, end: 30}, {start: 40, end: 50}], start: 3, seekTo: 32, + expectEvent: true, expectedEndTime: 40, }); }); // with large gaps @@ -1061,9 +1139,11 @@ describe('Playhead', () => { manifest, config, /* startTime= */ 12, - Util.spyFunc(onSeek)); + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); jasmine.clock().tick(500); + expect(onEvent).not.toHaveBeenCalled(); // Append a segment before seeking. playhead.notifyOfBufferingChange(); @@ -1107,7 +1187,8 @@ describe('Playhead', () => { manifest, config, /* startTime= */ 0, - Util.spyFunc(onSeek)); + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); playhead.notifyOfBufferingChange(); jasmine.clock().tick(500); @@ -1128,7 +1209,8 @@ describe('Playhead', () => { manifest, config, /* startTime= */ 5, - Util.spyFunc(onSeek)); + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); playhead.notifyOfBufferingChange(); jasmine.clock().tick(500); @@ -1151,7 +1233,8 @@ describe('Playhead', () => { manifest, config, /* startTime= */ 0, - Util.spyFunc(onSeek)); + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); playhead.notifyOfBufferingChange(); jasmine.clock().tick(500); @@ -1174,7 +1257,8 @@ describe('Playhead', () => { manifest, config, /* startTime= */ 0, - Util.spyFunc(onSeek)); + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); playhead.notifyOfBufferingChange(); jasmine.clock().tick(500); @@ -1193,14 +1277,18 @@ describe('Playhead', () => { video.currentTime = data.start; video.readyState = HTMLMediaElement.HAVE_ENOUGH_DATA; + onEvent.and.callFake((event) => {}); + playhead = new shaka.media.MediaSourcePlayhead( video, manifest, config, /* startTime= */ data.start, - Util.spyFunc(onSeek)); + Util.spyFunc(onSeek), + Util.spyFunc(onEvent)); jasmine.clock().tick(500); + expect(onEvent).not.toHaveBeenCalled(); // Seek to the given position and update ready state. video.currentTime = data.seekTo; @@ -1232,6 +1320,7 @@ describe('Playhead', () => { jasmine.clock().tick(250); } + expect(onEvent).toHaveBeenCalledTimes(data.expectEvent ? 1 : 0); expect(video.currentTime).toBe(data.expectedEndTime); }); } diff --git a/test/media/stall_detector_unit.js b/test/media/stall_detector_unit.js index bbe1b55739a..aaab2ae6c9e 100644 --- a/test/media/stall_detector_unit.js +++ b/test/media/stall_detector_unit.js @@ -5,6 +5,8 @@ */ describe('StallDetector', () => { + const Util = shaka.test.Util; + /** * @implements {shaka.media.StallDetector.Implementation} * @final @@ -38,16 +40,21 @@ describe('StallDetector', () => { /** @type {!jasmine.Spy} */ let onStall; + /** @type {!jasmine.Spy} */ + let onEvent; + beforeEach(() => { onStall = jasmine.createSpy('onStall'); + onEvent = jasmine.createSpy('onEvent'); implementation = new TestImplementation(); detector = new shaka.media.StallDetector( implementation, - /* stallThresholdSeconds= */ 1); + /* stallThresholdSeconds= */ 1, + Util.spyFunc(onEvent)); - detector.onStall(shaka.test.Util.spyFunc(onStall)); + detector.onStall(Util.spyFunc(onStall)); }); it('does not call onStall when values changes', () => { @@ -63,6 +70,7 @@ describe('StallDetector', () => { detector.poll(); + expect(onEvent).not.toHaveBeenCalled(); expect(onStall).not.toHaveBeenCalled(); }); @@ -74,6 +82,7 @@ describe('StallDetector', () => { implementation.wallSeconds = time; detector.poll(); + expect(onEvent).not.toHaveBeenCalled(); expect(onStall).not.toHaveBeenCalled(); } @@ -81,6 +90,7 @@ describe('StallDetector', () => { implementation.wallSeconds = 1; detector.poll(); + expect(onEvent).toHaveBeenCalled(); expect(onStall).toHaveBeenCalledOnceMoreWith([ /* stalledWith= */ 5, /* stallDurationSeconds= */ 1, @@ -96,6 +106,7 @@ describe('StallDetector', () => { implementation.wallSeconds = 10; detector.poll(); + expect(onEvent).toHaveBeenCalled(); expect(onStall).toHaveBeenCalledOnceMoreWith([ /* stalledWith= */ 5, /* stallDurationms= */ 10, @@ -117,6 +128,7 @@ describe('StallDetector', () => { detector.poll(); + expect(onEvent).not.toHaveBeenCalled(); expect(onStall).not.toHaveBeenCalled(); }); @@ -135,6 +147,7 @@ describe('StallDetector', () => { implementation.wallSeconds = 10; detector.poll(); + expect(onEvent).not.toHaveBeenCalled(); expect(onStall).not.toHaveBeenCalled(); }); @@ -143,16 +156,20 @@ describe('StallDetector', () => { implementation.presentationSeconds = 0; implementation.wallSeconds = 0; detector.poll(); + expect(onEvent).not.toHaveBeenCalled(); expect(onStall).not.toHaveBeenCalled(); implementation.wallSeconds = 10; detector.poll(); + expect(onEvent).toHaveBeenCalled(); expect(onStall).toHaveBeenCalled(); + onEvent.calls.reset(); onStall.calls.reset(); // This is the same stall, should not be called again. implementation.wallSeconds = 20; detector.poll(); + expect(onEvent).not.toHaveBeenCalled(); expect(onStall).not.toHaveBeenCalled(); // Now that we changed time, we should get another call. @@ -161,6 +178,7 @@ describe('StallDetector', () => { detector.poll(); implementation.wallSeconds = 40; detector.poll(); + expect(onEvent).toHaveBeenCalled(); expect(onStall).toHaveBeenCalled(); }); }); diff --git a/test/media/streaming_engine_integration.js b/test/media/streaming_engine_integration.js index 150403ecaf5..36af6b43d67 100644 --- a/test/media/streaming_engine_integration.js +++ b/test/media/streaming_engine_integration.js @@ -214,7 +214,8 @@ describe('StreamingEngine', () => { manifest, config, /* startTime= */ null, - onSeek); + onSeek, + shaka.test.Util.spyFunc(onEvent)); } function setupManifest(