UNPKG

@ktt45678/vidstack

Version:

UI component library for building high-quality, accessible video and audio experiences on the web.

374 lines (367 loc) 12 kB
import { isString, isBoolean, deferredPromise, isNull, getScope, listenEvent, scoped, createContext, useContext, isDOMNode, isFunction, setAttribute, isTouchEvent, onDispose, setStyle, effect, toggleClass, signal, animationFrameThrottle, computed } from './vidstack-41uXLVgN.js'; import { autoUpdate, computePosition, flip, shift } from '@floating-ui/dom'; const AUDIO_EXTENSIONS = /\.(m4a|m4b|mp4a|mpga|mp2|mp2a|mp3|m2a|m3a|wav|weba|aac|oga|spx|flac)($|\?)/i; const AUDIO_TYPES = /* @__PURE__ */ new Set([ "audio/mpeg", "audio/ogg", "audio/3gp", "audio/mp3", "audio/webm", "audio/flac" ]); const VIDEO_EXTENSIONS = /\.(mp4|og[gv]|webm|mov|m4v)(#t=[,\d+]+)?($|\?)/i; const VIDEO_TYPES = /* @__PURE__ */ new Set([ "video/mp4", "video/webm", "video/3gp", "video/ogg", "video/avi", "video/mpeg" ]); const HLS_VIDEO_EXTENSIONS = /\.(m3u8)($|\?)/i; const DASH_VIDEO_EXTENSIONS = /\.(mpd)($|\?)/i; const HLS_VIDEO_TYPES = /* @__PURE__ */ new Set([ // Apple sanctioned "application/vnd.apple.mpegurl", // Apple sanctioned for backwards compatibility "audio/mpegurl", // Very common "audio/x-mpegurl", // Very common "application/x-mpegurl", // Included for completeness "video/x-mpegurl", "video/mpegurl", "application/mpegurl" ]); const DASH_VIDEO_TYPES = /* @__PURE__ */ new Set([ // Dash xml "application/dash+xml", // Common xml "application/xml", // Included for completeness "video/dash+xml", "video/xml", "video/dash" ]); function isAudioSrc({ src, type }) { return isString(src) ? AUDIO_EXTENSIONS.test(src) || AUDIO_TYPES.has(type) || src.startsWith("blob:") && type === "audio/object" : type === "audio/object"; } function isVideoSrc(src) { return isString(src.src) ? VIDEO_EXTENSIONS.test(src.src) || VIDEO_TYPES.has(src.type) || src.src.startsWith("blob:") && src.type === "video/object" || isHLSSrc(src) && true : src.type === "video/object"; } function isHLSSrc({ src, type }) { return isString(src) && HLS_VIDEO_EXTENSIONS.test(src) || HLS_VIDEO_TYPES.has(type); } function isDASHSrc({ src, type }) { return isParsedManifest(src) || isString(src) && DASH_VIDEO_EXTENSIONS.test(src) || DASH_VIDEO_TYPES.has(type); } function canGoogleCastSrc(src) { return isString(src.src) && (isAudioSrc(src) || isVideoSrc(src) || isHLSSrc(src)); } function isMediaStream(src) { return false; } function isParsedManifest(src) { return typeof src === "object" && "protocol" in src && src.protocol === "DASH"; } function preconnect(url, rel = "preconnect") { return false; } const pendingRequests = {}; function loadScript(src) { if (pendingRequests[src]) return pendingRequests[src].promise; const promise = deferredPromise(), exists = document.querySelector(`script[src="${src}"]`); if (!isNull(exists)) { promise.resolve(); return promise.promise; } const script = document.createElement("script"); script.src = src; script.onload = () => { promise.resolve(); delete pendingRequests[src]; }; script.onerror = () => { promise.reject(); delete pendingRequests[src]; }; setTimeout(() => document.head.append(script), 0); return promise.promise; } function getRequestCredentials(crossOrigin) { return crossOrigin === "use-credentials" ? "include" : isString(crossOrigin) ? "same-origin" : void 0; } function getDownloadFile({ title, src, download }) { const url = isBoolean(download) || download === "" ? src.src : isString(download) ? download : download?.url; if (!isValidFileDownload({ url, src, download })) return null; return { url, name: !isBoolean(download) && !isString(download) && download?.filename || title.toLowerCase() || "media" }; } function isValidFileDownload({ url, src, download }) { return isString(url) && (download && download !== true || isAudioSrc(src) || isVideoSrc(src)); } function findActiveCue(cues, time) { for (let i = 0, len = cues.length; i < len; i++) { if (isCueActive(cues[i], time)) return cues[i]; } return null; } function isCueActive(cue, time) { return time >= cue.startTime && time < cue.endTime; } function watchActiveTextTrack(tracks, kind, onChange) { let currentTrack = null, scope = getScope(); function onModeChange() { const kinds = isString(kind) ? [kind] : kind, track = tracks.toArray().find((track2) => kinds.includes(track2.kind) && track2.mode === "showing"); if (track === currentTrack) return; if (!track) { onChange(null); currentTrack = null; return; } if (track.readyState == 2) { onChange(track); } else { onChange(null); scoped(() => { }, scope); } currentTrack = track; } onModeChange(); return listenEvent(); } function watchCueTextChange(tracks, kind, callback) { watchActiveTextTrack(tracks, kind, (track) => { if (!track) { callback(""); return; } const onCueChange = () => { const activeCue = track?.activeCues[0]; callback(activeCue?.text || ""); }; onCueChange(); }); } const mediaContext = createContext(); function useMediaContext() { return useContext(mediaContext); } function useMediaState() { return useMediaContext().$state; } function isEventInside(el, event) { const target = event.composedPath()[0]; return isDOMNode(target) && el.contains(target); } const rafJobs = /* @__PURE__ */ new Set(); function scheduleRafJob(job) { rafJobs.add(job); return () => rafJobs.delete(job); } function setAttributeIfEmpty(target, name, value) { if (!target.hasAttribute(name)) target.setAttribute(name, value); } function setARIALabel(target, $label) { if (target.hasAttribute("aria-label") || target.hasAttribute("data-no-label")) return; if (!isFunction($label)) { setAttribute(target, "aria-label", $label); return; } function updateAriaDescription() { setAttribute(target, "aria-label", $label()); } updateAriaDescription(); } function isElementVisible(el) { const style = getComputedStyle(el); return style.display !== "none" && parseInt(style.opacity) > 0; } function checkVisibility(el) { return !!el && ("checkVisibility" in el ? el.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true }) : isElementVisible(el)); } function observeVisibility(el, callback) { return scheduleRafJob(() => callback(checkVisibility(el))); } function isElementParent(owner, node, test) { while (node) { if (node === owner) { return true; } else if (test?.(node)) { break; } else { node = node.parentElement; } } return false; } function onPress(target, handler) { } function isTouchPinchEvent(event) { return isTouchEvent(event) && (event.touches.length > 1 || event.changedTouches.length > 1); } function requestScopedAnimationFrame(callback) { return callback(); } function cloneTemplate(template, length, onCreate) { let current, prev = template, parent = template.parentElement, content = template.content.firstElementChild, elements = []; if (!content && template.firstElementChild) { template.innerHTML = template.firstElementChild.outerHTML; template.firstElementChild.remove(); content = template.content.firstElementChild; } for (let i = 0; i < length; i++) { current = document.importNode(content, true); onCreate?.(current, i); parent.insertBefore(current, prev.nextSibling); elements.push(current); prev = current; } onDispose(() => { for (let i = 0; i < elements.length; i++) elements[i].remove(); }); return elements; } function createTemplate(content) { const template = document.createElement("template"); template.innerHTML = content; return template.content; } function cloneTemplateContent(content) { const fragment = content.cloneNode(true); return fragment.firstElementChild; } function autoPlacement(el, trigger, placement, { offsetVarName, xOffset, yOffset, ...options }) { if (!el) return; const floatingPlacement = placement.replace(" ", "-").replace("-center", ""); setStyle(el, "visibility", !trigger ? "hidden" : null); if (!trigger) return; let isTop = placement.includes("top"); const negateX = (x) => placement.includes("left") ? `calc(-1 * ${x})` : x, negateY = (y) => isTop ? `calc(-1 * ${y})` : y; return autoUpdate(trigger, el, () => { computePosition(trigger, el, { placement: floatingPlacement, middleware: [ ...options.middleware ?? [], flip({ fallbackAxisSideDirection: "start", crossAxis: false }), shift() ], ...options }).then(({ x, y, middlewareData }) => { const hasFlipped = !!middlewareData.flip?.index; isTop = placement.includes(hasFlipped ? "bottom" : "top"); el.setAttribute( "data-placement", hasFlipped ? placement.startsWith("top") ? placement.replace("top", "bottom") : placement.replace("bottom", "top") : placement ); Object.assign(el.style, { top: `calc(${y + "px"} + ${negateY( yOffset ? yOffset + "px" : `var(--${offsetVarName}-y-offset, 0px)` )})`, left: `calc(${x + "px"} + ${negateX( xOffset ? xOffset + "px" : `var(--${offsetVarName}-x-offset, 0px)` )})` }); }); }); } function hasAnimation(el) { const styles = getComputedStyle(el); return styles.animationName !== "none"; } function createSlot(name) { const slot = document.createElement("slot"); slot.name = name; return slot; } function useTransitionActive($el) { const $active = signal(false); effect(() => { const el = $el(); if (!el) return; }); return $active; } function useResizeObserver($el, onResize) { function onElementChange() { const el = $el(); if (!el) return; onResize(); const observer = new ResizeObserver(animationFrameThrottle()); observer.observe(el); return () => observer.disconnect(); } effect(onElementChange); } function useActive($el) { const $isMouseEnter = useMouseEnter($el), $isFocusedIn = useFocusIn($el); let prevMouseEnter = false; return computed(() => { const isMouseEnter = $isMouseEnter(); if (prevMouseEnter && !isMouseEnter) return false; prevMouseEnter = isMouseEnter; return isMouseEnter || $isFocusedIn(); }); } function useMouseEnter($el) { const $isMouseEnter = signal(false); effect(() => { const el = $el(); if (!el) { $isMouseEnter.set(false); return; } }); return $isMouseEnter; } function useFocusIn($el) { const $isFocusIn = signal(false); effect(() => { const el = $el(); if (!el) { $isFocusIn.set(false); return; } }); return $isFocusIn; } function isHTMLElement(el) { return el instanceof HTMLElement; } function useColorSchemePreference() { const colorScheme = signal("dark"); return colorScheme; } function watchColorScheme(el, colorScheme) { effect(() => { const scheme = colorScheme(); if (scheme === "system") { const preference = useColorSchemePreference(); effect(() => updateColorScheme(preference())); return; } updateColorScheme(scheme); }); function updateColorScheme(scheme) { toggleClass(el, "light", scheme === "light"); toggleClass(el, "dark", scheme === "dark"); } } export { AUDIO_EXTENSIONS, AUDIO_TYPES, DASH_VIDEO_EXTENSIONS, DASH_VIDEO_TYPES, HLS_VIDEO_EXTENSIONS, HLS_VIDEO_TYPES, VIDEO_EXTENSIONS, VIDEO_TYPES, autoPlacement, canGoogleCastSrc, cloneTemplate, cloneTemplateContent, createSlot, createTemplate, findActiveCue, getDownloadFile, getRequestCredentials, hasAnimation, isAudioSrc, isCueActive, isDASHSrc, isElementParent, isEventInside, isHLSSrc, isHTMLElement, isMediaStream, isParsedManifest, isTouchPinchEvent, isVideoSrc, loadScript, mediaContext, observeVisibility, onPress, preconnect, requestScopedAnimationFrame, setARIALabel, setAttributeIfEmpty, useActive, useMediaContext, useMediaState, useResizeObserver, useTransitionActive, watchActiveTextTrack, watchColorScheme, watchCueTextChange };