mstf-kit
Version:
一个现代化的 JavaScript/TypeScript 工具库,提供了丰富的常用工具函数
457 lines (456 loc) • 14.3 kB
JavaScript
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
};