diff --git a/lib/media/streaming_engine.js b/lib/media/streaming_engine.js index d6048b9cc5..a501370c52 100644 --- a/lib/media/streaming_engine.js +++ b/lib/media/streaming_engine.js @@ -628,7 +628,7 @@ shaka.media.StreamingEngine = class { // Don't clear the buffer unless something is buffered. This extra // check prevents extra, useless calls to clear the buffer. - if (somethingBuffered) { + if (somethingBuffered || mediaState.performingUpdate) { this.forceClearBuffer_(mediaState); streamCleared = true; } diff --git a/test/media/streaming_engine_unit.js b/test/media/streaming_engine_unit.js index 23edba3b85..b88d58b34a 100644 --- a/test/media/streaming_engine_unit.js +++ b/test/media/streaming_engine_unit.js @@ -97,6 +97,9 @@ describe('StreamingEngine', () => { /** @type {!shaka.media.StreamingEngine} */ let streamingEngine; + /** @type {function(function(), number)} */ + let realSetTimeout; + /** * Runs the fake event loop. * @param {function()=} callback An optional callback that is executed @@ -116,6 +119,7 @@ describe('StreamingEngine', () => { } beforeAll(() => { + realSetTimeout = window.setTimeout; jasmine.clock().install(); jasmine.clock().mockDate(); }); @@ -1438,6 +1442,66 @@ describe('StreamingEngine', () => { }); }); + it('into unbuffered regions when nothing is buffered ' + + 'and mediaState is performing an update', async () => { + // Here we go! + streamingEngine.switchVariant(variant); + streamingEngine.switchTextStream(textStream); + // ensure init source buffer promise does not resolve before seeked() + // so mediaState remains in "performingUpdate" state + const initSourceBufferPromise = new shaka.util.PublicPromise(); + mediaSourceEngine.setStreamProperties.and + .returnValue(initSourceBufferPromise); + await streamingEngine.start(); + playing = true; + + // tick to trigger mediaState updates + jasmine.clock().tick(1); + // give a chance for fetchAndAppend_ to be invoked + // by async onUpdate_ callback + await Util.shortDelay(realSetTimeout); + + // Nothing is buffered yet. + expect(mediaSourceEngine.segments).toEqual({ + audio: [false, false, false, false], + video: [false, false, false, false], + text: [false, false, false, false], + }); + + // Seek forward to an unbuffered region in the first Period. + presentationTimeInSeconds = 15; + streamingEngine.seeked(); + + // resolve initSourceBufferPromise after seeked(), waitingToClearBuffer + // should have been set, so this will now trigger the actual flush of + // the buffer + initSourceBufferPromise.resolve(); + mediaSourceEngine.setStreamProperties.and + .returnValue(Promise.resolve()); + + // allow mediaState update to resolve + await Util.shortDelay(realSetTimeout); + + onTick.and.callFake(() => { + expect(mediaSourceEngine.clear).toHaveBeenCalled(); + onTick.and.stub(); + }); + + await runTest(Util.spyFunc(onTick)); + + // Verify buffers. + expect(mediaSourceEngine.initSegments).toEqual({ + audio: [false, true], + video: [false, true], + text: [], + }); + expect(mediaSourceEngine.segments).toEqual({ + audio: [false, true, true, true], + video: [false, true, true, true], + text: [false, true, true, true], + }); + }); + it('into unbuffered regions near segment start', async () => { // Here we go! streamingEngine.switchVariant(variant);