diff --git a/src/controller/audio-stream-controller.js b/src/controller/audio-stream-controller.js index 1fffa02a675..bb45ca56a23 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 @@ -45,6 +47,7 @@ class AudioStreamController extends BaseStreamController { this.initPTS = []; this.waitingFragment = null; this.videoTrackCC = null; + this.waitingVideoCC = null; } // Signal that video PTS was found @@ -177,7 +180,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'); @@ -218,31 +221,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 +228,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 +298,26 @@ 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.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) { + logger.log(`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 +335,16 @@ class AudioStreamController extends BaseStreamController { } } + clearWaitingFragment () { + const waitingFrag = this.waitingFragment; + if (waitingFrag) { + this.fragmentTracker.removeFragment(waitingFrag.frag); + this.waitingFragment = null; + this.waitingVideoCC = null; + this.state = State.IDLE; + } + } + onMediaAttached (data) { let media = this.media = this.mediaBuffer = data.media; this.onvseeking = this.onMediaSeeking.bind(this); @@ -399,8 +387,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) { @@ -530,8 +518,9 @@ 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.waitingVideoCC = this.videoTrackCC; this.state = State.WAITING_INIT_PTS; } }