Skip to content

Commit f69a9f1

Browse files
authored
Download init segments in parallel (#7867)
* implemented parallel init segment downloading * Replace reactive data.push with single dataset assignment in timeline chart & address issue with length assignment --------- Authored-by: Kyle Seager <kyle.seager@mydirectv.com>
1 parent 971727c commit f69a9f1

8 files changed

Lines changed: 352 additions & 74 deletions

File tree

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ export class AudioStreamController extends BaseStreamController implements Netwo
201201
// (undocumented)
202202
_handleFragmentLoadProgress(data: FragLoadedData): void;
203203
// (undocumented)
204-
protected loadFragment(frag: Fragment, track: Level, targetBufferTime: number): void;
204+
protected loadFragment(frag: MediaFragment, track: Level, targetBufferTime: number): void;
205205
get nextAudioTrack(): number;
206206
// (undocumented)
207207
protected onError(event: Events.ERROR, data: ErrorData): void;
@@ -529,9 +529,9 @@ export class BaseStreamController extends TaskLoop implements NetworkComponentAP
529529
// (undocumented)
530530
protected getMaxBufferLength(levelBitrate?: number): number;
531531
// (undocumented)
532-
protected getNextFragment(pos: number, levelDetails: LevelDetails): Fragment | null;
532+
protected getNextFragment(pos: number, levelDetails: LevelDetails): MediaFragment | null;
533533
// (undocumented)
534-
protected getNextFragmentLoopLoading(frag: Fragment, levelDetails: LevelDetails, bufferInfo: BufferInfo, playlistType: PlaylistLevelType, maxBufLen: number): Fragment | null;
534+
protected getNextFragmentLoopLoading(frag: MediaFragment, levelDetails: LevelDetails, bufferInfo: BufferInfo, playlistType: PlaylistLevelType, maxBufLen: number): MediaFragment | null;
535535
// (undocumented)
536536
getNextPart(partList: Part[], frag: Fragment, targetBufferTime: number): number;
537537
// (undocumented)
@@ -547,6 +547,8 @@ export class BaseStreamController extends TaskLoop implements NetworkComponentAP
547547
// (undocumented)
548548
get inFlightFrag(): InFlightData;
549549
// (undocumented)
550+
protected initFragmentLoader: FragmentLoader;
551+
// (undocumented)
550552
protected initPTS: TimestampOffset[];
551553
// (undocumented)
552554
protected isLoopLoading(frag: Fragment, targetBufferTime: number): boolean;
@@ -563,9 +565,7 @@ export class BaseStreamController extends TaskLoop implements NetworkComponentAP
563565
// (undocumented)
564566
protected loadingParts: boolean;
565567
// (undocumented)
566-
protected _loadInitSegment(fragment: Fragment, level: Level): void;
567-
// (undocumented)
568-
protected mapToInitFragWhenRequired<T extends Fragment | null>(frag: T): T | Fragment;
568+
protected _loadInitSegment(initFrag: Fragment): Promise<void>;
569569
// (undocumented)
570570
protected media: HTMLMediaElement | null;
571571
// (undocumented)
@@ -1991,7 +1991,7 @@ export class FragmentLoader {
19911991
// (undocumented)
19921992
destroy(): void;
19931993
// (undocumented)
1994-
load(frag: Fragment, isIFrame?: boolean, onProgress?: FragmentLoadProgressCallback): Promise<FragLoadedData>;
1994+
load(frag: Fragment, isIFrame?: boolean, onProgress?: FragmentLoadProgressCallback, progressGate?: Promise<void>): Promise<FragLoadedData>;
19951995
// (undocumented)
19961996
loadPart(frag: Fragment, part: Part, onProgress: FragmentLoadProgressCallback): Promise<FragLoadedData>;
19971997
}
@@ -4905,7 +4905,7 @@ export class StreamController extends BaseStreamController implements NetworkCom
49054905
// (undocumented)
49064906
immediateLevelSwitch(): void;
49074907
// (undocumented)
4908-
protected loadFragment(frag: Fragment, level: Level, targetBufferTime: number): void;
4908+
protected loadFragment(frag: MediaFragment, level: Level, targetBufferTime: number): void;
49094909
// (undocumented)
49104910
get maxBufferLength(): number;
49114911
// (undocumented)
@@ -5000,7 +5000,7 @@ export class SubtitleStreamController extends BaseStreamController implements Ne
50005000
// (undocumented)
50015001
_handleFragmentLoadComplete(fragLoadedData: FragLoadedData): void;
50025002
// (undocumented)
5003-
protected loadFragment(frag: Fragment, level: Level, targetBufferTime: number): void;
5003+
protected loadFragment(frag: MediaFragment, level: Level, targetBufferTime: number): void;
50045004
// (undocumented)
50055005
get mediaBufferTimeRanges(): Bufferable;
50065006
// (undocumented)

src/controller/audio-stream-controller.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,7 +1032,7 @@ class AudioStreamController
10321032
}
10331033

