Skip to content

Commit

Permalink
Merge pull request #2919 from video-dev/bugfix/audio-waiting-fragment
Browse files Browse the repository at this point in the history
Fix stalling on discontinuity with audio track
  • Loading branch information
robwalch committed Jul 26, 2020
2 parents ff3b84b + ef4c729 commit 8234f43
Showing 1 changed file with 34 additions and 45 deletions.
79 changes: 34 additions & 45 deletions src/controller/audio-stream-controller.js
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -218,42 +221,17 @@ 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) {
maxFragLookUpTolerance = 0;
}

// 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
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}
}
Expand Down

0 comments on commit 8234f43

Please sign in to comment.