diff --git a/src/controller/gap-controller.ts b/src/controller/gap-controller.ts index 8e6ef1f6b07..fc8affb4f13 100644 --- a/src/controller/gap-controller.ts +++ b/src/controller/gap-controller.ts @@ -5,8 +5,8 @@ import { Events } from '../events'; import { logger } from '../utils/logger'; import type Hls from '../hls'; import type { HlsConfig } from '../config'; +import type { Fragment } from '../loader/fragment'; import type { FragmentTracker } from './fragment-tracker'; -import { Fragment } from '../loader/fragment'; export const STALL_MINIMUM_DURATION_MS = 250; export const MAX_START_GAP_JUMP = 2.0; @@ -43,7 +43,7 @@ export default class GapController { * * @param {number} lastCurrentTime Previously read playhead position */ - public poll(lastCurrentTime: number) { + public poll(lastCurrentTime: number, activeFrag: Fragment | null) { const { config, media, stalled } = this; if (media === null) { return; @@ -104,6 +104,7 @@ export default class GapController { // Next buffered range is too far ahead to jump to while still seeking const noBufferGap = !nextStart || + (activeFrag && activeFrag.start <= currentTime) || (nextStart - currentTime > MAX_START_GAP_JUMP && !this.fragmentTracker.getPartialFragment(currentTime)); if (hasEnoughBuffer || noBufferGap) { diff --git a/src/controller/stream-controller.ts b/src/controller/stream-controller.ts index f052d2c00bf..d3c77c889d8 100644 --- a/src/controller/stream-controller.ts +++ b/src/controller/stream-controller.ts @@ -925,7 +925,8 @@ export default class StreamController this.seekToStartPos(); } else { // Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers - gapController.poll(this.lastCurrentTime); + const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null; + gapController.poll(this.lastCurrentTime, activeFrag); } this.lastCurrentTime = media.currentTime; diff --git a/tests/unit/controller/gap-controller.js b/tests/unit/controller/gap-controller.js index dec6b3dd89d..776ba827c7d 100644 --- a/tests/unit/controller/gap-controller.js +++ b/tests/unit/controller/gap-controller.js @@ -269,6 +269,30 @@ describe('GapController', function () { wallClock.tick(2 * STALL_HANDLING_RETRY_PERIOD_MS); }); + it('should not detect stalls when loading an earlier fragment while seeking', function () { + wallClock.tick(2 * STALL_HANDLING_RETRY_PERIOD_MS); + mockMedia.currentTime += 0.1; + gapController.poll(0); + expect(gapController.stalled).to.equal(null, 'buffered start'); + + wallClock.tick(2 * STALL_HANDLING_RETRY_PERIOD_MS); + mockMedia.currentTime += 5; + mockMedia.seeking = true; + mockTimeRangesData.length = 1; + mockTimeRangesData[0] = [5.5, 10]; + gapController.poll(mockMedia.currentTime - 5); + expect(gapController.stalled).to.equal(null, 'new seek position'); + + wallClock.tick(2 * STALL_HANDLING_RETRY_PERIOD_MS); + gapController.poll(mockMedia.currentTime, { + start: 5, + }); + expect(gapController.stalled).to.equal( + null, + 'seeking while loading fragment' + ); + }); + it('should trigger reportStall when stalling for 250ms or longer', function () { setStalling(); wallClock.tick(250);