10341034
protected loadFragment(
1035-
frag: Fragment,
1035+
frag: MediaFragment,
10361036
track: Level,
10371037
targetBufferTime: number,
10381038
) {
@@ -1045,9 +1045,7 @@ class AudioStreamController
10451045
fragState === FragmentState.NOT_LOADED ||
10461046
fragState === FragmentState.PARTIAL
10471047
) {
1048-
if (!isMediaFragment(frag)) {
1049-
this._loadInitSegment(frag, track);
1050-
} else if (track.details?.live && !this.initPTS[frag.cc]) {
1048+
if (track.details?.live && !this.initPTS[frag.cc]) {
10511049
this.log(
10521050
`Waiting for video PTS in continuity counter ${frag.cc} of live stream before loading audio fragment ${frag.sn} of level ${this.trackId}`,
10531051
);

src/controller/base-stream-controller.ts

Lines changed: 76 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ export default class BaseStreamController
119119
protected retryDate: number = 0;
120120
protected levels: Array<Level> | null = null;
121121
protected fragmentLoader: FragmentLoader;
122+
protected initFragmentLoader: FragmentLoader;
122123
protected keyLoader: KeyLoader;
123124
protected levelLastLoaded: Level | null = null;
124125
protected startFragRequested: boolean = false;
@@ -139,6 +140,7 @@ export default class BaseStreamController
139140
this.playlistType = playlistType;
140141
this.hls = hls;
141142
this.fragmentLoader = new FragmentLoader(hls.config);
143+
this.initFragmentLoader = new FragmentLoader(hls.config);
142144
this.keyLoader = keyLoader;
143145
this.fragmentTracker = fragmentTracker;
144146
this.config = hls.config;
@@ -176,6 +178,7 @@ export default class BaseStreamController
176178
return;
177179
}
178180
this.fragmentLoader.abort();
181+
this.initFragmentLoader.abort();
179182
this.keyLoader.abort(this.playlistType);
180183
const frag = this.fragCurrent;
181184
if (frag?.loader) {
@@ -511,6 +514,9 @@ export default class BaseStreamController
511514
if (this.fragmentLoader) {
512515
this.fragmentLoader.destroy();
513516
}
517+
if (this.initFragmentLoader) {
518+
this.initFragmentLoader.destroy();
519+
}
514520
if (this.keyLoader) {
515521
this.keyLoader.destroy();
516522
}
@@ -521,7 +527,8 @@ export default class BaseStreamController
521527
//@ts-ignore
522528
this.hls = this.decrypter = this.keyLoader = null;
523529
//@ts-ignore
524-
this.fragmentLoader = this.fragmentTracker = null;
530+
this.fragmentLoader = this.initFragmentLoader = this.fragmentTracker = null;
531+
525532
super.onHandlerDestroyed();
526533
}
527534

@@ -698,18 +705,24 @@ export default class BaseStreamController
698705
this.hls.trigger(Events.BUFFER_FLUSHING, flushScope);
699706
}
700707

701-
protected _loadInitSegment(fragment: Fragment, level: Level) {
702-
this._doFragLoad(fragment, level)
708+
protected _loadInitSegment(initFrag: Fragment): Promise<void> {
709+
const { hls } = this;
710+
this.initFragmentLoader.abort();
711+
hls.trigger(Events.FRAG_LOADING, { frag: initFrag, targetBufferTime: 0 });
712+
return this.initFragmentLoader
713+
.load(initFrag)
703714
.then((data) => {
704715
const frag = data?.frag;
705-
if (!frag || this.fragContextChanged(frag) || !this.levels) {
716+
if (
717+
!frag ||
718+
!fragmentsAreEqual(frag, this.fragCurrent?.initSegment) ||
719+
!this.levels
720+
) {
706721
throw new Error('init load aborted');
707722
}
708-
709723
return data;
710724
})
711725
.then((data: FragLoadedData) => {
712-
const { hls } = this;
713726
const { frag, payload } = data;
714727
const decryptData = frag.decryptdata;
715728

@@ -744,6 +757,7 @@ export default class BaseStreamController
744757
reason: err.message,
745758
frag,
746759
});
760+
this.fragmentLoader.abort();
747761
throw err;
748762
})
749763
.then((decryptedData) => {
@@ -761,29 +775,47 @@ export default class BaseStreamController
761775
});
762776
}
763777
return this.completeInitSegmentLoad(data);
764-
})
765-
.catch((reason) => {
766-
if (this.state === State.STOPPED || this.state === State.ERROR) {
767-
return;
768-
}
769-
this.warn(reason);
770-
this.resetFragmentLoading(fragment);
771778
});
772779
}
773780

