UNPKG

@livepeer/core-web

Version:

Livepeer UI Kit's core web library, for adding reactive stores to video elements.

147 lines (145 loc) 4.09 kB
// src/hls/hls.ts import { calculateVideoQualityDimensions } from "@livepeer/core/media"; import Hls from "hls.js"; // src/media/utils.ts import { noop } from "@livepeer/core/utils"; var isClient = () => typeof window !== "undefined"; // src/hls/hls.ts var VIDEO_HLS_INITIALIZED_ATTRIBUTE = "data-livepeer-video-hls-initialized"; var isHlsSupported = () => isClient() ? Hls.isSupported() : true; var createNewHls = ({ source, element, callbacks, aspectRatio, config, initialQuality }) => { if (element.getAttribute(VIDEO_HLS_INITIALIZED_ATTRIBUTE) === "true") { return { setQuality: () => { }, destroy: () => { } }; } element.setAttribute(VIDEO_HLS_INITIALIZED_ATTRIBUTE, "true"); const hls = new Hls({ backBufferLength: 60 * 1.5, manifestLoadingMaxRetry: 0, fragLoadingMaxRetry: 0, levelLoadingMaxRetry: 0, appendErrorMaxRetry: 0, ...config, ...config?.liveSyncDurationCount ? { liveSyncDurationCount: config.liveSyncDurationCount } : { liveMaxLatencyDurationCount: 7, liveSyncDurationCount: 3 } }); const onDestroy = () => { hls?.destroy?.(); element?.removeAttribute?.(VIDEO_HLS_INITIALIZED_ATTRIBUTE); }; if (element) { hls.attachMedia(element); } let redirected = false; hls.on(Hls.Events.LEVEL_LOADED, async (_e, data) => { const { live, totalduration: duration, url } = data.details; if (!redirected) { callbacks?.onRedirect?.(url ?? null); redirected = true; } callbacks?.onLive?.(Boolean(live)); callbacks?.onDuration?.(duration ?? 0); }); hls.on(Hls.Events.MEDIA_ATTACHED, async () => { hls.loadSource(source); hls.on(Hls.Events.MANIFEST_PARSED, (_event, _data) => { setQuality({ hls: hls ?? null, quality: initialQuality, aspectRatio }); callbacks?.onCanPlay?.(); if (config.autoPlay) element?.play?.(); }); }); hls.on(Hls.Events.ERROR, async (_event, data) => { const { details, fatal } = data; const isManifestParsingError = details === "manifestParsingError"; if (!fatal && !isManifestParsingError) return; callbacks?.onError?.(data); if (fatal) { console.error(`Fatal error : ${data.details}`); switch (data.type) { case Hls.ErrorTypes.MEDIA_ERROR: hls.recoverMediaError(); break; case Hls.ErrorTypes.NETWORK_ERROR: console.error(`A network error occurred: ${data.details}`); break; default: console.error(`An unrecoverable error occurred: ${data.details}`); hls.destroy(); break; } } }); function updateOffset() { const currentDate = Date.now(); const newDate = hls.playingDate; if (newDate && currentDate) { callbacks?.onPlaybackOffsetUpdated?.(currentDate - newDate.getTime()); } } const updateOffsetInterval = setInterval(updateOffset, 2e3); return { destroy: () => { onDestroy?.(); clearInterval?.(updateOffsetInterval); element?.removeAttribute?.(VIDEO_HLS_INITIALIZED_ATTRIBUTE); }, setQuality: (videoQuality) => { setQuality({ hls: hls ?? null, quality: videoQuality, aspectRatio }); } }; }; var setQuality = ({ hls, quality, aspectRatio }) => { if (hls) { const { width } = calculateVideoQualityDimensions(quality, aspectRatio); if (!width || quality === "auto") { hls.currentLevel = -1; return; } if (hls.levels && hls.levels.length > 0) { const sortedLevels = hls.levels.map((level, index) => ({ ...level, index })).sort( (a, b) => Math.abs((width ?? 0) - a.width) - Math.abs((width ?? 0) - b.width) ); const bestMatchLevel = sortedLevels?.[0]; if ((bestMatchLevel?.index ?? -1) >= 0) { hls.currentLevel = bestMatchLevel.index; } else { hls.currentLevel = -1; } } } }; export { VIDEO_HLS_INITIALIZED_ATTRIBUTE, createNewHls, isHlsSupported }; //# sourceMappingURL=index.js.map