Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix stalling on discontinuity with audio track #2919

Merged
merged 3 commits into from Jul 26, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

videoTrackCC is set in onInitPTS so I don't see how this check would ever be true.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What we really want to know here is stream-controller current fragment - which fragment cc and start time is being loaded and whether that is close to or precedes our waiting fragment without any audio buffer gaps in between.

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) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're actually waiting for videoTrackCC to increment so this is not a good check.

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