774781
private completeInitSegmentLoad(data: FragLoadedData) {
775-
const { levels } = this;
776-
if (!levels) {
777-
throw new Error('init load aborted, missing levels');
778-
}
779782
const stats = data.frag.stats;
780-
if (this.state !== State.STOPPED) {
781-
this.state = State.IDLE;
782-
}
783783
data.frag.data = new Uint8Array(data.payload);
784784
stats.parsing.start = stats.buffering.start = self.performance.now();
785785
stats.parsing.end = stats.buffering.end = self.performance.now();
786-
this.tick();
786+
}
787+
788+
private loadInitSegmentIfNeeded(
789+
mediaFrag: Fragment,
790+
): Promise<void> | undefined {
791+
const { initSegment } = mediaFrag;
792+
if (
793+
!this.bitrateTest &&
794+
isMediaFragment(mediaFrag) &&
795+
initSegment &&
796+
!initSegment.data
797+
) {
798+
const initPromise =
799+
initSegment.encrypted && !initSegment.decryptdata?.key
800+
? this.keyLoader
801+
.load(initSegment)
802+
.then(() => this._loadInitSegment(initSegment))
803+
: this._loadInitSegment(initSegment);
804+
805+
return initPromise.catch((reason: LoadError | Error) => {
806+
if (this.state === State.STOPPED || this.state === State.ERROR) {
807+
throw reason;
808+
}
809+
if ('data' in reason) {
810+
reason.data.frag = mediaFrag;
811+
this.fragmentLoader.abort();
812+
this.handleFragLoadError(reason);
813+
}
814+
this.warn(reason);
815+
this.resetFragmentLoading(mediaFrag);
816+
throw reason;
817+
});
818+
}
787819
}
788820

