From dd89cde2722e152b74d24d01a7b82d34db73bd08 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Fri, 24 Jul 2020 18:35:40 -0400 Subject: [PATCH 1/3] Fix stalling on discontinuity with audio track #2913 --- src/controller/audio-stream-controller.js | 70 +++++++++-------------- 1 file changed, 27 insertions(+), 43 deletions(-) diff --git a/src/controller/audio-stream-controller.js b/src/controller/audio-stream-controller.js index 1fffa02a675..33644f423e2 100644 --- a/src/controller/audio-stream-controller.js +++ b/src/controller/audio-stream-controller.js @@ -15,6 +15,8 @@ import { FragmentState } from './fragment-tracker'; import { ElementaryStreamTypes } from '../loader/fragment'; import BaseStreamController, { State } from './base-stream-controller'; import { MAX_START_GAP_JUMP } from './gap-controller'; +import { fragmentWithinToleranceTest } from './fragment-finders'; + const { performance } = window; const TICK_INTERVAL = 100; // how often to tick in ms @@ -218,31 +220,6 @@ class AudioStreamController extends BaseStreamController { let foundFrag; let maxFragLookUpTolerance = config.maxFragLookUpTolerance; const fragNext = fragPrevious ? fragments[fragPrevious.sn - fragments[0].sn + 1] : undefined; - let fragmentWithinToleranceTest = (candidate) => { - // offset should be within fragment boundary - config.maxFragLookUpTolerance - // this is to cope with situations like - // bufferEnd = 9.991 - // frag[Ø] : [0,10] - // frag[1] : [10,20] - // bufferEnd is within frag[0] range ... although what we are expecting is to return frag[1] here - // frag start frag start+duration - // |-----------------------------| - // <---> <---> - // ...--------><-----------------------------><---------.... - // previous frag matching fragment next frag - // return -1 return 0 return 1 - // logger.log(`level/sn/start/end/bufEnd:${level}/${candidate.sn}/${candidate.start}/${(candidate.start+candidate.duration)}/${bufferEnd}`); - // Set the lookup tolerance to be small enough to detect the current segment - ensures we don't skip over very small segments - let candidateLookupTolerance = Math.min(maxFragLookUpTolerance, candidate.duration); - if ((candidate.start + candidate.duration - candidateLookupTolerance) <= bufferEnd) { - return 1; - } else if (candidate.start - candidateLookupTolerance > bufferEnd && candidate.start) { - // if maxFragLookUpTolerance will have negative value then don't return -1 for first element - return -1; - } - - return 0; - }; if (bufferEnd < end) { if (bufferEnd > end - maxFragLookUpTolerance) { @@ -250,10 +227,10 @@ class AudioStreamController extends BaseStreamController { } // Prefer the next fragment if it's within tolerance - if (fragNext && !fragmentWithinToleranceTest(fragNext)) { + if (fragNext && !fragmentWithinToleranceTest(bufferEnd, maxFragLookUpTolerance, fragNext)) { foundFrag = fragNext; } else { - foundFrag = BinarySearch.search(fragments, fragmentWithinToleranceTest); + foundFrag = BinarySearch.search(fragments, (frag) => fragmentWithinToleranceTest(bufferEnd, maxFragLookUpTolerance, frag)); } } else { // reach end of playlist @@ -320,26 +297,24 @@ class AudioStreamController extends BaseStreamController { } break; case State.WAITING_INIT_PTS: - const videoTrackCC = this.videoTrackCC; - if (this.initPTS[videoTrackCC] === undefined) { - break; - } - // Ensure we don't get stuck in the WAITING_INIT_PTS state if the waiting frag CC doesn't match any initPTS const waitingFrag = this.waitingFragment; if (waitingFrag) { const waitingFragCC = waitingFrag.frag.cc; - if (videoTrackCC !== waitingFragCC) { - track = this.tracks[this.trackId]; - if (track.details && track.details.live) { - logger.warn(`Waiting fragment CC (${waitingFragCC}) does not match video track CC (${videoTrackCC})`); - this.waitingFragment = null; - this.state = State.IDLE; - } - } else { - this.state = State.FRAG_LOADING; - this.onFragLoaded(this.waitingFragment); + if (this.initPTS[waitingFragCC] !== undefined) { this.waitingFragment = null; + this.state = State.FRAG_LOADING; + this.onFragLoaded(waitingFrag); + } else if (!this.fragPrevious) { + logger.warn(`Waiting fragment CC (${waitingFragCC}) cancelled because of continuity change`); + this.clearWaitingFragment(); + } else { + const bufferInfo = BufferHelper.bufferInfo(this.mediaBuffer, this.media.currentTime, config.maxBufferHole); + const waitingFragmentAtPosition = fragmentWithinToleranceTest(bufferInfo.end, config.maxFragLookUpTolerance, waitingFrag.frag); + if (waitingFragmentAtPosition !== 0) { + logger.warn(`Waiting fragment CC (${waitingFragCC}) @ ${waitingFrag.frag.start} cancelled because another fragment at ${bufferInfo.end} is needed`); + this.clearWaitingFragment(); + } } } else { this.state = State.IDLE; @@ -357,6 +332,15 @@ class AudioStreamController extends BaseStreamController { } } + clearWaitingFragment () { + const waitingFrag = this.waitingFragment; + if (waitingFrag) { + this.fragmentTracker.removeFragment(waitingFrag.frag); + this.waitingFragment = null; + this.state = State.IDLE; + } + } + onMediaAttached (data) { let media = this.media = this.mediaBuffer = data.media; this.onvseeking = this.onMediaSeeking.bind(this); @@ -399,8 +383,8 @@ class AudioStreamController extends BaseStreamController { this.trackId = data.id; this.fragCurrent = null; + this.clearWaitingFragment(); this.state = State.PAUSED; - this.waitingFragment = null; // destroy useless demuxer when switching audio to main if (!altAudio) { if (this.demuxer) { From a3c587cf2209ef27f2dfc262beef094c5919fddf Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Fri, 24 Jul 2020 19:05:50 -0400 Subject: [PATCH 2/3] Change "CC" and "continuity counter" to "cc" in log messages for consistency --- src/controller/audio-stream-controller.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/controller/audio-stream-controller.js b/src/controller/audio-stream-controller.js index 33644f423e2..b87fce131b7 100644 --- a/src/controller/audio-stream-controller.js +++ b/src/controller/audio-stream-controller.js @@ -179,7 +179,7 @@ class AudioStreamController extends BaseStreamController { end = fragments[fragLen - 1].start + fragments[fragLen - 1].duration, frag; - // When switching audio track, reload audio as close as possible to currentTime + // When switching audio track, reload audio as close as possible to currentTime if (audioSwitch) { if (trackDetails.live && !trackDetails.PTSKnown) { logger.log('switching audiotrack, live stream, unknown PTS,load first fragment'); @@ -306,13 +306,13 @@ class AudioStreamController extends BaseStreamController { this.state = State.FRAG_LOADING; this.onFragLoaded(waitingFrag); } else if (!this.fragPrevious) { - logger.warn(`Waiting fragment CC (${waitingFragCC}) cancelled because of continuity change`); + logger.log(`Waiting fragment cc (${waitingFragCC}) cancelled because of continuity change`); this.clearWaitingFragment(); } else { const bufferInfo = BufferHelper.bufferInfo(this.mediaBuffer, this.media.currentTime, config.maxBufferHole); const waitingFragmentAtPosition = fragmentWithinToleranceTest(bufferInfo.end, config.maxFragLookUpTolerance, waitingFrag.frag); if (waitingFragmentAtPosition !== 0) { - logger.warn(`Waiting fragment CC (${waitingFragCC}) @ ${waitingFrag.frag.start} cancelled because another fragment at ${bufferInfo.end} is needed`); + logger.log(`Waiting fragment cc (${waitingFragCC}) @ ${waitingFrag.frag.start} cancelled because another fragment at ${bufferInfo.end} is needed`); this.clearWaitingFragment(); } } @@ -514,7 +514,7 @@ class AudioStreamController extends BaseStreamController { let accurateTimeOffset = false; // details.PTSKnown || !details.live; this.demuxer.push(data.payload, initSegmentData, audioCodec, null, fragCurrent, duration, accurateTimeOffset, initPTS); } else { - logger.log(`unknown video PTS for continuity counter ${cc}, waiting for video PTS before demuxing audio frag ${sn} of [${details.startSN} ,${details.endSN}],track ${trackId}`); + logger.log(`Unknown video PTS for cc ${cc}, waiting for video PTS before demuxing audio frag ${sn} of [${details.startSN} ,${details.endSN}],track ${trackId}`); this.waitingFragment = data; this.state = State.WAITING_INIT_PTS; } From ef4c7291cf050743e7dd58e10959ddefe13393ad Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Fri, 24 Jul 2020 20:05:46 -0400 Subject: [PATCH 3/3] Drop waiting fragment if videoTrackCC has changed since waitingFragment was set and initPTS was not found --- src/controller/audio-stream-controller.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/controller/audio-stream-controller.js b/src/controller/audio-stream-controller.js index b87fce131b7..bb45ca56a23 100644 --- a/src/controller/audio-stream-controller.js +++ b/src/controller/audio-stream-controller.js @@ -47,6 +47,7 @@ class AudioStreamController extends BaseStreamController { this.initPTS = []; this.waitingFragment = null; this.videoTrackCC = null; + this.waitingVideoCC = null; } // Signal that video PTS was found @@ -305,13 +306,15 @@ class AudioStreamController extends BaseStreamController { this.waitingFragment = null; this.state = State.FRAG_LOADING; this.onFragLoaded(waitingFrag); - } else if (!this.fragPrevious) { - logger.log(`Waiting fragment cc (${waitingFragCC}) cancelled because of continuity change`); + } else if (this.videoTrackCC !== this.waitingVideoCC) { + // Drop waiting fragment if videoTrackCC has changed since waitingFragment was set and initPTS was not found + logger.log(`Waiting fragment cc (${waitingFragCC}) cancelled because video is at cc ${this.videoTrackCC}`); this.clearWaitingFragment(); } else { + // Drop waiting fragment if an earlier fragment is needed const bufferInfo = BufferHelper.bufferInfo(this.mediaBuffer, this.media.currentTime, config.maxBufferHole); const waitingFragmentAtPosition = fragmentWithinToleranceTest(bufferInfo.end, config.maxFragLookUpTolerance, waitingFrag.frag); - if (waitingFragmentAtPosition !== 0) { + if (waitingFragmentAtPosition < 0) { logger.log(`Waiting fragment cc (${waitingFragCC}) @ ${waitingFrag.frag.start} cancelled because another fragment at ${bufferInfo.end} is needed`); this.clearWaitingFragment(); } @@ -337,6 +340,7 @@ class AudioStreamController extends BaseStreamController { if (waitingFrag) { this.fragmentTracker.removeFragment(waitingFrag.frag); this.waitingFragment = null; + this.waitingVideoCC = null; this.state = State.IDLE; } } @@ -516,6 +520,7 @@ class AudioStreamController extends BaseStreamController { } else { logger.log(`Unknown video PTS for cc ${cc}, waiting for video PTS before demuxing audio frag ${sn} of [${details.startSN} ,${details.endSN}],track ${trackId}`); this.waitingFragment = data; + this.waitingVideoCC = this.videoTrackCC; this.state = State.WAITING_INIT_PTS; } }