Skip to content

Commit

Permalink
Merge pull request #6922 from video-dev/bugfix/live-av-desync-init-pts
Browse files Browse the repository at this point in the history
- Do not adjust initPTS without accurate playlist time offset (when live playlist is out of sync).
- Do not re-emit AUDIO_TRACK_LOADED for track sync as it reruns playlist merge logic.
- Keep audio-stream-controller startPosition within playlist bounds.
- Fix audio-only main <-> track switching buffer flushing issues
- Do not treat live playlists as expired after three "misses" (static responses -> archived live / load segments anyway)
  • Loading branch information
robwalch authored Dec 21, 2024
2 parents 699901b + 607a6ae commit e811aba
Show file tree
Hide file tree
Showing 9 changed files with 322 additions and 48 deletions.
45 changes: 27 additions & 18 deletions src/controller/audio-stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
alignMediaPlaylistByPDT,
} from '../utils/discontinuities';
import { mediaAttributesIdentical } from '../utils/media-option-attributes';
import { useAlternateAudio } from '../utils/rendition-helper';
import type { FragmentTracker } from './fragment-tracker';
import type Hls from '../hls';
import type { Fragment, MediaFragment, Part } from '../loader/fragment';
Expand Down Expand Up @@ -519,7 +520,7 @@ class AudioStreamController
const cachedTrackLoadedData = this.cachedTrackLoadedData;
if (cachedTrackLoadedData) {
this.cachedTrackLoadedData = null;
this.hls.trigger(Events.AUDIO_TRACK_LOADED, cachedTrackLoadedData);
this.onAudioTrackLoaded(Events.AUDIO_TRACK_LOADED, cachedTrackLoadedData);
}
}