789821
protected unhandledEncryptionError(
@@ -983,12 +1015,15 @@ export default class BaseStreamController
9831015
this.nextLoadPosition = part.start + part.duration;
9841016
this.state = State.FRAG_LOADING;
9851017
let result: Promise<PartsLoadedData | FragLoadedData | null>;
986-
if (keyLoadingPromise) {
987-
result = keyLoadingPromise
988-
.then((keyLoadedData) => {
1018+
const initDataPromise = this.loadInitSegmentIfNeeded(frag);
1019+
if (keyLoadingPromise || initDataPromise) {
1020+
const useKeyLoader = !!keyLoadingPromise;
1021+
result = Promise.all([keyLoadingPromise, initDataPromise])
1022+
.then(([keyLoadedData]) => {
9891023
if (
990-
!keyLoadedData ||
991-
this.fragContextChanged(keyLoadedData.frag)
1024+
useKeyLoader &&
1025+
(!keyLoadedData ||
1026+
this.fragContextChanged(keyLoadedData.frag))
9921027
) {
9931028
return null;
9941029
}
@@ -1057,6 +1092,9 @@ export default class BaseStreamController
10571092
// Load key before streaming fragment data
10581093
const dataOnProgress =
10591094
this.config.progressive && frag.type !== PlaylistLevelType.SUBTITLE;
1095+
1096+
const initDataPromise = this.loadInitSegmentIfNeeded(frag);
1097+
10601098
let result: Promise<PartsLoadedData | FragLoadedData | null>;
10611099
if (dataOnProgress && keyLoadingPromise) {
10621100
result = keyLoadingPromise
@@ -1068,6 +1106,7 @@ export default class BaseStreamController
10681106
frag,
10691107
this.iframesOnly,
10701108
progressCallback,
1109+
initDataPromise,
10711110
);
10721111
})
10731112
.catch((error) => this.handleFragLoadError(error));
@@ -1079,8 +1118,10 @@ export default class BaseStreamController
10791118
frag,
10801119
this.iframesOnly,
10811120
dataOnProgress ? progressCallback : undefined,
1121+
dataOnProgress ? initDataPromise : undefined,
10821122
),
10831123
keyLoadingPromise,
1124+
initDataPromise,
10841125
])
10851126
.then(([fragLoadedData]) => {
10861127
if (!dataOnProgress && progressCallback) {
@@ -1096,7 +1137,10 @@ export default class BaseStreamController
10961137
new Error(`frag load aborted, context changed in FRAG_LOADING`),
10971138
);
10981139
}
1099-
return result;
1140+
1141+
return Promise.all([result, initDataPromise]).then(
1142+
([fragData]) => fragData,
1143+
);
11001144
}
11011145

11021146
private doFragPartsLoad(
@@ -1431,7 +1475,7 @@ export default class BaseStreamController
14311475
protected getNextFragment(
14321476
pos: number,
14331477
levelDetails: LevelDetails,
1434-
): Fragment | null {
1478+
): MediaFragment | null {
14351479
const fragments = levelDetails.fragments;
14361480
const fragLen = fragments.length;
14371481

@@ -1523,7 +1567,7 @@ export default class BaseStreamController
15231567
levelDetails,
15241568
);
15251569
}
1526-
return this.mapToInitFragWhenRequired(programFrag);
1570+
return programFrag;
15271571
}
15281572

15291573
protected isLoopLoading(frag: Fragment, targetBufferTime: number): boolean {
@@ -1546,13 +1590,13 @@ export default class BaseStreamController
15461590
}
15471591

15481592
protected getNextFragmentLoopLoading(
1549-
frag: Fragment,
1593+
frag: MediaFragment,
15501594
levelDetails: LevelDetails,
15511595
bufferInfo: BufferInfo,
15521596
playlistType: PlaylistLevelType,
15531597
maxBufLen: number,
1554-
): Fragment | null {
1555-
let nextFragment: Fragment | null = null;
1598+
): MediaFragment | null {
1599+
let nextFragment: MediaFragment | null = null;
15561600
if (frag.gap) {
15571601
nextFragment = this.getNextFragment(this.nextLoadPosition, levelDetails);
15581602
if (nextFragment && !nextFragment.gap && bufferInfo.nextStart) {
@@ -1656,17 +1700,6 @@ export default class BaseStreamController
16561700
return frag;
16571701
}
16581702

1659-
protected mapToInitFragWhenRequired<T extends Fragment | null>(
1660-
frag: T,
1661-
): T | Fragment {
1662-
// If an initSegment is present, it must be buffered first
1663-
if (frag?.initSegment && !frag.initSegment.data && !this.bitrateTest) {
1664-
return frag.initSegment;
1665-
}
1666-
1667-
return frag;
1668-
}
1669-
16701703
getNextPart(
16711704
partList: Part[],
16721705
frag: Fragment,

src/controller/stream-controller.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -374,13 +374,11 @@ export default class StreamController
374374
return;
375375
}
376376

377-
frag = this.mapToInitFragWhenRequired(frag);
378-
379377
this.loadFragment(frag, levelInfo, targetBufferTime);
380378
}
381379

382380
protected loadFragment(
383-
frag: Fragment,
381+
frag: MediaFragment,
384382
level: Level,
385383
targetBufferTime: number,
386384
) {
@@ -390,9 +388,7 @@ export default class StreamController
390388
fragState === FragmentState.NOT_LOADED ||
391389
fragState === FragmentState.PARTIAL
392390
) {
393-
if (!isMediaFragment(frag)) {
394-
this._loadInitSegment(frag, level);
395-
} else if (this.bitrateTest) {
391+
if (this.bitrateTest) {
396392
this.log(
397393
`Fragment ${frag.sn} of level ${frag.level} is being downloaded to test bitrate and will not be buffered`,
398394
);

0 commit comments

Comments
 (0)