From 0e24ba32c94d6fa02a6aaf0be8cdc1bd962a47ad Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Fri, 16 Oct 2020 18:25:05 -0400 Subject: [PATCH] Run sync to live edge following level update --- src/controller/base-stream-controller.ts | 29 +++++++++--------- src/controller/stream-controller.ts | 18 ++---------- tests/mocks/data.js | 34 ++++++++++++---------- tests/unit/controller/stream-controller.ts | 2 +- 4 files changed, 35 insertions(+), 48 deletions(-) diff --git a/src/controller/base-stream-controller.ts b/src/controller/base-stream-controller.ts index acf8e8687a9..42007e3e7f6 100644 --- a/src/controller/base-stream-controller.ts +++ b/src/controller/base-stream-controller.ts @@ -375,8 +375,7 @@ export default class BaseStreamController extends TaskLoop implements NetworkCom // find fragment index, contiguous with end of buffer position const start = fragments[0].start; - const end = fragments[fragLen - 1].start + fragments[fragLen - 1].duration; - let loadPosition = pos; + const end = levelDetails.edge; let frag; // If an initSegment is present, it must be buffered first @@ -388,11 +387,6 @@ export default class BaseStreamController extends TaskLoop implements NetworkCom this.warn(`Not enough fragments to start playback (have: ${fragLen}, need: ${initialLiveManifestSize})`); return null; } - // Check to see if we're within the live range; if not, this method will seek to the live edge and return the new position - const syncPos = this.synchronizeToLiveEdge(start, end, loadPosition, levelDetails); - if (syncPos !== null) { - loadPosition = syncPos; - } // The real fragment start times for a live stream are only known after the PTS range for that level is known. // In order to discover the range, we load the best matching fragment for that level and demux it. // Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that @@ -400,14 +394,14 @@ export default class BaseStreamController extends TaskLoop implements NetworkCom if (!levelDetails.PTSKnown && !startFragRequested) { frag = this.getInitialLiveFragment(levelDetails, fragments); } - } else if (loadPosition < start) { + } else if (pos < start) { // VoD playlist: if loadPosition before start of playlist, load first fragment frag = fragments[0]; } // If we haven't run into any special cases already, just load the fragment most closely matching the requested position if (!frag) { - frag = this.getFragmentAtPosition(loadPosition, end, levelDetails); + frag = this.getFragmentAtPosition(pos, end, levelDetails); } return frag; @@ -510,19 +504,22 @@ export default class BaseStreamController extends TaskLoop implements NetworkCom return frag; } - protected synchronizeToLiveEdge (start: number, end: number, bufferEnd: number, levelDetails: LevelDetails): number | null { + protected synchronizeToLiveEdge (levelDetails: LevelDetails): number | null { const { config, media } = this; const liveSyncPosition = this.hls.liveSyncPosition; - if (liveSyncPosition !== null) { + const currentTime = media.currentTime; + if (liveSyncPosition !== null && media?.readyState && media.duration > liveSyncPosition && liveSyncPosition > currentTime) { const maxLatency = config.liveMaxLatencyDuration !== undefined ? config.liveMaxLatencyDuration : config.liveMaxLatencyDurationCount * levelDetails.targetduration; - if (bufferEnd < Math.max(start - config.maxFragLookUpTolerance, end - maxLatency)) { - this.warn(`Buffer end: ${bufferEnd.toFixed(3)} is located too far from the end of live sliding playlist, reset currentTime to : ${liveSyncPosition.toFixed(3)}`); - this.nextLoadPosition = liveSyncPosition; - if (media?.readyState && media.duration > liveSyncPosition && liveSyncPosition > media.currentTime) { - media.currentTime = liveSyncPosition; + const start = levelDetails.fragments[0].start; + const end = levelDetails.edge; + if (currentTime < Math.max(start - config.maxFragLookUpTolerance, end - maxLatency)) { + this.warn(`Playback: ${currentTime.toFixed(3)} is located too far from the end of live sliding playlist: ${end}, reset currentTime to : ${liveSyncPosition.toFixed(3)}`); + if (!this.loadedmetadata) { + this.nextLoadPosition = liveSyncPosition; } + media.currentTime = liveSyncPosition; return liveSyncPosition; } } diff --git a/src/controller/stream-controller.ts b/src/controller/stream-controller.ts index a903ae06f20..6c1b5e03722 100644 --- a/src/controller/stream-controller.ts +++ b/src/controller/stream-controller.ts @@ -23,7 +23,6 @@ import type { MediaAttachedData, AudioTrackSwitchingData, LevelsUpdatedData, - LevelUpdatedData, AudioTrackSwitchedData, BufferCreatedData, ErrorData, @@ -82,7 +81,6 @@ export default class StreamController extends BaseStreamController implements Ne hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this); hls.on(Events.BUFFER_FLUSHED, this.onBufferFlushed, this); hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); - hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this); hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this); } @@ -101,7 +99,6 @@ export default class StreamController extends BaseStreamController implements Ne hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this); hls.off(Events.BUFFER_FLUSHED, this.onBufferFlushed, this); hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); - hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this); hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this); } @@ -614,6 +611,8 @@ export default class StreamController extends BaseStreamController implements Ne if (!this.startFragRequested) { this.setStartPosition(newDetails, sliding); + } else if (newDetails.live) { + this.synchronizeToLiveEdge(newDetails); } // trigger handler right now @@ -923,19 +922,6 @@ export default class StreamController extends BaseStreamController implements Ne this.levels = data.levels; } - onLevelUpdated (event: Events.LEVEL_UPDATED, data: LevelUpdatedData) { - const { media, mediaBuffer } = this; - const currentTime = media ? media.currentTime : null; - const levelDetails = data.details; - const fragments = levelDetails.fragments; - const fragLen = fragments.length; - const start = fragments[0].start; - const end = fragments[fragments.length - 1].start + fragments[fragLen - 1].duration; - const bufferInfo = BufferHelper.bufferInfo(mediaBuffer || media, currentTime, this.config.maxBufferHole); - const pos = bufferInfo.end; - this.synchronizeToLiveEdge(start, end, pos, levelDetails); - } - swapAudioCodec () { this.audioCodecSwap = !this.audioCodecSwap; } diff --git a/tests/mocks/data.js b/tests/mocks/data.js index 9b4e41b3c0d..585dc7de8b4 100644 --- a/tests/mocks/data.js +++ b/tests/mocks/data.js @@ -1,48 +1,52 @@ +import Fragment from '../../src/loader/fragment'; +import { PlaylistLevelType } from '../../src/types/loader'; + +function fragment (options) { + const frag = new Fragment(PlaylistLevelType.MAIN, ''); + Object.assign(frag, options); + return frag; +} + export const mockFragments = [ - { + fragment({ programDateTime: 1505502661523, - endProgramDateTime: 1505502666523, level: 2, duration: 5.000, start: 0, sn: 0, cc: 0 - }, + }), // Discontinuity with PDT 1505502671523 which does not exist in level 1 as per fragPrevious - { + fragment({ programDateTime: 1505502671523, - endProgramDateTime: 1505502676523, level: 2, duration: 5.000, start: 5.000, sn: 1, cc: 1 - }, - { + }), + fragment({ programDateTime: 1505502676523, - endProgramDateTime: 1505502681523, level: 2, duration: 5.000, start: 10.000, sn: 2, cc: 1 - }, - { + }), + fragment({ programDateTime: 1505502681523, - endProgramDateTime: 1505502686523, level: 2, duration: 5.000, start: 15.000, sn: 3, cc: 1 - }, - { + }), + fragment({ programDateTime: 1505502686523, - endProgramDateTime: 1505502691523, level: 2, duration: 5.000, start: 20.000, sn: 4, cc: 1 - } + }) ]; diff --git a/tests/unit/controller/stream-controller.ts b/tests/unit/controller/stream-controller.ts index c45d891bb63..40ad89e3b06 100644 --- a/tests/unit/controller/stream-controller.ts +++ b/tests/unit/controller/stream-controller.ts @@ -111,8 +111,8 @@ describe('StreamController', function () { expect(foundFragment).to.equal(mockFragments[3], 'Expected sn 3, found sn segment ' + resultSN); }); - // TODO: This test fails if using a real instance of Hls it('PTS search choosing the right segment if fragPrevious is not available', function () { + streamController['fragPrevious'] = null; const foundFragment = streamController['getNextFragment'](bufferEnd, levelDetails); const resultSN = foundFragment ? foundFragment.sn : -1; expect(foundFragment).to.equal(mockFragments[3], 'Expected sn 3, found sn segment ' + resultSN);