Expand All @@ -528,25 +529,28 @@ class AudioStreamController
data: TrackLoadedData,
) {
const { levels } = this;
const { details: newDetails, id: trackId } = data;
const { details: newDetails, id: trackId, groupId, track } = data;
if (!levels) {
this.warn(
`Audio tracks reset while loading track ${trackId} "${track.name}" of "${groupId}"`,
);
return;
}
const mainDetails = this.mainDetails;
if (
!mainDetails ||
mainDetails.expired ||
newDetails.endCC > mainDetails.endCC
newDetails.endCC > mainDetails.endCC ||
mainDetails.expired
) {
this.cachedTrackLoadedData = data;
if (this.state !== State.STOPPED) {
this.state = State.WAITING_TRACK;
}
return;
}
if (!levels) {
this.warn(`Audio tracks were reset while loading level ${trackId}`);
return;
}
this.cachedTrackLoadedData = null;
this.log(
`Audio track ${trackId} loaded [${newDetails.startSN},${
`Audio track ${trackId} "${track.name}" of "${groupId}" loaded [${newDetails.startSN},${
newDetails.endSN
}]${
newDetails.lastPartSn
Expand All @@ -555,18 +559,18 @@ class AudioStreamController
},duration:${newDetails.totalduration}`,
);

const track = levels[trackId];
const trackLevel = levels[trackId];
let sliding = 0;
if (newDetails.live || track.details?.live) {
if (newDetails.live || trackLevel.details?.live) {
this.checkLiveUpdate(newDetails);
if (newDetails.deltaUpdateFailed) {
return;
}

if (track.details) {
if (trackLevel.details) {
sliding = this.alignPlaylists(
newDetails,
track.details,
trackLevel.details,
this.levelLastLoaded?.details,
);
}
Expand All @@ -580,8 +584,8 @@ class AudioStreamController
sliding = newDetails.fragmentStart;
}
}
track.details = newDetails;
this.levelLastLoaded = track;
trackLevel.details = newDetails;
this.levelLastLoaded = trackLevel;

// compute start position if we are aligned with the main playlist
if (!this.startFragRequested) {
Expand Down Expand Up @@ -1026,9 +1030,14 @@ class AudioStreamController
bufferedTrack.name !== switchingTrack.name ||
bufferedTrack.lang !== switchingTrack.lang)
) {
this.log('Switching audio track : flushing all audio');
super.flushMainBuffer(0, Number.POSITIVE_INFINITY, 'audio');
this.bufferedTrack = null;
if (useAlternateAudio(switchingTrack.url, this.hls)) {
this.log('Switching audio track : flushing all audio');
super.flushMainBuffer(0, Number.POSITIVE_INFINITY, 'audio');
this.bufferedTrack = null;
} else {
// Main is being buffered. Set bufferedTrack so that it is flushed when switching back to alt-audio
this.bufferedTrack = switchingTrack;
}
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/controller/base-stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1308,7 +1308,9 @@ export default class BaseStreamController
const mainStart = this.hls.startPosition;
const liveSyncPosition = this.hls.liveSyncPosition;
const startPosition = frag
? (mainStart !== -1 ? mainStart : liveSyncPosition) || frag.start
? (mainStart !== -1 && mainStart >= start
? mainStart
: liveSyncPosition) || frag.start
: pos;
this.log(
`Setting startPosition to ${startPosition} to match initial live edge. mainStart: ${mainStart} liveSyncPosition: ${liveSyncPosition} frag.start: ${frag?.start}`,
Expand Down
6 changes: 5 additions & 1 deletion src/controller/stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -882,12 +882,16 @@ export default class StreamController
}
// If switching from alt to main audio, flush all audio and trigger track switched
if (fromAltAudio) {
this.fragmentTracker.removeAllFragments();
hls.once(Events.BUFFER_FLUSHED, () => {
this.hls?.trigger(Events.AUDIO_TRACK_SWITCHED, data);
});
hls.trigger(Events.BUFFER_FLUSHING, {
startOffset: 0,
endOffset: Number.POSITIVE_INFINITY,
type: null,
});
this.fragmentTracker.removeAllFragments();
return;
}
hls.trigger(Events.AUDIO_TRACK_SWITCHED, data);
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/loader/level-details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ export class LevelDetails {
}

get expired(): boolean {
if (this.live && this.age) {
if (this.live && this.age && this.misses < 3) {
const playlistWindowDuration = this.partEnd - this.fragmentStart;
return (
this.age >
Expand Down
5 changes: 3 additions & 2 deletions src/remux/passthrough-remuxer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,9 @@ class PassThroughRemuxer implements Remuxer {
const startDTS = getStartDTS(initData, data);
const decodeTime = startDTS === null ? timeOffset : startDTS;
if (
isInvalidInitPts(initPTS, decodeTime, timeOffset, duration) ||
(initSegment.timescale !== initPTS.timescale && accurateTimeOffset)
(accurateTimeOffset || !initPTS) &&
(isInvalidInitPts(initPTS, decodeTime, timeOffset, duration) ||
initSegment.timescale !== initPTS.timescale)
) {
initSegment.initPTS = decodeTime - timeOffset;
if (initPTS && initPTS.timescale === 1) {
Expand Down
5 changes: 4 additions & 1 deletion src/utils/level-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,10 @@ export function updateFragPTSDTS(
export function mergeDetails(
oldDetails: LevelDetails,
newDetails: LevelDetails,
): void {
) {
if (oldDetails === newDetails) {
return;
}
// Track the last initSegment processed. Initialize it to the last one on the timeline.
let currentInitSegment: Fragment | null = null;
const oldFragments = oldDetails.fragments;
Expand Down
5 changes: 4 additions & 1 deletion src/utils/rendition-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,9 @@ function searchDownAndUpList(
return -1;
}

export function useAlternateAudio(audioTrackUrl: string, hls: Hls): boolean {
export function useAlternateAudio(
audioTrackUrl: string | undefined,
hls: Hls,
): boolean {
return !!audioTrackUrl && audioTrackUrl !== hls.levels[hls.loadLevel]?.uri;
}
Loading

0 comments on commit e811aba

Please sign in to comment.