UNPKG

mstf-kit

Version:

一个现代化的 JavaScript/TypeScript 工具库,提供了丰富的常用工具函数

457 lines (456 loc) 14.3 kB
const DEFAULT_PLYR_CDN = "https://unpkg.com/plyr@3.7.8/dist/plyr.min.js"; const DEFAULT_PLYR_CSS = "https://unpkg.com/plyr@3.7.8/dist/plyr.css"; const DEFAULT_I18N = { restart: "重播", play: "播放", pause: "暂停", volume: "音量", mute: "静音", pip: "画中画", normal: "默认", quality: "画质", download: "下载", enterFullscreen: "全屏", exitFullscreen: "关闭全屏", captions: "字幕", settings: "设置", speed: "倍速", loop: "循环播放" }; const DEFAULT_OPTIONS = { controls: [ "play-large", "play", "progress", "current-time", "mute", "volume", "captions", "settings", "pip", "airplay", "fullscreen" ], hideControls: true, resetOnEnd: false, fullscreen: { enabled: true, fallback: true, iosNative: true }, invertTime: false, displayDuration: true, storage: { enabled: true, key: "plyr-volume" }, speed: { selected: 1, options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2] }, markers: { enabled: false, points: [] }, loadSprite: true, debug: false, i18n: DEFAULT_I18N }; function loadCSS(url) { return new Promise((resolve, reject) => { if (typeof document === "undefined") { reject(new Error("Document is not available")); return; } const existingLink = document.querySelector(`link[href="${url}"]`); if (existingLink) { resolve(); return; } const link = document.createElement("link"); link.rel = "stylesheet"; link.href = url; link.onload = () => resolve(); link.onerror = () => reject(new Error(`Failed to load CSS: ${url}`)); document.head.appendChild(link); }); } async function loadPlyrLib(options) { var _a, _b, _c, _d; const source = ((_a = options == null ? void 0 : options.plyrLibOptions) == null ? void 0 : _a.source) || "npm"; const cdnUrl = ((_b = options == null ? void 0 : options.plyrLibOptions) == null ? void 0 : _b.cdnUrl) || DEFAULT_PLYR_CDN; const cssUrl = ((_c = options == null ? void 0 : options.plyrLibOptions) == null ? void 0 : _c.cssUrl) || DEFAULT_PLYR_CSS; if (source === "external" && ((_d = options == null ? void 0 : options.plyrLibOptions) == null ? void 0 : _d.externalLib)) { return options.plyrLibOptions.externalLib; } if (typeof window !== "undefined" && window.Plyr) { return window.Plyr; } if (source === "npm") { try { if ((options == null ? void 0 : options.autoCreateCss) !== false && typeof document !== "undefined") { } return await import("plyr").then((module) => module.default || module); } catch (error) { console.error("Failed to load Plyr from npm:", error); console.warn("Falling back to CDN loading..."); return loadPlyrFromCDN(cdnUrl, cssUrl, options == null ? void 0 : options.autoCreateCss); } } if (source === "cdn") { return loadPlyrFromCDN(cdnUrl, cssUrl, options == null ? void 0 : options.autoCreateCss); } throw new Error("Failed to load Plyr library"); } async function loadPlyrFromCDN(cdnUrl, cssUrl, autoCreateCss = true) { if (autoCreateCss !== false) { await loadCSS(cssUrl); } await loadScript(cdnUrl); if (typeof window !== "undefined" && window.Plyr) { return window.Plyr; } throw new Error("Failed to load Plyr from CDN"); } function loadScript(url) { return new Promise((resolve, reject) => { if (typeof document === "undefined") { reject(new Error("Document is not available")); return; } const script = document.createElement("script"); script.src = url; script.async = true; script.onload = () => resolve(); script.onerror = () => reject(new Error(`Failed to load script: ${url}`)); document.head.appendChild(script); }); } async function createPlyrPlayer(options) { const Plyr = await loadPlyrLib(options); let container; if (typeof options.container === "string") { const element = document.querySelector(options.container); if (!element) { throw new Error(`Container element not found: ${options.container}`); } container = element; } else if (typeof options.container === "function") { const element = options.container(); if (!element) { throw new Error("Container ref function returned null or undefined"); } container = element; } else { container = options.container; } let mediaElement = container.querySelector("audio, video"); if (!mediaElement && options.source) { const type = options.source.type || "video"; mediaElement = document.createElement(type); if (options.source.sources && options.source.sources.length > 0) { options.source.sources.forEach((source) => { const sourceElement = document.createElement("source"); sourceElement.src = source.src; if (source.type) { sourceElement.type = source.type; } mediaElement == null ? void 0 : mediaElement.appendChild(sourceElement); }); } if (type === "video" && options.source.poster) { mediaElement.poster = options.source.poster; } if (options.source.tracks && options.source.tracks.length > 0) { options.source.tracks.forEach((track) => { const trackElement = document.createElement("track"); trackElement.kind = track.kind; trackElement.label = track.label; trackElement.src = track.src; if (track.srclang) { trackElement.srclang = track.srclang; } if (track.default) { trackElement.default = track.default; } mediaElement == null ? void 0 : mediaElement.appendChild(trackElement); }); } container.appendChild(mediaElement); } if (!mediaElement) { throw new Error("No media element found and no source configuration provided"); } const mergedOptions = { ...DEFAULT_OPTIONS, ...options }; const player = new Plyr(mediaElement, mergedOptions); const enhancedPlayer = player; enhancedPlayer.getContainer = () => container; enhancedPlayer.getDefaultOptions = () => ({ ...DEFAULT_OPTIONS }); enhancedPlayer.setSource = async (source) => { if (typeof source === "string") { const isVideo = source.match(/\.(mp4|webm|ogg|ogv)$/i); const type = isVideo ? "video" : "audio"; const newSource = { type, sources: [{ src: source, type: isVideo ? `video/${source.split(".").pop()}` : `audio/${source.split(".").pop()}` }] }; player.source = newSource; } else { player.source = source; } return new Promise((resolve) => { const onCanPlay = () => { player.off("canplay", onCanPlay); resolve(); }; player.on("canplay", onCanPlay); }); }; enhancedPlayer.changeAspectRatio = (ratio) => { container.style.setProperty("--plyr-aspect-ratio", ratio); }; enhancedPlayer.addShortcut = (key, action) => { const handleKeyDown = (event) => { if (event.key === key) { action(); } }; document.addEventListener("keydown", handleKeyDown); const originalDestroy = player.destroy; player.destroy = () => { document.removeEventListener("keydown", handleKeyDown); originalDestroy.call(player); }; }; enhancedPlayer.getCaptureImage = () => { if (player.source.type !== "video") { return null; } const video = player.media; const canvas = document.createElement("canvas"); canvas.width = video.videoWidth; canvas.height = video.videoHeight; const ctx = canvas.getContext("2d"); ctx == null ? void 0 : ctx.drawImage(video, 0, 0, canvas.width, canvas.height); try { return canvas.toDataURL("image/jpeg"); } catch (e) { console.error("Failed to capture image:", e); return null; } }; enhancedPlayer.downloadMedia = (filename) => { var _a; const source = (_a = player.source.sources[0]) == null ? void 0 : _a.src; if (!source) { console.error("No media source found"); return; } const a = document.createElement("a"); a.href = source; a.download = filename || source.split("/").pop() || "media"; a.style.display = "none"; document.body.appendChild(a); a.click(); document.body.removeChild(a); }; enhancedPlayer.setPoster = (url) => { if (player.source.type === "video") { player.media.poster = url; } }; enhancedPlayer.isFullscreen = () => { return player.fullscreen.active; }; enhancedPlayer.enterPictureInPicture = async () => { if (player.source.type !== "video") { throw new Error("Picture-in-picture is only available for videos"); } const video = player.media; if (document.pictureInPictureElement === video) { return; } if (video.requestPictureInPicture) { await video.requestPictureInPicture(); } else { throw new Error("Picture-in-picture not supported"); } }; enhancedPlayer.exitPictureInPicture = async () => { if (document.pictureInPictureElement) { await document.exitPictureInPicture(); } }; enhancedPlayer.togglePictureInPicture = async () => { if (document.pictureInPictureElement) { await enhancedPlayer.exitPictureInPicture(); } else { await enhancedPlayer.enterPictureInPicture(); } }; enhancedPlayer.reload = () => { const currentTime = player.currentTime; player.media.load(); player.media.currentTime = currentTime; }; enhancedPlayer.getQualityOptions = () => { var _a; return ((_a = mergedOptions.quality) == null ? void 0 : _a.options) || []; }; enhancedPlayer.setQuality = (quality) => { if (player.quality !== void 0) { player.quality = quality; } }; enhancedPlayer.getSpeedOptions = () => { var _a; return ((_a = mergedOptions.speed) == null ? void 0 : _a.options) || []; }; enhancedPlayer.setCustomControls = (controls) => { const currentSource = player.source; const currentTime = player.currentTime; const currentVolume = player.volume; const currentMuted = player.muted; player.destroy(); const newPlayer = new Plyr(player.media, { ...mergedOptions, controls }); newPlayer.source = currentSource; newPlayer.currentTime = currentTime; newPlayer.volume = currentVolume; newPlayer.muted = currentMuted; Object.keys(player).forEach((key) => { player[key] = newPlayer[key]; }); }; enhancedPlayer.addMarker = (time, label, color) => { var _a, _b; if (!((_a = mergedOptions.markers) == null ? void 0 : _a.enabled) || !((_b = mergedOptions.markers) == null ? void 0 : _b.points)) { console.error("Markers are not enabled"); return; } const marker = { time, label, color }; mergedOptions.markers.points.push(marker); setTimeout(() => { const event = new CustomEvent("plyr:markers-update"); player.media.dispatchEvent(event); }, 0); }; enhancedPlayer.removeMarker = (time) => { var _a, _b; if (!((_a = mergedOptions.markers) == null ? void 0 : _a.enabled) || !((_b = mergedOptions.markers) == null ? void 0 : _b.points)) { console.error("Markers are not enabled"); return; } const index = mergedOptions.markers.points.findIndex((marker) => marker.time === time); if (index !== -1) { mergedOptions.markers.points.splice(index, 1); setTimeout(() => { const event = new CustomEvent("plyr:markers-update"); player.media.dispatchEvent(event); }, 0); } }; enhancedPlayer.clearMarkers = () => { var _a, _b; if (!((_a = mergedOptions.markers) == null ? void 0 : _a.enabled) || !((_b = mergedOptions.markers) == null ? void 0 : _b.points)) { console.error("Markers are not enabled"); return; } mergedOptions.markers.points = []; setTimeout(() => { const event = new CustomEvent("plyr:markers-update"); player.media.dispatchEvent(event); }, 0); }; return enhancedPlayer; } async function playWithPlyr(source, container, options = {}) { const sourceConfig = typeof source === "string" ? { type: source.match(/\.(mp4|webm|ogg|ogv)$/i) ? "video" : "audio", sources: [{ src: source, type: source.match(/\.(mp4|webm|ogg|ogv)$/i) ? `video/${source.split(".").pop()}` : `audio/${source.split(".").pop()}` }] } : source; const player = await createPlyrPlayer({ container, source: sourceConfig, autoplay: options.autoplay || false, muted: options.muted || false, volume: options.volume !== void 0 ? options.volume : 1, ...options }); if (options.autoplay) { try { await player.play(); } catch (error) { console.warn("Autoplay failed:", error); player.muted = true; await player.play().catch((e) => { console.error("Muted autoplay also failed:", e); }); } } return player; } async function createPlaylist(sources, container, options = {}) { var _a; if (!sources.length) { throw new Error("Empty playlist"); } let currentIndex = 0; const firstSource = sources[0]; const player = await playWithPlyr(firstSource, container, options); if ((_a = options.loop) == null ? void 0 : _a.active) { player.on("ended", () => { next(); }); } async function next() { if (currentIndex < sources.length - 1) { currentIndex++; } else { currentIndex = 0; } await player.setSource(sources[currentIndex]); await player.play(); } async function previous() { if (currentIndex > 0) { currentIndex--; } else { currentIndex = sources.length - 1; } await player.setSource(sources[currentIndex]); await player.play(); } async function playIndex(index) { if (index < 0 || index >= sources.length) { throw new Error(`Invalid playlist index: ${index}`); } currentIndex = index; await player.setSource(sources[currentIndex]); await player.play(); } return { player, next, previous, playIndex, get currentIndex() { return currentIndex; }, sources }; } export { createPlaylist, createPlyrPlayer, DEFAULT_OPTIONS as defaultPlyrOptions, playWithPlyr };