Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions api-extractor/report/hls.js.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ export class AudioStreamController extends BaseStreamController implements Netwo
// (undocumented)
_handleFragmentLoadProgress(data: FragLoadedData): void;
// (undocumented)
protected loadFragment(frag: Fragment, track: Level, targetBufferTime: number): void;
protected loadFragment(frag: MediaFragment, track: Level, targetBufferTime: number): void;
get nextAudioTrack(): number;
// (undocumented)
protected onError(event: Events.ERROR, data: ErrorData): void;
Expand Down Expand Up @@ -529,9 +529,9 @@ export class BaseStreamController extends TaskLoop implements NetworkComponentAP
// (undocumented)
protected getMaxBufferLength(levelBitrate?: number): number;
// (undocumented)
protected getNextFragment(pos: number, levelDetails: LevelDetails): Fragment | null;
protected getNextFragment(pos: number, levelDetails: LevelDetails): MediaFragment | null;
// (undocumented)
protected getNextFragmentLoopLoading(frag: Fragment, levelDetails: LevelDetails, bufferInfo: BufferInfo, playlistType: PlaylistLevelType, maxBufLen: number): Fragment | null;
protected getNextFragmentLoopLoading(frag: MediaFragment, levelDetails: LevelDetails, bufferInfo: BufferInfo, playlistType: PlaylistLevelType, maxBufLen: number): MediaFragment | null;
// (undocumented)
getNextPart(partList: Part[], frag: Fragment, targetBufferTime: number): number;
// (undocumented)
Expand All @@ -547,6 +547,8 @@ export class BaseStreamController extends TaskLoop implements NetworkComponentAP
// (undocumented)
get inFlightFrag(): InFlightData;
// (undocumented)
protected initFragmentLoader: FragmentLoader;
// (undocumented)
protected initPTS: TimestampOffset[];
// (undocumented)
protected isLoopLoading(frag: Fragment, targetBufferTime: number): boolean;
Expand All @@ -563,9 +565,7 @@ export class BaseStreamController extends TaskLoop implements NetworkComponentAP
// (undocumented)
protected loadingParts: boolean;
// (undocumented)
protected _loadInitSegment(fragment: Fragment, level: Level): void;
// (undocumented)
protected mapToInitFragWhenRequired<T extends Fragment | null>(frag: T): T | Fragment;
protected _loadInitSegment(initFrag: Fragment): Promise<void>;
// (undocumented)
protected media: HTMLMediaElement | null;
// (undocumented)
Expand Down Expand Up @@ -1991,7 +1991,7 @@ export class FragmentLoader {
// (undocumented)
destroy(): void;
// (undocumented)
load(frag: Fragment, isIFrame?: boolean, onProgress?: FragmentLoadProgressCallback): Promise<FragLoadedData>;
load(frag: Fragment, isIFrame?: boolean, onProgress?: FragmentLoadProgressCallback, progressGate?: Promise<void>): Promise<FragLoadedData>;
// (undocumented)
loadPart(frag: Fragment, part: Part, onProgress: FragmentLoadProgressCallback): Promise<FragLoadedData>;
}
Expand Down Expand Up @@ -4905,7 +4905,7 @@ export class StreamController extends BaseStreamController implements NetworkCom
// (undocumented)
immediateLevelSwitch(): void;
// (undocumented)
protected loadFragment(frag: Fragment, level: Level, targetBufferTime: number): void;
protected loadFragment(frag: MediaFragment, level: Level, targetBufferTime: number): void;
// (undocumented)
get maxBufferLength(): number;
// (undocumented)
Expand Down Expand Up @@ -5000,7 +5000,7 @@ export class SubtitleStreamController extends BaseStreamController implements Ne
// (undocumented)
_handleFragmentLoadComplete(fragLoadedData: FragLoadedData): void;
// (undocumented)
protected loadFragment(frag: Fragment, level: Level, targetBufferTime: number): void;
protected loadFragment(frag: MediaFragment, level: Level, targetBufferTime: number): void;
// (undocumented)
get mediaBufferTimeRanges(): Bufferable;
// (undocumented)
Expand Down
6 changes: 2 additions & 4 deletions src/controller/audio-stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1032,7 +1032,7 @@ class AudioStreamController
}

