From 8907354c30d9464cb567f492af096a8d9b2637db Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Mon, 13 Jan 2020 20:47:44 -0500 Subject: [PATCH 1/2] Remove live mid fragment loading logic because it breaks live streams with audio tracks --- src/controller/audio-stream-controller.js | 15 ++++++++++++--- src/controller/stream-controller.js | 18 ++++++++---------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/controller/audio-stream-controller.js b/src/controller/audio-stream-controller.js index 22e109c48e1..17fa69d0a28 100644 --- a/src/controller/audio-stream-controller.js +++ b/src/controller/audio-stream-controller.js @@ -397,8 +397,6 @@ class AudioStreamController extends BaseStreamController { // we already have details for that level, merge them LevelHelper.mergeDetails(curDetails, newDetails); sliding = newDetails.fragments[0].start; - // TODO - // this.liveSyncPosition = this.computeLivePosition(sliding, curDetails); if (newDetails.PTSKnown) { logger.log(`live audio playlist sliding:${sliding.toFixed(3)}`); } else { @@ -423,7 +421,13 @@ class AudioStreamController extends BaseStreamController { logger.log(`start time offset found in playlist, adjust startPosition to ${startTimeOffset}`); this.startPosition = startTimeOffset; } else { - this.startPosition = 0; + // if live playlist, set start position to be fragment N-this.config.liveSyncDurationCount (usually 3) + if (newDetails.live) { + this.startPosition = this.computeLivePosition(sliding, newDetails); + logger.log(`[audio track] configure startPosition to ${this.startPosition}`); + } else { + this.startPosition = 0; + } } } this.nextLoadPosition = this.startPosition; @@ -437,6 +441,11 @@ class AudioStreamController extends BaseStreamController { this.tick(); } + computeLivePosition (sliding, levelDetails) { + let targetLatency = this.config.liveSyncDuration !== undefined ? this.config.liveSyncDuration : this.config.liveSyncDurationCount * levelDetails.targetduration; + return sliding + Math.max(0, levelDetails.totalduration - targetLatency); + } + onKeyLoaded () { if (this.state === State.KEY_LOADING) { this.state = State.IDLE; diff --git a/src/controller/stream-controller.js b/src/controller/stream-controller.js index 8cb25f85bbe..3dabbbffd67 100644 --- a/src/controller/stream-controller.js +++ b/src/controller/stream-controller.js @@ -248,7 +248,7 @@ class StreamController extends BaseStreamController { return; } - frag = this._ensureFragmentAtLivePoint(levelDetails, bufferEnd, start, end, fragPrevious, fragments, fragLen); + frag = this._ensureFragmentAtLivePoint(levelDetails, bufferEnd, start, end, fragPrevious, fragments); // if it explicitely returns null don't load any fragment and exit function now if (frag === null) { return; @@ -275,14 +275,19 @@ class StreamController extends BaseStreamController { } } - _ensureFragmentAtLivePoint (levelDetails, bufferEnd, start, end, fragPrevious, fragments, fragLen) { + _ensureFragmentAtLivePoint (levelDetails, bufferEnd, start, end, fragPrevious, fragments) { const config = this.hls.config, media = this.media; let frag; // check if requested position is within seekable boundaries : // logger.log(`start/pos/bufEnd/seeking:${start.toFixed(3)}/${pos.toFixed(3)}/${bufferEnd.toFixed(3)}/${this.media.seeking}`); - let maxLatency = config.liveMaxLatencyDuration !== undefined ? config.liveMaxLatencyDuration : config.liveMaxLatencyDurationCount * levelDetails.targetduration; + let maxLatency = Infinity; + if (config.liveMaxLatencyDuration !== undefined) { + maxLatency = config.liveMaxLatencyDuration; + } else if (Number.isFinite(config.liveMaxLatencyDurationCount)) { + maxLatency = config.liveMaxLatencyDurationCount * levelDetails.targetduration; + } if (bufferEnd < Math.max(start - config.maxFragLookUpTolerance, end - maxLatency)) { let liveSyncPosition = this.liveSyncPosition = this.computeLivePosition(start, levelDetails); @@ -341,13 +346,6 @@ class StreamController extends BaseStreamController { } } } - if (!frag) { - /* we have no idea about which fragment should be loaded. - so let's load mid fragment. it will help computing playlist sliding and find the right one - */ - frag = fragments[Math.min(fragLen - 1, Math.round(fragLen / 2))]; - logger.log(`live playlist, switching playlist, unknown, load middle frag : ${frag.sn}`); - } } return frag; From 2d353088c96b8b7a654e4a6f18f8b1e28d3d3969 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Tue, 14 Jan 2020 19:00:00 -0500 Subject: [PATCH 2/2] Improve seek to start pos so that it always works for live streams --- src/controller/stream-controller.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/controller/stream-controller.js b/src/controller/stream-controller.js index 3dabbbffd67..97fb2c3f6e3 100644 --- a/src/controller/stream-controller.js +++ b/src/controller/stream-controller.js @@ -1151,7 +1151,10 @@ class StreamController extends BaseStreamController { this.hls.trigger(Event.FRAG_BUFFERED, { stats: stats, frag: frag, id: 'main' }); this.state = State.IDLE; } - this.tick(); + // Do not tick when _seekToStartPos needs to be called as seeking to the start can fail on live streams at this point + if (this.loadedmetadata || this.startPosition <= 0) { + this.tick(); + } } } @@ -1311,15 +1314,16 @@ class StreamController extends BaseStreamController { * @private */ _seekToStartPos () { - const { media } = this; + const { media, startPosition } = this; const currentTime = media.currentTime; // only adjust currentTime if different from startPosition or if startPosition not buffered // at that stage, there should be only one buffered range, as we reach that code after first fragment has been buffered - const startPosition = media.seeking ? currentTime : this.startPosition; - // if currentTime not matching with expected startPosition or startPosition not buffered but close to first buffered if (currentTime !== startPosition) { - // if startPosition not buffered, let's seek to buffered.start(0) - logger.log(`target start position not buffered, seek to buffered.start(0) ${startPosition} from current time ${currentTime} `); + if (media.seeking) { + logger.log(`could not seek to ${startPosition}, already seeking at ${currentTime}`); + return; + } + logger.log(`seek to target start position ${startPosition} from current time ${currentTime}. ready state ${media.readyState}`); media.currentTime = startPosition; } }