mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 10:56:15 -08:00
feat: add danmuku plugin documentation and i18n setup guide - Created a new documentation file for the danmuku plugin with code examples. - Added a comprehensive guide for language settings (i18n) including default languages, importing languages, adding new languages, and modifying existing languages. - Included code snippets for initializing the Artplayer with different language settings. Add internationalization support for multiple languages in Artplayer - Introduced Arabic (ar), Czech (cs), Spanish (es), Persian (fa), French (fr), Indonesian (id), Polish (pl), Russian (ru), Turkish (tr), Vietnamese (vi), and Traditional Chinese (zh-tw) translations. - Created corresponding JavaScript and ES module files for each language containing translated strings for player controls and settings. - Added a new documentation file for language settings in the project. feat: add initial implementation of start option page with interactive code execution buttons feat: add danmuku plugin documentation and i18n setup guide - Created a new documentation file for the danmuku plugin, providing code examples for usage. - Added a comprehensive guide for language settings, including default languages, importing languages, adding new languages, and modifying existing languages. - Included code snippets for initializing the Artplayer with different language settings and importing language files. refactor: update danmuku worker implementation and remove unused i18n files - Changed the way the Danmuku class creates a Web Worker by using a worker loader instead of creating a blob. - Removed Arabic, Czech, Spanish, Persian, French, Indonesian, Polish, Russian, Turkish, Vietnamese, and Traditional Chinese i18n files as they are no longer needed. - Updated the Vite build configuration to disable banners for inline workers and added a plugin to remove worker banners from generated code. feat: add danmuku plugin and i18n setup documentation - Created a new documentation file for the danmuku plugin, providing code examples for usage. - Added a new documentation file for language settings (i18n), detailing how to set default languages, import languages, and add new languages. - Included code snippets for initializing the Artplayer with different language settings and examples for modifying existing languages. feat: add danmuku plugin and i18n setup documentation - Created a new documentation file for the danmuku plugin with code examples. - Added a new documentation file for language settings (i18n) including import instructions and examples for setting default and custom languages. - Updated the i18n documentation to include details on adding and modifying languages. feat: add danmuku plugin and i18n setup documentation - Created a new JavaScript file for the danmuku plugin documentation with multiple code execution buttons. - Added a new JavaScript file for i18n (internationalization) setup documentation, including instructions for language settings, importing languages, and modifying language options. - Included examples for setting default languages and adding new languages in the i18n documentation. Refactor code structure for improved readability and maintainability
5014 lines
195 KiB
JavaScript
5014 lines
195 KiB
JavaScript
/*!
|
|
* artplayer.js v5.3.1
|
|
* Github: https://github.com/zhw2590582/ArtPlayer
|
|
* (c) 2017-2026 Harvey Zhao
|
|
* Released under the MIT License.
|
|
*/
|
|
function getDefaultExportFromCjs(x) {
|
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
|
|
}
|
|
var optionValidator$1 = { exports: {} };
|
|
var optionValidator = optionValidator$1.exports;
|
|
var hasRequiredOptionValidator;
|
|
function requireOptionValidator() {
|
|
if (hasRequiredOptionValidator) return optionValidator$1.exports;
|
|
hasRequiredOptionValidator = 1;
|
|
(function(module, exports$1) {
|
|
!(function(r, t) {
|
|
module.exports = t();
|
|
})(optionValidator, function() {
|
|
function e(r) {
|
|
return (e = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(r2) {
|
|
return typeof r2;
|
|
} : function(r2) {
|
|
return r2 && "function" == typeof Symbol && r2.constructor === Symbol && r2 !== Symbol.prototype ? "symbol" : typeof r2;
|
|
})(r);
|
|
}
|
|
var n2 = Object.prototype.toString, c = function(r) {
|
|
if (void 0 === r) return "undefined";
|
|
if (null === r) return "null";
|
|
var t = e(r);
|
|
if ("boolean" === t) return "boolean";
|
|
if ("string" === t) return "string";
|
|
if ("number" === t) return "number";
|
|
if ("symbol" === t) return "symbol";
|
|
if ("function" === t) return (function(r2) {
|
|
return "GeneratorFunction" === o2(r2);
|
|
})(r) ? "generatorfunction" : "function";
|
|
if ((function(r2) {
|
|
return Array.isArray ? Array.isArray(r2) : r2 instanceof Array;
|
|
})(r)) return "array";
|
|
if ((function(r2) {
|
|
if (r2.constructor && "function" == typeof r2.constructor.isBuffer) return r2.constructor.isBuffer(r2);
|
|
return false;
|
|
})(r)) return "buffer";
|
|
if ((function(r2) {
|
|
try {
|
|
if ("number" == typeof r2.length && "function" == typeof r2.callee) return true;
|
|
} catch (r3) {
|
|
if (-1 !== r3.message.indexOf("callee")) return true;
|
|
}
|
|
return false;
|
|
})(r)) return "arguments";
|
|
if ((function(r2) {
|
|
return r2 instanceof Date || "function" == typeof r2.toDateString && "function" == typeof r2.getDate && "function" == typeof r2.setDate;
|
|
})(r)) return "date";
|
|
if ((function(r2) {
|
|
return r2 instanceof Error || "string" == typeof r2.message && r2.constructor && "number" == typeof r2.constructor.stackTraceLimit;
|
|
})(r)) return "error";
|
|
if ((function(r2) {
|
|
return r2 instanceof RegExp || "string" == typeof r2.flags && "boolean" == typeof r2.ignoreCase && "boolean" == typeof r2.multiline && "boolean" == typeof r2.global;
|
|
})(r)) return "regexp";
|
|
switch (o2(r)) {
|
|
case "Symbol":
|
|
return "symbol";
|
|
case "Promise":
|
|
return "promise";
|
|
case "WeakMap":
|
|
return "weakmap";
|
|
case "WeakSet":
|
|
return "weakset";
|
|
case "Map":
|
|
return "map";
|
|
case "Set":
|
|
return "set";
|
|
case "Int8Array":
|
|
return "int8array";
|
|
case "Uint8Array":
|
|
return "uint8array";
|
|
case "Uint8ClampedArray":
|
|
return "uint8clampedarray";
|
|
case "Int16Array":
|
|
return "int16array";
|
|
case "Uint16Array":
|
|
return "uint16array";
|
|
case "Int32Array":
|
|
return "int32array";
|
|
case "Uint32Array":
|
|
return "uint32array";
|
|
case "Float32Array":
|
|
return "float32array";
|
|
case "Float64Array":
|
|
return "float64array";
|
|
}
|
|
if ((function(r2) {
|
|
return "function" == typeof r2.throw && "function" == typeof r2.return && "function" == typeof r2.next;
|
|
})(r)) return "generator";
|
|
switch (t = n2.call(r)) {
|
|
case "[object Object]":
|
|
return "object";
|
|
case "[object Map Iterator]":
|
|
return "mapiterator";
|
|
case "[object Set Iterator]":
|
|
return "setiterator";
|
|
case "[object String Iterator]":
|
|
return "stringiterator";
|
|
case "[object Array Iterator]":
|
|
return "arrayiterator";
|
|
}
|
|
return t.slice(8, -1).toLowerCase().replace(/\s/g, "");
|
|
};
|
|
function o2(r) {
|
|
return r.constructor ? r.constructor.name : null;
|
|
}
|
|
function f2(r, t) {
|
|
var e2 = 2 < arguments.length && void 0 !== arguments[2] ? arguments[2] : ["option"];
|
|
return s2(r, t, e2), y(r, t, e2), (function(a2, i, u) {
|
|
var r2 = c(i), t2 = c(a2);
|
|
if ("object" === r2) {
|
|
if ("object" !== t2) throw new Error("[Type Error]: '".concat(u.join("."), "' require 'object' type, but got '").concat(t2, "'"));
|
|
Object.keys(i).forEach(function(r3) {
|
|
var t3 = a2[r3], e3 = i[r3], n3 = u.slice();
|
|
n3.push(r3), s2(t3, e3, n3), y(t3, e3, n3), f2(t3, e3, n3);
|
|
});
|
|
}
|
|
if ("array" === r2) {
|
|
if ("array" !== t2) throw new Error("[Type Error]: '".concat(u.join("."), "' require 'array' type, but got '").concat(t2, "'"));
|
|
a2.forEach(function(r3, t3) {
|
|
var e3 = a2[t3], n3 = i[t3] || i[0], o3 = u.slice();
|
|
o3.push(t3), s2(e3, n3, o3), y(e3, n3, o3), f2(e3, n3, o3);
|
|
});
|
|
}
|
|
})(r, t, e2), r;
|
|
}
|
|
function s2(r, t, e2) {
|
|
if ("string" === c(t)) {
|
|
var n3 = c(r);
|
|
if ("?" === t[0] && (t = t.slice(1) + "|undefined"), !(-1 < t.indexOf("|") ? t.split("|").map(function(r2) {
|
|
return r2.toLowerCase().trim();
|
|
}).filter(Boolean).some(function(r2) {
|
|
return n3 === r2;
|
|
}) : t.toLowerCase().trim() === n3)) throw new Error("[Type Error]: '".concat(e2.join("."), "' require '").concat(t, "' type, but got '").concat(n3, "'"));
|
|
}
|
|
}
|
|
function y(r, t, e2) {
|
|
if ("function" === c(t)) {
|
|
var n3 = t(r, c(r), e2);
|
|
if (true !== n3) {
|
|
var o3 = c(n3);
|
|
throw "string" === o3 ? new Error(n3) : "error" === o3 ? n3 : new Error("[Validator Error]: The scheme for '".concat(e2.join("."), "' validator require return true, but got '").concat(n3, "'"));
|
|
}
|
|
}
|
|
}
|
|
return f2.kindOf = c, f2;
|
|
});
|
|
})(optionValidator$1);
|
|
return optionValidator$1.exports;
|
|
}
|
|
var optionValidatorExports = requireOptionValidator();
|
|
const validator = /* @__PURE__ */ getDefaultExportFromCjs(optionValidatorExports);
|
|
const version$1 = "5.3.1";
|
|
const config$1 = {
|
|
properties: [
|
|
"audioTracks",
|
|
"autoplay",
|
|
"buffered",
|
|
"controller",
|
|
"controls",
|
|
"crossOrigin",
|
|
"currentSrc",
|
|
"currentTime",
|
|
"defaultMuted",
|
|
"defaultPlaybackRate",
|
|
"duration",
|
|
"ended",
|
|
"error",
|
|
"loop",
|
|
"mediaGroup",
|
|
"muted",
|
|
"networkState",
|
|
"paused",
|
|
"playbackRate",
|
|
"played",
|
|
"preload",
|
|
"readyState",
|
|
"seekable",
|
|
"seeking",
|
|
"src",
|
|
"startDate",
|
|
"textTracks",
|
|
"videoTracks",
|
|
"volume"
|
|
],
|
|
methods: ["addTextTrack", "canPlayType", "load", "play", "pause"],
|
|
events: [
|
|
"abort",
|
|
"canplay",
|
|
"canplaythrough",
|
|
"durationchange",
|
|
"emptied",
|
|
"ended",
|
|
"error",
|
|
"loadeddata",
|
|
"loadedmetadata",
|
|
"loadstart",
|
|
"pause",
|
|
"play",
|
|
"playing",
|
|
"progress",
|
|
"ratechange",
|
|
"seeked",
|
|
"seeking",
|
|
"stalled",
|
|
"suspend",
|
|
"timeupdate",
|
|
"volumechange",
|
|
"waiting"
|
|
],
|
|
prototypes: [
|
|
"width",
|
|
"height",
|
|
"videoWidth",
|
|
"videoHeight",
|
|
"poster",
|
|
"webkitDecodedFrameCount",
|
|
"webkitDroppedFrameCount",
|
|
"playsInline",
|
|
"webkitSupportsFullscreen",
|
|
"webkitDisplayingFullscreen",
|
|
"onenterpictureinpicture",
|
|
"onleavepictureinpicture",
|
|
"disablePictureInPicture",
|
|
"cancelVideoFrameCallback",
|
|
"requestVideoFrameCallback",
|
|
"getVideoPlaybackQuality",
|
|
"requestPictureInPicture",
|
|
"webkitEnterFullScreen",
|
|
"webkitEnterFullscreen",
|
|
"webkitExitFullScreen",
|
|
"webkitExitFullscreen"
|
|
]
|
|
};
|
|
const userAgent = globalThis?.CUSTOM_USER_AGENT ?? (typeof navigator !== "undefined" ? navigator.userAgent : "");
|
|
const isSafari = /^(?:(?!chrome|android).)*safari/i.test(userAgent);
|
|
const isIOS = /iPad|iPhone|iPod/i.test(userAgent) && !window.MSStream;
|
|
const isIOS13 = isIOS || userAgent.includes("Macintosh") && navigator.maxTouchPoints >= 1;
|
|
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent) || isIOS13;
|
|
const isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
function query(selector, parent = document) {
|
|
return parent.querySelector(selector);
|
|
}
|
|
function queryAll(selector, parent = document) {
|
|
return Array.from(parent.querySelectorAll(selector));
|
|
}
|
|
function addClass(target, className) {
|
|
return target.classList.add(className);
|
|
}
|
|
function removeClass(target, className) {
|
|
return target.classList.remove(className);
|
|
}
|
|
function hasClass(target, className) {
|
|
return target.classList.contains(className);
|
|
}
|
|
function append(parent, child) {
|
|
if (child instanceof Element) {
|
|
parent.appendChild(child);
|
|
} else {
|
|
parent.insertAdjacentHTML("beforeend", String(child));
|
|
}
|
|
return parent.lastElementChild || parent.lastChild;
|
|
}
|
|
function remove(child) {
|
|
return child.parentNode.removeChild(child);
|
|
}
|
|
function setStyle(element, key, value) {
|
|
element.style[key] = value;
|
|
return element;
|
|
}
|
|
function setStyles(element, styles) {
|
|
for (const key in styles) {
|
|
setStyle(element, key, styles[key]);
|
|
}
|
|
return element;
|
|
}
|
|
function getStyle(element, key, numberType = true) {
|
|
const value = window.getComputedStyle(element, null).getPropertyValue(key);
|
|
return numberType ? Number.parseFloat(value) : value;
|
|
}
|
|
function siblings(target) {
|
|
return Array.from(target.parentElement.children).filter((item) => item !== target);
|
|
}
|
|
function inverseClass(target, className) {
|
|
siblings(target).forEach((item) => removeClass(item, className));
|
|
addClass(target, className);
|
|
}
|
|
function tooltip(target, msg, pos = "top") {
|
|
if (isMobile)
|
|
return;
|
|
target.setAttribute("aria-label", msg);
|
|
addClass(target, "hint--rounded");
|
|
addClass(target, `hint--${pos}`);
|
|
}
|
|
function isInViewport(el, offset = 0) {
|
|
const rect = el.getBoundingClientRect();
|
|
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
|
|
const windowWidth = window.innerWidth || document.documentElement.clientWidth;
|
|
const vertInView = rect.top - offset <= windowHeight && rect.top + rect.height + offset >= 0;
|
|
const horInView = rect.left - offset <= windowWidth + offset && rect.left + rect.width + offset >= 0;
|
|
return vertInView && horInView;
|
|
}
|
|
function includeFromEvent(event, target) {
|
|
return getComposedPath(event).includes(target);
|
|
}
|
|
function replaceElement(newChild, oldChild) {
|
|
oldChild.parentNode.replaceChild(newChild, oldChild);
|
|
return newChild;
|
|
}
|
|
function createElement(tag) {
|
|
return document.createElement(tag);
|
|
}
|
|
function getIcon(key = "", html = "") {
|
|
const icon = createElement("i");
|
|
addClass(icon, "art-icon");
|
|
addClass(icon, `art-icon-${key}`);
|
|
append(icon, html);
|
|
return icon;
|
|
}
|
|
function setStyleText(id2, style2) {
|
|
let $style = document.getElementById(id2);
|
|
if (!$style) {
|
|
$style = document.createElement("style");
|
|
$style.id = id2;
|
|
if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
document.head.appendChild($style);
|
|
});
|
|
} else {
|
|
(document.head || document.documentElement).appendChild($style);
|
|
}
|
|
}
|
|
$style.textContent = style2;
|
|
}
|
|
function supportsFlex() {
|
|
const div = document.createElement("div");
|
|
div.style.display = "flex";
|
|
return div.style.display === "flex";
|
|
}
|
|
function getRect(el) {
|
|
return el.getBoundingClientRect();
|
|
}
|
|
function loadImg(url, scale) {
|
|
return new Promise((resolve, reject) => {
|
|
const img = new Image();
|
|
img.onload = function() {
|
|
if (!scale || scale === 1) {
|
|
resolve(img);
|
|
} else {
|
|
const canvas = document.createElement("canvas");
|
|
const ctx = canvas.getContext("2d");
|
|
canvas.width = img.width * scale;
|
|
canvas.height = img.height * scale;
|
|
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
canvas.toBlob((blob) => {
|
|
const blobUrl = URL.createObjectURL(blob);
|
|
const scaledImg = new Image();
|
|
scaledImg.onload = function() {
|
|
resolve(scaledImg);
|
|
};
|
|
scaledImg.onerror = function() {
|
|
URL.revokeObjectURL(blobUrl);
|
|
reject(new Error(`Image load failed: ${url}`));
|
|
};
|
|
scaledImg.src = blobUrl;
|
|
});
|
|
}
|
|
};
|
|
img.onerror = function() {
|
|
reject(new Error(`Image load failed: ${url}`));
|
|
};
|
|
img.src = url;
|
|
});
|
|
}
|
|
function getComposedPath(event) {
|
|
if (event.composedPath)
|
|
return event.composedPath();
|
|
const path = [];
|
|
let node = event.target;
|
|
while (node) {
|
|
path.push(node);
|
|
node = node.parentNode;
|
|
}
|
|
if (!path.includes(window) && window !== void 0) {
|
|
path.push(window);
|
|
}
|
|
return path;
|
|
}
|
|
class ArtPlayerError extends Error {
|
|
constructor(message, context) {
|
|
super(message);
|
|
if (typeof Error.captureStackTrace === "function") {
|
|
Error.captureStackTrace(this, context || this.constructor);
|
|
}
|
|
this.name = "ArtPlayerError";
|
|
}
|
|
}
|
|
function errorHandle(condition, msg) {
|
|
if (!condition) {
|
|
throw new ArtPlayerError(msg);
|
|
}
|
|
return condition;
|
|
}
|
|
function getExt(url) {
|
|
if (url.includes("?")) {
|
|
return getExt(url.split("?")[0]);
|
|
}
|
|
if (url.includes("#")) {
|
|
return getExt(url.split("#")[0]);
|
|
}
|
|
return url.trim().toLowerCase().split(".").pop();
|
|
}
|
|
function download(url, name) {
|
|
const elink = document.createElement("a");
|
|
elink.style.display = "none";
|
|
elink.href = url;
|
|
elink.download = name;
|
|
document.body.appendChild(elink);
|
|
elink.click();
|
|
document.body.removeChild(elink);
|
|
}
|
|
function clamp(num, a2, b2) {
|
|
return Math.max(Math.min(num, Math.max(a2, b2)), Math.min(a2, b2));
|
|
}
|
|
function capitalize(str) {
|
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
}
|
|
function secondToTime(second) {
|
|
if (!second)
|
|
return "00:00";
|
|
const add0 = (num) => num < 10 ? `0${num}` : String(num);
|
|
const hour = Math.floor(second / 3600);
|
|
const min = Math.floor((second - hour * 3600) / 60);
|
|
const sec = Math.floor(second - hour * 3600 - min * 60);
|
|
return (hour > 0 ? [hour, min, sec] : [min, sec]).map(add0).join(":");
|
|
}
|
|
function escape(str) {
|
|
return str.replace(
|
|
/[&<>'"]/g,
|
|
(tag) => ({
|
|
"&": "&",
|
|
"<": "<",
|
|
">": ">",
|
|
"'": "'",
|
|
'"': """
|
|
})[tag] || tag
|
|
);
|
|
}
|
|
function unescape(str) {
|
|
const map = {
|
|
"&": "&",
|
|
"<": "<",
|
|
">": ">",
|
|
"'": "'",
|
|
""": '"'
|
|
};
|
|
const reg = new RegExp(`(${Object.keys(map).join("|")})`, "g");
|
|
return str.replace(reg, (tag) => map[tag] || tag);
|
|
}
|
|
const def = Object.defineProperty;
|
|
const { hasOwnProperty } = Object.prototype;
|
|
function has(obj, name) {
|
|
return hasOwnProperty.call(obj, name);
|
|
}
|
|
function get(obj, name) {
|
|
return Object.getOwnPropertyDescriptor(obj, name);
|
|
}
|
|
function mergeDeep(...objects) {
|
|
const isObject = (item) => item && typeof item === "object" && !Array.isArray(item);
|
|
return objects.reduce((prev, obj) => {
|
|
Object.keys(obj).forEach((key) => {
|
|
const pVal = prev[key];
|
|
const oVal = obj[key];
|
|
if (Array.isArray(pVal) && Array.isArray(oVal)) {
|
|
prev[key] = pVal.concat(...oVal);
|
|
} else if (isObject(pVal) && isObject(oVal)) {
|
|
prev[key] = mergeDeep(pVal, oVal);
|
|
} else {
|
|
prev[key] = oVal;
|
|
}
|
|
});
|
|
return prev;
|
|
}, {});
|
|
}
|
|
function fixSrt(srt) {
|
|
return srt.replace(/(\d\d:\d\d:\d\d)[,.](\d+)/g, (_, $1, $2) => {
|
|
let ms = $2.slice(0, 3);
|
|
if ($2.length === 1) {
|
|
ms = `${$2}00`;
|
|
}
|
|
if ($2.length === 2) {
|
|
ms = `${$2}0`;
|
|
}
|
|
return `${$1},${ms}`;
|
|
});
|
|
}
|
|
function srtToVtt(srtText) {
|
|
return "WEBVTT \r\n\r\n".concat(
|
|
fixSrt(srtText).replace(/\{\\([ibu])\}/g, "</$1>").replace(/\{\\([ibu])1\}/g, "<$1>").replace(/\{([ibu])\}/g, "<$1>").replace(/\{\/([ibu])\}/g, "</$1>").replace(/(\d\d:\d\d:\d\d),(\d\d\d)/g, "$1.$2").replace(/\{[\s\S]*?\}/g, "").concat("\r\n\r\n")
|
|
);
|
|
}
|
|
function vttToBlob(vttText) {
|
|
return URL.createObjectURL(
|
|
new Blob([vttText], {
|
|
type: "text/vtt"
|
|
})
|
|
);
|
|
}
|
|
function assToVtt(ass) {
|
|
const reAss = new RegExp(
|
|
"Dialogue:\\s\\d,(\\d+:\\d\\d:\\d\\d.\\d\\d),(\\d+:\\d\\d:\\d\\d.\\d\\d),([^,]*),([^,]*),(?:[^,]*,){4}([\\s\\S]*)$",
|
|
"i"
|
|
);
|
|
function fixTime(time2 = "") {
|
|
return time2.split(/[:.]/).map((item, index, arr) => {
|
|
if (index === arr.length - 1) {
|
|
if (item.length === 1) {
|
|
return `.${item}00`;
|
|
}
|
|
if (item.length === 2) {
|
|
return `.${item}0`;
|
|
}
|
|
} else if (item.length === 1) {
|
|
return (index === 0 ? "0" : ":0") + item;
|
|
}
|
|
return index === 0 ? item : index === arr.length - 1 ? `.${item}` : `:${item}`;
|
|
}).join("");
|
|
}
|
|
return `WEBVTT
|
|
|
|
${ass.split(/\r?\n/).map((line) => {
|
|
const m = line.match(reAss);
|
|
if (!m)
|
|
return null;
|
|
return {
|
|
start: fixTime(m[1].trim()),
|
|
end: fixTime(m[2].trim()),
|
|
text: m[5].replace(/\{[\s\S]*?\}/g, "").replace(/(\\N)/g, "\n").trim().split(/\r?\n/).map((item) => item.trim()).join("\n")
|
|
};
|
|
}).filter((line) => line).map((line, index) => {
|
|
if (line) {
|
|
return `${index + 1}
|
|
${line.start} --> ${line.end}
|
|
${line.text}`;
|
|
}
|
|
return "";
|
|
}).filter((line) => line.trim()).join("\n\n")}`;
|
|
}
|
|
function sleep(ms = 0) {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
function debounce(func, duration) {
|
|
let timeout;
|
|
return function(...args) {
|
|
const effect = () => {
|
|
timeout = null;
|
|
return func.apply(this, args);
|
|
};
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(effect, duration);
|
|
};
|
|
}
|
|
function throttle(func, duration) {
|
|
let shouldWait = false;
|
|
return function(...args) {
|
|
if (!shouldWait) {
|
|
func.apply(this, args);
|
|
shouldWait = true;
|
|
setTimeout(() => {
|
|
shouldWait = false;
|
|
}, duration);
|
|
}
|
|
};
|
|
}
|
|
const utils = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
__proto__: null,
|
|
ArtPlayerError,
|
|
addClass,
|
|
append,
|
|
assToVtt,
|
|
capitalize,
|
|
clamp,
|
|
createElement,
|
|
debounce,
|
|
def,
|
|
download,
|
|
errorHandle,
|
|
escape,
|
|
get,
|
|
getComposedPath,
|
|
getExt,
|
|
getIcon,
|
|
getRect,
|
|
getStyle,
|
|
has,
|
|
hasClass,
|
|
includeFromEvent,
|
|
inverseClass,
|
|
isBrowser,
|
|
isIOS,
|
|
isIOS13,
|
|
isInViewport,
|
|
isMobile,
|
|
isSafari,
|
|
loadImg,
|
|
mergeDeep,
|
|
query,
|
|
queryAll,
|
|
remove,
|
|
removeClass,
|
|
replaceElement,
|
|
secondToTime,
|
|
setStyle,
|
|
setStyleText,
|
|
setStyles,
|
|
siblings,
|
|
sleep,
|
|
srtToVtt,
|
|
supportsFlex,
|
|
throttle,
|
|
tooltip,
|
|
unescape,
|
|
userAgent,
|
|
vttToBlob
|
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
const a = "array";
|
|
const b = "boolean";
|
|
const s = "string";
|
|
const n = "number";
|
|
const o = "object";
|
|
const f = "function";
|
|
function validElement(value, type, paths) {
|
|
return errorHandle(
|
|
type === s || type === n || value instanceof Element,
|
|
`${paths.join(".")} require '${s}' or 'Element' type`
|
|
);
|
|
}
|
|
const ComponentOption = {
|
|
html: validElement,
|
|
disable: `?${b}`,
|
|
name: `?${s}`,
|
|
index: `?${n}`,
|
|
style: `?${o}`,
|
|
click: `?${f}`,
|
|
mounted: `?${f}`,
|
|
tooltip: `?${s}|${n}`,
|
|
width: `?${n}`,
|
|
selector: `?${a}`,
|
|
onSelect: `?${f}`,
|
|
switch: `?${b}`,
|
|
onSwitch: `?${f}`,
|
|
range: `?${a}`,
|
|
onRange: `?${f}`,
|
|
onChange: `?${f}`
|
|
};
|
|
const scheme = {
|
|
id: s,
|
|
container: validElement,
|
|
url: s,
|
|
poster: s,
|
|
type: s,
|
|
theme: s,
|
|
lang: s,
|
|
volume: n,
|
|
isLive: b,
|
|
muted: b,
|
|
autoplay: b,
|
|
autoSize: b,
|
|
autoMini: b,
|
|
loop: b,
|
|
flip: b,
|
|
playbackRate: b,
|
|
aspectRatio: b,
|
|
screenshot: b,
|
|
setting: b,
|
|
hotkey: b,
|
|
pip: b,
|
|
mutex: b,
|
|
backdrop: b,
|
|
fullscreen: b,
|
|
fullscreenWeb: b,
|
|
subtitleOffset: b,
|
|
miniProgressBar: b,
|
|
useSSR: b,
|
|
playsInline: b,
|
|
lock: b,
|
|
gesture: b,
|
|
fastForward: b,
|
|
autoPlayback: b,
|
|
autoOrientation: b,
|
|
airplay: b,
|
|
proxy: `?${f}`,
|
|
plugins: [f],
|
|
layers: [ComponentOption],
|
|
contextmenu: [ComponentOption],
|
|
settings: [ComponentOption],
|
|
controls: [
|
|
{
|
|
...ComponentOption,
|
|
position: (value, _, paths) => {
|
|
const position = ["top", "left", "right"];
|
|
return errorHandle(
|
|
position.includes(value),
|
|
`${paths.join(".")} only accept ${position.toString()} as parameters`
|
|
);
|
|
}
|
|
}
|
|
],
|
|
quality: [
|
|
{
|
|
default: `?${b}`,
|
|
html: s,
|
|
url: s
|
|
}
|
|
],
|
|
highlight: [
|
|
{
|
|
time: n,
|
|
text: s
|
|
}
|
|
],
|
|
thumbnails: {
|
|
url: s,
|
|
number: n,
|
|
column: n,
|
|
width: n,
|
|
height: n,
|
|
scale: n
|
|
},
|
|
subtitle: {
|
|
url: s,
|
|
name: s,
|
|
type: s,
|
|
style: o,
|
|
escape: b,
|
|
encoding: s,
|
|
onVttLoad: f
|
|
},
|
|
moreVideoAttr: o,
|
|
i18n: o,
|
|
icons: o,
|
|
cssVar: o,
|
|
customType: o
|
|
};
|
|
class Component {
|
|
constructor(art) {
|
|
this.id = 0;
|
|
this.art = art;
|
|
this.cache = /* @__PURE__ */ new Map();
|
|
this.add = this.add.bind(this);
|
|
this.remove = this.remove.bind(this);
|
|
this.update = this.update.bind(this);
|
|
}
|
|
get show() {
|
|
return hasClass(this.art.template.$player, `art-${this.name}-show`);
|
|
}
|
|
set show(value) {
|
|
const { $player } = this.art.template;
|
|
const className = `art-${this.name}-show`;
|
|
if (value) {
|
|
addClass($player, className);
|
|
} else {
|
|
removeClass($player, className);
|
|
}
|
|
this.art.emit(this.name, value);
|
|
}
|
|
toggle() {
|
|
this.show = !this.show;
|
|
}
|
|
add(getOption) {
|
|
const option = typeof getOption === "function" ? getOption(this.art) : getOption;
|
|
option.html = option.html || "";
|
|
validator(option, ComponentOption);
|
|
if (!this.$parent || !this.name || option.disable)
|
|
return;
|
|
const name = option.name || `${this.name}${this.id}`;
|
|
errorHandle(!this.cache.has(name), `Can't add an existing [${name}] to the [${this.name}]`);
|
|
this.id += 1;
|
|
const $ref = createElement("div");
|
|
addClass($ref, `art-${this.name}`);
|
|
addClass($ref, `art-${this.name}-${name}`);
|
|
const childs = Array.from(this.$parent.children);
|
|
$ref.dataset.index = option.index || this.id;
|
|
const nextChild = childs.find((item) => Number(item.dataset.index) >= Number($ref.dataset.index));
|
|
if (nextChild) {
|
|
nextChild.insertAdjacentElement("beforebegin", $ref);
|
|
} else {
|
|
append(this.$parent, $ref);
|
|
}
|
|
if (option.html) {
|
|
append($ref, option.html);
|
|
}
|
|
if (option.style) {
|
|
setStyles($ref, option.style);
|
|
}
|
|
if (option.tooltip) {
|
|
tooltip($ref, option.tooltip);
|
|
}
|
|
const events = [];
|
|
if (option.click) {
|
|
const destroyEvent = this.art.events.proxy($ref, "click", (event) => {
|
|
event.preventDefault();
|
|
option.click.call(this.art, this, event);
|
|
});
|
|
events.push(destroyEvent);
|
|
}
|
|
if (option.selector && ["left", "right"].includes(option.position)) {
|
|
this.selector(option, $ref, events);
|
|
}
|
|
this[name] = $ref;
|
|
this.cache.set(name, { $ref, events, option });
|
|
if (option.mounted) {
|
|
option.mounted.call(this.art, $ref);
|
|
}
|
|
return $ref;
|
|
}
|
|
remove(name) {
|
|
errorHandle(this.cache.has(name), `Can't find [${name}] from the [${this.name}]`);
|
|
const item = this.cache.get(name);
|
|
if (item.option.beforeUnmount) {
|
|
item.option.beforeUnmount.call(this.art, item.$ref);
|
|
}
|
|
for (const event of item.events) {
|
|
this.art.events.remove(event);
|
|
}
|
|
this.cache.delete(name);
|
|
delete this[name];
|
|
remove(item.$ref);
|
|
}
|
|
update(option) {
|
|
if (this.cache.has(option.name)) {
|
|
const item = this.cache.get(option.name);
|
|
option = Object.assign(item.option, option);
|
|
this.remove(option.name);
|
|
}
|
|
return this.add(option);
|
|
}
|
|
}
|
|
function aspectRatio$2(option) {
|
|
return (art) => {
|
|
const {
|
|
i18n,
|
|
constructor: { ASPECT_RATIO }
|
|
} = art;
|
|
const html = ASPECT_RATIO.map(
|
|
(item) => `<span data-value="${item}">${item === "default" ? i18n.get("Default") : item}</span>`
|
|
).join("");
|
|
return {
|
|
...option,
|
|
html: `${i18n.get("Aspect Ratio")}: ${html}`,
|
|
click: (contextmenu, event) => {
|
|
const { value } = event.target.dataset;
|
|
if (value) {
|
|
art.aspectRatio = value;
|
|
contextmenu.show = false;
|
|
}
|
|
},
|
|
mounted: ($panel) => {
|
|
const $default = query('[data-value="default"]', $panel);
|
|
if ($default) {
|
|
inverseClass($default, "art-current");
|
|
}
|
|
art.on("aspectRatio", (value) => {
|
|
const $current = queryAll("span", $panel).find((item) => item.dataset.value === value);
|
|
if ($current) {
|
|
inverseClass($current, "art-current");
|
|
}
|
|
});
|
|
}
|
|
};
|
|
};
|
|
}
|
|
function close$1(option) {
|
|
return (art) => ({
|
|
...option,
|
|
html: art.i18n.get("Close"),
|
|
click: (contextmenu) => {
|
|
contextmenu.show = false;
|
|
}
|
|
});
|
|
}
|
|
function flip$2(option) {
|
|
return (art) => {
|
|
const {
|
|
i18n,
|
|
constructor: { FLIP }
|
|
} = art;
|
|
const html = FLIP.map((item) => `<span data-value="${item}">${i18n.get(capitalize(item))}</span>`).join("");
|
|
return {
|
|
...option,
|
|
html: `${i18n.get("Video Flip")}: ${html}`,
|
|
click: (contextmenu, event) => {
|
|
const { value } = event.target.dataset;
|
|
if (value) {
|
|
art.flip = value.toLowerCase();
|
|
contextmenu.show = false;
|
|
}
|
|
},
|
|
mounted: ($panel) => {
|
|
const $default = query('[data-value="normal"]', $panel);
|
|
if ($default) {
|
|
inverseClass($default, "art-current");
|
|
}
|
|
art.on("flip", (value) => {
|
|
const $current = queryAll("span", $panel).find((item) => item.dataset.value === value);
|
|
if ($current) {
|
|
inverseClass($current, "art-current");
|
|
}
|
|
});
|
|
}
|
|
};
|
|
};
|
|
}
|
|
function info(option) {
|
|
return (art) => ({
|
|
...option,
|
|
html: art.i18n.get("Video Info"),
|
|
click: (contextmenu) => {
|
|
art.info.show = true;
|
|
contextmenu.show = false;
|
|
}
|
|
});
|
|
}
|
|
function playbackRate$2(option) {
|
|
return (art) => {
|
|
const {
|
|
i18n,
|
|
constructor: { PLAYBACK_RATE }
|
|
} = art;
|
|
const html = PLAYBACK_RATE.map(
|
|
(item) => `<span data-value="${item}">${item === 1 ? i18n.get("Normal") : item.toFixed(1)}</span>`
|
|
).join("");
|
|
return {
|
|
...option,
|
|
html: `${i18n.get("Play Speed")}: ${html}`,
|
|
click: (contextmenu, event) => {
|
|
const { value } = event.target.dataset;
|
|
if (value) {
|
|
art.playbackRate = Number(value);
|
|
contextmenu.show = false;
|
|
}
|
|
},
|
|
mounted: ($panel) => {
|
|
const $default = query('[data-value="1"]', $panel);
|
|
if ($default)
|
|
inverseClass($default, "art-current");
|
|
art.on("video:ratechange", () => {
|
|
const $current = queryAll("span", $panel).find(
|
|
(item) => Number(item.dataset.value) === art.playbackRate
|
|
);
|
|
if ($current) {
|
|
inverseClass($current, "art-current");
|
|
}
|
|
});
|
|
}
|
|
};
|
|
};
|
|
}
|
|
function version(option) {
|
|
const url = isBrowser ? location.href : "";
|
|
return {
|
|
...option,
|
|
html: `<a href="https://artplayer.org?ref=${encodeURIComponent(url)}" target="_blank" style="width:100%;">ArtPlayer ${version$1}</a>`
|
|
};
|
|
}
|
|
class Contextmenu extends Component {
|
|
constructor(art) {
|
|
super(art);
|
|
this.name = "contextmenu";
|
|
this.$parent = art.template.$contextmenu;
|
|
if (!isMobile) {
|
|
this.init();
|
|
}
|
|
}
|
|
init() {
|
|
const {
|
|
option,
|
|
proxy,
|
|
template: { $player, $contextmenu }
|
|
} = this.art;
|
|
if (option.playbackRate) {
|
|
this.add(
|
|
playbackRate$2({
|
|
name: "playbackRate",
|
|
index: 10
|
|
})
|
|
);
|
|
}
|
|
if (option.aspectRatio) {
|
|
this.add(
|
|
aspectRatio$2({
|
|
name: "aspectRatio",
|
|
index: 20
|
|
})
|
|
);
|
|
}
|
|
if (option.flip) {
|
|
this.add(
|
|
flip$2({
|
|
name: "flip",
|
|
index: 30
|
|
})
|
|
);
|
|
}
|
|
this.add(
|
|
info({
|
|
name: "info",
|
|
index: 40
|
|
})
|
|
);
|
|
this.add(
|
|
version({
|
|
name: "version",
|
|
index: 50
|
|
})
|
|
);
|
|
this.add(
|
|
close$1({
|
|
name: "close",
|
|
index: 60
|
|
})
|
|
);
|
|
for (let index = 0; index < option.contextmenu.length; index++) {
|
|
this.add(option.contextmenu[index]);
|
|
}
|
|
proxy($player, "contextmenu", (event) => {
|
|
if (!this.art.constructor.CONTEXTMENU)
|
|
return;
|
|
event.preventDefault();
|
|
this.show = true;
|
|
const mouseX = event.clientX;
|
|
const mouseY = event.clientY;
|
|
const { height: cHeight, width: cWidth, left: cLeft, top: cTop } = getRect($player);
|
|
const { height: mHeight, width: mWidth } = getRect($contextmenu);
|
|
let menuLeft = mouseX - cLeft;
|
|
let menuTop = mouseY - cTop;
|
|
if (mouseX + mWidth > cLeft + cWidth) {
|
|
menuLeft = cWidth - mWidth;
|
|
}
|
|
if (mouseY + mHeight > cTop + cHeight) {
|
|
menuTop = cHeight - mHeight;
|
|
}
|
|
setStyles($contextmenu, {
|
|
top: `${menuTop}px`,
|
|
left: `${menuLeft}px`
|
|
});
|
|
});
|
|
proxy($player, "click", (event) => {
|
|
if (!includeFromEvent(event, $contextmenu)) {
|
|
this.show = false;
|
|
}
|
|
});
|
|
this.art.on("blur", () => {
|
|
this.show = false;
|
|
});
|
|
}
|
|
}
|
|
function airplay$1(option) {
|
|
return (art) => ({
|
|
...option,
|
|
tooltip: art.i18n.get("AirPlay"),
|
|
mounted: ($control) => {
|
|
const { proxy, icons } = art;
|
|
append($control, icons.airplay);
|
|
proxy($control, "click", () => art.airplay());
|
|
}
|
|
});
|
|
}
|
|
function fullscreen(option) {
|
|
return (art) => ({
|
|
...option,
|
|
tooltip: art.i18n.get("Fullscreen"),
|
|
mounted: ($control) => {
|
|
const { proxy, icons, i18n } = art;
|
|
const $fullscreenOn = append($control, icons.fullscreenOn);
|
|
const $fullscreenOff = append($control, icons.fullscreenOff);
|
|
setStyle($fullscreenOff, "display", "none");
|
|
proxy($control, "click", () => {
|
|
art.fullscreen = !art.fullscreen;
|
|
});
|
|
art.on("fullscreen", (state2) => {
|
|
if (state2) {
|
|
tooltip($control, i18n.get("Exit Fullscreen"));
|
|
setStyle($fullscreenOn, "display", "none");
|
|
setStyle($fullscreenOff, "display", "inline-flex");
|
|
} else {
|
|
tooltip($control, i18n.get("Fullscreen"));
|
|
setStyle($fullscreenOn, "display", "inline-flex");
|
|
setStyle($fullscreenOff, "display", "none");
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
function fullscreenWeb(option) {
|
|
return (art) => ({
|
|
...option,
|
|
tooltip: art.i18n.get("Web Fullscreen"),
|
|
mounted: ($control) => {
|
|
const { proxy, icons, i18n } = art;
|
|
const $fullscreenWebOn = append($control, icons.fullscreenWebOn);
|
|
const $fullscreenWebOff = append($control, icons.fullscreenWebOff);
|
|
setStyle($fullscreenWebOff, "display", "none");
|
|
proxy($control, "click", () => {
|
|
art.fullscreenWeb = !art.fullscreenWeb;
|
|
});
|
|
art.on("fullscreenWeb", (value) => {
|
|
if (value) {
|
|
tooltip($control, i18n.get("Exit Web Fullscreen"));
|
|
setStyle($fullscreenWebOn, "display", "none");
|
|
setStyle($fullscreenWebOff, "display", "inline-flex");
|
|
} else {
|
|
tooltip($control, i18n.get("Web Fullscreen"));
|
|
setStyle($fullscreenWebOn, "display", "inline-flex");
|
|
setStyle($fullscreenWebOff, "display", "none");
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
function pip$1(option) {
|
|
return (art) => ({
|
|
...option,
|
|
tooltip: art.i18n.get("PIP Mode"),
|
|
mounted: ($control) => {
|
|
const { proxy, icons, i18n } = art;
|
|
append($control, icons.pip);
|
|
proxy($control, "click", () => {
|
|
art.pip = !art.pip;
|
|
});
|
|
art.on("pip", (value) => {
|
|
tooltip($control, i18n.get(value ? "Exit PIP Mode" : "PIP Mode"));
|
|
});
|
|
}
|
|
});
|
|
}
|
|
function playAndPause(option) {
|
|
return (art) => ({
|
|
...option,
|
|
mounted: ($control) => {
|
|
const { proxy, icons, i18n } = art;
|
|
const $play = append($control, icons.play);
|
|
const $pause = append($control, icons.pause);
|
|
tooltip($play, i18n.get("Play"));
|
|
tooltip($pause, i18n.get("Pause"));
|
|
proxy($play, "click", () => {
|
|
art.play();
|
|
});
|
|
proxy($pause, "click", () => {
|
|
art.pause();
|
|
});
|
|
function showPlay() {
|
|
setStyle($play, "display", "flex");
|
|
setStyle($pause, "display", "none");
|
|
}
|
|
function showPause() {
|
|
setStyle($play, "display", "none");
|
|
setStyle($pause, "display", "flex");
|
|
}
|
|
if (art.playing) {
|
|
showPause();
|
|
} else {
|
|
showPlay();
|
|
}
|
|
art.on("video:playing", () => {
|
|
showPause();
|
|
});
|
|
art.on("video:pause", () => {
|
|
showPlay();
|
|
});
|
|
}
|
|
});
|
|
}
|
|
function getPosFromEvent(art, event) {
|
|
const { $progress } = art.template;
|
|
const { left } = getRect($progress);
|
|
const eventLeft = isMobile ? event.touches[0].clientX : event.clientX;
|
|
const width = clamp(eventLeft - left, 0, $progress.clientWidth);
|
|
const second = width / $progress.clientWidth * art.duration;
|
|
const time2 = secondToTime(second);
|
|
const percentage = clamp(width / $progress.clientWidth, 0, 1);
|
|
return { second, time: time2, width, percentage };
|
|
}
|
|
function setCurrentTime(art, event) {
|
|
if (art.isRotate) {
|
|
const percentage = event.touches[0].clientY / art.height;
|
|
const second = percentage * art.duration;
|
|
art.emit("setBar", "played", percentage, event);
|
|
art.seek = second;
|
|
} else {
|
|
const { second, percentage } = getPosFromEvent(art, event);
|
|
art.emit("setBar", "played", percentage, event);
|
|
art.seek = second;
|
|
}
|
|
}
|
|
function progress(options) {
|
|
return (art) => {
|
|
const { icons, option, proxy } = art;
|
|
const { $player } = art.template;
|
|
return {
|
|
...options,
|
|
html: `
|
|
<div class="art-control-progress-inner">
|
|
<div class="art-progress-hover"></div>
|
|
<div class="art-progress-loaded"></div>
|
|
<div class="art-progress-played"></div>
|
|
<div class="art-progress-highlight"></div>
|
|
<div class="art-progress-indicator"></div>
|
|
<div class="art-progress-tip">00:00</div>
|
|
</div>
|
|
`,
|
|
mounted: ($control) => {
|
|
let tipTimer = null;
|
|
let isDragging = false;
|
|
const $hover = query(".art-progress-hover", $control);
|
|
const $loaded = query(".art-progress-loaded", $control);
|
|
const $played = query(".art-progress-played", $control);
|
|
const $highlight = query(".art-progress-highlight", $control);
|
|
const $indicator = query(".art-progress-indicator", $control);
|
|
const $tip = query(".art-progress-tip", $control);
|
|
if (icons.indicator) {
|
|
append($indicator, icons.indicator);
|
|
} else {
|
|
setStyle($indicator, "backgroundColor", "var(--art-theme)");
|
|
}
|
|
function showHighlight(event) {
|
|
const { width } = getPosFromEvent(art, event);
|
|
const { text } = event.target.dataset;
|
|
$tip.textContent = text;
|
|
const tipWidth = $tip.clientWidth;
|
|
if (width <= tipWidth / 2) {
|
|
setStyle($tip, "left", 0);
|
|
} else if (width > $control.clientWidth - tipWidth / 2) {
|
|
setStyle($tip, "left", `${$control.clientWidth - tipWidth}px`);
|
|
} else {
|
|
setStyle($tip, "left", `${width - tipWidth / 2}px`);
|
|
}
|
|
}
|
|
function showTime(event, touch) {
|
|
const { width, time: time2 } = touch || getPosFromEvent(art, event);
|
|
$tip.textContent = time2 || "00:00";
|
|
const tipWidth = $tip.clientWidth;
|
|
if (width <= tipWidth / 2) {
|
|
setStyle($tip, "left", 0);
|
|
} else if (width > $control.clientWidth - tipWidth / 2) {
|
|
setStyle($tip, "left", `${$control.clientWidth - tipWidth}px`);
|
|
} else {
|
|
setStyle($tip, "left", `${width - tipWidth / 2}px`);
|
|
}
|
|
}
|
|
function updateHighlight() {
|
|
$highlight.textContent = "";
|
|
for (let index = 0; index < option.highlight.length; index++) {
|
|
const item = option.highlight[index];
|
|
const left = clamp(item.time, 0, art.duration) / art.duration * 100;
|
|
const html = `<span data-text="${item.text}" data-time="${item.time}" style="left: ${left}%"></span>`;
|
|
append($highlight, html);
|
|
}
|
|
}
|
|
function setBar(type, percentage, event) {
|
|
const isMobileDragging = type === "played" && event && isMobile;
|
|
if (type === "loaded") {
|
|
setStyle($loaded, "width", `${percentage * 100}%`);
|
|
}
|
|
if (type === "hover") {
|
|
setStyle($hover, "width", `${percentage * 100}%`);
|
|
if (includeFromEvent(event, $highlight)) {
|
|
showHighlight(event);
|
|
} else {
|
|
showTime(event);
|
|
}
|
|
if (percentage === 0) {
|
|
removeClass($player, "art-progress-hover");
|
|
} else {
|
|
addClass($player, "art-progress-hover");
|
|
}
|
|
}
|
|
if (type === "played") {
|
|
setStyle($played, "width", `${percentage * 100}%`);
|
|
setStyle($indicator, "left", `${percentage * 100}%`);
|
|
}
|
|
if (isMobileDragging) {
|
|
addClass($player, "art-progress-hover");
|
|
const width = $control.clientWidth * percentage;
|
|
const time2 = secondToTime(percentage * art.duration);
|
|
showTime(event, { width, time: time2 });
|
|
clearTimeout(tipTimer);
|
|
tipTimer = setTimeout(() => {
|
|
removeClass($player, "art-progress-hover");
|
|
}, 500);
|
|
}
|
|
}
|
|
art.on("setBar", setBar);
|
|
art.on("video:loadedmetadata", updateHighlight);
|
|
if (art.constructor.USE_RAF) {
|
|
art.on("raf", () => {
|
|
art.emit("setBar", "played", art.played);
|
|
art.emit("setBar", "loaded", art.loaded);
|
|
});
|
|
} else {
|
|
art.on("video:timeupdate", () => {
|
|
art.emit("setBar", "played", art.played);
|
|
});
|
|
art.on("video:progress", () => {
|
|
art.emit("setBar", "loaded", art.loaded);
|
|
});
|
|
art.on("video:ended", () => {
|
|
art.emit("setBar", "played", 1);
|
|
});
|
|
}
|
|
art.emit("setBar", "loaded", art.loaded || 0);
|
|
if (!isMobile) {
|
|
proxy($control, "click", (event) => {
|
|
if (event.target !== $indicator) {
|
|
setCurrentTime(art, event);
|
|
}
|
|
});
|
|
proxy($control, "mousemove", (event) => {
|
|
const { percentage } = getPosFromEvent(art, event);
|
|
art.emit("setBar", "hover", percentage, event);
|
|
});
|
|
proxy($control, "mouseleave", (event) => {
|
|
art.emit("setBar", "hover", 0, event);
|
|
});
|
|
proxy($control, "mousedown", (event) => {
|
|
isDragging = event.button === 0;
|
|
});
|
|
art.on("document:mousemove", (event) => {
|
|
if (isDragging) {
|
|
const { second, percentage } = getPosFromEvent(art, event);
|
|
art.emit("setBar", "played", percentage, event);
|
|
art.seek = second;
|
|
}
|
|
});
|
|
art.on("document:mouseup", () => {
|
|
if (isDragging) {
|
|
isDragging = false;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
};
|
|
}
|
|
function screenshot$1(option) {
|
|
return (art) => ({
|
|
...option,
|
|
tooltip: art.i18n.get("Screenshot"),
|
|
mounted: ($control) => {
|
|
const { proxy, icons } = art;
|
|
append($control, icons.screenshot);
|
|
proxy($control, "click", () => {
|
|
art.screenshot();
|
|
});
|
|
}
|
|
});
|
|
}
|
|
function setting$1(option) {
|
|
return (art) => ({
|
|
...option,
|
|
tooltip: art.i18n.get("Show Setting"),
|
|
mounted: ($control) => {
|
|
const { proxy, icons, i18n } = art;
|
|
append($control, icons.setting);
|
|
proxy($control, "click", () => {
|
|
art.setting.toggle();
|
|
art.setting.resize();
|
|
});
|
|
art.on("setting", (value) => {
|
|
tooltip($control, i18n.get(value ? "Hide Setting" : "Show Setting"));
|
|
});
|
|
}
|
|
});
|
|
}
|
|
function time(option) {
|
|
return (art) => ({
|
|
...option,
|
|
style: isMobile ? {
|
|
fontSize: "12px",
|
|
padding: "0 5px"
|
|
} : {
|
|
cursor: "auto",
|
|
padding: "0 10px"
|
|
},
|
|
mounted: ($control) => {
|
|
function getTime() {
|
|
const newTime = `${secondToTime(art.currentTime)} / ${secondToTime(art.duration)}`;
|
|
if (newTime !== $control.textContent) {
|
|
$control.textContent = newTime;
|
|
}
|
|
}
|
|
getTime();
|
|
const events = ["video:loadedmetadata", "video:timeupdate", "video:progress"];
|
|
for (let index = 0; index < events.length; index++) {
|
|
art.on(events[index], getTime);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
function volume$1(option) {
|
|
return (art) => ({
|
|
...option,
|
|
mounted: ($control) => {
|
|
const { proxy, icons } = art;
|
|
const $volume = append($control, icons.volume);
|
|
const $close = append($control, icons.volumeClose);
|
|
const $panel = append($control, '<div class="art-volume-panel"></div>');
|
|
const $inner = append($panel, '<div class="art-volume-inner"></div>');
|
|
const $value = append($inner, `<div class="art-volume-val"></div>`);
|
|
const $slider = append($inner, `<div class="art-volume-slider"></div>`);
|
|
const $handle = append($slider, `<div class="art-volume-handle"></div>`);
|
|
const $loaded = append($handle, `<div class="art-volume-loaded"></div>`);
|
|
const $indicator = append($slider, `<div class="art-volume-indicator"></div>`);
|
|
function getVolumeFromEvent(event) {
|
|
const { top, height } = getRect($slider);
|
|
return 1 - (event.clientY - top) / height;
|
|
}
|
|
function update() {
|
|
if (art.muted || art.volume === 0) {
|
|
setStyle($volume, "display", "none");
|
|
setStyle($close, "display", "flex");
|
|
setStyle($indicator, "top", "100%");
|
|
setStyle($loaded, "top", "100%");
|
|
$value.textContent = 0;
|
|
} else {
|
|
const percentage = art.volume * 100;
|
|
setStyle($volume, "display", "flex");
|
|
setStyle($close, "display", "none");
|
|
setStyle($indicator, "top", `${100 - percentage}%`);
|
|
setStyle($loaded, "top", `${100 - percentage}%`);
|
|
$value.textContent = Math.floor(percentage);
|
|
}
|
|
}
|
|
update();
|
|
art.on("video:volumechange", update);
|
|
proxy($volume, "click", () => {
|
|
art.muted = true;
|
|
});
|
|
proxy($close, "click", () => {
|
|
art.muted = false;
|
|
});
|
|
if (isMobile) {
|
|
setStyle($panel, "display", "none");
|
|
} else {
|
|
let isDragging = false;
|
|
proxy($slider, "mousedown", (event) => {
|
|
isDragging = event.button === 0;
|
|
art.volume = getVolumeFromEvent(event);
|
|
});
|
|
art.on("document:mousemove", (event) => {
|
|
if (isDragging) {
|
|
art.muted = false;
|
|
art.volume = getVolumeFromEvent(event);
|
|
}
|
|
});
|
|
art.on("document:mouseup", () => {
|
|
if (isDragging) {
|
|
isDragging = false;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
class Control extends Component {
|
|
constructor(art) {
|
|
super(art);
|
|
this.isHover = false;
|
|
this.name = "control";
|
|
this.timer = Date.now();
|
|
const { constructor } = art;
|
|
const { $player, $bottom } = this.art.template;
|
|
art.on("mousemove", () => {
|
|
if (!isMobile) {
|
|
this.show = true;
|
|
}
|
|
});
|
|
art.on("click", () => {
|
|
if (isMobile) {
|
|
this.toggle();
|
|
} else {
|
|
this.show = true;
|
|
}
|
|
});
|
|
art.on("document:mousemove", (event) => {
|
|
this.isHover = includeFromEvent(event, $bottom);
|
|
});
|
|
art.on("video:timeupdate", () => {
|
|
if (!art.setting.show && !this.isHover && !art.isInput && art.playing && this.show && Date.now() - this.timer >= constructor.CONTROL_HIDE_TIME) {
|
|
this.show = false;
|
|
}
|
|
});
|
|
art.on("control", (state2) => {
|
|
if (state2) {
|
|
removeClass($player, "art-hide-cursor");
|
|
addClass($player, "art-hover");
|
|
this.timer = Date.now();
|
|
} else {
|
|
addClass($player, "art-hide-cursor");
|
|
removeClass($player, "art-hover");
|
|
}
|
|
});
|
|
this.init();
|
|
}
|
|
init() {
|
|
const { option } = this.art;
|
|
if (!option.isLive) {
|
|
this.add(
|
|
progress({
|
|
name: "progress",
|
|
position: "top",
|
|
index: 10
|
|
})
|
|
);
|
|
}
|
|
this.add({
|
|
name: "thumbnails",
|
|
position: "top",
|
|
index: 20
|
|
});
|
|
this.add(
|
|
playAndPause({
|
|
name: "playAndPause",
|
|
position: "left",
|
|
index: 10
|
|
})
|
|
);
|
|
this.add(
|
|
volume$1({
|
|
name: "volume",
|
|
position: "left",
|
|
index: 20
|
|
})
|
|
);
|
|
if (!option.isLive) {
|
|
this.add(
|
|
time({
|
|
name: "time",
|
|
position: "left",
|
|
index: 30
|
|
})
|
|
);
|
|
}
|
|
if (option.quality.length) {
|
|
sleep().then(() => {
|
|
this.art.quality = option.quality;
|
|
});
|
|
}
|
|
if (option.screenshot && !isMobile) {
|
|
this.add(
|
|
screenshot$1({
|
|
name: "screenshot",
|
|
position: "right",
|
|
index: 20
|
|
})
|
|
);
|
|
}
|
|
if (option.setting) {
|
|
this.add(
|
|
setting$1({
|
|
name: "setting",
|
|
position: "right",
|
|
index: 30
|
|
})
|
|
);
|
|
}
|
|
if (option.pip) {
|
|
this.add(
|
|
pip$1({
|
|
name: "pip",
|
|
position: "right",
|
|
index: 40
|
|
})
|
|
);
|
|
}
|
|
if (option.airplay && window.WebKitPlaybackTargetAvailabilityEvent) {
|
|
this.add(
|
|
airplay$1({
|
|
name: "airplay",
|
|
position: "right",
|
|
index: 50
|
|
})
|
|
);
|
|
}
|
|
if (option.fullscreenWeb) {
|
|
this.add(
|
|
fullscreenWeb({
|
|
name: "fullscreenWeb",
|
|
position: "right",
|
|
index: 60
|
|
})
|
|
);
|
|
}
|
|
if (option.fullscreen) {
|
|
this.add(
|
|
fullscreen({
|
|
name: "fullscreen",
|
|
position: "right",
|
|
index: 70
|
|
})
|
|
);
|
|
}
|
|
for (let index = 0; index < option.controls.length; index++) {
|
|
this.add(option.controls[index]);
|
|
}
|
|
}
|
|
add(getOption) {
|
|
const option = typeof getOption === "function" ? getOption(this.art) : getOption;
|
|
const { $progress, $controlsLeft, $controlsRight } = this.art.template;
|
|
switch (option.position) {
|
|
case "top":
|
|
this.$parent = $progress;
|
|
break;
|
|
case "left":
|
|
this.$parent = $controlsLeft;
|
|
break;
|
|
case "right":
|
|
this.$parent = $controlsRight;
|
|
break;
|
|
default:
|
|
errorHandle(false, `Control option.position must one of 'top', 'left', 'right'`);
|
|
break;
|
|
}
|
|
super.add(option);
|
|
}
|
|
check(target) {
|
|
if (!target) {
|
|
return;
|
|
}
|
|
target.$control_value.innerHTML = target.html;
|
|
for (let index = 0; index < target.$control_option.length; index++) {
|
|
const item = target.$control_option[index];
|
|
item.default = item === target;
|
|
if (item.default) {
|
|
inverseClass(item.$control_item, "art-current");
|
|
}
|
|
}
|
|
}
|
|
selector(option, $ref, events) {
|
|
const { proxy } = this.art.events;
|
|
addClass($ref, "art-control-selector");
|
|
const $value = createElement("div");
|
|
addClass($value, "art-selector-value");
|
|
append($value, option.html);
|
|
$ref.textContent = "";
|
|
append($ref, $value);
|
|
const $list = createElement("div");
|
|
addClass($list, "art-selector-list");
|
|
append($ref, $list);
|
|
for (let index = 0; index < option.selector.length; index++) {
|
|
const item = option.selector[index];
|
|
const $item = createElement("div");
|
|
addClass($item, "art-selector-item");
|
|
if (item.default)
|
|
addClass($item, "art-current");
|
|
$item.dataset.index = index;
|
|
$item.dataset.value = item.value;
|
|
$item.innerHTML = item.html;
|
|
append($list, $item);
|
|
def(item, "$control_option", {
|
|
get: () => option.selector
|
|
});
|
|
def(item, "$control_item", {
|
|
get: () => $item
|
|
});
|
|
def(item, "$control_value", {
|
|
get: () => $value
|
|
});
|
|
}
|
|
const event = proxy($list, "click", async (event2) => {
|
|
const path = getComposedPath(event2);
|
|
const item = option.selector.find(
|
|
(item2) => item2.$control_item === path.find(($item) => item2.$control_item === $item)
|
|
);
|
|
this.check(item);
|
|
if (option.onSelect) {
|
|
$value.innerHTML = await option.onSelect.call(this.art, item, item.$control_item, event2);
|
|
}
|
|
});
|
|
events.push(event);
|
|
}
|
|
}
|
|
function clickInit(art, events) {
|
|
const {
|
|
constructor,
|
|
template: { $player, $video }
|
|
} = art;
|
|
function onDocumentClick(event) {
|
|
if (includeFromEvent(event, $player)) {
|
|
art.isInput = event.target.tagName === "INPUT";
|
|
art.isFocus = true;
|
|
art.emit("focus", event);
|
|
} else {
|
|
art.isInput = false;
|
|
art.isFocus = false;
|
|
art.emit("blur", event);
|
|
}
|
|
}
|
|
art.on("document:click", onDocumentClick);
|
|
art.on("document:contextmenu", onDocumentClick);
|
|
let clickTimes = [];
|
|
events.proxy($video, "click", (event) => {
|
|
const now = Date.now();
|
|
clickTimes.push(now);
|
|
const { MOBILE_CLICK_PLAY, DBCLICK_TIME, MOBILE_DBCLICK_PLAY, DBCLICK_FULLSCREEN } = constructor;
|
|
const clicks = clickTimes.filter((t) => now - t <= DBCLICK_TIME);
|
|
switch (clicks.length) {
|
|
case 1:
|
|
art.emit("click", event);
|
|
if (isMobile) {
|
|
if (!art.isLock && MOBILE_CLICK_PLAY) {
|
|
art.toggle();
|
|
}
|
|
} else {
|
|
art.toggle();
|
|
}
|
|
clickTimes = clicks;
|
|
break;
|
|
case 2:
|
|
art.emit("dblclick", event);
|
|
if (isMobile) {
|
|
if (!art.isLock && MOBILE_DBCLICK_PLAY) {
|
|
art.toggle();
|
|
}
|
|
} else {
|
|
if (DBCLICK_FULLSCREEN) {
|
|
art.fullscreen = !art.fullscreen;
|
|
}
|
|
}
|
|
clickTimes = [];
|
|
break;
|
|
default:
|
|
clickTimes = [];
|
|
}
|
|
});
|
|
}
|
|
function GetSlideAngle(dx, dy) {
|
|
return Math.atan2(dy, dx) * 180 / Math.PI;
|
|
}
|
|
function GetSlideDirection(startX, startY, endX, endY) {
|
|
const dy = startY - endY;
|
|
const dx = endX - startX;
|
|
let result = 0;
|
|
if (Math.abs(dx) < 2 && Math.abs(dy) < 2) {
|
|
return result;
|
|
}
|
|
const angle = GetSlideAngle(dx, dy);
|
|
if (angle >= -45 && angle < 45) {
|
|
result = 4;
|
|
} else if (angle >= 45 && angle < 135) {
|
|
result = 1;
|
|
} else if (angle >= -135 && angle < -45) {
|
|
result = 2;
|
|
} else if (angle >= 135 && angle <= 180 || angle >= -180 && angle < -135) {
|
|
result = 3;
|
|
}
|
|
return result;
|
|
}
|
|
function gestureInit(art, events) {
|
|
if (isMobile && !art.option.isLive) {
|
|
const { $video, $progress } = art.template;
|
|
let touchTarget = null;
|
|
let isDragging = false;
|
|
let startX = 0;
|
|
let startY = 0;
|
|
let startTime = 0;
|
|
const onTouchStart = (event) => {
|
|
if (event.touches.length === 1 && !art.isLock) {
|
|
if (touchTarget === $progress) {
|
|
setCurrentTime(art, event);
|
|
}
|
|
isDragging = true;
|
|
const { pageX, pageY } = event.touches[0];
|
|
startX = pageX;
|
|
startY = pageY;
|
|
startTime = art.currentTime;
|
|
}
|
|
};
|
|
const onTouchMove = (event) => {
|
|
if (event.touches.length === 1 && isDragging && art.duration) {
|
|
const { pageX, pageY } = event.touches[0];
|
|
const direction = GetSlideDirection(startX, startY, pageX, pageY);
|
|
const isHorizontal = [3, 4].includes(direction);
|
|
const isVertical = [1, 2].includes(direction);
|
|
const isLegal = isHorizontal && !art.isRotate || isVertical && art.isRotate;
|
|
if (isLegal) {
|
|
const ratioX = clamp((pageX - startX) / art.width, -1, 1);
|
|
const ratioY = clamp((pageY - startY) / art.height, -1, 1);
|
|
const ratio = art.isRotate ? ratioY : ratioX;
|
|
const TOUCH_MOVE_RATIO = touchTarget === $video ? art.constructor.TOUCH_MOVE_RATIO : 1;
|
|
const currentTime = clamp(startTime + art.duration * ratio * TOUCH_MOVE_RATIO, 0, art.duration);
|
|
art.seek = currentTime;
|
|
art.emit("setBar", "played", clamp(currentTime / art.duration, 0, 1), event);
|
|
art.notice.show = `${secondToTime(currentTime)} / ${secondToTime(art.duration)}`;
|
|
}
|
|
}
|
|
};
|
|
const onTouchEnd = () => {
|
|
if (isDragging) {
|
|
startX = 0;
|
|
startY = 0;
|
|
startTime = 0;
|
|
isDragging = false;
|
|
touchTarget = null;
|
|
}
|
|
};
|
|
if (art.option.gesture) {
|
|
events.proxy($video, "touchstart", (event) => {
|
|
touchTarget = $video;
|
|
onTouchStart(event);
|
|
});
|
|
events.proxy($video, "touchmove", onTouchMove);
|
|
}
|
|
events.proxy($progress, "touchstart", (event) => {
|
|
touchTarget = $progress;
|
|
onTouchStart(event);
|
|
});
|
|
events.proxy($progress, "touchmove", onTouchMove);
|
|
art.on("document:touchend", onTouchEnd);
|
|
}
|
|
}
|
|
function globalInit(art, events) {
|
|
const documentEvents = [
|
|
"click",
|
|
"mouseup",
|
|
"keydown",
|
|
"touchend",
|
|
"touchmove",
|
|
"mousemove",
|
|
"pointerup",
|
|
"contextmenu",
|
|
"pointermove",
|
|
"visibilitychange",
|
|
"webkitfullscreenchange"
|
|
];
|
|
const windowEvents = [
|
|
"resize",
|
|
"scroll",
|
|
"orientationchange"
|
|
];
|
|
const destroyEvents = [];
|
|
function bindGlobalEvents(source = {}) {
|
|
for (let index = 0; index < destroyEvents.length; index++) {
|
|
events.remove(destroyEvents[index]);
|
|
}
|
|
destroyEvents.length = 0;
|
|
const { $player } = art.template;
|
|
documentEvents.forEach((name) => {
|
|
const doc = source.document || $player.ownerDocument || document;
|
|
const destroy = events.proxy(doc, name, (event) => {
|
|
art.emit(`document:${name}`, event);
|
|
});
|
|
destroyEvents.push(destroy);
|
|
});
|
|
windowEvents.forEach((name) => {
|
|
const win = source.window || $player.ownerDocument?.defaultView || window;
|
|
const destroy = events.proxy(win, name, (event) => {
|
|
art.emit(`window:${name}`, event);
|
|
});
|
|
destroyEvents.push(destroy);
|
|
});
|
|
}
|
|
bindGlobalEvents();
|
|
events.bindGlobalEvents = bindGlobalEvents;
|
|
}
|
|
function hoverInit(art, events) {
|
|
const { $player } = art.template;
|
|
events.hover(
|
|
$player,
|
|
(event) => {
|
|
addClass($player, "art-hover");
|
|
art.emit("hover", true, event);
|
|
},
|
|
(event) => {
|
|
removeClass($player, "art-hover");
|
|
art.emit("hover", false, event);
|
|
}
|
|
);
|
|
}
|
|
function moveInit(art, events) {
|
|
const { $player } = art.template;
|
|
events.proxy($player, "mousemove", (event) => {
|
|
art.emit("mousemove", event);
|
|
});
|
|
}
|
|
function resizeInit(art, events) {
|
|
const { option, constructor } = art;
|
|
art.on("resize", () => {
|
|
const { aspectRatio: aspectRatio2, notice } = art;
|
|
if (art.state === "standard" && option.autoSize) {
|
|
art.autoSize();
|
|
}
|
|
art.aspectRatio = aspectRatio2;
|
|
notice.show = "";
|
|
});
|
|
const resizeFn = debounce(() => art.emit("resize"), constructor.RESIZE_TIME);
|
|
art.on("window:orientationchange", () => resizeFn());
|
|
art.on("window:resize", () => resizeFn());
|
|
if (screen && screen.orientation && screen.orientation.onchange) {
|
|
events.proxy(screen.orientation, "change", () => resizeFn());
|
|
}
|
|
}
|
|
function updateInit(art) {
|
|
if (art.constructor.USE_RAF) {
|
|
let timer = null;
|
|
(function update() {
|
|
if (art.playing) {
|
|
art.emit("raf");
|
|
}
|
|
if (!art.isDestroy) {
|
|
timer = requestAnimationFrame(update);
|
|
}
|
|
})();
|
|
art.on("destroy", () => {
|
|
cancelAnimationFrame(timer);
|
|
});
|
|
}
|
|
}
|
|
function viewInit(art) {
|
|
const {
|
|
option,
|
|
constructor,
|
|
template: { $container }
|
|
} = art;
|
|
const scrollFn = throttle(() => {
|
|
art.emit("view", isInViewport($container, constructor.SCROLL_GAP));
|
|
}, constructor.SCROLL_TIME);
|
|
art.on("window:scroll", () => scrollFn());
|
|
art.on("view", (state2) => {
|
|
if (option.autoMini) {
|
|
art.mini = !state2;
|
|
}
|
|
});
|
|
}
|
|
class Events {
|
|
constructor(art) {
|
|
this.destroyEvents = /* @__PURE__ */ new Set();
|
|
this.proxy = this.proxy.bind(this);
|
|
this.hover = this.hover.bind(this);
|
|
clickInit(art, this);
|
|
hoverInit(art, this);
|
|
moveInit(art, this);
|
|
resizeInit(art, this);
|
|
gestureInit(art, this);
|
|
viewInit(art);
|
|
globalInit(art, this);
|
|
updateInit(art);
|
|
}
|
|
proxy(target, name, callback, option = {}) {
|
|
if (Array.isArray(name)) {
|
|
return name.map((item) => this.proxy(target, item, callback, option));
|
|
}
|
|
target.addEventListener(name, callback, option);
|
|
const destroy = () => target.removeEventListener(name, callback, option);
|
|
this.destroyEvents.add(destroy);
|
|
return destroy;
|
|
}
|
|
hover(target, mouseenter, mouseleave) {
|
|
if (mouseenter) {
|
|
this.proxy(target, "mouseenter", mouseenter);
|
|
}
|
|
if (mouseleave) {
|
|
this.proxy(target, "mouseleave", mouseleave);
|
|
}
|
|
}
|
|
remove(destroyEvent) {
|
|
if (this.destroyEvents.has(destroyEvent)) {
|
|
try {
|
|
destroyEvent();
|
|
} catch (error2) {
|
|
console.warn("Failed to remove event listener:", error2);
|
|
} finally {
|
|
this.destroyEvents.delete(destroyEvent);
|
|
}
|
|
}
|
|
}
|
|
destroy() {
|
|
for (const destroyEvent of this.destroyEvents) {
|
|
try {
|
|
destroyEvent();
|
|
} catch (error2) {
|
|
console.warn("Failed to destroy event listener:", error2);
|
|
}
|
|
}
|
|
this.destroyEvents.clear();
|
|
}
|
|
}
|
|
class Hotkey {
|
|
constructor(art) {
|
|
this.art = art;
|
|
this.keys = {};
|
|
if (!isMobile) {
|
|
this.init();
|
|
}
|
|
}
|
|
init() {
|
|
const { constructor } = this.art;
|
|
if (this.art.option.hotkey) {
|
|
this.add("Escape", () => {
|
|
if (this.art.fullscreenWeb) {
|
|
this.art.fullscreenWeb = false;
|
|
}
|
|
});
|
|
this.add("Space", () => {
|
|
this.art.toggle();
|
|
});
|
|
this.add("ArrowLeft", () => {
|
|
this.art.backward = constructor.SEEK_STEP;
|
|
});
|
|
this.add("ArrowUp", () => {
|
|
this.art.volume += constructor.VOLUME_STEP;
|
|
});
|
|
this.add("ArrowRight", () => {
|
|
this.art.forward = constructor.SEEK_STEP;
|
|
});
|
|
this.add("ArrowDown", () => {
|
|
this.art.volume -= constructor.VOLUME_STEP;
|
|
});
|
|
}
|
|
this.art.on("document:keydown", (event) => {
|
|
if (this.art.isFocus) {
|
|
const tag = document.activeElement.tagName.toUpperCase();
|
|
const editable = document.activeElement.getAttribute("contenteditable");
|
|
if (tag !== "INPUT" && tag !== "TEXTAREA" && editable !== "" && editable !== "true" && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) {
|
|
const events = this.keys[event.code];
|
|
if (events) {
|
|
event.preventDefault();
|
|
for (let index = 0; index < events.length; index++) {
|
|
events[index].call(this.art, event);
|
|
}
|
|
this.art.emit("hotkey", event);
|
|
}
|
|
}
|
|
}
|
|
this.art.emit("keydown", event);
|
|
});
|
|
}
|
|
add(key, event) {
|
|
if (this.keys[key]) {
|
|
if (!this.keys[key].includes(event)) {
|
|
this.keys[key].push(event);
|
|
}
|
|
} else {
|
|
this.keys[key] = [event];
|
|
}
|
|
return this;
|
|
}
|
|
remove(key, event) {
|
|
if (this.keys[key]) {
|
|
const index = this.keys[key].indexOf(event);
|
|
if (index !== -1) {
|
|
this.keys[key].splice(index, 1);
|
|
}
|
|
if (this.keys[key].length === 0) {
|
|
delete this.keys[key];
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
}
|
|
const zhCn = {
|
|
"Video Info": "统计信息",
|
|
"Close": "关闭",
|
|
"Video Load Failed": "加载失败",
|
|
"Volume": "音量",
|
|
"Play": "播放",
|
|
"Pause": "暂停",
|
|
"Rate": "速度",
|
|
"Mute": "静音",
|
|
"Video Flip": "画面翻转",
|
|
"Horizontal": "水平",
|
|
"Vertical": "垂直",
|
|
"Reconnect": "重新连接",
|
|
"Show Setting": "显示设置",
|
|
"Hide Setting": "隐藏设置",
|
|
"Screenshot": "截图",
|
|
"Play Speed": "播放速度",
|
|
"Aspect Ratio": "画面比例",
|
|
"Default": "默认",
|
|
"Normal": "正常",
|
|
"Open": "打开",
|
|
"Switch Video": "切换",
|
|
"Switch Subtitle": "切换字幕",
|
|
"Fullscreen": "全屏",
|
|
"Exit Fullscreen": "退出全屏",
|
|
"Web Fullscreen": "网页全屏",
|
|
"Exit Web Fullscreen": "退出网页全屏",
|
|
"Mini Player": "迷你播放器",
|
|
"PIP Mode": "开启画中画",
|
|
"Exit PIP Mode": "退出画中画",
|
|
"PIP Not Supported": "不支持画中画",
|
|
"Fullscreen Not Supported": "不支持全屏",
|
|
"Subtitle Offset": "字幕偏移",
|
|
"Last Seen": "上次看到",
|
|
"Jump Play": "跳转播放",
|
|
"AirPlay": "隔空播放",
|
|
"AirPlay Not Available": "隔空播放不可用"
|
|
};
|
|
if (typeof window !== "undefined") {
|
|
window["artplayer-i18n-zh-cn"] = zhCn;
|
|
}
|
|
class I18n {
|
|
constructor(art) {
|
|
this.art = art;
|
|
this.languages = {
|
|
"zh-cn": zhCn
|
|
};
|
|
this.language = {};
|
|
this.update(art.option.i18n);
|
|
}
|
|
init() {
|
|
const lang = this.art.option.lang.toLowerCase();
|
|
this.language = this.languages[lang] || {};
|
|
}
|
|
get(key) {
|
|
return this.language[key] || key;
|
|
}
|
|
update(value) {
|
|
this.languages = mergeDeep(this.languages, value);
|
|
this.init();
|
|
}
|
|
}
|
|
const airplay = '<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">\n <g>\n <path d="M16,1 L2,1 C1.447,1 1,1.447 1,2 L1,12 C1,12.553 1.447,13 2,13 L5,13 L5,11 L3,11 L3,3 L15,3 L15,11 L13,11 L13,13 L16,13 C16.553,13 17,12.553 17,12 L17,2 C17,1.447 16.553,1 16,1 L16,1 Z"></path>\n <polygon points="4 17 14 17 9 11"></polygon>\n </g>\n</svg>\n';
|
|
const arrowLeft = '<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" version="1.1" viewBox="0 0 32 32">\n <path d="M 19.41,20.09 14.83,15.5 19.41,10.91 18,9.5 l -6,6 6,6 z" />\n</svg>';
|
|
const arrowRight = '<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" version="1.1" viewBox="0 0 32 32">\n <path d="m 12.59,20.34 4.58,-4.59 -4.58,-4.59 1.41,-1.41 6,6 -6,6 z" />\n</svg>';
|
|
const aspectRatio$1 = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 88 88" preserveAspectRatio="xMidYMid meet" style="width: 100%; height: 100%; transform: translate3d(0px, 0px, 0px);"><defs><clipPath id="__lottie_element_216"><rect width="88" height="88" x="0" y="0"></rect></clipPath></defs><g clip-path="url(#__lottie_element_216)"><g transform="matrix(1,0,0,1,44,44)" opacity="1" style="display: block;"><g opacity="1" transform="matrix(1,0,0,1,0,0)"><path fill-opacity="1" d=" M12.437999725341797,-12.70199966430664 C12.437999725341797,-12.70199966430664 9.618000030517578,-9.881999969482422 9.618000030517578,-9.881999969482422 C8.82800006866455,-9.092000007629395 8.82800006866455,-7.831999778747559 9.618000030517578,-7.052000045776367 C9.618000030517578,-7.052000045776367 16.687999725341797,0.017999999225139618 16.687999725341797,0.017999999225139618 C16.687999725341797,0.017999999225139618 9.618000030517578,7.0879998207092285 9.618000030517578,7.0879998207092285 C8.82800006866455,7.877999782562256 8.82800006866455,9.137999534606934 9.618000030517578,9.918000221252441 C9.618000030517578,9.918000221252441 12.437999725341797,12.748000144958496 12.437999725341797,12.748000144958496 C13.227999687194824,13.527999877929688 14.48799991607666,13.527999877929688 15.267999649047852,12.748000144958496 C15.267999649047852,12.748000144958496 26.58799934387207,1.437999963760376 26.58799934387207,1.437999963760376 C27.368000030517578,0.6579999923706055 27.368000030517578,-0.6119999885559082 26.58799934387207,-1.3919999599456787 C26.58799934387207,-1.3919999599456787 15.267999649047852,-12.70199966430664 15.267999649047852,-12.70199966430664 C14.48799991607666,-13.491999626159668 13.227999687194824,-13.491999626159668 12.437999725341797,-12.70199966430664z M-12.442000389099121,-12.70199966430664 C-13.182000160217285,-13.442000389099121 -14.362000465393066,-13.482000350952148 -15.142000198364258,-12.821999549865723 C-15.142000198364258,-12.821999549865723 -15.272000312805176,-12.70199966430664 -15.272000312805176,-12.70199966430664 C-15.272000312805176,-12.70199966430664 -26.582000732421875,-1.3919999599456787 -26.582000732421875,-1.3919999599456787 C-27.32200050354004,-0.6520000100135803 -27.36199951171875,0.5180000066757202 -26.70199966430664,1.3079999685287476 C-26.70199966430664,1.3079999685287476 -26.582000732421875,1.437999963760376 -26.582000732421875,1.437999963760376 C-26.582000732421875,1.437999963760376 -15.272000312805176,12.748000144958496 -15.272000312805176,12.748000144958496 C-14.531999588012695,13.48799991607666 -13.362000465393066,13.527999877929688 -12.571999549865723,12.868000030517578 C-12.571999549865723,12.868000030517578 -12.442000389099121,12.748000144958496 -12.442000389099121,12.748000144958496 C-12.442000389099121,12.748000144958496 -9.612000465393066,9.918000221252441 -9.612000465393066,9.918000221252441 C-8.871999740600586,9.178000450134277 -8.831999778747559,8.008000373840332 -9.501999855041504,7.2179999351501465 C-9.501999855041504,7.2179999351501465 -9.612000465393066,7.0879998207092285 -9.612000465393066,7.0879998207092285 C-9.612000465393066,7.0879998207092285 -16.68199920654297,0.017999999225139618 -16.68199920654297,0.017999999225139618 C-16.68199920654297,0.017999999225139618 -9.612000465393066,-7.052000045776367 -9.612000465393066,-7.052000045776367 C-8.871999740600586,-7.791999816894531 -8.831999778747559,-8.961999893188477 -9.501999855041504,-9.751999855041504 C-9.501999855041504,-9.751999855041504 -9.612000465393066,-9.881999969482422 -9.612000465393066,-9.881999969482422 C-9.612000465393066,-9.881999969482422 -12.442000389099121,-12.70199966430664 -12.442000389099121,-12.70199966430664z M28,-28 C32.41999816894531,-28 36,-24.420000076293945 36,-20 C36,-20 36,20 36,20 C36,24.420000076293945 32.41999816894531,28 28,28 C28,28 -28,28 -28,28 C-32.41999816894531,28 -36,24.420000076293945 -36,20 C-36,20 -36,-20 -36,-20 C-36,-24.420000076293945 -32.41999816894531,-28 -28,-28 C-28,-28 28,-28 28,-28z" data-darkreader-inline-fill="" style="--darkreader-inline-fill:#a8a6a4;"></path></g></g></g></svg>';
|
|
const check = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 24 24" style="width: 100%; height: 100%;">\n<path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z" />\n</svg>';
|
|
const close = '<?xml version="1.0" standalone="no"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n<svg t="1655876154826" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="22" height="22">\n<path d="M571.733333 512l268.8-268.8c17.066667-17.066667 17.066667-42.666667 0-59.733333-17.066667-17.066667-42.666667-17.066667-59.733333 0L512 452.266667 243.2 183.466667c-17.066667-17.066667-42.666667-17.066667-59.733333 0-17.066667 17.066667-17.066667 42.666667 0 59.733333L452.266667 512 183.466667 780.8c-17.066667 17.066667-17.066667 42.666667 0 59.733333 8.533333 8.533333 19.2 12.8 29.866666 12.8s21.333333-4.266667 29.866667-12.8L512 571.733333l268.8 268.8c8.533333 8.533333 19.2 12.8 29.866667 12.8s21.333333-4.266667 29.866666-12.8c17.066667-17.066667 17.066667-42.666667 0-59.733333L571.733333 512z" p-id="2131">\n</path>\n</svg>';
|
|
const config = '<svg height="24" viewBox="0 0 24 24" width="24"><path d="M15,17h6v1h-6V17z M11,17H3v1h8v2h1v-2v-1v-2h-1V17z M14,8h1V6V5V3h-1v2H3v1h11V8z M18,5v1h3V5H18z M6,14h1v-2v-1V9H6v2H3v1 h3V14z M10,12h11v-1H10V12z" data-darkreader-inline-fill="" style="--darkreader-inline-fill:#a8a6a4;"></path></svg>';
|
|
const error = '<?xml version="1.0" standalone="no"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n<svg t="1652850026663" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2749" xmlns:xlink="http://www.w3.org/1999/xlink" width="50" height="50">\n<path d="M593.8176 168.5504l356.00384 595.21024c26.15296 43.74528 10.73152 99.7376-34.44736 125.05088-14.39744 8.06912-30.72 12.30848-47.37024 12.30848H155.97568C103.75168 901.12 61.44 860.16 61.44 809.61536c0-16.09728 4.38272-31.92832 12.71808-45.8752L430.16192 168.5504c26.17344-43.7248 84.00896-58.65472 129.20832-33.34144a93.0816 93.0816 0 0 1 34.44736 33.34144zM512 819.2a61.44 61.44 0 1 0 0-122.88 61.44 61.44 0 0 0 0 122.88z m0-512a72.31488 72.31488 0 0 0-71.76192 81.3056l25.72288 205.7216a46.40768 46.40768 0 0 0 92.07808 0l25.72288-205.74208A72.31488 72.31488 0 0 0 512 307.2z" p-id="2750">\n</path>\n</svg>';
|
|
const flip$1 = '<?xml version="1.0" standalone="no"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n<svg t="1652445277062" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24">\n<path d="M554.666667 810.666667v85.333333h-85.333334v-85.333333h85.333334zM170.666667 178.005333a42.666667 42.666667 0 0 1 34.986666 18.218667l203.904 291.328a42.666667 42.666667 0 0 1 0 48.896l-203.946666 291.328A42.666667 42.666667 0 0 1 128 803.328V220.672a42.666667 42.666667 0 0 1 42.666667-42.666667z m682.666666 0a42.666667 42.666667 0 0 1 42.368 37.717334l0.298667 4.949333v582.656a42.666667 42.666667 0 0 1-74.24 28.629333l-3.413333-4.181333-203.904-291.328a42.666667 42.666667 0 0 1-3.029334-43.861333l3.029334-5.034667 203.946666-291.328A42.666667 42.666667 0 0 1 853.333333 178.005333zM554.666667 640v85.333333h-85.333334v-85.333333h85.333334zM196.266667 319.104V716.8L335.957333 512 196.309333 319.104zM554.666667 469.333333v85.333334h-85.333334v-85.333334h85.333334z m0-170.666666v85.333333h-85.333334V298.666667h85.333334z m0-170.666667v85.333333h-85.333334V128h85.333334z">\n</path>\n</svg>\n';
|
|
const fullscreenOff = '<?xml version="1.0" standalone="no"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n<svg class="icon" width="22" height="22" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">\n<path d="M768 298.666667h170.666667v85.333333h-256V128h85.333333v170.666667zM341.333333 384H85.333333V298.666667h170.666667V128h85.333333v256z m426.666667 341.333333v170.666667h-85.333333v-256h256v85.333333h-170.666667zM341.333333 640v256H256v-170.666667H85.333333v-85.333333h256z" />\n</svg>\n';
|
|
const fullscreenOn = '<?xml version="1.0" standalone="no"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n<svg class="icon" width="22" height="22" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">\n<path d="M625.777778 256h142.222222V398.222222h113.777778V142.222222H625.777778v113.777778zM256 398.222222V256H398.222222v-113.777778H142.222222V398.222222h113.777778zM768 625.777778v142.222222H625.777778v113.777778h256V625.777778h-113.777778zM398.222222 768H256V625.777778h-113.777778v256H398.222222v-113.777778z" />\n</svg>\n';
|
|
const fullscreenWebOff = '<?xml version="1.0" standalone="no"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n<svg class="icon" width="18" height="18" viewBox="0 0 1152 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">\n<path d="M1075.2 0H76.8A76.8 76.8 0 0 0 0 76.8v870.4A76.8 76.8 0 0 0 76.8 1024h998.4a76.8 76.8 0 0 0 76.8-76.8V76.8A76.8 76.8 0 0 0 1075.2 0zM1024 128v768H128V128h896zM896 512a64 64 0 0 1 7.488 127.552L896 640h-128v128a64 64 0 0 1-56.512 63.552L704 832a64 64 0 0 1-63.552-56.512L640 768V582.592c0-34.496 25.024-66.112 61.632-70.208L709.632 512H896zM256 512a64 64 0 0 1-7.488-127.552L256 384h128V256a64 64 0 0 1 56.512-63.552L448 192a64 64 0 0 1 63.552 56.512L512 256v185.408c0 34.432-25.024 66.112-61.632 70.144L442.368 512H256z" />\n</svg>\n';
|
|
const fullscreenWebOn = '<?xml version="1.0" standalone="no"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n<svg class="icon" width="18" height="18" viewBox="0 0 1152 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">\n<path d="M1075.2 0H76.8A76.8 76.8 0 0 0 0 76.8v870.4A76.8 76.8 0 0 0 76.8 1024h998.4a76.8 76.8 0 0 0 76.8-76.8V76.8A76.8 76.8 0 0 0 1075.2 0zM1024 128v768H128V128h896zM448 192a64 64 0 0 1 7.488 127.552L448 320H320v128a64 64 0 0 1-56.512 63.552L256 512a64 64 0 0 1-63.552-56.512L192 448V262.592c0-34.432 25.024-66.112 61.632-70.144L261.632 192H448zM704 832a64 64 0 0 1-7.488-127.552L704 704h128V576a64 64 0 0 1 56.512-63.552L896 512a64 64 0 0 1 63.552 56.512L960 576v185.408c0 34.496-25.024 66.112-61.632 70.208l-8 0.384H704z" />\n</svg>\n';
|
|
const loading = '<svg xmlns="http://www.w3.org/2000/svg" width="50px" height="50px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="uil-default">\n <rect x="0" y="0" width="100" height="100" fill="none" class="bk"/>\n <rect x="47" y="40" width="6" height="20" rx="5" ry="5" transform="rotate(0 50 50) translate(0 -30)">\n <animate attributeName="opacity" from="1" to="0" dur="1s" begin="-1s" repeatCount="indefinite"/>\n </rect>\n <rect x="47" y="40" width="6" height="20" rx="5" ry="5" transform="rotate(30 50 50) translate(0 -30)">\n <animate attributeName="opacity" from="1" to="0" dur="1s" begin="-0.9166666666666666s" repeatCount="indefinite"/>\n </rect>\n <rect x="47" y="40" width="6" height="20" rx="5" ry="5" transform="rotate(60 50 50) translate(0 -30)">\n <animate attributeName="opacity" from="1" to="0" dur="1s" begin="-0.8333333333333334s" repeatCount="indefinite"/>\n </rect>\n <rect x="47" y="40" width="6" height="20" rx="5" ry="5" transform="rotate(90 50 50) translate(0 -30)">\n <animate attributeName="opacity" from="1" to="0" dur="1s" begin="-0.75s" repeatCount="indefinite"/></rect>\n <rect x="47" y="40" width="6" height="20" rx="5" ry="5" transform="rotate(120 50 50) translate(0 -30)">\n <animate attributeName="opacity" from="1" to="0" dur="1s" begin="-0.6666666666666666s" repeatCount="indefinite"/>\n </rect>\n <rect x="47" y="40" width="6" height="20" rx="5" ry="5" transform="rotate(150 50 50) translate(0 -30)">\n <animate attributeName="opacity" from="1" to="0" dur="1s" begin="-0.5833333333333334s" repeatCount="indefinite"/>\n </rect>\n <rect x="47" y="40" width="6" height="20" rx="5" ry="5" transform="rotate(180 50 50) translate(0 -30)">\n <animate attributeName="opacity" from="1" to="0" dur="1s" begin="-0.5s" repeatCount="indefinite"/></rect>\n <rect x="47" y="40" width="6" height="20" rx="5" ry="5" transform="rotate(210 50 50) translate(0 -30)">\n <animate attributeName="opacity" from="1" to="0" dur="1s" begin="-0.4166666666666667s" repeatCount="indefinite"/>\n </rect>\n <rect x="47" y="40" width="6" height="20" rx="5" ry="5" transform="rotate(240 50 50) translate(0 -30)">\n <animate attributeName="opacity" from="1" to="0" dur="1s" begin="-0.3333333333333333s" repeatCount="indefinite"/>\n </rect>\n <rect x="47" y="40" width="6" height="20" rx="5" ry="5" transform="rotate(270 50 50) translate(0 -30)">\n <animate attributeName="opacity" from="1" to="0" dur="1s" begin="-0.25s" repeatCount="indefinite"/></rect>\n <rect x="47" y="40" width="6" height="20" rx="5" ry="5" transform="rotate(300 50 50) translate(0 -30)">\n <animate attributeName="opacity" from="1" to="0" dur="1s" begin="-0.16666666666666666s" repeatCount="indefinite"/>\n </rect>\n <rect x="47" y="40" width="6" height="20" rx="5" ry="5" transform="rotate(330 50 50) translate(0 -30)">\n <animate attributeName="opacity" from="1" to="0" dur="1s" begin="-0.08333333333333333s" repeatCount="indefinite"/>\n </rect>\n</svg>';
|
|
const lock$1 = '<?xml version="1.0" standalone="no"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n<svg t="1650612139149" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20">\n<path d="M298.666667 426.666667V341.333333a213.333333 213.333333 0 1 1 426.666666 0v85.333334h42.666667a85.333333 85.333333 0 0 1 85.333333 85.333333v256a85.333333 85.333333 0 0 1-85.333333 85.333333H256a85.333333 85.333333 0 0 1-85.333333-85.333333v-256a85.333333 85.333333 0 0 1 85.333333-85.333333h42.666667z m213.333333-213.333334a128 128 0 0 0-128 128v85.333334h256V341.333333a128 128 0 0 0-128-128z"></path>\n</svg>\n';
|
|
const pause = '<svg xmlns="http://www.w3.org/2000/svg" height="22" width="22" viewBox="0 0 22 22">\n <path d="M7 3a2 2 0 0 0-2 2v12a2 2 0 1 0 4 0V5a2 2 0 0 0-2-2zM15 3a2 2 0 0 0-2 2v12a2 2 0 1 0 4 0V5a2 2 0 0 0-2-2z"></path>\n</svg>';
|
|
const pip = '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="22" height="22">\n<path d="M844.8 219.648h-665.6c-6.144 0-10.24 4.608-10.24 10.752v563.2c0 5.632 4.096 10.24 10.24 10.24h256v92.16h-256a102.4 102.4 0 0 1-102.4-102.4v-563.2c0-56.832 45.568-102.4 102.4-102.4h665.6a102.4 102.4 0 0 1 102.4 102.4v204.8h-92.16v-204.8c0-6.144-4.608-10.752-10.24-10.752zM614.4 588.8c-28.672 0-51.2 22.528-51.2 51.2v204.8c0 28.16 22.528 51.2 51.2 51.2h281.6c28.16 0 51.2-23.04 51.2-51.2v-204.8c0-28.672-23.04-51.2-51.2-51.2H614.4z"></path>\n</svg>';
|
|
const play = '<svg xmlns="http://www.w3.org/2000/svg" height="22" width="22" viewBox="0 0 22 22">\n <path d="M17.982 9.275L8.06 3.27A2.013 2.013 0 0 0 5 4.994v12.011a2.017 2.017 0 0 0 3.06 1.725l9.922-6.005a2.017 2.017 0 0 0 0-3.45z"></path>\n</svg>';
|
|
const playbackRate$1 = '<svg height="24" viewBox="0 0 24 24" width="24"><path d="M10,8v8l6-4L10,8L10,8z M6.3,5L5.7,4.2C7.2,3,9,2.2,11,2l0.1,1C9.3,3.2,7.7,3.9,6.3,5z M5,6.3L4.2,5.7C3,7.2,2.2,9,2,11 l1,.1C3.2,9.3,3.9,7.7,5,6.3z M5,17.7c-1.1-1.4-1.8-3.1-2-4.8L2,13c0.2,2,1,3.8,2.2,5.4L5,17.7z M11.1,21c-1.8-0.2-3.4-0.9-4.8-2 l-0.6,.8C7.2,21,9,21.8,11,22L11.1,21z M22,12c0-5.2-3.9-9.4-9-10l-0.1,1c4.6,.5,8.1,4.3,8.1,9s-3.5,8.5-8.1,9l0.1,1 C18.2,21.5,22,17.2,22,12z" data-darkreader-inline-fill="" style="--darkreader-inline-fill:#a8a6a4;"></path></svg>';
|
|
const screenshot = '<svg xmlns="http://www.w3.org/2000/svg" height="22" width="22" viewBox="0 0 50 50">\n <path d="M 19.402344 6 C 17.019531 6 14.96875 7.679688 14.5 10.011719 L 14.097656 12 L 9 12 C 6.238281 12 4 14.238281 4 17 L 4 38 C 4 40.761719 6.238281 43 9 43 L 41 43 C 43.761719 43 46 40.761719 46 38 L 46 17 C 46 14.238281 43.761719 12 41 12 L 35.902344 12 L 35.5 10.011719 C 35.03125 7.679688 32.980469 6 30.597656 6 Z M 25 17 C 30.519531 17 35 21.480469 35 27 C 35 32.519531 30.519531 37 25 37 C 19.480469 37 15 32.519531 15 27 C 15 21.480469 19.480469 17 25 17 Z M 25 19 C 20.589844 19 17 22.589844 17 27 C 17 31.410156 20.589844 35 25 35 C 29.410156 35 33 31.410156 33 27 C 33 22.589844 29.410156 19 25 19 Z "/>\n</svg>\n';
|
|
const setting = '<svg xmlns="http://www.w3.org/2000/svg" height="22" width="22" viewBox="0 0 22 22">\n <circle cx="11" cy="11" r="2"></circle>\n <path d="M19.164 8.861L17.6 8.6a6.978 6.978 0 0 0-1.186-2.099l.574-1.533a1 1 0 0 0-.436-1.217l-1.997-1.153a1.001 1.001 0 0 0-1.272.23l-1.008 1.225a7.04 7.04 0 0 0-2.55.001L8.716 2.829a1 1 0 0 0-1.272-.23L5.447 3.751a1 1 0 0 0-.436 1.217l.574 1.533A6.997 6.997 0 0 0 4.4 8.6l-1.564.261A.999.999 0 0 0 2 9.847v2.306c0 .489.353.906.836.986l1.613.269a7 7 0 0 0 1.228 2.075l-.558 1.487a1 1 0 0 0 .436 1.217l1.997 1.153c.423.244.961.147 1.272-.23l1.04-1.263a7.089 7.089 0 0 0 2.272 0l1.04 1.263a1 1 0 0 0 1.272.23l1.997-1.153a1 1 0 0 0 .436-1.217l-.557-1.487c.521-.61.94-1.31 1.228-2.075l1.613-.269a.999.999 0 0 0 .835-.986V9.847a.999.999 0 0 0-.836-.986zM11 15a4 4 0 1 1 0-8 4 4 0 0 1 0 8z"></path>\n</svg>';
|
|
const state = '<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 24 24">\n<path d="M9.5 9.325v5.35q0 .575.525.875t1.025-.05l4.15-2.65q.475-.3.475-.85t-.475-.85L11.05 8.5q-.5-.35-1.025-.05t-.525.875ZM12 22q-2.075 0-3.9-.788t-3.175-2.137q-1.35-1.35-2.137-3.175T2 12q0-2.075.788-3.9t2.137-3.175q1.35-1.35 3.175-2.137T12 2q2.075 0 3.9.788t3.175 2.137q1.35 1.35 2.138 3.175T22 12q0 2.075-.788 3.9t-2.137 3.175q-1.35 1.35-3.175 2.138T12 22Z"/>\n</svg>\n';
|
|
const switchOff = '<?xml version="1.0" standalone="no"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n<svg class="icon" width="26" height="26" viewBox="0 0 1740 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">\n <path d="M511.8976 1024h670.5152c282.4192-0.4096 511.1808-229.4784 511.1808-511.8976 0-282.4192-228.7616-511.488-511.1808-511.8976H511.8976C229.4784 0.6144 0.7168 229.6832 0.7168 512.1024c0 282.4192 228.7616 511.488 511.1808 511.8976zM511.3344 48.64A464.5888 464.5888 0 1 1 48.0256 513.024 463.872 463.872 0 0 1 511.3344 48.4352V48.64z" />\n</svg>\n';
|
|
const switchOn = '<?xml version="1.0" standalone="no"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n<svg class="icon" width="26" height="26" viewBox="0 0 1664 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">\n <path fill="#648FFC" d="M1152 0H512a512 512 0 0 0 0 1024h640a512 512 0 0 0 0-1024z m0 960a448 448 0 1 1 448-448 448 448 0 0 1-448 448z" />\n</svg>';
|
|
const unlock = '<?xml version="1.0" standalone="no"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n<svg t="1650612464266" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20"><path d="M666.752 194.517333L617.386667 268.629333A128 128 0 0 0 384 341.333333l0.042667 85.333334h384a85.333333 85.333333 0 0 1 85.333333 85.333333v256a85.333333 85.333333 0 0 1-85.333333 85.333333H256a85.333333 85.333333 0 0 1-85.333333-85.333333v-256a85.333333 85.333333 0 0 1 85.333333-85.333333h42.666667V341.333333a213.333333 213.333333 0 0 1 368.085333-146.816z"></path></svg>\n';
|
|
const volumeClose = '<svg xmlns="http://www.w3.org/2000/svg" height="22" width="22" viewBox="0 0 22 22">\n <path d="M15 11a3.998 3.998 0 0 0-2-3.465v2.636l1.865 1.865A4.02 4.02 0 0 0 15 11z"></path>\n <path d="M13.583 5.583A5.998 5.998 0 0 1 17 11a6 6 0 0 1-.585 2.587l1.477 1.477a8.001 8.001 0 0 0-3.446-11.286 1 1 0 0 0-.863 1.805zM18.778 18.778l-2.121-2.121-1.414-1.414-1.415-1.415L13 13l-2-2-3.889-3.889-3.889-3.889a.999.999 0 1 0-1.414 1.414L5.172 8H5a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h1l4.188 3.35a.5.5 0 0 0 .812-.39v-3.131l2.587 2.587-.01.005a1 1 0 0 0 .86 1.806c.215-.102.424-.214.627-.333l2.3 2.3a1.001 1.001 0 0 0 1.414-1.416zM11 5.04a.5.5 0 0 0-.813-.39L8.682 5.854 11 8.172V5.04z"></path>\n</svg>';
|
|
const volume = '<svg xmlns="http://www.w3.org/2000/svg" height="22" width="22" viewBox="0 0 22 22">\n <path d="M10.188 4.65L6 8H5a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h1l4.188 3.35a.5.5 0 0 0 .812-.39V5.04a.498.498 0 0 0-.812-.39zM14.446 3.778a1 1 0 0 0-.862 1.804 6.002 6.002 0 0 1-.007 10.838 1 1 0 0 0 .86 1.806A8.001 8.001 0 0 0 19 11a8.001 8.001 0 0 0-4.554-7.222z"></path>\n <path d="M15 11a3.998 3.998 0 0 0-2-3.465v6.93A3.998 3.998 0 0 0 15 11z"></path>\n</svg>';
|
|
class Icons {
|
|
constructor(art) {
|
|
const icons = {
|
|
loading,
|
|
state,
|
|
play,
|
|
pause,
|
|
check,
|
|
volume,
|
|
volumeClose,
|
|
screenshot,
|
|
setting,
|
|
pip,
|
|
arrowLeft,
|
|
arrowRight,
|
|
playbackRate: playbackRate$1,
|
|
aspectRatio: aspectRatio$1,
|
|
config,
|
|
lock: lock$1,
|
|
flip: flip$1,
|
|
unlock,
|
|
fullscreenOff,
|
|
fullscreenOn,
|
|
fullscreenWebOff,
|
|
fullscreenWebOn,
|
|
switchOn,
|
|
switchOff,
|
|
error,
|
|
close,
|
|
airplay,
|
|
...art.option.icons
|
|
};
|
|
for (const key in icons) {
|
|
def(this, key, {
|
|
get: () => getIcon(key, icons[key])
|
|
});
|
|
}
|
|
}
|
|
}
|
|
class Info extends Component {
|
|
constructor(art) {
|
|
super(art);
|
|
this.name = "info";
|
|
if (!isMobile) {
|
|
this.init();
|
|
}
|
|
}
|
|
init() {
|
|
const {
|
|
proxy,
|
|
constructor,
|
|
template: { $infoPanel, $infoClose, $video }
|
|
} = this.art;
|
|
proxy($infoClose, "click", () => {
|
|
this.show = false;
|
|
});
|
|
let timer = null;
|
|
const $types = queryAll("[data-video]", $infoPanel) || [];
|
|
this.art.on("destroy", () => clearTimeout(timer));
|
|
function loop() {
|
|
for (let index = 0; index < $types.length; index++) {
|
|
const item = $types[index];
|
|
const value = $video[item.dataset.video];
|
|
const textContent = typeof value === "number" ? value.toFixed(2) : value;
|
|
if (item.textContent !== textContent) {
|
|
item.textContent = textContent;
|
|
}
|
|
}
|
|
timer = setTimeout(loop, constructor.INFO_LOOP_TIME);
|
|
}
|
|
loop();
|
|
}
|
|
}
|
|
class Layer extends Component {
|
|
constructor(art) {
|
|
super(art);
|
|
const {
|
|
option,
|
|
template: { $layer }
|
|
} = art;
|
|
this.name = "layer";
|
|
this.$parent = $layer;
|
|
for (let index = 0; index < option.layers.length; index++) {
|
|
this.add(option.layers[index]);
|
|
}
|
|
}
|
|
}
|
|
class Loading extends Component {
|
|
constructor(art) {
|
|
super(art);
|
|
this.name = "loading";
|
|
append(art.template.$loading, art.icons.loading);
|
|
}
|
|
}
|
|
class Mask extends Component {
|
|
constructor(art) {
|
|
super(art);
|
|
this.name = "mask";
|
|
const { template, icons, events } = art;
|
|
const $state = append(template.$state, icons.state);
|
|
const $error = append(template.$state, icons.error);
|
|
setStyle($error, "display", "none");
|
|
art.on("destroy", () => {
|
|
setStyle($state, "display", "none");
|
|
setStyle($error, "display", null);
|
|
});
|
|
events.proxy(template.$state, "click", () => art.play());
|
|
}
|
|
}
|
|
class Notice {
|
|
constructor(art) {
|
|
this.art = art;
|
|
this.timer = null;
|
|
art.on("destroy", () => this.destroy());
|
|
}
|
|
destroy() {
|
|
if (this.timer) {
|
|
clearTimeout(this.timer);
|
|
this.timer = null;
|
|
}
|
|
}
|
|
set show(msg) {
|
|
const {
|
|
constructor,
|
|
template: { $player, $noticeInner }
|
|
} = this.art;
|
|
if (msg) {
|
|
$noticeInner.textContent = msg instanceof Error ? msg.message.trim() : msg;
|
|
addClass($player, "art-notice-show");
|
|
clearTimeout(this.timer);
|
|
this.timer = setTimeout(() => {
|
|
$noticeInner.textContent = "";
|
|
removeClass($player, "art-notice-show");
|
|
}, constructor.NOTICE_TIME);
|
|
} else {
|
|
removeClass($player, "art-notice-show");
|
|
}
|
|
}
|
|
get show() {
|
|
const {
|
|
template: { $player }
|
|
} = this.art;
|
|
return $player.classList.contains("art-notice-show");
|
|
}
|
|
}
|
|
function airplayMix(art) {
|
|
const {
|
|
i18n,
|
|
notice,
|
|
proxy,
|
|
template: { $video }
|
|
} = art;
|
|
let available = true;
|
|
if (window.WebKitPlaybackTargetAvailabilityEvent && $video.webkitShowPlaybackTargetPicker) {
|
|
proxy($video, "webkitplaybacktargetavailabilitychanged", (event) => {
|
|
switch (event.availability) {
|
|
case "available":
|
|
available = true;
|
|
break;
|
|
case "not-available":
|
|
available = false;
|
|
break;
|
|
}
|
|
});
|
|
} else {
|
|
available = false;
|
|
}
|
|
def(art, "airplay", {
|
|
value() {
|
|
if (available) {
|
|
$video.webkitShowPlaybackTargetPicker();
|
|
art.emit("airplay");
|
|
} else {
|
|
notice.show = i18n.get("AirPlay Not Available");
|
|
}
|
|
}
|
|
});
|
|
}
|
|
function aspectRatioMix(art) {
|
|
const {
|
|
i18n,
|
|
notice,
|
|
template: { $video, $player }
|
|
} = art;
|
|
def(art, "aspectRatio", {
|
|
get() {
|
|
return $player.dataset.aspectRatio || "default";
|
|
},
|
|
set(ratio) {
|
|
if (!ratio)
|
|
ratio = "default";
|
|
if (ratio === "default") {
|
|
setStyle($video, "width", null);
|
|
setStyle($video, "height", null);
|
|
setStyle($video, "margin", null);
|
|
delete $player.dataset.aspectRatio;
|
|
} else {
|
|
const ratioArray = ratio.split(":").map(Number);
|
|
const { clientWidth, clientHeight } = $player;
|
|
const playerRatio = clientWidth / clientHeight;
|
|
const setupRatio = ratioArray[0] / ratioArray[1];
|
|
if (playerRatio > setupRatio) {
|
|
setStyle($video, "width", `${setupRatio * clientHeight}px`);
|
|
setStyle($video, "height", "100%");
|
|
setStyle($video, "margin", "0 auto");
|
|
} else {
|
|
setStyle($video, "width", "100%");
|
|
setStyle($video, "height", `${clientWidth / setupRatio}px`);
|
|
setStyle($video, "margin", "auto 0");
|
|
}
|
|
$player.dataset.aspectRatio = ratio;
|
|
}
|
|
notice.show = `${i18n.get("Aspect Ratio")}: ${ratio === "default" ? i18n.get("Default") : ratio}`;
|
|
art.emit("aspectRatio", ratio);
|
|
}
|
|
});
|
|
}
|
|
function attrMix(art) {
|
|
const {
|
|
template: { $video }
|
|
} = art;
|
|
def(art, "attr", {
|
|
value(key, value) {
|
|
if (value === void 0)
|
|
return $video[key];
|
|
$video[key] = value;
|
|
}
|
|
});
|
|
}
|
|
function autoHeightMix(art) {
|
|
const {
|
|
template: { $container, $video }
|
|
} = art;
|
|
def(art, "autoHeight", {
|
|
value() {
|
|
const { clientWidth } = $container;
|
|
const { videoHeight, videoWidth } = $video;
|
|
const height = videoHeight * (clientWidth / videoWidth);
|
|
setStyle($container, "height", `${height}px`);
|
|
art.emit("autoHeight", height);
|
|
}
|
|
});
|
|
}
|
|
function autoSizeMix(art) {
|
|
const { $container, $player, $video } = art.template;
|
|
def(art, "autoSize", {
|
|
value() {
|
|
const { videoWidth, videoHeight } = $video;
|
|
const { width, height } = getRect($container);
|
|
const videoRatio = videoWidth / videoHeight;
|
|
const containerRatio = width / height;
|
|
if (containerRatio > videoRatio) {
|
|
const percentage = height * videoRatio / width * 100;
|
|
setStyle($player, "width", `${percentage}%`);
|
|
setStyle($player, "height", "100%");
|
|
} else {
|
|
const percentage = width / videoRatio / height * 100;
|
|
setStyle($player, "width", "100%");
|
|
setStyle($player, "height", `${percentage}%`);
|
|
}
|
|
art.emit("autoSize", {
|
|
width: art.width,
|
|
height: art.height
|
|
});
|
|
}
|
|
});
|
|
}
|
|
function cssVarMix(art) {
|
|
const { $player } = art.template;
|
|
def(art, "cssVar", {
|
|
value(key, value) {
|
|
if (value) {
|
|
return $player.style.setProperty(key, value);
|
|
} else {
|
|
return getComputedStyle($player).getPropertyValue(key);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
function currentTimeMix(art) {
|
|
const { $video } = art.template;
|
|
def(art, "currentTime", {
|
|
get: () => $video.currentTime || 0,
|
|
set: (time2) => {
|
|
time2 = Number.parseFloat(time2);
|
|
if (Number.isNaN(time2))
|
|
return;
|
|
$video.currentTime = clamp(time2, 0, art.duration);
|
|
}
|
|
});
|
|
}
|
|
function durationMix(art) {
|
|
def(art, "duration", {
|
|
get: () => {
|
|
const { duration } = art.template.$video;
|
|
if (duration === Infinity)
|
|
return 0;
|
|
return duration || 0;
|
|
}
|
|
});
|
|
}
|
|
function eventInit(art) {
|
|
const {
|
|
i18n,
|
|
notice,
|
|
option,
|
|
constructor,
|
|
proxy,
|
|
template: { $player, $video, $poster }
|
|
} = art;
|
|
let reconnectTime = 0;
|
|
for (let index = 0; index < config$1.events.length; index++) {
|
|
proxy($video, config$1.events[index], (event) => {
|
|
art.emit(`video:${event.type}`, event);
|
|
});
|
|
}
|
|
art.on("video:canplay", () => {
|
|
reconnectTime = 0;
|
|
art.loading.show = false;
|
|
});
|
|
art.once("video:canplay", () => {
|
|
art.loading.show = false;
|
|
art.controls.show = true;
|
|
art.mask.show = true;
|
|
art.isReady = true;
|
|
art.emit("ready");
|
|
});
|
|
art.on("video:ended", () => {
|
|
if (option.loop) {
|
|
art.seek = 0;
|
|
art.play();
|
|
art.controls.show = false;
|
|
art.mask.show = false;
|
|
} else {
|
|
art.controls.show = true;
|
|
art.mask.show = true;
|
|
}
|
|
});
|
|
art.on("video:error", async (error2) => {
|
|
if (reconnectTime < constructor.RECONNECT_TIME_MAX) {
|
|
await sleep(constructor.RECONNECT_SLEEP_TIME);
|
|
reconnectTime += 1;
|
|
art.url = option.url;
|
|
notice.show = `${i18n.get("Reconnect")}: ${reconnectTime}`;
|
|
art.emit("error", error2, reconnectTime);
|
|
} else {
|
|
art.mask.show = true;
|
|
art.loading.show = false;
|
|
art.controls.show = true;
|
|
addClass($player, "art-error");
|
|
await sleep(constructor.RECONNECT_SLEEP_TIME);
|
|
notice.show = i18n.get("Video Load Failed");
|
|
}
|
|
});
|
|
art.on("video:loadedmetadata", () => {
|
|
art.emit("resize");
|
|
if (isMobile) {
|
|
art.loading.show = false;
|
|
art.controls.show = true;
|
|
art.mask.show = true;
|
|
}
|
|
});
|
|
art.on("video:loadstart", () => {
|
|
art.loading.show = true;
|
|
art.mask.show = false;
|
|
art.controls.show = true;
|
|
});
|
|
art.on("video:pause", () => {
|
|
art.controls.show = true;
|
|
art.mask.show = true;
|
|
});
|
|
art.on("video:play", () => {
|
|
art.mask.show = false;
|
|
setStyle($poster, "display", "none");
|
|
});
|
|
art.on("video:playing", () => {
|
|
art.mask.show = false;
|
|
});
|
|
art.on("video:progress", () => {
|
|
if (art.playing) {
|
|
art.loading.show = false;
|
|
}
|
|
});
|
|
art.on("video:seeked", () => {
|
|
art.loading.show = false;
|
|
art.mask.show = true;
|
|
});
|
|
art.on("video:seeking", () => {
|
|
art.loading.show = true;
|
|
art.mask.show = false;
|
|
});
|
|
art.on("video:timeupdate", () => {
|
|
art.mask.show = false;
|
|
});
|
|
art.on("video:waiting", () => {
|
|
art.loading.show = true;
|
|
art.mask.show = false;
|
|
});
|
|
}
|
|
function flipMix(art) {
|
|
const {
|
|
template: { $player },
|
|
i18n,
|
|
notice
|
|
} = art;
|
|
def(art, "flip", {
|
|
get() {
|
|
return $player.dataset.flip || "normal";
|
|
},
|
|
set(flip2) {
|
|
if (!flip2)
|
|
flip2 = "normal";
|
|
if (flip2 === "normal") {
|
|
delete $player.dataset.flip;
|
|
} else {
|
|
$player.dataset.flip = flip2;
|
|
}
|
|
notice.show = `${i18n.get("Video Flip")}: ${i18n.get(capitalize(flip2))}`;
|
|
art.emit("flip", flip2);
|
|
}
|
|
});
|
|
}
|
|
const methodMap = [
|
|
[
|
|
"requestFullscreen",
|
|
"exitFullscreen",
|
|
"fullscreenElement",
|
|
"fullscreenEnabled",
|
|
"fullscreenchange",
|
|
"fullscreenerror"
|
|
],
|
|
// New WebKit
|
|
[
|
|
"webkitRequestFullscreen",
|
|
"webkitExitFullscreen",
|
|
"webkitFullscreenElement",
|
|
"webkitFullscreenEnabled",
|
|
"webkitfullscreenchange",
|
|
"webkitfullscreenerror"
|
|
],
|
|
// Old WebKit
|
|
[
|
|
"webkitRequestFullScreen",
|
|
"webkitCancelFullScreen",
|
|
"webkitCurrentFullScreenElement",
|
|
"webkitCancelFullScreen",
|
|
"webkitfullscreenchange",
|
|
"webkitfullscreenerror"
|
|
],
|
|
[
|
|
"mozRequestFullScreen",
|
|
"mozCancelFullScreen",
|
|
"mozFullScreenElement",
|
|
"mozFullScreenEnabled",
|
|
"mozfullscreenchange",
|
|
"mozfullscreenerror"
|
|
],
|
|
[
|
|
"msRequestFullscreen",
|
|
"msExitFullscreen",
|
|
"msFullscreenElement",
|
|
"msFullscreenEnabled",
|
|
"MSFullscreenChange",
|
|
"MSFullscreenError"
|
|
]
|
|
];
|
|
const nativeAPI = (() => {
|
|
if (typeof document === "undefined") {
|
|
return false;
|
|
}
|
|
const unprefixedMethods = methodMap[0];
|
|
const returnValue = {};
|
|
for (const methodList of methodMap) {
|
|
const exitFullscreenMethod = methodList[1];
|
|
if (exitFullscreenMethod in document) {
|
|
for (const [index, method] of methodList.entries()) {
|
|
returnValue[unprefixedMethods[index]] = method;
|
|
}
|
|
return returnValue;
|
|
}
|
|
}
|
|
return false;
|
|
})();
|
|
const eventNameMap = {
|
|
change: nativeAPI.fullscreenchange,
|
|
error: nativeAPI.fullscreenerror
|
|
};
|
|
const screenfull = {
|
|
request(element = document.documentElement, options) {
|
|
return new Promise((resolve, reject) => {
|
|
const onFullScreenEntered = () => {
|
|
screenfull.off("change", onFullScreenEntered);
|
|
resolve();
|
|
};
|
|
screenfull.on("change", onFullScreenEntered);
|
|
const returnPromise = element[nativeAPI.requestFullscreen](options);
|
|
if (returnPromise instanceof Promise) {
|
|
returnPromise.then(onFullScreenEntered).catch(reject);
|
|
}
|
|
});
|
|
},
|
|
exit() {
|
|
return new Promise((resolve, reject) => {
|
|
if (!screenfull.isFullscreen) {
|
|
resolve();
|
|
return;
|
|
}
|
|
const onFullScreenExit = () => {
|
|
screenfull.off("change", onFullScreenExit);
|
|
resolve();
|
|
};
|
|
screenfull.on("change", onFullScreenExit);
|
|
const returnPromise = document[nativeAPI.exitFullscreen]();
|
|
if (returnPromise instanceof Promise) {
|
|
returnPromise.then(onFullScreenExit).catch(reject);
|
|
}
|
|
});
|
|
},
|
|
toggle(element, options) {
|
|
return screenfull.isFullscreen ? screenfull.exit() : screenfull.request(element, options);
|
|
},
|
|
onchange(callback) {
|
|
screenfull.on("change", callback);
|
|
},
|
|
onerror(callback) {
|
|
screenfull.on("error", callback);
|
|
},
|
|
on(event, callback) {
|
|
const eventName = eventNameMap[event];
|
|
if (eventName) {
|
|
document.addEventListener(eventName, callback, false);
|
|
}
|
|
},
|
|
off(event, callback) {
|
|
const eventName = eventNameMap[event];
|
|
if (eventName) {
|
|
document.removeEventListener(eventName, callback, false);
|
|
}
|
|
},
|
|
raw: nativeAPI
|
|
};
|
|
Object.defineProperties(screenfull, {
|
|
isFullscreen: {
|
|
get: () => Boolean(document[nativeAPI.fullscreenElement])
|
|
},
|
|
element: {
|
|
enumerable: true,
|
|
get: () => document[nativeAPI.fullscreenElement]
|
|
},
|
|
isEnabled: {
|
|
enumerable: true,
|
|
get: () => Boolean(document[nativeAPI.fullscreenEnabled])
|
|
}
|
|
});
|
|
function fullscreenMix(art) {
|
|
const {
|
|
i18n,
|
|
notice,
|
|
template: { $video, $player }
|
|
} = art;
|
|
const nativeScreenfull = (art2) => {
|
|
screenfull.on("change", () => {
|
|
art2.emit("fullscreen", screenfull.isFullscreen);
|
|
if (screenfull.isFullscreen) {
|
|
art2.state = "fullscreen";
|
|
addClass($player, "art-fullscreen");
|
|
} else {
|
|
removeClass($player, "art-fullscreen");
|
|
}
|
|
art2.emit("resize");
|
|
});
|
|
screenfull.on("error", (event) => {
|
|
art2.emit("fullscreenError", event);
|
|
});
|
|
def(art2, "fullscreen", {
|
|
get() {
|
|
return screenfull.isFullscreen;
|
|
},
|
|
async set(value) {
|
|
if (value) {
|
|
await screenfull.request($player);
|
|
} else {
|
|
await screenfull.exit();
|
|
}
|
|
}
|
|
});
|
|
};
|
|
const webkitScreenfull = (art2) => {
|
|
art2.on("document:webkitfullscreenchange", () => {
|
|
art2.emit("fullscreen", art2.fullscreen);
|
|
art2.emit("resize");
|
|
});
|
|
def(art2, "fullscreen", {
|
|
get() {
|
|
return document.fullscreenElement === $video;
|
|
},
|
|
set(value) {
|
|
if (value) {
|
|
art2.state = "fullscreen";
|
|
$video.webkitEnterFullscreen();
|
|
} else {
|
|
$video.webkitExitFullscreen();
|
|
}
|
|
}
|
|
});
|
|
};
|
|
art.once("video:loadedmetadata", () => {
|
|
if (screenfull.isEnabled) {
|
|
nativeScreenfull(art);
|
|
} else if ($video.webkitSupportsFullscreen) {
|
|
webkitScreenfull(art);
|
|
} else {
|
|
def(art, "fullscreen", {
|
|
get() {
|
|
return false;
|
|
},
|
|
set() {
|
|
notice.show = i18n.get("Fullscreen Not Supported");
|
|
}
|
|
});
|
|
}
|
|
def(art, "fullscreen", get(art, "fullscreen"));
|
|
});
|
|
}
|
|
function fullscreenWebMix(art) {
|
|
const {
|
|
constructor,
|
|
template: { $container, $player }
|
|
} = art;
|
|
let cssText = "";
|
|
def(art, "fullscreenWeb", {
|
|
get() {
|
|
return hasClass($player, "art-fullscreen-web");
|
|
},
|
|
set(value) {
|
|
if (value) {
|
|
cssText = $player.style.cssText;
|
|
if (constructor.FULLSCREEN_WEB_IN_BODY) {
|
|
append(document.body, $player);
|
|
}
|
|
art.state = "fullscreenWeb";
|
|
setStyle($player, "width", "100%");
|
|
setStyle($player, "height", "100%");
|
|
addClass($player, "art-fullscreen-web");
|
|
art.emit("fullscreenWeb", true);
|
|
} else {
|
|
if (constructor.FULLSCREEN_WEB_IN_BODY) {
|
|
append($container, $player);
|
|
}
|
|
if (cssText) {
|
|
$player.style.cssText = cssText;
|
|
cssText = "";
|
|
}
|
|
removeClass($player, "art-fullscreen-web");
|
|
art.emit("fullscreenWeb", false);
|
|
}
|
|
art.emit("resize");
|
|
}
|
|
});
|
|
}
|
|
function loadedMix(art) {
|
|
const { $video } = art.template;
|
|
def(art, "loaded", {
|
|
get: () => art.loadedTime / $video.duration
|
|
});
|
|
def(art, "loadedTime", {
|
|
get: () => {
|
|
return $video.buffered.length ? $video.buffered.end($video.buffered.length - 1) : 0;
|
|
}
|
|
});
|
|
}
|
|
function miniMix(art) {
|
|
const {
|
|
icons,
|
|
proxy,
|
|
storage,
|
|
template: { $player, $video }
|
|
} = art;
|
|
let isDragging = false;
|
|
let lastPageX = 0;
|
|
let lastPageY = 0;
|
|
function hideMini() {
|
|
const { $mini } = art.template;
|
|
if ($mini) {
|
|
removeClass($player, "art-mini");
|
|
setStyle($mini, "display", "none");
|
|
$player.prepend($video);
|
|
art.emit("mini", false);
|
|
}
|
|
}
|
|
function initState($play, $pause) {
|
|
if (art.playing) {
|
|
setStyle($play, "display", `none`);
|
|
setStyle($pause, "display", `flex`);
|
|
} else {
|
|
setStyle($play, "display", `flex`);
|
|
setStyle($pause, "display", `none`);
|
|
}
|
|
}
|
|
function createMini() {
|
|
const { $mini } = art.template;
|
|
if ($mini) {
|
|
append($mini, $video);
|
|
return setStyle($mini, "display", "flex");
|
|
} else {
|
|
const $mini2 = createElement("div");
|
|
addClass($mini2, "art-mini-popup");
|
|
append(document.body, $mini2);
|
|
art.template.$mini = $mini2;
|
|
append($mini2, $video);
|
|
const $close = append($mini2, `<div class="art-mini-close"></div>`);
|
|
append($close, icons.close);
|
|
proxy($close, "click", hideMini);
|
|
const $state = append($mini2, `<div class="art-mini-state"></div>`);
|
|
const $play = append($state, icons.play);
|
|
const $pause = append($state, icons.pause);
|
|
proxy($play, "click", () => art.play());
|
|
proxy($pause, "click", () => art.pause());
|
|
initState($play, $pause);
|
|
art.on("video:playing", () => initState($play, $pause));
|
|
art.on("video:pause", () => initState($play, $pause));
|
|
art.on("video:timeupdate", () => initState($play, $pause));
|
|
proxy($mini2, "mousedown", (event) => {
|
|
isDragging = event.button === 0;
|
|
lastPageX = event.pageX;
|
|
lastPageY = event.pageY;
|
|
});
|
|
art.on("document:mousemove", (event) => {
|
|
if (isDragging) {
|
|
addClass($mini2, "art-mini-dragging");
|
|
const x = event.pageX - lastPageX;
|
|
const y = event.pageY - lastPageY;
|
|
setStyle($mini2, "transform", `translate(${x}px, ${y}px)`);
|
|
}
|
|
});
|
|
art.on("document:mouseup", () => {
|
|
if (isDragging) {
|
|
isDragging = false;
|
|
removeClass($mini2, "art-mini-dragging");
|
|
const rect = getRect($mini2);
|
|
storage.set("left", rect.left);
|
|
storage.set("top", rect.top);
|
|
setStyle($mini2, "left", `${rect.left}px`);
|
|
setStyle($mini2, "top", `${rect.top}px`);
|
|
setStyle($mini2, "transform", null);
|
|
}
|
|
});
|
|
return $mini2;
|
|
}
|
|
}
|
|
function initMini() {
|
|
const { $mini } = art.template;
|
|
const rect = getRect($mini);
|
|
const top = window.innerHeight - rect.height - 50;
|
|
const left = window.innerWidth - rect.width - 50;
|
|
storage.set("top", top);
|
|
storage.set("left", left);
|
|
setStyle($mini, "top", `${top}px`);
|
|
setStyle($mini, "left", `${left}px`);
|
|
}
|
|
def(art, "mini", {
|
|
get() {
|
|
return hasClass($player, "art-mini");
|
|
},
|
|
set(value) {
|
|
if (value) {
|
|
art.state = "mini";
|
|
addClass($player, "art-mini");
|
|
const $mini = createMini();
|
|
const top = storage.get("top");
|
|
const left = storage.get("left");
|
|
if (typeof top === "number" && typeof left === "number") {
|
|
setStyle($mini, "top", `${top}px`);
|
|
setStyle($mini, "left", `${left}px`);
|
|
if (!isInViewport($mini)) {
|
|
initMini();
|
|
}
|
|
} else {
|
|
initMini();
|
|
}
|
|
art.emit("mini", true);
|
|
} else {
|
|
hideMini();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
function optionInit(art) {
|
|
const {
|
|
option,
|
|
storage,
|
|
template: { $video, $poster }
|
|
} = art;
|
|
for (const key in option.moreVideoAttr) {
|
|
art.attr(key, option.moreVideoAttr[key]);
|
|
}
|
|
if (option.muted) {
|
|
art.muted = option.muted;
|
|
}
|
|
if (option.volume) {
|
|
$video.volume = clamp(option.volume, 0, 1);
|
|
}
|
|
const volumeStorage = storage.get("volume");
|
|
if (typeof volumeStorage === "number") {
|
|
$video.volume = clamp(volumeStorage, 0, 1);
|
|
}
|
|
if (option.poster) {
|
|
setStyle($poster, "backgroundImage", `url(${option.poster})`);
|
|
}
|
|
if (option.autoplay) {
|
|
$video.autoplay = option.autoplay;
|
|
}
|
|
if (option.playsInline) {
|
|
$video.playsInline = true;
|
|
$video["webkit-playsinline"] = true;
|
|
}
|
|
if (option.theme) {
|
|
option.cssVar["--art-theme"] = option.theme;
|
|
}
|
|
for (const key in option.cssVar) {
|
|
art.cssVar(key, option.cssVar[key]);
|
|
}
|
|
art.url = option.url;
|
|
}
|
|
function pauseMix(art) {
|
|
const {
|
|
template: { $video },
|
|
i18n,
|
|
notice
|
|
} = art;
|
|
def(art, "pause", {
|
|
value() {
|
|
const result = $video.pause();
|
|
notice.show = i18n.get("Pause");
|
|
art.emit("pause");
|
|
return result;
|
|
}
|
|
});
|
|
}
|
|
function nativePip(art) {
|
|
const {
|
|
template: { $video },
|
|
proxy,
|
|
notice
|
|
} = art;
|
|
$video.disablePictureInPicture = false;
|
|
def(art, "pip", {
|
|
get() {
|
|
return document.pictureInPictureElement;
|
|
},
|
|
set(value) {
|
|
if (value) {
|
|
art.state = "pip";
|
|
$video.requestPictureInPicture().catch((err) => {
|
|
notice.show = err;
|
|
throw err;
|
|
});
|
|
} else {
|
|
document.exitPictureInPicture().catch((err) => {
|
|
notice.show = err;
|
|
throw err;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
proxy($video, "enterpictureinpicture", () => {
|
|
art.emit("pip", true);
|
|
});
|
|
proxy($video, "leavepictureinpicture", () => {
|
|
art.emit("pip", false);
|
|
});
|
|
}
|
|
function webkitPip(art) {
|
|
const { $video } = art.template;
|
|
$video.webkitSetPresentationMode("inline");
|
|
def(art, "pip", {
|
|
get() {
|
|
return $video.webkitPresentationMode === "picture-in-picture";
|
|
},
|
|
set(value) {
|
|
if (value) {
|
|
art.state = "pip";
|
|
$video.webkitSetPresentationMode("picture-in-picture");
|
|
art.emit("pip", true);
|
|
} else {
|
|
$video.webkitSetPresentationMode("inline");
|
|
art.emit("pip", false);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
function pipMix(art) {
|
|
const {
|
|
i18n,
|
|
notice,
|
|
template: { $video }
|
|
} = art;
|
|
if (document.pictureInPictureEnabled) {
|
|
nativePip(art);
|
|
} else if ($video.webkitSupportsPresentationMode) {
|
|
webkitPip(art);
|
|
} else {
|
|
def(art, "pip", {
|
|
get() {
|
|
return false;
|
|
},
|
|
set() {
|
|
notice.show = i18n.get("PIP Not Supported");
|
|
}
|
|
});
|
|
}
|
|
}
|
|
function playbackRateMix(art) {
|
|
const {
|
|
template: { $video },
|
|
i18n,
|
|
notice
|
|
} = art;
|
|
def(art, "playbackRate", {
|
|
get() {
|
|
return $video.playbackRate;
|
|
},
|
|
set(rate) {
|
|
if (rate) {
|
|
if (rate === $video.playbackRate)
|
|
return;
|
|
$video.playbackRate = rate;
|
|
notice.show = `${i18n.get("Rate")}: ${rate === 1 ? i18n.get("Normal") : `${rate}x`}`;
|
|
} else {
|
|
art.playbackRate = 1;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
function playedMix(art) {
|
|
def(art, "played", {
|
|
get: () => art.currentTime / art.duration
|
|
});
|
|
}
|
|
function playingMix(art) {
|
|
const { $video } = art.template;
|
|
def(art, "playing", {
|
|
get: () => {
|
|
if (typeof $video.playing === "boolean")
|
|
return $video.playing;
|
|
return !!($video.currentTime > 0 && !$video.paused && !$video.ended && $video.readyState > 2);
|
|
}
|
|
});
|
|
}
|
|
function playMix(art) {
|
|
const {
|
|
i18n,
|
|
notice,
|
|
option,
|
|
constructor: { instances: instances2 },
|
|
template: { $video }
|
|
} = art;
|
|
def(art, "play", {
|
|
async value() {
|
|
const result = await $video.play();
|
|
notice.show = i18n.get("Play");
|
|
art.emit("play");
|
|
if (option.mutex) {
|
|
for (let index = 0; index < instances2.length; index++) {
|
|
const instance = instances2[index];
|
|
if (instance !== art) {
|
|
instance.pause();
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
});
|
|
}
|
|
function posterMix(art) {
|
|
const {
|
|
template: { $poster }
|
|
} = art;
|
|
def(art, "poster", {
|
|
get: () => {
|
|
try {
|
|
return $poster.style.backgroundImage.match(/"(.*)"/)[1];
|
|
} catch {
|
|
return "";
|
|
}
|
|
},
|
|
set(url) {
|
|
setStyle($poster, "backgroundImage", `url(${url})`);
|
|
}
|
|
});
|
|
}
|
|
function qualityMix(art) {
|
|
def(art, "quality", {
|
|
set(quality) {
|
|
const { controls, notice, i18n } = art;
|
|
const qualityDefault = quality.find((item) => item.default) || quality[0];
|
|
controls.update({
|
|
name: "quality",
|
|
position: "right",
|
|
index: 10,
|
|
style: {
|
|
marginRight: "10px"
|
|
},
|
|
html: qualityDefault?.html || "",
|
|
selector: quality,
|
|
async onSelect(item) {
|
|
await art.switchQuality(item.url);
|
|
notice.show = `${i18n.get("Switch Video")}: ${item.html}`;
|
|
return item.html;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
function rectMix(art) {
|
|
def(art, "rect", {
|
|
get: () => {
|
|
return getRect(art.template.$player);
|
|
}
|
|
});
|
|
const keys = ["bottom", "height", "left", "right", "top", "width"];
|
|
for (let index = 0; index < keys.length; index++) {
|
|
const key = keys[index];
|
|
def(art, key, {
|
|
get: () => {
|
|
return art.rect[key];
|
|
}
|
|
});
|
|
}
|
|
def(art, "x", {
|
|
get: () => {
|
|
return art.left + window.pageXOffset;
|
|
}
|
|
});
|
|
def(art, "y", {
|
|
get: () => {
|
|
return art.top + window.pageYOffset;
|
|
}
|
|
});
|
|
}
|
|
function screenshotMix(art) {
|
|
const {
|
|
notice,
|
|
template: { $video }
|
|
} = art;
|
|
const $canvas = createElement("canvas");
|
|
def(art, "getDataURL", {
|
|
value: () => new Promise((resolve, reject) => {
|
|
try {
|
|
$canvas.width = $video.videoWidth;
|
|
$canvas.height = $video.videoHeight;
|
|
$canvas.getContext("2d").drawImage($video, 0, 0);
|
|
resolve($canvas.toDataURL("image/png"));
|
|
} catch (err) {
|
|
notice.show = err;
|
|
reject(err);
|
|
}
|
|
})
|
|
});
|
|
def(art, "getBlobUrl", {
|
|
value: () => new Promise((resolve, reject) => {
|
|
try {
|
|
$canvas.width = $video.videoWidth;
|
|
$canvas.height = $video.videoHeight;
|
|
$canvas.getContext("2d").drawImage($video, 0, 0);
|
|
$canvas.toBlob((blob) => {
|
|
resolve(URL.createObjectURL(blob));
|
|
});
|
|
} catch (err) {
|
|
notice.show = err;
|
|
reject(err);
|
|
}
|
|
})
|
|
});
|
|
def(art, "screenshot", {
|
|
value: async (name) => {
|
|
const dataUri = await art.getDataURL();
|
|
const fileName = name || `artplayer_${secondToTime($video.currentTime)}`;
|
|
download(dataUri, `${fileName}.png`);
|
|
art.emit("screenshot", dataUri);
|
|
return dataUri;
|
|
}
|
|
});
|
|
}
|
|
function seekMix(art) {
|
|
const { notice } = art;
|
|
def(art, "seek", {
|
|
set(time2) {
|
|
art.currentTime = time2;
|
|
if (art.duration) {
|
|
notice.show = `${secondToTime(art.currentTime)} / ${secondToTime(art.duration)}`;
|
|
}
|
|
art.emit("seek", art.currentTime, time2);
|
|
}
|
|
});
|
|
def(art, "forward", {
|
|
set(time2) {
|
|
art.seek = art.currentTime + time2;
|
|
}
|
|
});
|
|
def(art, "backward", {
|
|
set(time2) {
|
|
art.seek = art.currentTime - time2;
|
|
}
|
|
});
|
|
}
|
|
function stateMix(art) {
|
|
const states = ["mini", "pip", "fullscreen", "fullscreenWeb"];
|
|
def(art, "state", {
|
|
get: () => states.find((name) => art[name]) || "standard",
|
|
set(name) {
|
|
for (let index = 0; index < states.length; index++) {
|
|
const prop = states[index];
|
|
if (prop !== name && art[prop]) {
|
|
art[prop] = false;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
function subtitleOffsetMix(art) {
|
|
const { notice, i18n, template } = art;
|
|
def(art, "subtitleOffset", {
|
|
get() {
|
|
return template.$track?.offset || 0;
|
|
},
|
|
set(value) {
|
|
const { cues } = art.subtitle;
|
|
if (!template.$track || cues.length === 0)
|
|
return;
|
|
const offset = clamp(value, -10, 10);
|
|
template.$track.offset = offset;
|
|
for (let index = 0; index < cues.length; index++) {
|
|
const cue = cues[index];
|
|
cue.originalStartTime = cue.originalStartTime ?? cue.startTime;
|
|
cue.originalEndTime = cue.originalEndTime ?? cue.endTime;
|
|
cue.startTime = clamp(cue.originalStartTime + offset, 0, art.duration);
|
|
cue.endTime = clamp(cue.originalEndTime + offset, 0, art.duration);
|
|
}
|
|
art.subtitle.update();
|
|
notice.show = `${i18n.get("Subtitle Offset")}: ${value}s`;
|
|
art.emit("subtitleOffset", value);
|
|
}
|
|
});
|
|
}
|
|
function switchMix(art) {
|
|
function switchUrl(url, currentTime) {
|
|
return new Promise((resolve, reject) => {
|
|
if (url === art.url) {
|
|
resolve();
|
|
return;
|
|
}
|
|
const { playing, aspectRatio: aspectRatio2, playbackRate: playbackRate2 } = art;
|
|
art.pause();
|
|
art.url = url;
|
|
art.notice.show = "";
|
|
const handlers = {};
|
|
handlers.error = (error2) => {
|
|
art.off("video:canplay", handlers.canplay);
|
|
art.off("video:loadedmetadata", handlers.metadata);
|
|
reject(error2);
|
|
};
|
|
handlers.metadata = () => {
|
|
art.currentTime = currentTime;
|
|
};
|
|
handlers.canplay = async () => {
|
|
art.off("video:error", handlers.error);
|
|
art.playbackRate = playbackRate2;
|
|
art.aspectRatio = aspectRatio2;
|
|
if (playing) {
|
|
await art.play();
|
|
}
|
|
art.notice.show = "";
|
|
resolve();
|
|
};
|
|
art.once("video:error", handlers.error);
|
|
art.once("video:loadedmetadata", handlers.metadata);
|
|
art.once("video:canplay", handlers.canplay);
|
|
});
|
|
}
|
|
def(art, "switchQuality", {
|
|
value: (url) => {
|
|
return switchUrl(url, art.currentTime);
|
|
}
|
|
});
|
|
def(art, "switchUrl", {
|
|
value: (url) => {
|
|
return switchUrl(url, 0);
|
|
}
|
|
});
|
|
def(art, "switch", {
|
|
set: art.switchUrl
|
|
});
|
|
}
|
|
function themeMix(art) {
|
|
def(art, "theme", {
|
|
get() {
|
|
return art.cssVar("--art-theme");
|
|
},
|
|
set(theme) {
|
|
art.cssVar("--art-theme", theme);
|
|
}
|
|
});
|
|
}
|
|
function thumbnailsMix(art) {
|
|
const {
|
|
option,
|
|
template: { $progress, $video }
|
|
} = art;
|
|
let timer = null;
|
|
let loding = false;
|
|
let image = null;
|
|
function reset() {
|
|
clearTimeout(timer);
|
|
timer = null;
|
|
loding = false;
|
|
image = null;
|
|
}
|
|
function showThumbnails(posWidth) {
|
|
const $thumbnails = art.controls?.thumbnails;
|
|
if (!$thumbnails)
|
|
return;
|
|
const { number, column, width, height, scale } = option.thumbnails;
|
|
const width2 = width * scale || image.naturalWidth / column;
|
|
const height2 = height * scale || width2 / ($video.videoWidth / $video.videoHeight);
|
|
const perWidth = $progress.clientWidth / number;
|
|
const perIndex = Math.floor(posWidth / perWidth);
|
|
const yIndex = Math.ceil(perIndex / column) - 1;
|
|
const xIndex = perIndex % column || column - 1;
|
|
setStyle($thumbnails, "backgroundImage", `url(${image.src})`);
|
|
setStyle($thumbnails, "height", `${height2}px`);
|
|
setStyle($thumbnails, "width", `${width2}px`);
|
|
setStyle($thumbnails, "backgroundPosition", `-${xIndex * width2}px -${yIndex * height2}px`);
|
|
if (posWidth <= width2 / 2) {
|
|
setStyle($thumbnails, "left", 0);
|
|
} else if (posWidth > $progress.clientWidth - width2 / 2) {
|
|
setStyle($thumbnails, "left", `${$progress.clientWidth - width2}px`);
|
|
} else {
|
|
setStyle($thumbnails, "left", `${posWidth - width2 / 2}px`);
|
|
}
|
|
}
|
|
art.on("setBar", async (type, percentage, event) => {
|
|
const $thumbnails = art.controls?.thumbnails;
|
|
const { url, scale } = option.thumbnails;
|
|
if (!$thumbnails || !url)
|
|
return;
|
|
const isMobileDragging = type === "played" && event && isMobile;
|
|
if (type === "hover" || isMobileDragging) {
|
|
if (!image && !loding) {
|
|
loding = true;
|
|
image = await loadImg(url, scale);
|
|
loding = false;
|
|
}
|
|
if (!image)
|
|
return;
|
|
const width = $progress.clientWidth * percentage;
|
|
if (width > 0 && width < $progress.clientWidth) {
|
|
showThumbnails(width);
|
|
}
|
|
}
|
|
});
|
|
def(art, "thumbnails", {
|
|
get() {
|
|
return art.option.thumbnails;
|
|
},
|
|
set(thumbnails) {
|
|
if (thumbnails.url && !art.option.isLive) {
|
|
art.option.thumbnails = thumbnails;
|
|
reset();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
function toggleMix(art) {
|
|
def(art, "toggle", {
|
|
value() {
|
|
if (art.playing) {
|
|
return art.pause();
|
|
} else {
|
|
return art.play();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
function typeMix(art) {
|
|
def(art, "type", {
|
|
get() {
|
|
return art.option.type;
|
|
},
|
|
set(type) {
|
|
art.option.type = type;
|
|
}
|
|
});
|
|
}
|
|
function urlMix(art) {
|
|
const {
|
|
option,
|
|
template: { $video }
|
|
} = art;
|
|
def(art, "url", {
|
|
get() {
|
|
return $video.src;
|
|
},
|
|
async set(newUrl) {
|
|
if (newUrl) {
|
|
const oldUrl = art.url;
|
|
const typeName = option.type || getExt(newUrl);
|
|
const typeCallback = option.customType[typeName];
|
|
if (typeName && typeCallback) {
|
|
await sleep();
|
|
art.loading.show = true;
|
|
typeCallback.call(art, $video, newUrl, art);
|
|
} else {
|
|
URL.revokeObjectURL(oldUrl);
|
|
$video.src = newUrl;
|
|
}
|
|
if (oldUrl !== art.url) {
|
|
art.option.url = newUrl;
|
|
if (art.isReady && oldUrl) {
|
|
art.once("video:canplay", () => {
|
|
art.emit("restart", newUrl);
|
|
});
|
|
}
|
|
}
|
|
} else {
|
|
await sleep();
|
|
art.loading.show = true;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
function volumeMix(art) {
|
|
const {
|
|
template: { $video },
|
|
i18n,
|
|
notice,
|
|
storage
|
|
} = art;
|
|
def(art, "volume", {
|
|
get: () => $video.volume || 0,
|
|
set: (percentage) => {
|
|
$video.volume = clamp(percentage, 0, 1);
|
|
notice.show = `${i18n.get("Volume")}: ${Number.parseInt($video.volume * 100, 10)}`;
|
|
if ($video.volume !== 0) {
|
|
storage.set("volume", $video.volume);
|
|
}
|
|
}
|
|
});
|
|
def(art, "muted", {
|
|
get: () => $video.muted,
|
|
set: (muted) => {
|
|
$video.muted = muted;
|
|
art.emit("muted", muted);
|
|
}
|
|
});
|
|
}
|
|
class Player {
|
|
constructor(art) {
|
|
urlMix(art);
|
|
attrMix(art);
|
|
playMix(art);
|
|
pauseMix(art);
|
|
toggleMix(art);
|
|
seekMix(art);
|
|
volumeMix(art);
|
|
currentTimeMix(art);
|
|
durationMix(art);
|
|
switchMix(art);
|
|
playbackRateMix(art);
|
|
aspectRatioMix(art);
|
|
screenshotMix(art);
|
|
fullscreenMix(art);
|
|
fullscreenWebMix(art);
|
|
pipMix(art);
|
|
loadedMix(art);
|
|
playedMix(art);
|
|
playingMix(art);
|
|
autoSizeMix(art);
|
|
rectMix(art);
|
|
flipMix(art);
|
|
miniMix(art);
|
|
posterMix(art);
|
|
autoHeightMix(art);
|
|
cssVarMix(art);
|
|
themeMix(art);
|
|
typeMix(art);
|
|
stateMix(art);
|
|
subtitleOffsetMix(art);
|
|
airplayMix(art);
|
|
qualityMix(art);
|
|
thumbnailsMix(art);
|
|
eventInit(art);
|
|
optionInit(art);
|
|
}
|
|
}
|
|
function autoOrientation(art) {
|
|
const {
|
|
notice,
|
|
constructor,
|
|
template: { $player, $video }
|
|
} = art;
|
|
const WEB_CLASS = "art-auto-orientation";
|
|
const FS_CLASS = "art-auto-orientation-fullscreen";
|
|
let fsLocked = false;
|
|
function applyWebRotate() {
|
|
const viewWidth = document.documentElement.clientWidth;
|
|
const viewHeight = document.documentElement.clientHeight;
|
|
setStyle($player, "width", `${viewHeight}px`);
|
|
setStyle($player, "height", `${viewWidth}px`);
|
|
setStyle($player, "transform-origin", "0 0");
|
|
setStyle($player, "transform", `rotate(90deg) translate(0, -${viewWidth}px)`);
|
|
addClass($player, WEB_CLASS);
|
|
art.isRotate = true;
|
|
art.emit("resize");
|
|
}
|
|
function clearWebRotate() {
|
|
setStyle($player, "width", "");
|
|
setStyle($player, "height", "");
|
|
setStyle($player, "transform-origin", "");
|
|
setStyle($player, "transform", "");
|
|
removeClass($player, WEB_CLASS);
|
|
art.isRotate = false;
|
|
art.emit("resize");
|
|
}
|
|
function needRotate() {
|
|
const { videoWidth, videoHeight } = $video;
|
|
const vw = document.documentElement.clientWidth;
|
|
const vh = document.documentElement.clientHeight;
|
|
return videoWidth > videoHeight && vw < vh || videoWidth < videoHeight && vw > vh;
|
|
}
|
|
art.on("fullscreenWeb", (state2) => {
|
|
if (state2) {
|
|
if (needRotate()) {
|
|
const delay = Number(constructor.AUTO_ORIENTATION_TIME ?? 0);
|
|
setTimeout(() => {
|
|
if (art.fullscreenWeb && !hasClass($player, WEB_CLASS)) {
|
|
applyWebRotate();
|
|
}
|
|
}, delay);
|
|
}
|
|
} else {
|
|
if (hasClass($player, WEB_CLASS))
|
|
clearWebRotate();
|
|
}
|
|
});
|
|
art.on("fullscreen", async (state2) => {
|
|
const canLock = !!screen?.orientation?.lock;
|
|
if (state2) {
|
|
if (canLock && needRotate()) {
|
|
try {
|
|
const last = screen.orientation.type;
|
|
const opposite = last.startsWith("portrait") ? "landscape" : "portrait";
|
|
await screen.orientation.lock(opposite);
|
|
fsLocked = true;
|
|
addClass($player, FS_CLASS);
|
|
} catch (err) {
|
|
fsLocked = false;
|
|
notice.show = err;
|
|
}
|
|
}
|
|
} else {
|
|
if (hasClass($player, FS_CLASS)) {
|
|
removeClass($player, FS_CLASS);
|
|
}
|
|
if (canLock && fsLocked) {
|
|
try {
|
|
screen.orientation.unlock();
|
|
} catch {
|
|
}
|
|
fsLocked = false;
|
|
}
|
|
}
|
|
});
|
|
return {
|
|
name: "autoOrientation",
|
|
get state() {
|
|
return hasClass($player, WEB_CLASS);
|
|
}
|
|
};
|
|
}
|
|
function autoPlayback(art) {
|
|
const {
|
|
i18n,
|
|
icons,
|
|
storage,
|
|
constructor,
|
|
proxy,
|
|
template: { $poster }
|
|
} = art;
|
|
const $autoPlayback = art.layers.add({
|
|
name: "auto-playback",
|
|
html: `
|
|
<div class="art-auto-playback-close"></div>
|
|
<div class="art-auto-playback-last"></div>
|
|
<div class="art-auto-playback-jump"></div>
|
|
`
|
|
});
|
|
const $last = query(".art-auto-playback-last", $autoPlayback);
|
|
const $jump = query(".art-auto-playback-jump", $autoPlayback);
|
|
const $close = query(".art-auto-playback-close", $autoPlayback);
|
|
append($close, icons.close);
|
|
let timer = null;
|
|
art.on("video:timeupdate", () => {
|
|
if (art.playing) {
|
|
const times = storage.get("times") || {};
|
|
const keys = Object.keys(times);
|
|
if (keys.length > constructor.AUTO_PLAYBACK_MAX) {
|
|
delete times[keys[0]];
|
|
}
|
|
times[art.option.id || art.option.url] = art.currentTime;
|
|
storage.set("times", times);
|
|
}
|
|
});
|
|
function init() {
|
|
const times = storage.get("times") || {};
|
|
const currentTime = times[art.option.id || art.option.url];
|
|
clearTimeout(timer);
|
|
setStyle($autoPlayback, "display", "none");
|
|
if (currentTime && currentTime >= constructor.AUTO_PLAYBACK_MIN) {
|
|
setStyle($autoPlayback, "display", "flex");
|
|
$last.textContent = `${i18n.get("Last Seen")} ${secondToTime(currentTime)}`;
|
|
$jump.textContent = i18n.get("Jump Play");
|
|
proxy($close, "click", () => {
|
|
setStyle($autoPlayback, "display", "none");
|
|
});
|
|
proxy($jump, "click", () => {
|
|
art.seek = currentTime;
|
|
art.play();
|
|
setStyle($poster, "display", "none");
|
|
setStyle($autoPlayback, "display", "none");
|
|
});
|
|
art.once("video:timeupdate", () => {
|
|
timer = setTimeout(() => {
|
|
setStyle($autoPlayback, "display", "none");
|
|
}, constructor.AUTO_PLAYBACK_TIMEOUT);
|
|
});
|
|
}
|
|
}
|
|
art.on("ready", init);
|
|
art.on("restart", init);
|
|
return {
|
|
name: "auto-playback",
|
|
get times() {
|
|
return storage.get("times") || {};
|
|
},
|
|
clear() {
|
|
return storage.del("times");
|
|
},
|
|
delete(id2) {
|
|
const times = storage.get("times") || {};
|
|
delete times[id2];
|
|
storage.set("times", times);
|
|
return times;
|
|
}
|
|
};
|
|
}
|
|
function fastForward(art) {
|
|
const {
|
|
constructor,
|
|
proxy,
|
|
template: { $player, $video }
|
|
} = art;
|
|
let timer = null;
|
|
let isPress = false;
|
|
let lastPlaybackRate = 1;
|
|
const onStart = (event) => {
|
|
if (event.touches.length === 1 && art.playing && !art.isLock) {
|
|
timer = setTimeout(() => {
|
|
isPress = true;
|
|
lastPlaybackRate = art.playbackRate;
|
|
art.playbackRate = constructor.FAST_FORWARD_VALUE;
|
|
addClass($player, "art-fast-forward");
|
|
}, constructor.FAST_FORWARD_TIME);
|
|
}
|
|
};
|
|
const onStop = () => {
|
|
clearTimeout(timer);
|
|
if (isPress) {
|
|
isPress = false;
|
|
art.playbackRate = lastPlaybackRate;
|
|
removeClass($player, "art-fast-forward");
|
|
}
|
|
};
|
|
proxy($video, "touchstart", onStart);
|
|
art.on("document:touchmove", onStop);
|
|
art.on("document:touchend", onStop);
|
|
return {
|
|
name: "fastForward",
|
|
get state() {
|
|
return hasClass($player, "art-fast-forward");
|
|
}
|
|
};
|
|
}
|
|
function lock(art) {
|
|
const {
|
|
layers,
|
|
icons,
|
|
template: { $player }
|
|
} = art;
|
|
function getState() {
|
|
return hasClass($player, "art-lock");
|
|
}
|
|
function setLock() {
|
|
addClass($player, "art-lock");
|
|
art.isLock = true;
|
|
art.emit("lock", true);
|
|
}
|
|
function setUnlock() {
|
|
removeClass($player, "art-lock");
|
|
art.isLock = false;
|
|
art.emit("lock", false);
|
|
}
|
|
layers.add({
|
|
name: "lock",
|
|
mounted($el) {
|
|
const $lock = append($el, icons.lock);
|
|
const $unlock = append($el, icons.unlock);
|
|
setStyle($lock, "display", "none");
|
|
art.on("lock", (state2) => {
|
|
if (state2) {
|
|
setStyle($lock, "display", "inline-flex");
|
|
setStyle($unlock, "display", "none");
|
|
} else {
|
|
setStyle($lock, "display", "none");
|
|
setStyle($unlock, "display", "inline-flex");
|
|
}
|
|
});
|
|
},
|
|
click() {
|
|
if (getState()) {
|
|
setUnlock();
|
|
} else {
|
|
setLock();
|
|
}
|
|
}
|
|
});
|
|
return {
|
|
name: "lock",
|
|
get state() {
|
|
return getState();
|
|
},
|
|
set state(value) {
|
|
if (value) {
|
|
setLock();
|
|
} else {
|
|
setUnlock();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
function miniProgressBar(art) {
|
|
art.on("control", (state2) => {
|
|
if (state2) {
|
|
removeClass(art.template.$player, "art-mini-progress-bar");
|
|
} else {
|
|
addClass(art.template.$player, "art-mini-progress-bar");
|
|
}
|
|
});
|
|
return { name: "mini-progress-bar" };
|
|
}
|
|
class Plugins {
|
|
constructor(art) {
|
|
this.art = art;
|
|
this.id = 0;
|
|
const { option } = art;
|
|
if (option.miniProgressBar && !option.isLive) {
|
|
this.add(miniProgressBar);
|
|
}
|
|
if (option.lock && isMobile) {
|
|
this.add(lock);
|
|
}
|
|
if (option.autoPlayback && !option.isLive) {
|
|
this.add(autoPlayback);
|
|
}
|
|
if (option.autoOrientation && isMobile) {
|
|
this.add(autoOrientation);
|
|
}
|
|
if (option.fastForward && isMobile && !option.isLive) {
|
|
this.add(fastForward);
|
|
}
|
|
for (let index = 0; index < option.plugins.length; index++) {
|
|
this.add(option.plugins[index]);
|
|
}
|
|
}
|
|
add(plugin) {
|
|
this.id += 1;
|
|
const result = plugin.call(this.art, this.art);
|
|
if (result instanceof Promise) {
|
|
return result.then((res) => this.next(plugin, res));
|
|
} else {
|
|
return this.next(plugin, result);
|
|
}
|
|
}
|
|
next(plugin, result) {
|
|
const pluginName = result && result.name || plugin.name || `plugin${this.id}`;
|
|
errorHandle(!has(this, pluginName), `Cannot add a plugin that already has the same name: ${pluginName}`);
|
|
def(this, pluginName, {
|
|
value: result
|
|
});
|
|
return this;
|
|
}
|
|
}
|
|
function aspectRatio(art) {
|
|
const {
|
|
i18n,
|
|
icons,
|
|
constructor: { SETTING_ITEM_WIDTH, ASPECT_RATIO }
|
|
} = art;
|
|
function getI18n(value) {
|
|
return value === "default" ? i18n.get("Default") : value;
|
|
}
|
|
function update() {
|
|
const target = art.setting.find(`aspect-ratio-${art.aspectRatio}`);
|
|
art.setting.check(target);
|
|
}
|
|
return {
|
|
width: SETTING_ITEM_WIDTH,
|
|
name: "aspect-ratio",
|
|
html: i18n.get("Aspect Ratio"),
|
|
icon: icons.aspectRatio,
|
|
tooltip: getI18n(art.aspectRatio),
|
|
selector: ASPECT_RATIO.map((item) => {
|
|
return {
|
|
value: item,
|
|
name: `aspect-ratio-${item}`,
|
|
default: item === art.aspectRatio,
|
|
html: getI18n(item)
|
|
};
|
|
}),
|
|
onSelect(item) {
|
|
art.aspectRatio = item.value;
|
|
return item.html;
|
|
},
|
|
mounted: () => {
|
|
update();
|
|
art.on("aspectRatio", () => update());
|
|
}
|
|
};
|
|
}
|
|
function flip(art) {
|
|
const {
|
|
i18n,
|
|
icons,
|
|
constructor: { SETTING_ITEM_WIDTH, FLIP }
|
|
} = art;
|
|
function getI18n(value) {
|
|
return i18n.get(capitalize(value));
|
|
}
|
|
function update() {
|
|
const target = art.setting.find(`flip-${art.flip}`);
|
|
art.setting.check(target);
|
|
}
|
|
return {
|
|
width: SETTING_ITEM_WIDTH,
|
|
name: "flip",
|
|
html: i18n.get("Video Flip"),
|
|
tooltip: getI18n(art.flip),
|
|
icon: icons.flip,
|
|
selector: FLIP.map((item) => {
|
|
return {
|
|
value: item,
|
|
name: `flip-${item}`,
|
|
default: item === art.flip,
|
|
html: getI18n(item)
|
|
};
|
|
}),
|
|
onSelect(item) {
|
|
art.flip = item.value;
|
|
return item.html;
|
|
},
|
|
mounted: () => {
|
|
update();
|
|
art.on("flip", () => update());
|
|
}
|
|
};
|
|
}
|
|
function playbackRate(art) {
|
|
const {
|
|
i18n,
|
|
icons,
|
|
constructor: { SETTING_ITEM_WIDTH, PLAYBACK_RATE }
|
|
} = art;
|
|
function getI18n(value) {
|
|
return value === 1 ? i18n.get("Normal") : value.toFixed(1);
|
|
}
|
|
function update() {
|
|
const target = art.setting.find(`playback-rate-${art.playbackRate}`);
|
|
art.setting.check(target);
|
|
}
|
|
return {
|
|
width: SETTING_ITEM_WIDTH,
|
|
name: "playback-rate",
|
|
html: i18n.get("Play Speed"),
|
|
tooltip: getI18n(art.playbackRate),
|
|
icon: icons.playbackRate,
|
|
selector: PLAYBACK_RATE.map((item) => {
|
|
return {
|
|
value: item,
|
|
name: `playback-rate-${item}`,
|
|
default: item === art.playbackRate,
|
|
html: getI18n(item)
|
|
};
|
|
}),
|
|
onSelect(item) {
|
|
art.playbackRate = item.value;
|
|
return item.html;
|
|
},
|
|
mounted: () => {
|
|
update();
|
|
art.on("video:ratechange", () => update());
|
|
}
|
|
};
|
|
}
|
|
function subtitleOffset(art) {
|
|
const { i18n, icons, constructor } = art;
|
|
return {
|
|
width: constructor.SETTING_ITEM_WIDTH,
|
|
name: "subtitle-offset",
|
|
html: i18n.get("Subtitle Offset"),
|
|
icon: icons.subtitle,
|
|
tooltip: "0s",
|
|
range: [0, -10, 10, 0.1],
|
|
onChange(item) {
|
|
art.subtitleOffset = item.range[0];
|
|
return `${item.range[0]}s`;
|
|
},
|
|
mounted: (_, item) => {
|
|
art.on("subtitleOffset", (value) => {
|
|
item.$range.value = value;
|
|
item.tooltip = `${value}s`;
|
|
});
|
|
}
|
|
};
|
|
}
|
|
class Setting extends Component {
|
|
constructor(art) {
|
|
super(art);
|
|
const {
|
|
option,
|
|
controls,
|
|
template: { $setting }
|
|
} = art;
|
|
this.name = "setting";
|
|
this.$parent = $setting;
|
|
this.id = 0;
|
|
this.active = null;
|
|
this.cache = /* @__PURE__ */ new Map();
|
|
this.option = [...this.builtin, ...option.settings];
|
|
if (option.setting) {
|
|
this.format();
|
|
this.render();
|
|
art.on("blur", () => {
|
|
if (this.show) {
|
|
this.show = false;
|
|
this.render();
|
|
}
|
|
});
|
|
art.on("focus", (event) => {
|
|
const isControl = includeFromEvent(event, controls.setting);
|
|
const isSetting = includeFromEvent(event, this.$parent);
|
|
if (this.show && !isControl && !isSetting) {
|
|
this.show = false;
|
|
this.render();
|
|
}
|
|
});
|
|
art.on("resize", () => this.resize());
|
|
}
|
|
}
|
|
get builtin() {
|
|
const result = [];
|
|
const { option } = this.art;
|
|
if (option.playbackRate) {
|
|
result.push(playbackRate(this.art));
|
|
}
|
|
if (option.aspectRatio) {
|
|
result.push(aspectRatio(this.art));
|
|
}
|
|
if (option.flip) {
|
|
result.push(flip(this.art));
|
|
}
|
|
if (option.subtitleOffset) {
|
|
result.push(subtitleOffset(this.art));
|
|
}
|
|
return result;
|
|
}
|
|
traverse(callback, option = this.option) {
|
|
for (let index = 0; index < option.length; index++) {
|
|
const item = option[index];
|
|
callback(item);
|
|
if (item.selector?.length) {
|
|
this.traverse(callback, item.selector);
|
|
}
|
|
}
|
|
}
|
|
check(target) {
|
|
if (!target) {
|
|
return;
|
|
}
|
|
target.$parent.tooltip = target.html;
|
|
this.traverse((item) => {
|
|
item.default = item === target;
|
|
if (item.default && item.$item) {
|
|
inverseClass(item.$item, "art-current");
|
|
}
|
|
}, target.$option);
|
|
this.render(target.$parents);
|
|
}
|
|
format(option = this.option, parent, parents, names = []) {
|
|
for (let index = 0; index < option.length; index++) {
|
|
const item = option[index];
|
|
if (item?.name) {
|
|
errorHandle(!names.includes(item.name), `The [${item.name}] already exists in [setting]`);
|
|
names.push(item.name);
|
|
} else {
|
|
item.name = `setting-${this.id++}`;
|
|
}
|
|
if (!item.$formatted) {
|
|
def(item, "$parent", {
|
|
get: () => parent
|
|
});
|
|
def(item, "$parents", {
|
|
get: () => parents
|
|
});
|
|
def(item, "$option", {
|
|
get: () => option
|
|
});
|
|
const $events = [];
|
|
def(item, "$events", {
|
|
get: () => $events
|
|
});
|
|
def(item, "$formatted", {
|
|
get: () => true
|
|
});
|
|
}
|
|
this.format(item.selector || [], item, option, names);
|
|
}
|
|
this.option = option;
|
|
}
|
|
find(name = "") {
|
|
let result = null;
|
|
this.traverse((item) => {
|
|
if (item.name === name) {
|
|
result = item;
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
resize() {
|
|
const {
|
|
controls,
|
|
constructor: { SETTING_WIDTH, SETTING_ITEM_HEIGHT },
|
|
template: { $player, $setting }
|
|
} = this.art;
|
|
if (controls.setting && this.show) {
|
|
const settingWidth = this.active[0]?.$parent?.width || SETTING_WIDTH;
|
|
const { left: controlLeft, width: controlWidth } = getRect(controls.setting);
|
|
const { left: playerLeft, width: playerWidth } = getRect($player);
|
|
const settingLeft = controlLeft - playerLeft + controlWidth / 2 - settingWidth / 2;
|
|
const settingHeight = this.active === this.option ? this.active.length * SETTING_ITEM_HEIGHT : (this.active.length + 1) * SETTING_ITEM_HEIGHT;
|
|
setStyle($setting, "height", `${settingHeight}px`);
|
|
setStyle($setting, "width", `${settingWidth}px`);
|
|
if (this.art.isRotate || isMobile)
|
|
return;
|
|
if (settingLeft + settingWidth > playerWidth) {
|
|
setStyle($setting, "left", null);
|
|
setStyle($setting, "right", null);
|
|
} else {
|
|
setStyle($setting, "left", `${settingLeft}px`);
|
|
setStyle($setting, "right", "auto");
|
|
}
|
|
}
|
|
}
|
|
inactivate(item) {
|
|
for (let index = 0; index < item.$events.length; index++) {
|
|
this.art.events.remove(item.$events[index]);
|
|
}
|
|
item.$events.length = 0;
|
|
}
|
|
remove(name) {
|
|
const item = this.find(name);
|
|
errorHandle(item, `Can't find [${name}] in the [setting]`);
|
|
const index = item.$option.indexOf(item);
|
|
item.$option.splice(index, 1);
|
|
this.inactivate(item);
|
|
if (item.$item)
|
|
remove(item.$item);
|
|
this.render();
|
|
}
|
|
update(target) {
|
|
const item = this.find(target.name);
|
|
if (item) {
|
|
this.inactivate(item);
|
|
Object.assign(item, target);
|
|
this.format();
|
|
this.createItem(item, true);
|
|
this.render();
|
|
return item;
|
|
} else {
|
|
return this.add(target);
|
|
}
|
|
}
|
|
add(item, option = this.option) {
|
|
option.push(item);
|
|
this.format();
|
|
this.createItem(item);
|
|
this.render();
|
|
return item;
|
|
}
|
|
createHeader(item) {
|
|
if (!this.cache.has(item.$option))
|
|
return;
|
|
const $panel = this.cache.get(item.$option);
|
|
const {
|
|
proxy,
|
|
icons: { arrowLeft: arrowLeft2 },
|
|
constructor: { SETTING_ITEM_HEIGHT }
|
|
} = this.art;
|
|
const $item = createElement("div");
|
|
setStyle($item, "height", `${SETTING_ITEM_HEIGHT}px`);
|
|
addClass($item, "art-setting-item");
|
|
addClass($item, "art-setting-item-back");
|
|
const $left = append($item, '<div class="art-setting-item-left"></div>');
|
|
const $icon = createElement("div");
|
|
addClass($icon, "art-setting-item-left-icon");
|
|
append($icon, arrowLeft2);
|
|
append($left, $icon);
|
|
append($left, item.$parent.html);
|
|
const event = proxy($item, "click", () => this.render(item.$parents));
|
|
item.$parent.$events.push(event);
|
|
append($panel, $item);
|
|
}
|
|
createItem(item, isUpdate = false) {
|
|
if (!this.cache.has(item.$option))
|
|
return;
|
|
const $panel = this.cache.get(item.$option);
|
|
const oldItem = item.$item;
|
|
let type = "selector";
|
|
if (has(item, "switch")) {
|
|
type = "switch";
|
|
}
|
|
if (has(item, "range")) {
|
|
type = "range";
|
|
}
|
|
if (has(item, "onClick")) {
|
|
type = "button";
|
|
}
|
|
const { icons, proxy, constructor } = this.art;
|
|
const $item = createElement("div");
|
|
addClass($item, "art-setting-item");
|
|
setStyle($item, "height", `${constructor.SETTING_ITEM_HEIGHT}px`);
|
|
$item.dataset.name = item.name || "";
|
|
$item.dataset.value = item.value || "";
|
|
const $left = append($item, '<div class="art-setting-item-left"></div>');
|
|
const $right = append($item, '<div class="art-setting-item-right"></div>');
|
|
const $icon = createElement("div");
|
|
addClass($icon, "art-setting-item-left-icon");
|
|
switch (type) {
|
|
case "button":
|
|
case "switch":
|
|
case "range":
|
|
append($icon, item.icon || icons.config);
|
|
break;
|
|
case "selector":
|
|
if (item.selector?.length) {
|
|
append($icon, item.icon || icons.config);
|
|
} else {
|
|
append($icon, icons.check);
|
|
}
|
|
break;
|
|
}
|
|
append($left, $icon);
|
|
def(item, "$icon", {
|
|
configurable: true,
|
|
get: () => $icon
|
|
});
|
|
def(item, "icon", {
|
|
configurable: true,
|
|
get() {
|
|
return $icon.innerHTML;
|
|
},
|
|
set(value) {
|
|
$icon.innerHTML = "";
|
|
append($icon, value);
|
|
}
|
|
});
|
|
const $html = createElement("div");
|
|
addClass($html, "art-setting-item-left-text");
|
|
append($html, item.html || "");
|
|
append($left, $html);
|
|
def(item, "$html", {
|
|
configurable: true,
|
|
get: () => $html
|
|
});
|
|
def(item, "html", {
|
|
configurable: true,
|
|
get() {
|
|
return $html.innerHTML;
|
|
},
|
|
set(value) {
|
|
$html.innerHTML = "";
|
|
append($html, value);
|
|
}
|
|
});
|
|
const $tooltip = createElement("div");
|
|
addClass($tooltip, "art-setting-item-right-tooltip");
|
|
append($tooltip, item.tooltip || "");
|
|
append($right, $tooltip);
|
|
def(item, "$tooltip", {
|
|
configurable: true,
|
|
get: () => $tooltip
|
|
});
|
|
def(item, "tooltip", {
|
|
configurable: true,
|
|
get() {
|
|
return $tooltip.innerHTML;
|
|
},
|
|
set(value) {
|
|
$tooltip.innerHTML = "";
|
|
append($tooltip, value);
|
|
}
|
|
});
|
|
switch (type) {
|
|
case "switch": {
|
|
const $switch = createElement("div");
|
|
addClass($switch, "art-setting-item-right-icon");
|
|
const $switchOn = append($switch, icons.switchOn);
|
|
const $switchOff = append($switch, icons.switchOff);
|
|
setStyle(item.switch ? $switchOff : $switchOn, "display", "none");
|
|
append($right, $switch);
|
|
def(item, "$switch", {
|
|
configurable: true,
|
|
get: () => $switch
|
|
});
|
|
let $switchValue = item.switch;
|
|
def(item, "switch", {
|
|
configurable: true,
|
|
get: () => $switchValue,
|
|
set(value) {
|
|
$switchValue = value;
|
|
if (value) {
|
|
setStyle($switchOff, "display", "none");
|
|
setStyle($switchOn, "display", null);
|
|
} else {
|
|
setStyle($switchOff, "display", null);
|
|
setStyle($switchOn, "display", "none");
|
|
}
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
case "range":
|
|
{
|
|
const $state = createElement("div");
|
|
addClass($state, "art-setting-item-right-icon");
|
|
const $range = append($state, '<input type="range">');
|
|
$range.value = item.range[0];
|
|
$range.min = item.range[1];
|
|
$range.max = item.range[2];
|
|
$range.step = item.range[3];
|
|
addClass($range, "art-setting-range");
|
|
append($right, $state);
|
|
def(item, "$range", {
|
|
configurable: true,
|
|
get: () => $range
|
|
});
|
|
let $rangeValue = [...item.range];
|
|
def(item, "range", {
|
|
configurable: true,
|
|
get: () => $rangeValue,
|
|
set(value) {
|
|
$rangeValue = [...value];
|
|
$range.value = value[0];
|
|
$range.min = value[1];
|
|
$range.max = value[2];
|
|
$range.step = value[3];
|
|
}
|
|
});
|
|
}
|
|
break;
|
|
case "selector":
|
|
if (item.selector?.length) {
|
|
const $state = createElement("div");
|
|
addClass($state, "art-setting-item-right-icon");
|
|
append($state, icons.arrowRight);
|
|
append($right, $state);
|
|
}
|
|
break;
|
|
}
|
|
switch (type) {
|
|
case "switch": {
|
|
if (item.onSwitch) {
|
|
const event = proxy($item, "click", async (event2) => {
|
|
item.switch = await item.onSwitch.call(this.art, item, $item, event2);
|
|
});
|
|
item.$events.push(event);
|
|
}
|
|
break;
|
|
}
|
|
case "range": {
|
|
if (item.$range) {
|
|
if (item.onRange) {
|
|
const event = proxy(item.$range, "change", async (event2) => {
|
|
item.range[0] = item.$range.valueAsNumber;
|
|
item.tooltip = await item.onRange.call(this.art, item, $item, event2);
|
|
});
|
|
item.$events.push(event);
|
|
}
|
|
if (item.onChange) {
|
|
const event = proxy(item.$range, "input", async (event2) => {
|
|
item.range[0] = item.$range.valueAsNumber;
|
|
item.tooltip = await item.onChange.call(this.art, item, $item, event2);
|
|
});
|
|
item.$events.push(event);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case "selector":
|
|
{
|
|
const event = proxy($item, "click", async (event2) => {
|
|
if (item.selector?.length) {
|
|
this.render(item.selector);
|
|
} else {
|
|
this.check(item);
|
|
if (item.$parent.onSelect) {
|
|
item.$parent.tooltip = await item.$parent.onSelect.call(this.art, item, $item, event2);
|
|
}
|
|
}
|
|
});
|
|
item.$events.push(event);
|
|
if (item.default) {
|
|
addClass($item, "art-current");
|
|
}
|
|
}
|
|
break;
|
|
case "button":
|
|
if (item.onClick) {
|
|
const event = proxy($item, "click", async (event2) => {
|
|
item.tooltip = await item.onClick.call(this.art, item, $item, event2);
|
|
});
|
|
item.$events.push(event);
|
|
}
|
|
break;
|
|
}
|
|
def(item, "$item", {
|
|
configurable: true,
|
|
get: () => $item
|
|
});
|
|
if (isUpdate) {
|
|
replaceElement($item, oldItem);
|
|
} else {
|
|
append($panel, $item);
|
|
}
|
|
if (item.mounted) {
|
|
setTimeout(() => item.mounted.call(this.art, item.$item, item), 0);
|
|
}
|
|
}
|
|
render(option = this.option) {
|
|
this.active = option;
|
|
if (this.cache.has(option)) {
|
|
const $panel = this.cache.get(option);
|
|
inverseClass($panel, "art-current");
|
|
} else {
|
|
const $panel = createElement("div");
|
|
this.cache.set(option, $panel);
|
|
addClass($panel, "art-setting-panel");
|
|
append(this.$parent, $panel);
|
|
inverseClass($panel, "art-current");
|
|
if (option[0]?.$parent) {
|
|
this.createHeader(option[0]);
|
|
}
|
|
for (let index = 0; index < option.length; index++) {
|
|
this.createItem(option[index]);
|
|
}
|
|
}
|
|
this.resize();
|
|
}
|
|
}
|
|
class Storage {
|
|
constructor() {
|
|
this.name = "artplayer_settings";
|
|
this.settings = {};
|
|
}
|
|
get(key) {
|
|
try {
|
|
const storage = JSON.parse(window.localStorage.getItem(this.name)) || {};
|
|
return key ? storage[key] : storage;
|
|
} catch {
|
|
return key ? this.settings[key] : this.settings;
|
|
}
|
|
}
|
|
set(key, value) {
|
|
try {
|
|
const storage = Object.assign({}, this.get(), {
|
|
[key]: value
|
|
});
|
|
window.localStorage.setItem(this.name, JSON.stringify(storage));
|
|
} catch {
|
|
this.settings[key] = value;
|
|
}
|
|
}
|
|
del(key) {
|
|
try {
|
|
const storage = this.get();
|
|
delete storage[key];
|
|
window.localStorage.setItem(this.name, JSON.stringify(storage));
|
|
} catch {
|
|
delete this.settings[key];
|
|
}
|
|
}
|
|
clear() {
|
|
try {
|
|
window.localStorage.removeItem(this.name);
|
|
} catch {
|
|
this.settings = {};
|
|
}
|
|
}
|
|
}
|
|
const style = '.art-video-player{--art-theme: #f00;--art-font-color: #fff;--art-background-color: #000;--art-text-shadow-color: rgba(0, 0, 0, .5);--art-transition-duration: .2s;--art-padding: 10px;--art-border-radius: 3px;--art-progress-height: 6px;--art-progress-color: rgba(255, 255, 255, .25);--art-progress-top-gap: 8px;--art-hover-color: rgba(255, 255, 255, .25);--art-loaded-color: rgba(255, 255, 255, .25);--art-state-size: 80px;--art-state-opacity: .8;--art-bottom-height: 100px;--art-bottom-offset: 20px;--art-bottom-gap: 5px;--art-highlight-width: 8px;--art-highlight-color: rgba(255, 255, 255, .5);--art-control-height: 46px;--art-control-opacity: .75;--art-control-icon-size: 36px;--art-control-icon-scale: 1.1;--art-volume-height: 120px;--art-volume-handle-size: 14px;--art-lock-size: 36px;--art-indicator-scale: 0;--art-indicator-size: 16px;--art-fullscreen-web-index: 9999;--art-settings-icon-size: 24px;--art-settings-max-height: 300px;--art-selector-max-height: 300px;--art-contextmenus-min-width: 250px;--art-subtitle-font-size: 20px;--art-subtitle-gap: 5px;--art-subtitle-bottom: 15px;--art-subtitle-border: #000;--art-widget-background: rgba(0, 0, 0, .85);--art-tip-background: rgba(0, 0, 0, .7);--art-scrollbar-size: 4px;--art-scrollbar-background: rgba(255, 255, 255, .25);--art-scrollbar-background-hover: rgba(255, 255, 255, .5);--art-mini-progress-height: 2px}.art-bg-cover{background-position:center center;background-repeat:no-repeat;background-size:cover}.art-bottom-gradient{background-image:linear-gradient(to top,#000,rgba(0,0,0,.4),transparent);background-repeat:repeat-x;background-position:center bottom}.art-backdrop-filter{-webkit-backdrop-filter:saturate(180%) blur(20px);backdrop-filter:saturate(180%) blur(20px);background-color:#000000bf!important}.art-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.art-video-player{position:relative;margin:0 auto;width:100%;height:100%;outline:0;zoom:1;padding:0;text-align:left;direction:ltr;font-size:14px;line-height:1.3;user-select:none;box-sizing:border-box;color:var(--art-font-color);background-color:var(--art-background-color);text-shadow:0 0 2px var(--art-text-shadow-color);font-family:PingFang SC,Helvetica Neue,Microsoft YaHei,Roboto,Arial,sans-serif;-webkit-tap-highlight-color:rgba(0,0,0,0);-ms-touch-action:manipulation;touch-action:manipulation;-ms-high-contrast-adjust:none}.art-video-player *,.art-video-player *:before,.art-video-player *:after{box-sizing:border-box}.art-video-player ::-webkit-scrollbar{width:var(--art-scrollbar-size);height:var(--art-scrollbar-size)}.art-video-player ::-webkit-scrollbar-thumb{background-color:var(--art-scrollbar-background)}.art-video-player ::-webkit-scrollbar-thumb:hover{background-color:var(--art-scrollbar-background-hover)}.art-video-player img{max-width:100%;vertical-align:top}.art-video-player svg{fill:var(--art-font-color)}.art-video-player a{color:var(--art-font-color);text-decoration:none}.art-icon{line-height:1;display:flex;justify-content:center;align-items:center}.art-video-player.art-backdrop .art-contextmenus,.art-video-player.art-backdrop .art-info,.art-video-player.art-backdrop .art-settings,.art-video-player.art-backdrop .art-layer-auto-playback,.art-video-player.art-backdrop .art-selector-list,.art-video-player.art-backdrop .art-volume-inner{-webkit-backdrop-filter:saturate(180%) blur(20px);backdrop-filter:saturate(180%) blur(20px);background-color:#000000bf!important}.art-video{position:absolute;inset:0;z-index:10;width:100%;height:100%;cursor:pointer}.art-poster{position:absolute;inset:0;z-index:11;width:100%;height:100%;background-position:center center;background-repeat:no-repeat;background-size:cover;pointer-events:none}.art-video-player .art-subtitle{display:none;justify-content:center;align-items:center;flex-direction:column;position:absolute;z-index:20;width:100%;padding:0 5%;text-align:center;pointer-events:none;gap:var(--art-subtitle-gap);bottom:var(--art-subtitle-bottom);font-size:var(--art-subtitle-font-size);transition:bottom var(--art-transition-duration) ease;text-shadow:var(--art-subtitle-border) 1px 0 1px,var(--art-subtitle-border) 0 1px 1px,var(--art-subtitle-border) -1px 0 1px,var(--art-subtitle-border) 0 -1px 1px,var(--art-subtitle-border) 1px 1px 1px,var(--art-subtitle-border) -1px -1px 1px,var(--art-subtitle-border) 1px -1px 1px,var(--art-subtitle-border) -1px 1px 1px}.art-video-player.art-subtitle-show .art-subtitle{display:flex}.art-video-player.art-control-show .art-subtitle{bottom:calc(var(--art-control-height) + var(--art-subtitle-bottom))}.art-danmuku{position:absolute;inset:0;z-index:30;width:100%;height:100%;pointer-events:none;overflow:hidden}.art-video-player .art-layers{position:absolute;inset:0;z-index:40;width:100%;height:100%;display:none;pointer-events:none}.art-video-player .art-layers .art-layer{pointer-events:auto}.art-video-player.art-layer-show .art-layers{display:flex}.art-video-player .art-mask{display:flex;justify-content:center;align-items:center;position:absolute;inset:0;z-index:50;width:100%;height:100%;pointer-events:none}.art-video-player .art-mask .art-state{display:flex;justify-content:center;align-items:center;opacity:0;transform:scale(2);width:var(--art-state-size);height:var(--art-state-size);transition:all var(--art-transition-duration) ease}.art-video-player.art-mask-show .art-state{cursor:pointer;pointer-events:auto;opacity:var(--art-state-opacity);transform:scale(1)}.art-video-player.art-loading-show .art-state{display:none}.art-video-player .art-loading{display:none;justify-content:center;align-items:center;position:absolute;inset:0;z-index:70;width:100%;height:100%;pointer-events:none}.art-video-player.art-loading-show .art-loading{display:flex}.art-video-player.art-loading-show .art-mask{display:none}.art-video-player .art-bottom{position:absolute;inset:0;z-index:60;width:100%;height:100%;display:flex;flex-direction:column;justify-content:flex-end;opacity:0;overflow:hidden;pointer-events:none;padding:0 var(--art-padding);transition:all var(--art-transition-duration) ease;background-size:100% var(--art-bottom-height);background-image:linear-gradient(to top,#000,rgba(0,0,0,.4),transparent);background-repeat:repeat-x;background-position:center bottom}.art-video-player .art-bottom .art-controls,.art-video-player .art-bottom .art-progress{transform:translateY(var(--art-bottom-offset));transition:transform var(--art-transition-duration) ease}.art-video-player.art-control-show .art-bottom,.art-video-player.art-hover .art-bottom{opacity:1}.art-video-player.art-control-show .art-bottom .art-controls,.art-video-player.art-hover .art-bottom .art-controls,.art-video-player.art-control-show .art-bottom .art-progress,.art-video-player.art-hover .art-bottom .art-progress{transform:translateY(0)}.art-bottom .art-progress{position:relative;z-index:0;pointer-events:auto;padding-top:var(--art-progress-top-gap);padding-bottom:var(--art-bottom-gap)}.art-bottom .art-progress .art-control-progress{position:relative;display:flex;justify-content:center;align-items:center;cursor:pointer;height:var(--art-progress-height)}.art-bottom .art-progress .art-control-progress .art-control-progress-inner{display:flex;align-items:center;position:relative;height:50%;width:100%;transition:height var(--art-transition-duration) ease;background-color:var(--art-progress-color)}.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-hover{position:absolute;inset:0;z-index:0;width:100%;height:100%;width:0%;background-color:var(--art-hover-color)}.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-loaded{position:absolute;inset:0;z-index:10;width:100%;height:100%;width:0%;background-color:var(--art-loaded-color)}.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-played{position:absolute;inset:0;z-index:20;width:100%;height:100%;width:0%;background-color:var(--art-theme)}.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-highlight{position:absolute;inset:0;z-index:30;width:100%;height:100%;pointer-events:none}.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-highlight span{position:absolute;inset:0 auto 0 0;z-index:0;width:100%;height:100%;pointer-events:auto;width:var(--art-highlight-width)!important;transform:translate(calc(var(--art-highlight-width) / -2));background-color:var(--art-highlight-color)}.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-indicator{display:flex;justify-content:center;align-items:center;position:absolute;z-index:40;left:0;border-radius:50%;width:var(--art-indicator-size);height:var(--art-indicator-size);transform:scale(var(--art-indicator-scale));margin-left:calc(var(--art-indicator-size) / -2);transition:transform var(--art-transition-duration) ease}.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-indicator .art-icon{width:100%;height:100%;pointer-events:none}.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-indicator:hover{transform:scale(1.2)!important}.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-indicator:active{transform:scale(1)!important}.art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-tip{transform-origin:bottom center;transform:scale(.5);opacity:0;position:absolute;z-index:50;top:-25px;left:0;padding:3px 5px;line-height:1;font-size:12px;border-radius:var(--art-border-radius);white-space:nowrap;background-color:var(--art-tip-background);transition:transform var(--art-transition-duration) ease,opacity var(--art-transition-duration) ease}.art-bottom .art-progress .art-control-progress:hover .art-control-progress-inner{height:100%}.art-bottom .art-progress .art-control-thumbnails{transform-origin:bottom center;transform:scale(.5);opacity:0;position:absolute;bottom:calc(var(--art-bottom-gap) + 10px);left:0;border-radius:var(--art-border-radius);pointer-events:none;background-color:var(--art-widget-background);transition:transform var(--art-transition-duration) ease,opacity var(--art-transition-duration) ease;box-shadow:0 1px 3px #0003,0 1px 2px -1px #0003}.art-bottom:hover .art-progress .art-control-progress .art-control-progress-inner .art-progress-indicator{transform:scale(1)}.art-progress-hover .art-bottom .art-progress .art-control-progress .art-control-progress-inner .art-progress-tip,.art-progress-hover .art-bottom .art-progress .art-control-thumbnails{transform:scale(1);opacity:1}.art-video-player .art-controls{position:relative;z-index:10;pointer-events:auto;display:flex;align-items:center;justify-content:space-between;height:var(--art-control-height)}.art-video-player .art-controls .art-controls-left,.art-video-player .art-controls .art-controls-right{display:flex;height:100%}.art-video-player .art-controls .art-controls-center{display:none;justify-content:center;align-items:center;flex:1;height:100%;padding:0 10px}.art-video-player .art-controls .art-controls-right{justify-content:flex-end}.art-video-player .art-controls .art-control{display:flex;justify-content:center;align-items:center;flex-shrink:0;cursor:pointer;white-space:nowrap;opacity:var(--art-control-opacity);min-height:var(--art-control-height);min-width:var(--art-control-height);transition:opacity var(--art-transition-duration) ease}.art-video-player .art-controls .art-control .art-icon{height:var(--art-control-icon-size);width:var(--art-control-icon-size);transform:scale(var(--art-control-icon-scale));transition:transform var(--art-transition-duration) ease}.art-video-player .art-controls .art-control .art-icon:active{transform:scale(calc(var(--art-control-icon-scale) * .8))}.art-video-player .art-controls .art-control:hover{opacity:1}.art-control-volume{position:relative}.art-control-volume .art-volume-panel{display:flex;justify-content:center;align-items:center;position:absolute;left:0;right:0;padding:0 5px;font-size:12px;text-align:center;cursor:default;opacity:0;transform:translateY(10px);pointer-events:none;bottom:var(--art-control-height);width:var(--art-control-height);height:var(--art-volume-height);transition:all var(--art-transition-duration) ease}.art-control-volume .art-volume-panel .art-volume-inner{display:flex;flex-direction:column;align-items:center;gap:10px;height:100%;width:100%;padding:10px 0 12px;border-radius:var(--art-border-radius);background-color:var(--art-widget-background)}.art-control-volume .art-volume-panel .art-volume-inner .art-volume-slider{flex:1;width:100%;display:flex;cursor:pointer;position:relative;justify-content:center}.art-control-volume .art-volume-panel .art-volume-inner .art-volume-slider .art-volume-handle{position:relative;display:flex;justify-content:center;width:2px;border-radius:var(--art-border-radius);overflow:hidden;background-color:#ffffff40}.art-control-volume .art-volume-panel .art-volume-inner .art-volume-slider .art-volume-handle .art-volume-loaded{position:absolute;inset:0;z-index:0;width:100%;height:100%;background-color:var(--art-theme)}.art-control-volume .art-volume-panel .art-volume-inner .art-volume-slider .art-volume-indicator{position:absolute;width:var(--art-volume-handle-size);height:var(--art-volume-handle-size);margin-top:calc(var(--art-volume-handle-size) / -2);flex-shrink:0;transform:scale(1);border-radius:100%;background-color:var(--art-theme);transition:transform var(--art-transition-duration) ease}.art-control-volume .art-volume-panel .art-volume-inner .art-volume-slider:active .art-volume-indicator{transform:scale(.9)}.art-control-volume:hover .art-volume-panel{opacity:1;transform:translateY(0);pointer-events:auto}.art-video-player .art-notice{display:none;position:absolute;inset:0 0 auto;z-index:80;width:100%;height:100%;height:auto;padding:var(--art-padding);pointer-events:none}.art-video-player .art-notice .art-notice-inner{display:inline-flex;padding:5px;line-height:1;border-radius:var(--art-border-radius);background-color:var(--art-tip-background)}.art-video-player.art-notice-show .art-notice{display:flex}.art-video-player .art-contextmenus{display:none;flex-direction:column;position:absolute;z-index:120;padding:5px 0;border-radius:var(--art-border-radius);font-size:12px;background-color:var(--art-widget-background);min-width:var(--art-contextmenus-min-width)}.art-video-player .art-contextmenus .art-contextmenu{cursor:pointer;display:flex;padding:10px 15px;border-bottom:1px solid rgba(255,255,255,.1)}.art-video-player .art-contextmenus .art-contextmenu span{padding:0 8px}.art-video-player .art-contextmenus .art-contextmenu span:hover,.art-video-player .art-contextmenus .art-contextmenu span.art-current{color:var(--art-theme)}.art-video-player .art-contextmenus .art-contextmenu:hover{background-color:#ffffff1a}.art-video-player .art-contextmenus .art-contextmenu:last-child{border-bottom:none}.art-video-player.art-contextmenu-show .art-contextmenus{display:flex}.art-video-player .art-settings{display:none;flex-direction:column;position:absolute;z-index:90;left:auto;overflow-y:auto;overflow-x:hidden;border-radius:var(--art-border-radius);max-height:var(--art-settings-max-height);right:var(--art-padding);bottom:var(--art-control-height);transition:all var(--art-transition-duration) ease;background-color:var(--art-widget-background)}.art-video-player .art-settings .art-setting-panel{display:none;flex-direction:column}.art-video-player .art-settings .art-setting-panel.art-current{display:flex}.art-video-player .art-settings .art-setting-panel .art-setting-item{display:flex;justify-content:space-between;align-items:center;padding:0 5px;cursor:pointer;overflow:hidden;transition:background-color var(--art-transition-duration) ease}.art-video-player .art-settings .art-setting-panel .art-setting-item:hover{background-color:#ffffff1a}.art-video-player .art-settings .art-setting-panel .art-setting-item.art-current{color:var(--art-theme)}.art-video-player .art-settings .art-setting-panel .art-setting-item .art-icon-check{visibility:hidden;height:15px}.art-video-player .art-settings .art-setting-panel .art-setting-item.art-current .art-icon-check{visibility:visible}.art-video-player .art-settings .art-setting-panel .art-setting-item .art-setting-item-left{display:flex;justify-content:center;align-items:center;flex-shrink:0;gap:5px}.art-video-player .art-settings .art-setting-panel .art-setting-item .art-setting-item-left .art-setting-item-left-icon{display:flex;justify-content:center;align-items:center;height:var(--art-settings-icon-size);width:var(--art-settings-icon-size)}.art-video-player .art-settings .art-setting-panel .art-setting-item .art-setting-item-right{display:flex;justify-content:center;align-items:center;gap:5px;font-size:12px}.art-video-player .art-settings .art-setting-panel .art-setting-item .art-setting-item-right .art-setting-item-right-tooltip{white-space:nowrap;color:#ffffff80}.art-video-player .art-settings .art-setting-panel .art-setting-item .art-setting-item-right .art-setting-item-right-icon{display:flex;justify-content:center;align-items:center;min-width:32px;height:24px}.art-video-player .art-settings .art-setting-panel .art-setting-item .art-setting-item-right .art-setting-range{height:3px;width:80px;outline:none;appearance:none;background-color:#fff3}.art-video-player .art-settings .art-setting-panel .art-setting-item-back{border-bottom:1px solid rgba(255,255,255,.1)}.art-video-player.art-setting-show .art-settings{display:flex}.art-video-player .art-info{display:none;position:absolute;left:var(--art-padding);top:var(--art-padding);z-index:100;padding:10px;font-size:12px;border-radius:var(--art-border-radius);background-color:var(--art-widget-background)}.art-video-player .art-info .art-info-panel{display:flex;flex-direction:column;gap:5px}.art-video-player .art-info .art-info-panel .art-info-item{display:flex;align-items:center;gap:5px}.art-video-player .art-info .art-info-panel .art-info-item .art-info-title{width:100px;text-align:right}.art-video-player .art-info .art-info-panel .art-info-item .art-info-content{width:250px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;user-select:all}.art-video-player .art-info .art-info-close{position:absolute;top:5px;right:5px;cursor:pointer}.art-video-player.art-info-show .art-info{display:flex}.art-hide-cursor *{cursor:none!important}.art-video-player[data-aspect-ratio]{overflow:hidden}.art-video-player[data-aspect-ratio] .art-video{object-fit:fill;box-sizing:content-box}.art-fullscreen{--art-progress-height: 8px;--art-indicator-size: 20px;--art-control-height: 60px;--art-control-icon-scale: 1.3}.art-fullscreen-web{--art-progress-height: 8px;--art-indicator-size: 20px;--art-control-height: 60px;--art-control-icon-scale: 1.3;position:fixed;inset:0;z-index:var(--art-fullscreen-web-index);width:100%;height:100%}.art-mini-popup{position:fixed;z-index:9999;width:320px;height:180px;background:#000;border-radius:var(--art-border-radius);cursor:move;user-select:none;overflow:hidden;transition:opacity .2s ease;box-shadow:0 0 5px #00000080}.art-mini-popup svg{fill:#fff}.art-mini-popup .art-video{pointer-events:none}.art-mini-popup .art-mini-close{position:absolute;z-index:20;right:10px;top:10px;cursor:pointer;opacity:0;transition:opacity .2s ease}.art-mini-popup .art-mini-state{position:absolute;inset:0;z-index:30;width:100%;height:100%;display:flex;justify-content:center;align-items:center;pointer-events:none;opacity:0;transition:opacity .2s ease;background-color:#00000040}.art-mini-popup .art-mini-state .art-icon{opacity:.75;cursor:pointer;transform:scale(3);pointer-events:auto;transition:transform .2s ease}.art-mini-popup .art-mini-state .art-icon:active{transform:scale(2.5)}.art-mini-popup.art-mini-dragging{opacity:.9}.art-mini-popup:hover .art-mini-close,.art-mini-popup:hover .art-mini-state{opacity:1}.art-video-player[data-flip=horizontal] .art-video{transform:scaleX(-1)}.art-video-player[data-flip=vertical] .art-video{transform:scaleY(-1)}.art-video-player .art-layer-lock{display:none;justify-content:center;align-items:center;position:absolute;top:50%;border-radius:50%;transform:translateY(-50%);height:var(--art-lock-size);width:var(--art-lock-size);left:var(--art-padding);background-color:var(--art-tip-background)}.art-video-player .art-layer-auto-playback{display:none;gap:10px;align-items:center;position:absolute;border-radius:var(--art-border-radius);padding:10px;line-height:1;left:var(--art-padding);bottom:calc(var(--art-control-height) + var(--art-bottom-gap) + 10px);background-color:var(--art-widget-background)}.art-video-player .art-layer-auto-playback .art-auto-playback-close{display:flex;justify-content:center;align-items:center;cursor:pointer}.art-video-player .art-layer-auto-playback .art-auto-playback-close svg{width:15px;height:15px;fill:var(--art-theme)}.art-video-player .art-layer-auto-playback .art-auto-playback-jump{color:var(--art-theme);cursor:pointer}.art-video-player.art-lock .art-subtitle{bottom:var(--art-subtitle-bottom)!important}.art-video-player.art-mini-progress-bar .art-bottom,.art-video-player.art-lock .art-bottom{opacity:1;padding:0;background-image:none}.art-video-player.art-mini-progress-bar .art-bottom .art-controls,.art-video-player.art-lock .art-bottom .art-controls,.art-video-player.art-mini-progress-bar .art-bottom .art-progress,.art-video-player.art-lock .art-bottom .art-progress{transform:translateY(calc(var(--art-control-height) + var(--art-bottom-gap) + var(--art-progress-height) / 4))}.art-video-player.art-mini-progress-bar .art-bottom .art-progress-indicator,.art-video-player.art-lock .art-bottom .art-progress-indicator{display:none!important}.art-video-player.art-control-show .art-layer-lock{display:flex}.art-control-selector{position:relative;display:flex;justify-content:center}.art-control-selector .art-selector-list{display:flex;flex-direction:column;align-items:center;text-align:center;position:absolute;border-radius:var(--art-border-radius);overflow-y:auto;overflow-x:hidden;opacity:0;transform:translateY(10px);pointer-events:none;bottom:var(--art-control-height);max-height:var(--art-selector-max-height);background-color:var(--art-widget-background);transition:all var(--art-transition-duration) ease}.art-control-selector .art-selector-list .art-selector-item{display:flex;justify-content:center;align-items:center;width:100%;padding:10px 15px;flex-shrink:0;line-height:1}.art-control-selector .art-selector-list .art-selector-item:hover{background-color:#ffffff1a}.art-control-selector .art-selector-list .art-selector-item:hover,.art-control-selector .art-selector-list .art-selector-item.art-current{color:var(--art-theme)}.art-control-selector:hover .art-selector-list{opacity:1;transform:translateY(0);pointer-events:auto}.art-video-player [class*=hint--]{position:relative;display:inline-block;font-style:normal}.art-video-player [class*=hint--]:before,.art-video-player [class*=hint--]:after{position:absolute;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);transform:translateZ(0);visibility:hidden;opacity:0;z-index:1000000;pointer-events:none;-webkit-transition:.3s ease;-moz-transition:.3s ease;transition:.3s ease;-webkit-transition-delay:0ms;-moz-transition-delay:0ms;transition-delay:0ms}.art-video-player [class*=hint--]:hover:before,.art-video-player [class*=hint--]:hover:after{visibility:visible;opacity:1}.art-video-player [class*=hint--]:hover:before,.art-video-player [class*=hint--]:hover:after{-webkit-transition-delay:.1s;-moz-transition-delay:.1s;transition-delay:.1s}.art-video-player [class*=hint--]:before{content:"";position:absolute;background:transparent;border:6px solid transparent;z-index:1000001}.art-video-player [class*=hint--]:after{background:#000;color:#fff;padding:8px 10px;font-size:12px;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;line-height:12px;white-space:nowrap}.art-video-player [class*=hint--][aria-label]:after{content:attr(aria-label)}.art-video-player [class*=hint--][data-hint]:after{content:attr(data-hint)}.art-video-player [aria-label=""]:before,.art-video-player [aria-label=""]:after,.art-video-player [data-hint=""]:before,.art-video-player [data-hint=""]:after{display:none!important}.art-video-player .hint--top-left:before{border-top-color:#000}.art-video-player .hint--top-right:before{border-top-color:#000}.art-video-player .hint--top:before{border-top-color:#000}.art-video-player .hint--bottom-left:before{border-bottom-color:#000}.art-video-player .hint--bottom-right:before{border-bottom-color:#000}.art-video-player .hint--bottom:before{border-bottom-color:#000}.art-video-player .hint--left:before{border-left-color:#000}.art-video-player .hint--right:before{border-right-color:#000}.art-video-player .hint--top:before{margin-bottom:-11px}.art-video-player .hint--top:before,.art-video-player .hint--top:after{bottom:100%;left:50%}.art-video-player .hint--top:before{left:calc(50% - 6px)}.art-video-player .hint--top:after{-webkit-transform:translateX(-50%);-moz-transform:translateX(-50%);transform:translate(-50%)}.art-video-player .hint--top:hover:before{-webkit-transform:translateY(-8px);-moz-transform:translateY(-8px);transform:translateY(-8px)}.art-video-player .hint--top:hover:after{-webkit-transform:translateX(-50%) translateY(-8px);-moz-transform:translateX(-50%) translateY(-8px);transform:translate(-50%) translateY(-8px)}.art-video-player .hint--bottom:before{margin-top:-11px}.art-video-player .hint--bottom:before,.art-video-player .hint--bottom:after{top:100%;left:50%}.art-video-player .hint--bottom:before{left:calc(50% - 6px)}.art-video-player .hint--bottom:after{-webkit-transform:translateX(-50%);-moz-transform:translateX(-50%);transform:translate(-50%)}.art-video-player .hint--bottom:hover:before{-webkit-transform:translateY(8px);-moz-transform:translateY(8px);transform:translateY(8px)}.art-video-player .hint--bottom:hover:after{-webkit-transform:translateX(-50%) translateY(8px);-moz-transform:translateX(-50%) translateY(8px);transform:translate(-50%) translateY(8px)}.art-video-player .hint--right:before{margin-left:-11px;margin-bottom:-6px}.art-video-player .hint--right:after{margin-bottom:-14px}.art-video-player .hint--right:before,.art-video-player .hint--right:after{left:100%;bottom:50%}.art-video-player .hint--right:hover:before{-webkit-transform:translateX(8px);-moz-transform:translateX(8px);transform:translate(8px)}.art-video-player .hint--right:hover:after{-webkit-transform:translateX(8px);-moz-transform:translateX(8px);transform:translate(8px)}.art-video-player .hint--left:before{margin-right:-11px;margin-bottom:-6px}.art-video-player .hint--left:after{margin-bottom:-14px}.art-video-player .hint--left:before,.art-video-player .hint--left:after{right:100%;bottom:50%}.art-video-player .hint--left:hover:before{-webkit-transform:translateX(-8px);-moz-transform:translateX(-8px);transform:translate(-8px)}.art-video-player .hint--left:hover:after{-webkit-transform:translateX(-8px);-moz-transform:translateX(-8px);transform:translate(-8px)}.art-video-player .hint--top-left:before{margin-bottom:-11px}.art-video-player .hint--top-left:before,.art-video-player .hint--top-left:after{bottom:100%;left:50%}.art-video-player .hint--top-left:before{left:calc(50% - 6px)}.art-video-player .hint--top-left:after{-webkit-transform:translateX(-100%);-moz-transform:translateX(-100%);transform:translate(-100%)}.art-video-player .hint--top-left:after{margin-left:12px}.art-video-player .hint--top-left:hover:before{-webkit-transform:translateY(-8px);-moz-transform:translateY(-8px);transform:translateY(-8px)}.art-video-player .hint--top-left:hover:after{-webkit-transform:translateX(-100%) translateY(-8px);-moz-transform:translateX(-100%) translateY(-8px);transform:translate(-100%) translateY(-8px)}.art-video-player .hint--top-right:before{margin-bottom:-11px}.art-video-player .hint--top-right:before,.art-video-player .hint--top-right:after{bottom:100%;left:50%}.art-video-player .hint--top-right:before{left:calc(50% - 6px)}.art-video-player .hint--top-right:after{-webkit-transform:translateX(0);-moz-transform:translateX(0);transform:translate(0)}.art-video-player .hint--top-right:after{margin-left:-12px}.art-video-player .hint--top-right:hover:before{-webkit-transform:translateY(-8px);-moz-transform:translateY(-8px);transform:translateY(-8px)}.art-video-player .hint--top-right:hover:after{-webkit-transform:translateY(-8px);-moz-transform:translateY(-8px);transform:translateY(-8px)}.art-video-player .hint--bottom-left:before{margin-top:-11px}.art-video-player .hint--bottom-left:before,.art-video-player .hint--bottom-left:after{top:100%;left:50%}.art-video-player .hint--bottom-left:before{left:calc(50% - 6px)}.art-video-player .hint--bottom-left:after{-webkit-transform:translateX(-100%);-moz-transform:translateX(-100%);transform:translate(-100%)}.art-video-player .hint--bottom-left:after{margin-left:12px}.art-video-player .hint--bottom-left:hover:before{-webkit-transform:translateY(8px);-moz-transform:translateY(8px);transform:translateY(8px)}.art-video-player .hint--bottom-left:hover:after{-webkit-transform:translateX(-100%) translateY(8px);-moz-transform:translateX(-100%) translateY(8px);transform:translate(-100%) translateY(8px)}.art-video-player .hint--bottom-right:before{margin-top:-11px}.art-video-player .hint--bottom-right:before,.art-video-player .hint--bottom-right:after{top:100%;left:50%}.art-video-player .hint--bottom-right:before{left:calc(50% - 6px)}.art-video-player .hint--bottom-right:after{-webkit-transform:translateX(0);-moz-transform:translateX(0);transform:translate(0)}.art-video-player .hint--bottom-right:after{margin-left:-12px}.art-video-player .hint--bottom-right:hover:before{-webkit-transform:translateY(8px);-moz-transform:translateY(8px);transform:translateY(8px)}.art-video-player .hint--bottom-right:hover:after{-webkit-transform:translateY(8px);-moz-transform:translateY(8px);transform:translateY(8px)}.art-video-player .hint--small:after,.art-video-player .hint--medium:after,.art-video-player .hint--large:after{white-space:normal;line-height:1.4em;word-wrap:break-word}.art-video-player .hint--small:after{width:80px}.art-video-player .hint--medium:after{width:150px}.art-video-player .hint--large:after{width:300px}.art-video-player [class*=hint--]:after{text-shadow:0 -1px 0px black;box-shadow:4px 4px 8px #0000004d}.art-video-player .hint--error:after{background-color:#b34e4d;text-shadow:0 -1px 0px #592726}.art-video-player .hint--error.hint--top-left:before{border-top-color:#b34e4d}.art-video-player .hint--error.hint--top-right:before{border-top-color:#b34e4d}.art-video-player .hint--error.hint--top:before{border-top-color:#b34e4d}.art-video-player .hint--error.hint--bottom-left:before{border-bottom-color:#b34e4d}.art-video-player .hint--error.hint--bottom-right:before{border-bottom-color:#b34e4d}.art-video-player .hint--error.hint--bottom:before{border-bottom-color:#b34e4d}.art-video-player .hint--error.hint--left:before{border-left-color:#b34e4d}.art-video-player .hint--error.hint--right:before{border-right-color:#b34e4d}.art-video-player .hint--warning:after{background-color:#c09854;text-shadow:0 -1px 0px #6c5328}.art-video-player .hint--warning.hint--top-left:before{border-top-color:#c09854}.art-video-player .hint--warning.hint--top-right:before{border-top-color:#c09854}.art-video-player .hint--warning.hint--top:before{border-top-color:#c09854}.art-video-player .hint--warning.hint--bottom-left:before{border-bottom-color:#c09854}.art-video-player .hint--warning.hint--bottom-right:before{border-bottom-color:#c09854}.art-video-player .hint--warning.hint--bottom:before{border-bottom-color:#c09854}.art-video-player .hint--warning.hint--left:before{border-left-color:#c09854}.art-video-player .hint--warning.hint--right:before{border-right-color:#c09854}.art-video-player .hint--info:after{background-color:#3986ac;text-shadow:0 -1px 0px #1a3c4d}.art-video-player .hint--info.hint--top-left:before{border-top-color:#3986ac}.art-video-player .hint--info.hint--top-right:before{border-top-color:#3986ac}.art-video-player .hint--info.hint--top:before{border-top-color:#3986ac}.art-video-player .hint--info.hint--bottom-left:before{border-bottom-color:#3986ac}.art-video-player .hint--info.hint--bottom-right:before{border-bottom-color:#3986ac}.art-video-player .hint--info.hint--bottom:before{border-bottom-color:#3986ac}.art-video-player .hint--info.hint--left:before{border-left-color:#3986ac}.art-video-player .hint--info.hint--right:before{border-right-color:#3986ac}.art-video-player .hint--success:after{background-color:#458746;text-shadow:0 -1px 0px #1a321a}.art-video-player .hint--success.hint--top-left:before{border-top-color:#458746}.art-video-player .hint--success.hint--top-right:before{border-top-color:#458746}.art-video-player .hint--success.hint--top:before{border-top-color:#458746}.art-video-player .hint--success.hint--bottom-left:before{border-bottom-color:#458746}.art-video-player .hint--success.hint--bottom-right:before{border-bottom-color:#458746}.art-video-player .hint--success.hint--bottom:before{border-bottom-color:#458746}.art-video-player .hint--success.hint--left:before{border-left-color:#458746}.art-video-player .hint--success.hint--right:before{border-right-color:#458746}.art-video-player .hint--always:after,.art-video-player .hint--always:before{opacity:1;visibility:visible}.art-video-player .hint--always.hint--top:before{-webkit-transform:translateY(-8px);-moz-transform:translateY(-8px);transform:translateY(-8px)}.art-video-player .hint--always.hint--top:after{-webkit-transform:translateX(-50%) translateY(-8px);-moz-transform:translateX(-50%) translateY(-8px);transform:translate(-50%) translateY(-8px)}.art-video-player .hint--always.hint--top-left:before{-webkit-transform:translateY(-8px);-moz-transform:translateY(-8px);transform:translateY(-8px)}.art-video-player .hint--always.hint--top-left:after{-webkit-transform:translateX(-100%) translateY(-8px);-moz-transform:translateX(-100%) translateY(-8px);transform:translate(-100%) translateY(-8px)}.art-video-player .hint--always.hint--top-right:before{-webkit-transform:translateY(-8px);-moz-transform:translateY(-8px);transform:translateY(-8px)}.art-video-player .hint--always.hint--top-right:after{-webkit-transform:translateY(-8px);-moz-transform:translateY(-8px);transform:translateY(-8px)}.art-video-player .hint--always.hint--bottom:before{-webkit-transform:translateY(8px);-moz-transform:translateY(8px);transform:translateY(8px)}.art-video-player .hint--always.hint--bottom:after{-webkit-transform:translateX(-50%) translateY(8px);-moz-transform:translateX(-50%) translateY(8px);transform:translate(-50%) translateY(8px)}.art-video-player .hint--always.hint--bottom-left:before{-webkit-transform:translateY(8px);-moz-transform:translateY(8px);transform:translateY(8px)}.art-video-player .hint--always.hint--bottom-left:after{-webkit-transform:translateX(-100%) translateY(8px);-moz-transform:translateX(-100%) translateY(8px);transform:translate(-100%) translateY(8px)}.art-video-player .hint--always.hint--bottom-right:before{-webkit-transform:translateY(8px);-moz-transform:translateY(8px);transform:translateY(8px)}.art-video-player .hint--always.hint--bottom-right:after{-webkit-transform:translateY(8px);-moz-transform:translateY(8px);transform:translateY(8px)}.art-video-player .hint--always.hint--left:before{-webkit-transform:translateX(-8px);-moz-transform:translateX(-8px);transform:translate(-8px)}.art-video-player .hint--always.hint--left:after{-webkit-transform:translateX(-8px);-moz-transform:translateX(-8px);transform:translate(-8px)}.art-video-player .hint--always.hint--right:before{-webkit-transform:translateX(8px);-moz-transform:translateX(8px);transform:translate(8px)}.art-video-player .hint--always.hint--right:after{-webkit-transform:translateX(8px);-moz-transform:translateX(8px);transform:translate(8px)}.art-video-player .hint--rounded:after{border-radius:4px}.art-video-player .hint--no-animate:before,.art-video-player .hint--no-animate:after{-webkit-transition-duration:0ms;-moz-transition-duration:0ms;transition-duration:0ms}.art-video-player .hint--bounce:before,.art-video-player .hint--bounce:after{-webkit-transition:opacity .3s ease,visibility .3s ease,-webkit-transform .3s cubic-bezier(.71,1.7,.77,1.24);-moz-transition:opacity .3s ease,visibility .3s ease,-moz-transform .3s cubic-bezier(.71,1.7,.77,1.24);transition:opacity .3s ease,visibility .3s ease,transform .3s cubic-bezier(.71,1.7,.77,1.24)}.art-video-player .hint--no-shadow:before,.art-video-player .hint--no-shadow:after{text-shadow:initial;box-shadow:initial}.art-video-player .hint--no-arrow:before{display:none}.art-video-player.art-mobile{--art-bottom-gap: 10px;--art-control-height: 38px;--art-control-icon-scale: 1;--art-state-size: 60px;--art-settings-max-height: 180px;--art-selector-max-height: 180px;--art-indicator-scale: 1;--art-control-opacity: 1}.art-video-player.art-mobile .art-controls-left{margin-left:calc(var(--art-padding) / -1)}.art-video-player.art-mobile .art-controls-right{margin-right:calc(var(--art-padding) / -1)}';
|
|
class Subtitle extends Component {
|
|
constructor(art) {
|
|
super(art);
|
|
this.name = "subtitle";
|
|
this.option = null;
|
|
this.destroyEvent = () => null;
|
|
this.init(art.option.subtitle);
|
|
let lastState = false;
|
|
art.on("video:timeupdate", () => {
|
|
if (!this.url)
|
|
return;
|
|
const state2 = this.art.template.$video.webkitDisplayingFullscreen;
|
|
if (typeof state2 !== "boolean")
|
|
return;
|
|
if (state2 !== lastState) {
|
|
lastState = state2;
|
|
this.createTrack(state2 ? "subtitles" : "metadata", this.url);
|
|
}
|
|
});
|
|
}
|
|
get url() {
|
|
return this.art.template.$track.src;
|
|
}
|
|
set url(url) {
|
|
this.switch(url);
|
|
}
|
|
get textTrack() {
|
|
return this.art.template.$video?.textTracks?.[0];
|
|
}
|
|
get activeCues() {
|
|
if (!this.textTrack)
|
|
return [];
|
|
return Array.from(this.textTrack.activeCues);
|
|
}
|
|
get cues() {
|
|
if (!this.textTrack)
|
|
return [];
|
|
return Array.from(this.textTrack.cues);
|
|
}
|
|
style(key, value) {
|
|
const { $subtitle } = this.art.template;
|
|
if (typeof key === "object") {
|
|
return setStyles($subtitle, key);
|
|
}
|
|
return setStyle($subtitle, key, value);
|
|
}
|
|
update() {
|
|
const {
|
|
option: { subtitle },
|
|
template: { $subtitle }
|
|
} = this.art;
|
|
$subtitle.innerHTML = "";
|
|
if (!this.activeCues.length)
|
|
return;
|
|
this.art.emit("subtitleBeforeUpdate", this.activeCues);
|
|
$subtitle.innerHTML = this.activeCues.map(
|
|
(cue, index) => cue.text.split(/\r?\n/).filter((line) => line.trim()).map(
|
|
(line) => `<div class="art-subtitle-line" data-group="${index}">
|
|
${subtitle.escape ? escape(line) : line}
|
|
</div>`
|
|
).join("")
|
|
).join("");
|
|
this.art.emit("subtitleAfterUpdate", this.activeCues);
|
|
}
|
|
async switch(url, newOption = {}) {
|
|
const { i18n, notice, option } = this.art;
|
|
const subtitleOption = { ...option.subtitle, ...newOption, url };
|
|
const subUrl = await this.init(subtitleOption);
|
|
if (newOption.name) {
|
|
notice.show = `${i18n.get("Switch Subtitle")}: ${newOption.name}`;
|
|
}
|
|
return subUrl;
|
|
}
|
|
createTrack(kind, url) {
|
|
const { template, proxy, option } = this.art;
|
|
const { $video, $track } = template;
|
|
const $newTrack = createElement("track");
|
|
$newTrack.default = true;
|
|
$newTrack.kind = kind;
|
|
$newTrack.src = url;
|
|
$newTrack.label = option.subtitle.name || "Artplayer";
|
|
$newTrack.track.mode = "hidden";
|
|
$newTrack.onload = () => {
|
|
this.art.emit("subtitleLoad", this.cues, this.option);
|
|
};
|
|
this.art.events.remove(this.destroyEvent);
|
|
$track.onload = null;
|
|
remove($track);
|
|
append($video, $newTrack);
|
|
template.$track = $newTrack;
|
|
this.destroyEvent = proxy(this.textTrack, "cuechange", () => this.update());
|
|
}
|
|
async init(subtitleOption) {
|
|
const {
|
|
notice,
|
|
template: { $subtitle }
|
|
} = this.art;
|
|
if (!this.textTrack)
|
|
return null;
|
|
validator(subtitleOption, scheme.subtitle);
|
|
if (!subtitleOption.url)
|
|
return;
|
|
this.option = subtitleOption;
|
|
this.style(subtitleOption.style);
|
|
return fetch(subtitleOption.url).then((response) => response.arrayBuffer()).then((buffer) => {
|
|
const decoder = new TextDecoder(subtitleOption.encoding);
|
|
const text = decoder.decode(buffer);
|
|
switch (subtitleOption.type || getExt(subtitleOption.url)) {
|
|
case "srt": {
|
|
const vtt = srtToVtt(text);
|
|
const vttNew = subtitleOption.onVttLoad(vtt);
|
|
return vttToBlob(vttNew);
|
|
}
|
|
case "ass": {
|
|
const vtt = assToVtt(text);
|
|
const vttNew = subtitleOption.onVttLoad(vtt);
|
|
return vttToBlob(vttNew);
|
|
}
|
|
case "vtt": {
|
|
const vttNew = subtitleOption.onVttLoad(text);
|
|
return vttToBlob(vttNew);
|
|
}
|
|
default:
|
|
return subtitleOption.url;
|
|
}
|
|
}).then((subUrl) => {
|
|
$subtitle.innerHTML = "";
|
|
if (this.url === subUrl)
|
|
return subUrl;
|
|
URL.revokeObjectURL(this.url);
|
|
this.createTrack("metadata", subUrl);
|
|
return subUrl;
|
|
}).catch((err) => {
|
|
$subtitle.innerHTML = "";
|
|
notice.show = err;
|
|
throw err;
|
|
});
|
|
}
|
|
}
|
|
class Template {
|
|
constructor(art) {
|
|
this.art = art;
|
|
const { option, constructor } = art;
|
|
if (option.container instanceof Element) {
|
|
this.$container = option.container;
|
|
} else {
|
|
this.$container = query(option.container);
|
|
errorHandle(this.$container, `No container element found by ${option.container}`);
|
|
}
|
|
errorHandle(supportsFlex(), "The current browser does not support flex layout");
|
|
const type = this.$container.tagName.toLowerCase();
|
|
errorHandle(type === "div", `Unsupported container element type, only support 'div' but got '${type}'`);
|
|
errorHandle(
|
|
constructor.instances.every((ins) => ins.template.$container !== this.$container),
|
|
"Cannot mount multiple instances on the same dom element"
|
|
);
|
|
this.query = this.query.bind(this);
|
|
this.$container.dataset.artId = art.id;
|
|
this.init();
|
|
}
|
|
static get html() {
|
|
return `
|
|
<div class="art-video-player art-subtitle-show art-layer-show art-control-show art-mask-show">
|
|
<video class="art-video">
|
|
<track default kind="metadata" src=""></track>
|
|
</video>
|
|
<div class="art-poster"></div>
|
|
<div class="art-subtitle"></div>
|
|
<div class="art-danmuku"></div>
|
|
<div class="art-layers"></div>
|
|
<div class="art-mask">
|
|
<div class="art-state"></div>
|
|
</div>
|
|
<div class="art-bottom">
|
|
<div class="art-progress"></div>
|
|
<div class="art-controls">
|
|
<div class="art-controls-left"></div>
|
|
<div class="art-controls-center"></div>
|
|
<div class="art-controls-right"></div>
|
|
</div>
|
|
</div>
|
|
<div class="art-loading"></div>
|
|
<div class="art-notice">
|
|
<div class="art-notice-inner"></div>
|
|
</div>
|
|
<div class="art-settings"></div>
|
|
<div class="art-info">
|
|
<div class="art-info-panel">
|
|
<div class="art-info-item">
|
|
<div class="art-info-title">Player version:</div>
|
|
<div class="art-info-content">${version$1}</div>
|
|
</div>
|
|
<div class="art-info-item">
|
|
<div class="art-info-title">Video url:</div>
|
|
<div class="art-info-content" data-video="currentSrc"></div>
|
|
</div>
|
|
<div class="art-info-item">
|
|
<div class="art-info-title">Video volume:</div>
|
|
<div class="art-info-content" data-video="volume"></div>
|
|
</div>
|
|
<div class="art-info-item">
|
|
<div class="art-info-title">Video time:</div>
|
|
<div class="art-info-content" data-video="currentTime"></div>
|
|
</div>
|
|
<div class="art-info-item">
|
|
<div class="art-info-title">Video duration:</div>
|
|
<div class="art-info-content" data-video="duration"></div>
|
|
</div>
|
|
<div class="art-info-item">
|
|
<div class="art-info-title">Video resolution:</div>
|
|
<div class="art-info-content">
|
|
<span data-video="videoWidth"></span> x <span data-video="videoHeight"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="art-info-close">[x]</div>
|
|
</div>
|
|
<div class="art-contextmenus"></div>
|
|
</div>
|
|
`;
|
|
}
|
|
query(className) {
|
|
return query(className, this.$container);
|
|
}
|
|
init() {
|
|
const { option } = this.art;
|
|
if (!option.useSSR) {
|
|
this.$container.innerHTML = Template.html;
|
|
}
|
|
this.$player = this.query(".art-video-player");
|
|
this.$video = this.query(".art-video");
|
|
this.$track = this.query("track");
|
|
this.$poster = this.query(".art-poster");
|
|
this.$subtitle = this.query(".art-subtitle");
|
|
this.$danmuku = this.query(".art-danmuku");
|
|
this.$bottom = this.query(".art-bottom");
|
|
this.$progress = this.query(".art-progress");
|
|
this.$controls = this.query(".art-controls");
|
|
this.$controlsLeft = this.query(".art-controls-left");
|
|
this.$controlsCenter = this.query(".art-controls-center");
|
|
this.$controlsRight = this.query(".art-controls-right");
|
|
this.$layer = this.query(".art-layers");
|
|
this.$loading = this.query(".art-loading");
|
|
this.$notice = this.query(".art-notice");
|
|
this.$noticeInner = this.query(".art-notice-inner");
|
|
this.$mask = this.query(".art-mask");
|
|
this.$state = this.query(".art-state");
|
|
this.$setting = this.query(".art-settings");
|
|
this.$info = this.query(".art-info");
|
|
this.$infoPanel = this.query(".art-info-panel");
|
|
this.$infoClose = this.query(".art-info-close");
|
|
this.$contextmenu = this.query(".art-contextmenus");
|
|
if (option.proxy) {
|
|
const video = option.proxy.call(this.art, this.art);
|
|
errorHandle(
|
|
video instanceof HTMLVideoElement || video instanceof HTMLCanvasElement,
|
|
`Function 'option.proxy' needs to return 'HTMLVideoElement' or 'HTMLCanvasElement'`
|
|
);
|
|
replaceElement(video, this.$video);
|
|
video.className = "art-video";
|
|
this.$video = video;
|
|
}
|
|
if (option.backdrop) {
|
|
addClass(this.$player, "art-backdrop");
|
|
}
|
|
if (isMobile) {
|
|
addClass(this.$player, "art-mobile");
|
|
}
|
|
}
|
|
destroy(removeHtml) {
|
|
if (removeHtml) {
|
|
this.$container.innerHTML = "";
|
|
} else {
|
|
addClass(this.$player, "art-destroy");
|
|
}
|
|
}
|
|
}
|
|
class Emitter {
|
|
on(name, fn, ctx) {
|
|
const e = this.e || (this.e = {});
|
|
(e[name] || (e[name] = [])).push({ fn, ctx });
|
|
return this;
|
|
}
|
|
once(name, fn, ctx) {
|
|
const self = this;
|
|
function listener(...args) {
|
|
self.off(name, listener);
|
|
fn.apply(ctx, args);
|
|
}
|
|
listener._ = fn;
|
|
return this.on(name, listener, ctx);
|
|
}
|
|
emit(name, ...data) {
|
|
const evtArr = ((this.e || (this.e = {}))[name] || []).slice();
|
|
for (let i = 0; i < evtArr.length; i += 1) {
|
|
evtArr[i].fn.apply(evtArr[i].ctx, data);
|
|
}
|
|
return this;
|
|
}
|
|
off(name, callback) {
|
|
const e = this.e || (this.e = {});
|
|
const evts = e[name];
|
|
const liveEvents = [];
|
|
if (evts && callback) {
|
|
for (let i = 0, len = evts.length; i < len; i += 1) {
|
|
if (evts[i].fn !== callback && evts[i].fn._ !== callback)
|
|
liveEvents.push(evts[i]);
|
|
}
|
|
}
|
|
if (liveEvents.length) {
|
|
e[name] = liveEvents;
|
|
} else {
|
|
delete e[name];
|
|
}
|
|
return this;
|
|
}
|
|
}
|
|
let id = 0;
|
|
const instances = [];
|
|
class Artplayer extends Emitter {
|
|
constructor(option, readyCallback) {
|
|
super();
|
|
if (!isBrowser) {
|
|
throw new Error("Artplayer can only be used in the browser environment");
|
|
}
|
|
this.id = ++id;
|
|
const mergeOption = mergeDeep(Artplayer.option, option);
|
|
mergeOption.container = option.container;
|
|
this.option = validator(mergeOption, scheme);
|
|
this.isLock = false;
|
|
this.isReady = false;
|
|
this.isFocus = false;
|
|
this.isInput = false;
|
|
this.isRotate = false;
|
|
this.isDestroy = false;
|
|
this.template = new Template(this);
|
|
this.events = new Events(this);
|
|
this.storage = new Storage(this);
|
|
this.icons = new Icons(this);
|
|
this.i18n = new I18n(this);
|
|
this.notice = new Notice(this);
|
|
this.player = new Player(this);
|
|
this.layers = new Layer(this);
|
|
this.controls = new Control(this);
|
|
this.contextmenu = new Contextmenu(this);
|
|
this.subtitle = new Subtitle(this);
|
|
this.info = new Info(this);
|
|
this.loading = new Loading(this);
|
|
this.hotkey = new Hotkey(this);
|
|
this.mask = new Mask(this);
|
|
this.setting = new Setting(this);
|
|
this.plugins = new Plugins(this);
|
|
if (typeof readyCallback === "function") {
|
|
this.on("ready", () => readyCallback.call(this, this));
|
|
}
|
|
if (Artplayer.DEBUG) {
|
|
const log = (msg) => console.log(`[ART.${this.id}] -> ${msg}`);
|
|
log(`Version@${Artplayer.version}`);
|
|
for (let index = 0; index < config$1.events.length; index++) {
|
|
this.on(`video:${config$1.events[index]}`, (event) => log(`Event@${event.type}`));
|
|
}
|
|
}
|
|
instances.push(this);
|
|
}
|
|
static get instances() {
|
|
return instances;
|
|
}
|
|
static get version() {
|
|
return version$1;
|
|
}
|
|
static get config() {
|
|
return config$1;
|
|
}
|
|
static get utils() {
|
|
return utils;
|
|
}
|
|
static get scheme() {
|
|
return scheme;
|
|
}
|
|
static get Emitter() {
|
|
return Emitter;
|
|
}
|
|
static get validator() {
|
|
return validator;
|
|
}
|
|
static get kindOf() {
|
|
return validator.kindOf;
|
|
}
|
|
static get html() {
|
|
return Template.html;
|
|
}
|
|
static get option() {
|
|
return {
|
|
id: "",
|
|
container: "#artplayer",
|
|
url: "",
|
|
poster: "",
|
|
type: "",
|
|
theme: "#f00",
|
|
volume: 0.7,
|
|
isLive: false,
|
|
muted: false,
|
|
autoplay: false,
|
|
autoSize: false,
|
|
autoMini: false,
|
|
loop: false,
|
|
flip: false,
|
|
playbackRate: false,
|
|
aspectRatio: false,
|
|
screenshot: false,
|
|
setting: false,
|
|
hotkey: true,
|
|
pip: false,
|
|
mutex: true,
|
|
backdrop: true,
|
|
fullscreen: false,
|
|
fullscreenWeb: false,
|
|
subtitleOffset: false,
|
|
miniProgressBar: false,
|
|
useSSR: false,
|
|
playsInline: true,
|
|
lock: false,
|
|
gesture: true,
|
|
fastForward: false,
|
|
autoPlayback: false,
|
|
autoOrientation: false,
|
|
airplay: false,
|
|
proxy: void 0,
|
|
layers: [],
|
|
contextmenu: [],
|
|
controls: [],
|
|
settings: [],
|
|
quality: [],
|
|
highlight: [],
|
|
plugins: [],
|
|
thumbnails: {
|
|
url: "",
|
|
number: 60,
|
|
column: 10,
|
|
width: 0,
|
|
height: 0,
|
|
scale: 1
|
|
},
|
|
subtitle: {
|
|
url: "",
|
|
type: "",
|
|
style: {},
|
|
name: "",
|
|
escape: true,
|
|
encoding: "utf-8",
|
|
onVttLoad: (vtt) => vtt
|
|
},
|
|
moreVideoAttr: {
|
|
controls: false,
|
|
preload: isSafari ? "auto" : "metadata"
|
|
},
|
|
i18n: {},
|
|
icons: {},
|
|
cssVar: {},
|
|
customType: {},
|
|
lang: navigator?.language.toLowerCase()
|
|
};
|
|
}
|
|
get proxy() {
|
|
return this.events.proxy;
|
|
}
|
|
get query() {
|
|
return this.template.query;
|
|
}
|
|
get video() {
|
|
return this.template.$video;
|
|
}
|
|
reset() {
|
|
this.video.removeAttribute("src");
|
|
this.video.load();
|
|
}
|
|
destroy(removeHtml = true) {
|
|
if (Artplayer.REMOVE_SRC_WHEN_DESTROY) {
|
|
this.reset();
|
|
}
|
|
this.events.destroy();
|
|
this.template.destroy(removeHtml);
|
|
instances.splice(instances.indexOf(this), 1);
|
|
this.isDestroy = true;
|
|
this.emit("destroy");
|
|
}
|
|
}
|
|
Artplayer.STYLE = style;
|
|
Artplayer.DEBUG = false;
|
|
Artplayer.CONTEXTMENU = true;
|
|
Artplayer.NOTICE_TIME = 2e3;
|
|
Artplayer.SETTING_WIDTH = 250;
|
|
Artplayer.SETTING_ITEM_WIDTH = 200;
|
|
Artplayer.SETTING_ITEM_HEIGHT = 35;
|
|
Artplayer.RESIZE_TIME = 200;
|
|
Artplayer.SCROLL_TIME = 200;
|
|
Artplayer.SCROLL_GAP = 50;
|
|
Artplayer.AUTO_PLAYBACK_MAX = 10;
|
|
Artplayer.AUTO_PLAYBACK_MIN = 5;
|
|
Artplayer.AUTO_PLAYBACK_TIMEOUT = 3e3;
|
|
Artplayer.RECONNECT_TIME_MAX = 5;
|
|
Artplayer.RECONNECT_SLEEP_TIME = 1e3;
|
|
Artplayer.CONTROL_HIDE_TIME = 3e3;
|
|
Artplayer.DBCLICK_TIME = 300;
|
|
Artplayer.DBCLICK_FULLSCREEN = true;
|
|
Artplayer.MOBILE_DBCLICK_PLAY = true;
|
|
Artplayer.MOBILE_CLICK_PLAY = false;
|
|
Artplayer.AUTO_ORIENTATION_TIME = 200;
|
|
Artplayer.INFO_LOOP_TIME = 1e3;
|
|
Artplayer.FAST_FORWARD_VALUE = 3;
|
|
Artplayer.FAST_FORWARD_TIME = 1e3;
|
|
Artplayer.TOUCH_MOVE_RATIO = 0.5;
|
|
Artplayer.VOLUME_STEP = 0.1;
|
|
Artplayer.SEEK_STEP = 5;
|
|
Artplayer.PLAYBACK_RATE = [0.5, 0.75, 1, 1.25, 1.5, 2];
|
|
Artplayer.ASPECT_RATIO = ["default", "4:3", "16:9"];
|
|
Artplayer.FLIP = ["normal", "horizontal", "vertical"];
|
|
Artplayer.FULLSCREEN_WEB_IN_BODY = true;
|
|
Artplayer.LOG_VERSION = true;
|
|
Artplayer.USE_RAF = false;
|
|
Artplayer.REMOVE_SRC_WHEN_DESTROY = true;
|
|
if (isBrowser) {
|
|
window.Artplayer = Artplayer;
|
|
setStyleText("artplayer-style", style);
|
|
setTimeout(() => {
|
|
if (Artplayer.LOG_VERSION) {
|
|
console.log(
|
|
`%c ArtPlayer %c ${Artplayer.version} %c https://artplayer.org`,
|
|
"color: #fff; background: #5f5f5f",
|
|
"color: #fff; background: #4bc729",
|
|
""
|
|
);
|
|
}
|
|
}, 100);
|
|
}
|
|
export {
|
|
Artplayer as default
|
|
};
|