protected loadFragment(
frag: Fragment,
frag: MediaFragment,
track: Level,
targetBufferTime: number,
) {
Expand All @@ -1045,9 +1045,7 @@ class AudioStreamController
fragState === FragmentState.NOT_LOADED ||
fragState === FragmentState.PARTIAL
) {
if (!isMediaFragment(frag)) {
this._loadInitSegment(frag, track);
} else if (track.details?.live && !this.initPTS[frag.cc]) {
if (track.details?.live && !this.initPTS[frag.cc]) {
this.log(
`Waiting for video PTS in continuity counter ${frag.cc} of live stream before loading audio fragment ${frag.sn} of level ${this.trackId}`,
);
Expand Down
119 changes: 76 additions & 43 deletions src/controller/base-stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export default class BaseStreamController
protected retryDate: number = 0;
protected levels: Array<Level> | null = null;
protected fragmentLoader: FragmentLoader;
protected initFragmentLoader: FragmentLoader;
protected keyLoader: KeyLoader;
protected levelLastLoaded: Level | null = null;
protected startFragRequested: boolean = false;
Expand All @@ -139,6 +140,7 @@ export default class BaseStreamController
this.playlistType = playlistType;
this.hls = hls;
this.fragmentLoader = new FragmentLoader(hls.config);
this.initFragmentLoader = new FragmentLoader(hls.config);
this.keyLoader = keyLoader;
this.fragmentTracker = fragmentTracker;
this.config = hls.config;
Expand Down Expand Up @@ -176,6 +178,7 @@ export default class BaseStreamController
return;
}
this.fragmentLoader.abort();
this.initFragmentLoader.abort();
this.keyLoader.abort(this.playlistType);
const frag = this.fragCurrent;
if (frag?.loader) {
Expand Down Expand Up @@ -511,6 +514,9 @@ export default class BaseStreamController
if (this.fragmentLoader) {
this.fragmentLoader.destroy();
}
if (this.initFragmentLoader) {
this.initFragmentLoader.destroy();
}
if (this.keyLoader) {
this.keyLoader.destroy();
}
Expand All @@ -521,7 +527,8 @@ export default class BaseStreamController
//@ts-ignore
this.hls = this.decrypter = this.keyLoader = null;
//@ts-ignore
this.fragmentLoader = this.fragmentTracker = null;
this.fragmentLoader = this.initFragmentLoader = this.fragmentTracker = null;

super.onHandlerDestroyed();
}

Expand Down Expand Up @@ -698,18 +705,24 @@ export default class BaseStreamController
this.hls.trigger(Events.BUFFER_FLUSHING, flushScope);
}

protected _loadInitSegment(fragment: Fragment, level: Level) {
this._doFragLoad(fragment, level)
protected _loadInitSegment(initFrag: Fragment): Promise<void> {
const { hls } = this;
this.initFragmentLoader.abort();
hls.trigger(Events.FRAG_LOADING, { frag: initFrag, targetBufferTime: 0 });
return this.initFragmentLoader
.load(initFrag)
.then((data) => {
const frag = data?.frag;
if (!frag || this.fragContextChanged(frag) || !this.levels) {
if (
!frag ||
!fragmentsAreEqual(frag, this.fragCurrent?.initSegment) ||
!this.levels
) {
throw new Error('init load aborted');
}

return data;
})
.then((data: FragLoadedData) => {
const { hls } = this;
const { frag, payload } = data;
const decryptData = frag.decryptdata;

Expand Down Expand Up @@ -744,6 +757,7 @@ export default class BaseStreamController
reason: err.message,
frag,
});
this.fragmentLoader.abort();
throw err;
})
.then((decryptedData) => {
Expand All @@ -761,29 +775,47 @@ export default class BaseStreamController
});
}
return this.completeInitSegmentLoad(data);
})
.catch((reason) => {
if (this.state === State.STOPPED || this.state === State.ERROR) {
return;
}
this.warn(reason);
this.resetFragmentLoading(fragment);
});
}

private completeInitSegmentLoad(data: FragLoadedData) {
const { levels } = this;
if (!levels) {
throw new Error('init load aborted, missing levels');
}
const stats = data.frag.stats;
if (this.state !== State.STOPPED) {
this.state = State.IDLE;
}
data.frag.data = new Uint8Array(data.payload);
stats.parsing.start = stats.buffering.start = self.performance.now();
stats.parsing.end = stats.buffering.end = self.performance.now();
this.tick();
}

private loadInitSegmentIfNeeded(
mediaFrag: Fragment,
): Promise<void> | undefined {
const { initSegment } = mediaFrag;
if (
!this.bitrateTest &&
isMediaFragment(mediaFrag) &&
initSegment &&
!initSegment.data
) {
const initPromise =
initSegment.encrypted && !initSegment.decryptdata?.key
? this.keyLoader
.load(initSegment)
.then(() => this._loadInitSegment(initSegment))
: this._loadInitSegment(initSegment);

return initPromise.catch((reason: LoadError | Error) => {
if (this.state === State.STOPPED || this.state === State.ERROR) {
throw reason;
}
if ('data' in reason) {
reason.data.frag = mediaFrag;
this.fragmentLoader.abort();
this.handleFragLoadError(reason);
}
this.warn(reason);
this.resetFragmentLoading(mediaFrag);
throw reason;
});
}
}

