feat(core): implement preload for HLS media (#1125)

This commit is contained in:
Wesley Luyten
2026-03-26 14:59:04 -05:00
committed by GitHub
parent 17a73cb3dd
commit 9142841d34
2 changed files with 55 additions and 8 deletions
+55 -4
View File
@@ -1,5 +1,4 @@
import Hls from 'hls.js';
import { type Delegate, DelegateMixin } from '../../../core/media/delegate';
import { CustomVideoElement } from '../custom-media-element';
import { VideoProxy } from '../proxy';
@@ -9,6 +8,7 @@ export { Hls };
export type PlaybackType = (typeof PlaybackTypes)[keyof typeof PlaybackTypes];
export type SourceType = (typeof SourceTypes)[keyof typeof SourceTypes];
export type PreloadType = '' | 'none' | 'metadata' | 'auto';
export const PlaybackTypes = {
MSE: 'mse',
@@ -26,22 +26,28 @@ const defaultConfig = {
liveDurationInfinity: true,
capLevelToPlayerSize: true,
capLevelOnFPSDrop: true,
// Disable auto quality level/fragment loading (preload).
autoStartLoad: false,
};
export class HlsMediaDelegateBase implements Delegate {
#target: EventTarget | null = null;
#target: HTMLMediaElement | null = null;
#engine: Hls | null = null;
#loadRequested?: Promise<void> | null;
#src: string = '';
#debug: boolean = false;
#type: SourceType | undefined;
#preferPlayback: PlaybackType | undefined = 'mse';
#preloadOnPlayAbort?: AbortController;
#defaultMaxBufferLength = 0;
#defaultMaxBufferSize = 0;
constructor() {
this.#initialize();
}
#initialize(): void {
this.#preloadOnPlayAbort?.abort();
this.#engine?.destroy();
this.#engine = null;
@@ -53,6 +59,8 @@ export class HlsMediaDelegateBase implements Delegate {
...defaultConfig,
debug: this.#debug,
});
this.#defaultMaxBufferLength = this.#engine.config.maxBufferLength;
this.#defaultMaxBufferSize = this.#engine.config.maxBufferSize;
if (this.#target) {
this.#engine.attachMedia(this.#target as HTMLMediaElement);
@@ -73,6 +81,16 @@ export class HlsMediaDelegateBase implements Delegate {
return this.#engine;
}
get preload(): PreloadType {
return this.#target?.preload || 'metadata';
}
set preload(value: PreloadType) {
if (!this.#target || this.#target.preload === value) return;
this.#target.preload = value;
this.#updatePreload();
}
/** Explicit source type. When unset, inferred from the source URL extension. */
get type(): SourceType | undefined {
return this.#type ?? inferSourceType(this.#src);
@@ -129,26 +147,59 @@ export class HlsMediaDelegateBase implements Delegate {
load(): void {
if (this.#engine) {
this.#engine.loadSource(this.#src);
this.#updatePreload();
} else if (this.#target) {
(this.#target as HTMLMediaElement).src = this.#src;
}
}
attach(target: EventTarget): void {
attach(target: HTMLMediaElement): void {
this.#target = target;
this.#engine?.attachMedia(target as HTMLMediaElement);
this.#engine?.attachMedia(target);
this.#updatePreload();
}
detach(): void {
this.#preloadOnPlayAbort?.abort();
this.#engine?.detachMedia();
this.#target = null;
}
destroy(): void {
this.#preloadOnPlayAbort?.abort();
this.#engine?.destroy();
this.#engine = null;
this.#target = null;
}
#updatePreload(): void {
this.#preloadOnPlayAbort?.abort();
if (!this.#target || !this.#engine) return;
const preload = (length?: number, size?: number) => {
if (!this.#engine) return;
this.#engine.config.maxBufferLength = length ?? this.#defaultMaxBufferLength;
this.#engine.config.maxBufferSize = size ?? this.#defaultMaxBufferSize;
this.#engine.startLoad();
};
if (this.preload === 'auto' || !this.#target.paused) {
preload();
return;
}
if (this.preload === 'metadata') {
preload(1, 1);
}
// preload === 'none' or preload === 'metadata' both defer full load until play.
this.#preloadOnPlayAbort = new AbortController();
this.#target.addEventListener('play', () => preload(), {
signal: this.#preloadOnPlayAbort.signal,
once: true,
});
}
}
function inferSourceType(src: string): SourceType {
-4
View File
@@ -108,10 +108,6 @@ export class MuxMediaDelegate extends HlsMediaDelegate implements Delegate {
this.#metadata = value;
}
attach(target: EventTarget): void {
super.attach(target);
}
detach(): void {
this.#MuxDataSdk?.destroyMonitor(this.target as HTMLMediaElement);