mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
125 lines
4.2 KiB
JavaScript
125 lines
4.2 KiB
JavaScript
import { MediaStreamTypes } from "../../core/types.js";
|
|
import Hls from "hls.js";
|
|
//#region src/dom/hls-js/live.ts
|
|
function HlsJsMediaLiveMixin(BaseClass) {
|
|
class HlsJsMediaLive extends BaseClass {
|
|
#targetLiveWindow = NaN;
|
|
#liveEdgeStartOffset;
|
|
#seekToLiveAbort = null;
|
|
#seekToLivePending = false;
|
|
constructor(...args) {
|
|
super(...args);
|
|
const { engine } = this;
|
|
engine?.on(Hls.Events.MANIFEST_LOADING, () => {
|
|
this.#reset();
|
|
this.#armSeekToLive();
|
|
});
|
|
engine?.on(Hls.Events.MEDIA_ATTACHED, () => this.#armSeekToLive());
|
|
engine?.on(Hls.Events.MEDIA_DETACHED, () => this.#disarmSeekToLive());
|
|
engine?.on(Hls.Events.DESTROYING, () => {
|
|
this.#reset();
|
|
this.#disarmSeekToLive();
|
|
});
|
|
engine?.on(Hls.Events.LEVEL_LOADED, (_event, data) => {
|
|
this.#derive(data.details);
|
|
if (this.#seekToLivePending) this.#trySeekToLive();
|
|
});
|
|
}
|
|
get targetLiveWindow() {
|
|
return this.#targetLiveWindow;
|
|
}
|
|
get liveEdgeStart() {
|
|
if (this.#liveEdgeStartOffset === void 0) return NaN;
|
|
const { target } = this;
|
|
if (!target) return NaN;
|
|
const { seekable } = target;
|
|
if (!seekable.length) return NaN;
|
|
return seekable.end(seekable.length - 1) - this.#liveEdgeStartOffset;
|
|
}
|
|
#derive(details) {
|
|
if (!details.live) return this.#reset();
|
|
const info = getStreamInfoFromHlsjsLevelDetails(details);
|
|
this.#liveEdgeStartOffset = info.liveEdgeStartOffset;
|
|
this.#updateConfig(info);
|
|
this.#setTargetLiveWindow(info.targetLiveWindow);
|
|
}
|
|
#reset() {
|
|
this.#liveEdgeStartOffset = void 0;
|
|
this.#setTargetLiveWindow(NaN);
|
|
}
|
|
#updateConfig({ streamType, lowLatency }) {
|
|
if (streamType === MediaStreamTypes.LIVE) {
|
|
const hls = this.engine;
|
|
if (!hls) return;
|
|
if (lowLatency) {
|
|
hls.config.backBufferLength = hls.userConfig.backBufferLength ?? 4;
|
|
hls.config.maxFragLookUpTolerance = hls.userConfig.maxFragLookUpTolerance ?? .001;
|
|
hls.config.abrBandWidthUpFactor = hls.userConfig.abrBandWidthUpFactor ?? hls.config.abrBandWidthFactor;
|
|
} else hls.config.backBufferLength = hls.userConfig.backBufferLength ?? 8;
|
|
}
|
|
}
|
|
#setTargetLiveWindow(value) {
|
|
if (Object.is(this.#targetLiveWindow, value)) return;
|
|
this.#targetLiveWindow = value;
|
|
this.dispatchEvent(new Event("targetlivewindowchange"));
|
|
}
|
|
/**
|
|
* Arm a one-shot seek-to-live on the first user-initiated `play`. Skipped
|
|
* when `autoplay` is set, since hls.js positions at the live edge during
|
|
* its own startup sequence and a programmatic seek would race that.
|
|
*/
|
|
#armSeekToLive() {
|
|
this.#disarmSeekToLive();
|
|
const target = this.target;
|
|
if (!target || target.autoplay) return;
|
|
this.#seekToLiveAbort = new AbortController();
|
|
target.addEventListener("play", () => {
|
|
this.#seekToLivePending = true;
|
|
this.#trySeekToLive();
|
|
}, {
|
|
signal: this.#seekToLiveAbort.signal,
|
|
once: true
|
|
});
|
|
}
|
|
#disarmSeekToLive() {
|
|
this.#seekToLiveAbort?.abort();
|
|
this.#seekToLiveAbort = null;
|
|
this.#seekToLivePending = false;
|
|
}
|
|
#trySeekToLive() {
|
|
const target = this.target;
|
|
if (!target) return;
|
|
const { liveEdgeStart } = this;
|
|
if (!Number.isFinite(liveEdgeStart)) return;
|
|
if (target.currentTime < liveEdgeStart) target.currentTime = liveEdgeStart;
|
|
this.#seekToLivePending = false;
|
|
}
|
|
}
|
|
return HlsJsMediaLive;
|
|
}
|
|
const getStreamInfoFromHlsjsLevelDetails = (levelDetails) => {
|
|
const playlistType = levelDetails.type;
|
|
const streamType = toStreamTypeFromPlaylistType(playlistType);
|
|
const targetLiveWindow = toTargetLiveWindowFromPlaylistType(playlistType);
|
|
const lowLatency = !!levelDetails.partList?.length;
|
|
let liveEdgeStartOffset;
|
|
if (streamType === MediaStreamTypes.LIVE) liveEdgeStartOffset = lowLatency ? levelDetails.partHoldBack || levelDetails.partTarget * 2 : levelDetails.holdBack || levelDetails.targetduration * 3;
|
|
return {
|
|
streamType,
|
|
targetLiveWindow,
|
|
liveEdgeStartOffset,
|
|
lowLatency
|
|
};
|
|
};
|
|
const toStreamTypeFromPlaylistType = (playlistType) => {
|
|
return playlistType === "VOD" ? MediaStreamTypes.ON_DEMAND : MediaStreamTypes.LIVE;
|
|
};
|
|
const toTargetLiveWindowFromPlaylistType = (playlistType) => {
|
|
if (playlistType === "EVENT") return Number.POSITIVE_INFINITY;
|
|
if (playlistType === "VOD") return NaN;
|
|
return 0;
|
|
};
|
|
//#endregion
|
|
export { HlsJsMediaLiveMixin };
|
|
|
|
//# sourceMappingURL=live.js.map
|