From 8df2c6e29d205be323ba6b2a28f9d9ac8657863b Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Wed, 7 Apr 2021 15:14:48 -0400 Subject: [PATCH] Honor startPosition from config/startLoad with live streams Do not sync with live edge when inside sliding window and liveMaxLatencyDuration Fixes #3736 --- demo/chart/timeline-chart.ts | 18 +++++++++++++--- src/controller/base-stream-controller.ts | 25 ++++++++++++++++------ src/controller/stream-controller.ts | 1 - tests/unit/controller/stream-controller.ts | 19 +++++++++++++++- 4 files changed, 51 insertions(+), 12 deletions(-) diff --git a/demo/chart/timeline-chart.ts b/demo/chart/timeline-chart.ts index abb9dbb05d2..23c288ff875 100644 --- a/demo/chart/timeline-chart.ts +++ b/demo/chart/timeline-chart.ts @@ -283,12 +283,18 @@ export class TimelineChart { updateLevelOrTrack(details: LevelDetails) { const { targetduration, totalduration, url } = details; const { datasets } = this.chart.data; - const levelDataSet = arrayFind( + let levelDataSet = arrayFind( datasets, (dataset) => stripDeliveryDirectives(url) === stripDeliveryDirectives(dataset.url || '') ); + if (!levelDataSet) { + levelDataSet = arrayFind( + datasets, + (dataset) => details.fragments[0]?.level === dataset.level + ); + } if (!levelDataSet) { return; } @@ -381,10 +387,16 @@ export class TimelineChart { updateFragment(data: FragLoadedData | FragParsedData | FragChangedData) { const { datasets } = this.chart.data; const frag: Fragment = data.frag; - const levelDataSet = arrayFind( + let levelDataSet = arrayFind( datasets, - (dataset) => dataset.url === frag.baseurl + (dataset) => frag.baseurl === dataset.url ); + if (!levelDataSet) { + levelDataSet = arrayFind( + datasets, + (dataset) => frag.level === dataset.level + ); + } if (!levelDataSet) { return; } diff --git a/src/controller/base-stream-controller.ts b/src/controller/base-stream-controller.ts index 2bd4ec34d69..3f28dcce1f6 100644 --- a/src/controller/base-stream-controller.ts +++ b/src/controller/base-stream-controller.ts @@ -766,7 +766,11 @@ export default class BaseStreamController // 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 // we get the fragment matching that start time - if (!levelDetails.PTSKnown && !this.startFragRequested) { + if ( + !levelDetails.PTSKnown && + !this.startFragRequested && + this.startPosition === -1 + ) { frag = this.getInitialLiveFragment(levelDetails, fragments); } } else if (pos <= start) { @@ -981,20 +985,24 @@ export default class BaseStreamController const currentTime = media.currentTime; const start = levelDetails.fragments[0].start; const end = levelDetails.edge; + const withinSlidingWindow = + currentTime >= start - config.maxFragLookUpTolerance && + currentTime <= end; // Continue if we can seek forward to sync position or if current time is outside of sliding window if ( liveSyncPosition !== null && media.duration > liveSyncPosition && - (currentTime < liveSyncPosition || - currentTime < start - config.maxFragLookUpTolerance || - currentTime > end) + (currentTime < liveSyncPosition || !withinSlidingWindow) ) { // Continue if buffer is starving or if current time is behind max latency const maxLatency = config.liveMaxLatencyDuration !== undefined ? config.liveMaxLatencyDuration : config.liveMaxLatencyDurationCount * levelDetails.targetduration; - if (media.readyState < 4 || currentTime < end - maxLatency) { + if ( + (!withinSlidingWindow && media.readyState < 4) || + currentTime < end - maxLatency + ) { if (!this.loadedmetadata) { this.nextLoadPosition = liveSyncPosition; } @@ -1071,8 +1079,11 @@ export default class BaseStreamController this.startPosition = startTimeOffset; } else { if (details.live) { - this.startPosition = this.hls.liveSyncPosition || sliding; - this.log(`Configure startPosition to ${this.startPosition}`); + // Leave this.startPosition at -1, so that we can use `getInitialLiveFragment` logic when startPosition has + // not been specified via the config or an as an argument to startLoad (#3736). + this.nextLoadPosition = this.lastCurrentTime = + this.hls.liveSyncPosition || sliding; + return; } else { this.startPosition = 0; } diff --git a/src/controller/stream-controller.ts b/src/controller/stream-controller.ts index 30d053664f0..d744a61136c 100644 --- a/src/controller/stream-controller.ts +++ b/src/controller/stream-controller.ts @@ -8,7 +8,6 @@ import { FragmentState } from './fragment-tracker'; import type { Level } from '../types/level'; import { PlaylistLevelType } from '../types/loader'; import { Fragment, ElementaryStreamTypes } from '../loader/fragment'; -import FragmentLoader from '../loader/fragment-loader'; import TransmuxerInterface from '../demux/transmuxer-interface'; import type { TransmuxerResult } from '../types/transmuxer'; import { ChunkMetadata } from '../types/transmuxer'; diff --git a/tests/unit/controller/stream-controller.ts b/tests/unit/controller/stream-controller.ts index a879f3400eb..ba1ea1cf236 100644 --- a/tests/unit/controller/stream-controller.ts +++ b/tests/unit/controller/stream-controller.ts @@ -114,6 +114,7 @@ describe('StreamController', function () { beforeEach(function () { streamController['fragPrevious'] = fragPrevious; + levelDetails.live = false; levelDetails.startSN = mockFragments[0].sn; levelDetails.endSN = mockFragments[mockFragments.length - 1].sn; levelDetails.fragments = mockFragments; @@ -434,7 +435,7 @@ describe('StreamController', function () { expect(streamController['lastCurrentTime']).to.equal(5); }); - it('should set startPosition to lastCurrentTime if unset', function () { + it('should set startPosition to lastCurrentTime if unset and lastCurrentTime > 0', function () { streamController['lastCurrentTime'] = 5; streamController.startLoad(-1); assertStreamControllerStarted(streamController); @@ -443,6 +444,22 @@ describe('StreamController', function () { expect(streamController['lastCurrentTime']).to.equal(5); }); + it('should set startPosition when passed as an argument', function () { + streamController.startLoad(123); + assertStreamControllerStarted(streamController); + expect(streamController['nextLoadPosition']).to.equal(123); + expect(streamController['startPosition']).to.equal(123); + expect(streamController['lastCurrentTime']).to.equal(123); + }); + + it('should set startPosition to -1 when passed as an argument', function () { + streamController.startLoad(-1); + assertStreamControllerStarted(streamController); + expect(streamController['nextLoadPosition']).to.equal(-1); + expect(streamController['startPosition']).to.equal(-1); + expect(streamController['lastCurrentTime']).to.equal(-1); + }); + it('sets up for a bandwidth test if starting at auto', function () { streamController['startFragRequested'] = false; hls.startLevel = -1;