Skip to content

Commit e811aba

Browse files
authored
Merge pull request #6922 from video-dev/bugfix/live-av-desync-init-pts
- 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)
2 parents 699901b + 607a6ae commit e811aba

9 files changed

+322
-48
lines changed

src/controller/audio-stream-controller.ts

+27-18
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
alignMediaPlaylistByPDT,
1515
} from '../utils/discontinuities';
1616
import { mediaAttributesIdentical } from '../utils/media-option-attributes';
17+
import { useAlternateAudio } from '../utils/rendition-helper';
1718
import type { FragmentTracker } from './fragment-tracker';
1819
import type Hls from '../hls';
1920
import type { Fragment, MediaFragment, Part } from '../loader/fragment';
@@ -519,7 +520,7 @@ class AudioStreamController
519520
const cachedTrackLoadedData = this.cachedTrackLoadedData;
520521
if (cachedTrackLoadedData) {
521522
this.cachedTrackLoadedData = null;
522-
this.hls.trigger(Events.AUDIO_TRACK_LOADED, cachedTrackLoadedData);
523+
this.onAudioTrackLoaded(Events.AUDIO_TRACK_LOADED, cachedTrackLoadedData);
523524
}
524525
}
525526

@@ -528,25 +529,28 @@ class AudioStreamController
528529
data: TrackLoadedData,
529530
) {
530531
const { levels } = this;
531-
const { details: newDetails, id: trackId } = data;
532+
const { details: newDetails, id: trackId, groupId, track } = data;
533+
if (!levels) {
534+
this.warn(
535+
`Audio tracks reset while loading track ${trackId} "${track.name}" of "${groupId}"`,
536+
);
537+
return;
538+
}
532539
const mainDetails = this.mainDetails;
533540
if (
534541
!mainDetails ||
535-
mainDetails.expired ||
536-
newDetails.endCC > mainDetails.endCC
542+
newDetails.endCC > mainDetails.endCC ||
543+
mainDetails.expired
537544
) {
538545
this.cachedTrackLoadedData = data;
539546
if (this.state !== State.STOPPED) {
540547
this.state = State.WAITING_TRACK;
541548
}
542549
return;
543550
}
544-
if (!levels) {
545-
this.warn(`Audio tracks were reset while loading level ${trackId}`);
546-
return;
547-
}
551+
this.cachedTrackLoadedData = null;
548552
this.log(
549-
`Audio track ${trackId} loaded [${newDetails.startSN},${
553+
`Audio track ${trackId} "${track.name}" of "${groupId}" loaded [${newDetails.startSN},${
550554
newDetails.endSN
551555
}]${
552556
newDetails.lastPartSn
@@ -555,18 +559,18 @@ class AudioStreamController
555559
},duration:${newDetails.totalduration}`,
556560
);
557561

558-
const track = levels[trackId];
562+
const trackLevel = levels[trackId];
559563
let sliding = 0;
560-
if (newDetails.live || track.details?.live) {
564+
if (newDetails.live || trackLevel.details?.live) {
561565
this.checkLiveUpdate(newDetails);
562566
if (newDetails.deltaUpdateFailed) {
563567
return;
564568
}
565569

566-
if (track.details) {
570+
if (trackLevel.details) {
567571
sliding = this.alignPlaylists(
568572
newDetails,
569-
track.details,
573+
trackLevel.details,
570574
this.levelLastLoaded?.details,
571575
);
572576
}
@@ -580,8 +584,8 @@ class AudioStreamController
580584
sliding = newDetails.fragmentStart;
581585
}
582586
}
583-
track.details = newDetails;
584-
this.levelLastLoaded = track;
587+
trackLevel.details = newDetails;
588+
this.levelLastLoaded = trackLevel;
585589

586590
// compute start position if we are aligned with the main playlist
587591
if (!this.startFragRequested) {
@@ -1026,9 +1030,14 @@ class AudioStreamController
10261030
bufferedTrack.name !== switchingTrack.name ||
10271031
bufferedTrack.lang !== switchingTrack.lang)
10281032
) {
1029-
this.log('Switching audio track : flushing all audio');
1030-
super.flushMainBuffer(0, Number.POSITIVE_INFINITY, 'audio');
1031-
this.bufferedTrack = null;
1033+
if (useAlternateAudio(switchingTrack.url, this.hls)) {
1034+
this.log('Switching audio track : flushing all audio');
1035+
super.flushMainBuffer(0, Number.POSITIVE_INFINITY, 'audio');
1036+
this.bufferedTrack = null;
1037+
} else {
1038+
// Main is being buffered. Set bufferedTrack so that it is flushed when switching back to alt-audio
1039+
this.bufferedTrack = switchingTrack;
1040+
}
10321041
}
10331042
}
10341043

src/controller/base-stream-controller.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1308,7 +1308,9 @@ export default class BaseStreamController
13081308
const mainStart = this.hls.startPosition;
13091309
const liveSyncPosition = this.hls.liveSyncPosition;
13101310
const startPosition = frag
1311-
? (mainStart !== -1 ? mainStart : liveSyncPosition) || frag.start
1311+
? (mainStart !== -1 && mainStart >= start
1312+
? mainStart
1313+
: liveSyncPosition) || frag.start
13121314
: pos;
13131315
this.log(
13141316
`Setting startPosition to ${startPosition} to match initial live edge. mainStart: ${mainStart} liveSyncPosition: ${liveSyncPosition} frag.start: ${frag?.start}`,

src/controller/stream-controller.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -882,12 +882,16 @@ export default class StreamController
882882
}
883883
// If switching from alt to main audio, flush all audio and trigger track switched
884884
if (fromAltAudio) {
885+
this.fragmentTracker.removeAllFragments();
886+
hls.once(Events.BUFFER_FLUSHED, () => {
887+
this.hls?.trigger(Events.AUDIO_TRACK_SWITCHED, data);
888+
});
885889
hls.trigger(Events.BUFFER_FLUSHING, {
886890
startOffset: 0,
887891
endOffset: Number.POSITIVE_INFINITY,
888892
type: null,
889893
});
890-
this.fragmentTracker.removeAllFragments();
894+
return;
891895
}
892896
hls.trigger(Events.AUDIO_TRACK_SWITCHED, data);
893897
} else {

src/loader/level-details.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ export class LevelDetails {
179179
}
180180

181181
get expired(): boolean {
182-
if (this.live && this.age) {
182+
if (this.live && this.age && this.misses < 3) {
183183
const playlistWindowDuration = this.partEnd - this.fragmentStart;
184184
return (
185185
this.age >

src/remux/passthrough-remuxer.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,9 @@ class PassThroughRemuxer implements Remuxer {
184184
const startDTS = getStartDTS(initData, data);
185185
const decodeTime = startDTS === null ? timeOffset : startDTS;
186186
if (
187-
isInvalidInitPts(initPTS, decodeTime, timeOffset, duration) ||
188-
(initSegment.timescale !== initPTS.timescale && accurateTimeOffset)
187+
(accurateTimeOffset || !initPTS) &&
188+
(isInvalidInitPts(initPTS, decodeTime, timeOffset, duration) ||
189+
initSegment.timescale !== initPTS.timescale)
189190
) {
190191
initSegment.initPTS = decodeTime - timeOffset;
191192
if (initPTS && initPTS.timescale === 1) {

src/utils/level-helper.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,10 @@ export function updateFragPTSDTS(
136136
export function mergeDetails(
137137
oldDetails: LevelDetails,
138138
newDetails: LevelDetails,
139-
): void {
139+
) {
140+
if (oldDetails === newDetails) {
141+
return;
142+
}
140143
// Track the last initSegment processed. Initialize it to the last one on the timeline.
141144
let currentInitSegment: Fragment | null = null;
142145
const oldFragments = oldDetails.fragments;

src/utils/rendition-helper.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,9 @@ function searchDownAndUpList(
502502
return -1;
503503
}
504504

505-
export function useAlternateAudio(audioTrackUrl: string, hls: Hls): boolean {
505+
export function useAlternateAudio(
506+
audioTrackUrl: string | undefined,
507+
hls: Hls,
508+
): boolean {
506509
return !!audioTrackUrl && audioTrackUrl !== hls.levels[hls.loadLevel]?.uri;
507510
}

0 commit comments

Comments
 (0)