protected unhandledEncryptionError(
Expand Down Expand Up @@ -983,12 +1015,15 @@ export default class BaseStreamController
this.nextLoadPosition = part.start + part.duration;
this.state = State.FRAG_LOADING;
let result: Promise<PartsLoadedData | FragLoadedData | null>;
if (keyLoadingPromise) {
result = keyLoadingPromise
.then((keyLoadedData) => {
const initDataPromise = this.loadInitSegmentIfNeeded(frag);
if (keyLoadingPromise || initDataPromise) {
const useKeyLoader = !!keyLoadingPromise;
result = Promise.all([keyLoadingPromise, initDataPromise])
.then(([keyLoadedData]) => {
if (
!keyLoadedData ||
this.fragContextChanged(keyLoadedData.frag)
useKeyLoader &&
(!keyLoadedData ||
this.fragContextChanged(keyLoadedData.frag))
) {
return null;
}
Expand Down Expand Up @@ -1057,6 +1092,9 @@ export default class BaseStreamController
// Load key before streaming fragment data
const dataOnProgress =
this.config.progressive && frag.type !== PlaylistLevelType.SUBTITLE;

const initDataPromise = this.loadInitSegmentIfNeeded(frag);

let result: Promise<PartsLoadedData | FragLoadedData | null>;
if (dataOnProgress && keyLoadingPromise) {
result = keyLoadingPromise
Expand All @@ -1068,6 +1106,7 @@ export default class BaseStreamController
frag,
this.iframesOnly,
progressCallback,
initDataPromise,
);
})
.catch((error) => this.handleFragLoadError(error));
Expand All @@ -1079,8 +1118,10 @@ export default class BaseStreamController
frag,
this.iframesOnly,
dataOnProgress ? progressCallback : undefined,
dataOnProgress ? initDataPromise : undefined,
),
keyLoadingPromise,
initDataPromise,
])
.then(([fragLoadedData]) => {
if (!dataOnProgress && progressCallback) {
Expand All @@ -1096,7 +1137,10 @@ export default class BaseStreamController
new Error(`frag load aborted, context changed in FRAG_LOADING`),
);
}
return result;

return Promise.all([result, initDataPromise]).then(
([fragData]) => fragData,
);
}

private doFragPartsLoad(
Expand Down Expand Up @@ -1431,7 +1475,7 @@ export default class BaseStreamController
protected getNextFragment(
pos: number,
levelDetails: LevelDetails,
): Fragment | null {
): MediaFragment | null {
const fragments = levelDetails.fragments;
const fragLen = fragments.length;

Expand Down Expand Up @@ -1523,7 +1567,7 @@ export default class BaseStreamController
levelDetails,
);
}
return this.mapToInitFragWhenRequired(programFrag);
return programFrag;
}

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

protected getNextFragmentLoopLoading(
frag: Fragment,
frag: MediaFragment,
levelDetails: LevelDetails,
bufferInfo: BufferInfo,
playlistType: PlaylistLevelType,
maxBufLen: number,
): Fragment | null {
let nextFragment: Fragment | null = null;
): MediaFragment | null {
let nextFragment: MediaFragment | null = null;
if (frag.gap) {
nextFragment = this.getNextFragment(this.nextLoadPosition, levelDetails);
if (nextFragment && !nextFragment.gap && bufferInfo.nextStart) {
Expand Down Expand Up @@ -1656,17 +1700,6 @@ export default class BaseStreamController
return frag;
}

protected mapToInitFragWhenRequired<T extends Fragment | null>(
frag: T,
): T | Fragment {
// If an initSegment is present, it must be buffered first
if (frag?.initSegment && !frag.initSegment.data && !this.bitrateTest) {
return frag.initSegment;
}

return frag;
}

getNextPart(
partList: Part[],
frag: Fragment,
Expand Down
8 changes: 2 additions & 6 deletions src/controller/stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,13 +374,11 @@ export default class StreamController
return;
}

frag = this.mapToInitFragWhenRequired(frag);

this.loadFragment(frag, levelInfo, targetBufferTime);
}

protected loadFragment(
frag: Fragment,
frag: MediaFragment,
level: Level,
targetBufferTime: number,
) {
Expand All @@ -390,9 +388,7 @@ export default class StreamController
fragState === FragmentState.NOT_LOADED ||
fragState === FragmentState.PARTIAL
) {
if (!isMediaFragment(frag)) {
this._loadInitSegment(frag, level);
} else if (this.bitrateTest) {
if (this.bitrateTest) {
this.log(
`Fragment ${frag.sn} of level ${frag.level} is being downloaded to test bitrate and will not be buffered`,
);
Expand Down
Loading
Loading