Skip to content

Commit 275ac39

Browse files
committed
Sync audio fragment loading with main discontinuity and time
1 parent e349f03 commit 275ac39

19 files changed

+399
-309
lines changed

api-extractor/report/hls.js.api.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,7 +1429,7 @@ export class Fragment extends BaseSegment {
14291429
// (undocumented)
14301430
get end(): number;
14311431
// (undocumented)
1432-
endDTS: number;
1432+
endDTS?: number;
14331433
// (undocumented)
14341434
endList?: boolean;
14351435
// (undocumented)
@@ -1469,7 +1469,7 @@ export class Fragment extends BaseSegment {
14691469
// (undocumented)
14701470
start: number;
14711471
// (undocumented)
1472-
startDTS: number;
1472+
startDTS?: number;
14731473
// (undocumented)
14741474
startPTS?: number;
14751475
// (undocumented)
@@ -1976,7 +1976,7 @@ export interface InitPTSFoundData {
19761976
// (undocumented)
19771977
frag: MediaFragment;
19781978
// (undocumented)
1979-
id: string;
1979+
id: PlaylistLevelType;
19801980
// (undocumented)
19811981
initPTS: number;
19821982
// (undocumented)

src/controller/audio-stream-controller.ts

Lines changed: 85 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import BaseStreamController, { State } from './base-stream-controller';
22
import { Events } from '../events';
3-
import { Bufferable, BufferHelper } from '../utils/buffer-helper';
43
import { FragmentState } from './fragment-tracker';
54
import { Level } from '../types/level';
65
import { PlaylistContextType, PlaylistLevelType } from '../types/loader';
@@ -13,7 +12,7 @@ import {
1312
import ChunkCache from '../demux/chunk-cache';
1413
import TransmuxerInterface from '../demux/transmuxer-interface';
1514
import { ChunkMetadata } from '../types/transmuxer';
16-
import { fragmentWithinToleranceTest } from './fragment-finders';
15+
import { findFragWithCC, findNearestWithCC } from './fragment-finders';
1716
import { alignMediaPlaylistByPDT } from '../utils/discontinuities';
1817
import { mediaAttributesIdentical } from '../utils/media-option-attributes';
1918
import { ErrorDetails } from '../errors';
@@ -39,6 +38,8 @@ import type {
3938
FragBufferedData,
4039
ErrorData,
4140
BufferFlushingData,
41+
BufferCodecsData,
42+
FragLoadingData,
4243
} from '../types/events';
4344
import type { MediaPlaylist } from '../types/media-playlist';
4445

@@ -55,9 +56,8 @@ class AudioStreamController
5556
extends BaseStreamController
5657
implements NetworkComponentAPI
5758
{
58-
private videoBuffer: Bufferable | null = null;
59-
private videoTrackCC: number = -1;
60-
private waitingVideoCC: number = -1;
59+
private videoAnchor: MediaFragment | null = null;
60+
private mainFragLoading: FragLoadingData | null = null;
6161
private bufferedTrack: MediaPlaylist | null = null;
6262
private switchingTrack: MediaPlaylist | null = null;
6363
private trackId: number = -1;
@@ -102,6 +102,7 @@ class AudioStreamController
102102
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
103103
hls.on(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);
104104
hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
105+
hls.on(Events.FRAG_LOADING, this.onFragLoading, this);
105106
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
106107
}
107108

@@ -120,6 +121,7 @@ class AudioStreamController
120121
hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
121122
hls.off(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);
122123
hls.off(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
124+
hls.on(Events.FRAG_LOADING, this.onFragLoading, this);
123125
hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
124126
}
125127

@@ -130,20 +132,44 @@ class AudioStreamController
130132
) {
131133
// Always update the new INIT PTS
132134
// Can change due level switch
133-
if (id === 'main') {
135+
if (id === PlaylistLevelType.MAIN) {
134136
const cc = frag.cc;
135-
this.initPTS[frag.cc] = { baseTime: initPTS, timescale };
137+
const inFlightFrag = this.fragCurrent;
138+
this.initPTS[cc] = { baseTime: initPTS, timescale };
136139
this.log(
137140
`InitPTS for cc: ${cc} found from main: ${initPTS}/${timescale}`,
138141
);
139-
this.videoTrackCC = cc;
142+
this.videoAnchor = frag;
140143
// If we are waiting, tick immediately to unblock audio fragment transmuxing
141144
if (this.state === State.WAITING_INIT_PTS) {
145+
const waitingData = this.waitingData;
146+
if (!waitingData || waitingData.frag.cc !== cc) {
147+
this.nextLoadPosition = this.findSyncFrag(frag).start;
148+
}
142149
this.tick();
150+
} else if (
151+
!this.loadedmetadata &&
152+
inFlightFrag &&
153+
inFlightFrag.cc !== cc
154+
) {
155+
this.startFragRequested = false;
156+
this.nextLoadPosition = this.findSyncFrag(frag).start;
157+
inFlightFrag.abortRequests();
158+
this.resetLoadingState();
143159
}
144160
}
145161
}
146162

163+
private findSyncFrag(mainFrag: MediaFragment): MediaFragment {
164+
const trackDetails = this.getLevelDetails();
165+
const cc = mainFrag.cc;
166+
return (
167+
findNearestWithCC(trackDetails, cc, mainFrag) ||
168+
(trackDetails && findFragWithCC(trackDetails.fragments, cc)) ||
169+
mainFrag
170+
);
171+
}
172+
147173
startLoad(startPosition: number) {
148174
if (!this.levels) {
149175
this.startPosition = startPosition;
@@ -206,9 +232,9 @@ class AudioStreamController
206232
const waitingData = this.waitingData;
207233
if (waitingData) {
208234
const { frag, part, cache, complete } = waitingData;
235+
const videoAnchor = this.videoAnchor;
209236
if (this.initPTS[frag.cc] !== undefined) {
210237
this.waitingData = null;
211-
this.waitingVideoCC = -1;
212238
this.state = State.FRAG_LOADING;
213239
const payload = cache.flush();
214240
const data: FragLoadedData = {
@@ -221,33 +247,15 @@ class AudioStreamController
221247
if (complete) {
222248
super._handleFragmentLoadComplete(data);
223249
}
224-
} else if (this.videoTrackCC !== this.waitingVideoCC) {
250+
} else if (videoAnchor && videoAnchor.cc !== waitingData.frag.cc) {
225251
// Drop waiting fragment if videoTrackCC has changed since waitingFragment was set and initPTS was not found
226252
this.log(
227-
`Waiting fragment cc (${frag.cc}) cancelled because video is at cc ${this.videoTrackCC}`,
253+
`Waiting fragment cc (${frag.cc}) cancelled because video is at cc ${videoAnchor.cc}`,
228254
);
255+
this.nextLoadPosition = this.findSyncFrag(videoAnchor).start;
229256
this.clearWaitingFragment();
230-
} else {
231-
// Drop waiting fragment if an earlier fragment is needed
232-
const pos = this.getLoadPosition();
233-
const bufferInfo = BufferHelper.bufferInfo(
234-
this.mediaBuffer,
235-
pos,
236-
this.config.maxBufferHole,
237-
);
238-
const waitingFragmentAtPosition = fragmentWithinToleranceTest(
239-
bufferInfo.end,
240-
this.config.maxFragLookUpTolerance,
241-
frag,
242-
);
243-
if (waitingFragmentAtPosition < 0) {
244-
this.log(
245-
`Waiting fragment cc (${frag.cc}) @ ${frag.start} cancelled because another fragment at ${bufferInfo.end} is needed`,
246-
);
247-
this.clearWaitingFragment();
248-
}
249257
}
250-
} else {
258+
} else if (this.state !== State.STOPPED) {
251259
this.state = State.IDLE;
252260
}
253261
}
@@ -259,9 +267,12 @@ class AudioStreamController
259267
clearWaitingFragment() {
260268
const waitingData = this.waitingData;
261269
if (waitingData) {
270+
if (!this.loadedmetadata) {
271+
// Load overlapping fragment on start when discontinuity start times are not aligned
272+
this.startFragRequested = false;
273+
}
262274
this.fragmentTracker.removeFragment(waitingData.frag);
263275
this.waitingData = null;
264-
this.waitingVideoCC = -1;
265276
if (this.state !== State.STOPPED) {
266277
this.state = State.IDLE;
267278
}
@@ -343,12 +354,11 @@ class AudioStreamController
343354

344355
const fragments = trackDetails.fragments;
345356
const start = fragments[0].start;
346-
let targetBufferTime = this.flushing
347-
? this.getLoadPosition()
348-
: bufferInfo.end;
357+
const loadPosition = this.getLoadPosition();
358+
let targetBufferTime = this.flushing ? loadPosition : bufferInfo.end;
349359

350360
if (switchingTrack && media) {
351-
const pos = this.getLoadPosition();
361+
const pos = loadPosition;
352362
// STABLE
353363
if (
354364
bufferedTrack &&
@@ -378,10 +388,8 @@ class AudioStreamController
378388
}
379389

380390
let frag = this.getNextFragment(targetBufferTime, trackDetails);
381-
let atGap = false;
382391
// Avoid loop loading by using nextLoadPosition set for backtracking and skipping consecutive GAP tags
383392
if (frag && this.isLoopLoading(frag, targetBufferTime)) {
384-
atGap = !!frag.gap;
385393
frag = this.getNextFragmentLoopLoading(
386394
frag,
387395
trackDetails,
@@ -395,39 +403,26 @@ class AudioStreamController
395403
return;
396404
}
397405

398-
if (!trackDetails.live || targetBufferTime < this.hls.liveSyncPosition!) {
406+
if (
407+
this.startFragRequested &&
408+
(!trackDetails.live || targetBufferTime < this.hls.liveSyncPosition!)
409+
) {
399410
// Request audio segments up to one fragment ahead of main buffer
400-
const mainBufferInfo = this.getFwdBufferInfo(
401-
this.videoBuffer ? this.videoBuffer : this.media,
402-
PlaylistLevelType.MAIN,
403-
);
411+
const mainFragLoading = this.mainFragLoading;
412+
const mainTargetBufferEnd = mainFragLoading
413+
? (mainFragLoading.part || mainFragLoading.frag).end
414+
: null;
404415
const atBufferSyncLimit =
405-
!!mainBufferInfo && frag.start > mainBufferInfo.end + frag.duration;
406-
if (atBufferSyncLimit) {
407-
// Check fragment-tracker for main fragments since GAP segments do not show up in bufferInfo
408-
const mainFrag = this.fragmentTracker.getFragAtPos(
409-
frag.start,
410-
PlaylistLevelType.MAIN,
411-
);
412-
if (mainFrag === null) {
413-
return;
414-
}
415-
// Bridge gaps in main buffer (also prevents loop loading at gaps)
416-
atGap ||= !!mainFrag.gap || mainBufferInfo.len === 0;
417-
if (
418-
!atGap ||
419-
(bufferInfo.nextStart && bufferInfo.nextStart < mainFrag.end)
420-
) {
421-
return;
422-
}
416+
mainTargetBufferEnd !== null && frag.start > mainTargetBufferEnd;
417+
if (atBufferSyncLimit && !frag.endList) {
418+
return;
423419
}
424420
}
425421

426422
this.loadFragment(frag, levelInfo, targetBufferTime);
427423
}
428424

429425
protected onMediaDetaching() {
430-
this.videoBuffer = null;
431426
this.bufferFlushed = this.flushing = false;
432427
super.onMediaDetaching();
433428
}
@@ -477,18 +472,16 @@ class AudioStreamController
477472
}
478473

479474
protected onManifestLoading() {
480-
this.fragmentTracker.removeAllFragments();
481-
this.startPosition = this.lastCurrentTime = 0;
475+
super.onManifestLoading();
482476
this.bufferFlushed = this.flushing = false;
483-
this.levels =
484-
this.mainDetails =
477+
this.mainDetails =
485478
this.waitingData =
479+
this.videoAnchor =
486480
this.bufferedTrack =
487481
this.cachedTrackLoadedData =
488482
this.switchingTrack =
489483
null;
490-
this.startFragRequested = false;
491-
this.trackId = this.videoTrackCC = this.waitingVideoCC = -1;
484+
this.trackId = -1;
492485
}
493486

494487
private onLevelLoaded(event: Events.LEVEL_LOADED, data: LevelLoadedData) {
@@ -643,7 +636,6 @@ class AudioStreamController
643636
complete: false,
644637
});
645638
cache.push(new Uint8Array(payload));
646-
this.waitingVideoCC = this.videoTrackCC;
647639
this.state = State.WAITING_INIT_PTS;
648640
}
649641
}
@@ -658,7 +650,7 @@ class AudioStreamController
658650

659651
private onBufferReset(/* event: Events.BUFFER_RESET */) {
660652
// reset reference to sourcebuffers
661-
this.mediaBuffer = this.videoBuffer = null;
653+
this.mediaBuffer = null;
662654
this.loadedmetadata = false;
663655
}
664656

@@ -670,21 +662,30 @@ class AudioStreamController
670662
if (audioTrack) {
671663
this.mediaBuffer = audioTrack.buffer || null;
672664
}
673-
if (data.tracks.video) {
674-
this.videoBuffer = data.tracks.video.buffer || null;
665+
}
666+
667+
private onFragLoading(event: Events.FRAG_LOADING, data: FragLoadingData) {
668+
if (
669+
data.frag.type === PlaylistLevelType.MAIN &&
670+
data.frag.sn !== 'initSegment'
671+
) {
672+
this.mainFragLoading = data;
673+
if (this.state === State.IDLE) {
674+
this.tick();
675+
}
675676
}
676677
}
677678

678679
private onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData) {
679680
const { frag, part } = data;
680681
if (frag.type !== PlaylistLevelType.AUDIO) {
681682
if (!this.loadedmetadata && frag.type === PlaylistLevelType.MAIN) {
682-
const bufferable = this.videoBuffer || this.media;
683-
if (bufferable) {
684-
const bufferedTimeRanges = BufferHelper.getBuffered(bufferable);
685-
if (bufferedTimeRanges.length) {
686-
this.loadedmetadata = true;
687-
}
683+
const bufferedState = this.fragmentTracker.getState(frag);
684+
if (
685+
bufferedState === FragmentState.OK ||
686+
bufferedState === FragmentState.PARTIAL
687+
) {
688+
this.loadedmetadata = true;
688689
}
689690
}
690691
return;
@@ -889,12 +890,15 @@ class AudioStreamController
889890
if (tracks.video) {
890891
delete tracks.video;
891892
}
893+
if (tracks.audiovideo) {
894+
delete tracks.audiovideo;
895+
}
892896

893897
// include levelCodec in audio and video tracks
894-
const track = tracks.audio;
895-
if (!track) {
898+
if (!tracks.audio) {
896899
return;
897900
}
901+
const track = tracks.audio;
898902

899903
track.id = 'audio';
900904

@@ -906,7 +910,7 @@ class AudioStreamController
906910
if (variantAudioCodecs && variantAudioCodecs.split(',').length === 1) {
907911
track.levelCodec = variantAudioCodecs;
908912
}
909-
this.hls.trigger(Events.BUFFER_CODECS, tracks);
913+
this.hls.trigger(Events.BUFFER_CODECS, tracks as BufferCodecsData);
910914
const initSegment = track.initSegment;
911915
if (initSegment?.byteLength) {
912916
const segment: BufferAppendingData = {
@@ -930,7 +934,6 @@ class AudioStreamController
930934
) {
931935
// only load if fragment is not loaded or if in audio switch
932936
const fragState = this.fragmentTracker.getState(frag);
933-
this.fragCurrent = frag;
934937

935938
// we force a frag loading in audio switch as fragment tracker might not have evicted previous frags in case of quick audio switch
936939
if (
@@ -953,7 +956,6 @@ class AudioStreamController
953956
alignMediaPlaylistByPDT(track.details, mainDetails);
954957
}
955958
} else {
956-
this.startFragRequested = true;
957959
super.loadFragment(frag, track, targetBufferTime);
958960
}
959961
} else {

0 commit comments

Comments
 (0)