UNPKG

@livepeer/core-web

Version:

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

1 lines 11.1 kB
{"version":3,"sources":["../../src/hls.ts","../../src/hls/hls.ts","../../src/media/utils.ts"],"sourcesContent":["export {\n createNewHls,\n type HlsError,\n type HlsVideoConfig,\n isHlsSupported,\n VIDEO_HLS_INITIALIZED_ATTRIBUTE,\n type VideoConfig,\n} from \"./hls/hls\";\n","import {\n calculateVideoQualityDimensions,\n type VideoQuality,\n} from \"@livepeer/core/media\";\nimport Hls, { type ErrorData, type HlsConfig } from \"hls.js\";\nimport { isClient } from \"../media/utils\";\n\nexport const VIDEO_HLS_INITIALIZED_ATTRIBUTE =\n \"data-livepeer-video-hls-initialized\";\n\nexport type HlsError = ErrorData;\n\nexport type VideoConfig = { autoplay?: boolean };\nexport type HlsVideoConfig = Partial<HlsConfig> & {\n autoPlay?: boolean;\n};\n\n/**\n * Checks if hls.js can play in the browser.\n */\nexport const isHlsSupported = () => (isClient() ? Hls.isSupported() : true);\n\n/**\n * Create an hls.js instance and attach to the provided media element.\n */\nexport const createNewHls = <TElement extends HTMLMediaElement>({\n source,\n element,\n callbacks,\n aspectRatio,\n config,\n initialQuality,\n}: {\n source: string;\n element: TElement;\n initialQuality: VideoQuality;\n aspectRatio: number;\n callbacks: {\n onLive?: (v: boolean) => void;\n onPlaybackOffsetUpdated?: (d: number) => void;\n onDuration?: (v: number) => void;\n onCanPlay?: () => void;\n onError?: (data: HlsError) => void;\n onRedirect?: (url: string | null) => void;\n };\n config: HlsVideoConfig;\n}): {\n setQuality: (quality: VideoQuality) => void;\n destroy: () => void;\n} => {\n // do not attach twice\n if (element.getAttribute(VIDEO_HLS_INITIALIZED_ATTRIBUTE) === \"true\") {\n return {\n setQuality: () => {\n //\n },\n destroy: () => {\n //\n },\n };\n }\n\n element.setAttribute(VIDEO_HLS_INITIALIZED_ATTRIBUTE, \"true\");\n\n const hls = new Hls({\n backBufferLength: 60 * 1.5,\n manifestLoadingMaxRetry: 0,\n fragLoadingMaxRetry: 0,\n levelLoadingMaxRetry: 0,\n appendErrorMaxRetry: 0,\n ...config,\n ...(config?.liveSyncDurationCount\n ? {\n liveSyncDurationCount: config.liveSyncDurationCount,\n }\n : {\n liveMaxLatencyDurationCount: 7,\n liveSyncDurationCount: 3,\n }),\n });\n\n const onDestroy = () => {\n hls?.destroy?.();\n element?.removeAttribute?.(VIDEO_HLS_INITIALIZED_ATTRIBUTE);\n };\n\n if (element) {\n hls.attachMedia(element);\n }\n\n let redirected = false;\n\n hls.on(Hls.Events.LEVEL_LOADED, async (_e, data) => {\n const { live, totalduration: duration, url } = data.details;\n\n if (!redirected) {\n callbacks?.onRedirect?.(url ?? null);\n redirected = true;\n }\n\n callbacks?.onLive?.(Boolean(live));\n callbacks?.onDuration?.(duration ?? 0);\n });\n\n hls.on(Hls.Events.MEDIA_ATTACHED, async () => {\n hls.loadSource(source);\n\n hls.on(Hls.Events.MANIFEST_PARSED, (_event, _data) => {\n setQuality({\n hls: hls ?? null,\n quality: initialQuality,\n aspectRatio,\n });\n\n callbacks?.onCanPlay?.();\n if (config.autoPlay) element?.play?.();\n });\n });\n\n hls.on(Hls.Events.ERROR, async (_event, data) => {\n const { details, fatal } = data;\n\n const isManifestParsingError = details === \"manifestParsingError\";\n\n if (!fatal && !isManifestParsingError) return;\n callbacks?.onError?.(data);\n\n if (fatal) {\n console.error(`Fatal error : ${data.details}`);\n switch (data.type) {\n case Hls.ErrorTypes.MEDIA_ERROR:\n hls.recoverMediaError();\n break;\n case Hls.ErrorTypes.NETWORK_ERROR:\n console.error(`A network error occurred: ${data.details}`);\n break;\n default:\n console.error(`An unrecoverable error occurred: ${data.details}`);\n hls.destroy();\n break;\n }\n }\n });\n\n function updateOffset() {\n const currentDate = Date.now();\n const newDate = hls.playingDate;\n\n if (newDate && currentDate) {\n callbacks?.onPlaybackOffsetUpdated?.(currentDate - newDate.getTime());\n }\n }\n\n const updateOffsetInterval = setInterval(updateOffset, 2000);\n\n return {\n destroy: () => {\n onDestroy?.();\n clearInterval?.(updateOffsetInterval);\n element?.removeAttribute?.(VIDEO_HLS_INITIALIZED_ATTRIBUTE);\n },\n setQuality: (videoQuality) => {\n setQuality({\n hls: hls ?? null,\n quality: videoQuality,\n aspectRatio,\n });\n },\n };\n};\n\nconst setQuality = ({\n hls,\n quality,\n aspectRatio,\n}: {\n hls: Hls | null;\n quality: VideoQuality;\n aspectRatio: number;\n}) => {\n if (hls) {\n const { width } = calculateVideoQualityDimensions(quality, aspectRatio);\n\n if (!width || quality === \"auto\") {\n hls.currentLevel = -1; // Auto level\n return;\n }\n\n if (hls.levels && hls.levels.length > 0) {\n // Sort levels by the absolute difference between their width and the desired width\n const sortedLevels = hls.levels\n .map((level, index) => ({ ...level, index }))\n .sort(\n (a, b) =>\n Math.abs((width ?? 0) - a.width) - Math.abs((width ?? 0) - b.width),\n );\n\n // Choose the level with the smallest difference in width\n const bestMatchLevel = sortedLevels?.[0];\n\n if ((bestMatchLevel?.index ?? -1) >= 0) {\n hls.currentLevel = bestMatchLevel.index;\n } else {\n hls.currentLevel = -1;\n }\n }\n }\n};\n","import type { Src } from \"@livepeer/core/media\";\nimport { noop } from \"@livepeer/core/utils\";\n\nexport const isClient = () => typeof window !== \"undefined\";\nexport const ua = () =>\n isClient() ? window?.navigator?.userAgent?.toLowerCase() : \"\";\nexport const isIos = () => /iphone|ipad|ipod|ios|CriOS|FxiOS/.test(ua());\nexport const isAndroid = () => /android/.test(ua());\nexport const isMobile = () => isClient() && (isIos() || isAndroid());\nexport const isIphone = () =>\n isClient() && /(iPhone|iPod)/gi.test(window?.navigator?.platform);\nexport const isFirefox = () => /firefox/.test(ua());\nexport const isChrome = () => isClient() && !!window?.chrome;\nexport const isSafari = () =>\n Boolean(\n isClient() &&\n !isChrome() &&\n (window?.safari || isIos() || /(apple|safari)/.test(ua())),\n );\n\n/**\n * To detect autoplay, we create a video element and call play on it, if it is `paused` after\n * a `play()` call, autoplay is supported. Although this unintuitive, it works across browsers\n * and is currently the lightest way to detect autoplay without using a data source.\n *\n * @see {@link https://github.com/ampproject/amphtml/blob/9bc8756536956780e249d895f3e1001acdee0bc0/src/utils/video.js#L25}\n */\nexport const canAutoplay = (\n muted = true,\n playsinline = true,\n): Promise<boolean> => {\n if (!isClient()) return Promise.resolve(false);\n\n const video = document.createElement(\"video\");\n\n if (muted) {\n video.setAttribute(\"muted\", \"\");\n video.muted = true;\n }\n\n if (playsinline) {\n video.setAttribute(\"playsinline\", \"\");\n video.setAttribute(\"webkit-playsinline\", \"\");\n }\n\n video.setAttribute(\"height\", \"0\");\n video.setAttribute(\"width\", \"0\");\n\n video.style.position = \"fixed\";\n video.style.top = \"0\";\n video.style.width = \"0\";\n video.style.height = \"0\";\n video.style.opacity = \"0\";\n\n // Promise wrapped this way to catch both sync throws and async rejections.\n // More info: https://github.com/tc39/proposal-promise-try\n new Promise((resolve) => resolve(video.play())).catch(noop);\n\n return Promise.resolve(!video.paused);\n};\n\n/**\n * Checks if the native HTML5 video player can play the mime type.\n */\nexport const canPlayMediaNatively = (src: Src): boolean => {\n if (isClient() && src?.mime) {\n if (src?.type?.includes(\"audio\")) {\n const audio = document.createElement(\"audio\");\n return audio.canPlayType(src.mime).length > 0;\n }\n\n const video = document.createElement(\"video\");\n return video.canPlayType(src.mime).length > 0;\n }\n\n return true;\n};\n"],"mappings":"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","names":["Hls"]}