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, including code snippets for usage. - Added a new i18n setup guide, detailing language settings, default languages, language imports, and how to add or modify languages. - Included example code for initializing the Artplayer with different languages and importing language files.
This commit is contained in:
1 parent
bd43b6638d
commit
39a1fb1abd
82 files changed
+150499
-103
No files matched your search
+245
-1
@@ -4,4 +4,248 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
function l(e){return n=>{!function(e){const{version:n,utils:{errorHandle:a}}=e.constructor,i=n.split(".").map(Number);a(i[0]+i[1]/100>=5,`Artplayer.js@${n} is not compatible the artplayerPluginAds@${l.version}. Please update it to version Artplayer.js@5.x.x`)}(n);const{template:{$player:a},icons:{volume:i,volumeClose:t,fullscreenOn:r,fullscreenOff:d,loading:o},constructor:{validator:s,utils:{query:p,append:u,setStyle:c}}}=n;e=s({html:"",video:"",url:"",playDuration:5,totalDuration:10,muted:!1,i18n:{close:"\u5173\u95ed\u5e7f\u544a",countdown:"%s\u79d2",detail:"\u67e5\u770b\u8be6\u60c5",canBeClosed:"%s\u79d2\u540e\u53ef\u5173\u95ed\u5e7f\u544a"},...e},{html:"?string",video:"?string",url:"?string",playDuration:"number",totalDuration:"number",muted:"?boolean",i18n:{close:"string",countdown:"string",detail:"string",canBeClosed:"string"}});let y=null,g=null,m=null,f=null,v=null,x=null,h=0,b=null,w=!1,$=!1,k=!1;function D(l,e){return e.replace("%s",l)}function A(){w=!0,n.play(),e.video&&y.pause(),c(n.template.$ads,"display","none"),n.emit("artplayerPluginAds:skip",e)}function C(){w||(b=setTimeout(()=>{h+=1;const l=e.playDuration-h;l>=1?m.innerHTML=D(l,e.i18n.canBeClosed):(m.innerHTML=e.i18n.close,k||(k=!0)),f.innerHTML=D(e.totalDuration-h,e.i18n.countdown),h>=e.totalDuration?A():C()},1e3))}function P(){w||clearTimeout(b)}function B(){$||($=!0,function(){n.template.$ads=u(a,'<div class="artplayer-plugin-ads"></div>'),y=u(n.template.$ads,e.video?`<video class="artplayer-plugin-ads-video" src="${e.video}" loop playsInline></video>`:`<div class="artplayer-plugin-ads-html">${e.html}</div>`),x=u(n.template.$ads,'<div class="artplayer-plugin-ads-loading"></div>'),u(x,o),g=u(n.template.$ads,`<div class="artplayer-plugin-ads-timer">\n <div class="artplayer-plugin-ads-close">${e.playDuration<=0?e.i18n.close:D(e.playDuration,e.i18n.canBeClosed)}</div>\n <div class="artplayer-plugin-ads-countdown">${D(e.totalDuration,e.i18n.countdown)}</div>\n </div>`),m=p(".artplayer-plugin-ads-close",g),f=p(".artplayer-plugin-ads-countdown",g),e.playDuration>=e.totalDuration&&c(m,"display","none"),n.proxy(m,"click",()=>{k&&A()}),v=u(n.template.$ads,`<div class="artplayer-plugin-ads-control">\n <div class="artplayer-plugin-ads-detail">${e.i18n.detail}</div>\n <div class="artplayer-plugin-ads-muted"></div>\n <div class="artplayer-plugin-ads-fullscreen"></div>\n </div>`);const l=p(".artplayer-plugin-ads-detail",v),s=p(".artplayer-plugin-ads-muted",v),h=p(".artplayer-plugin-ads-fullscreen",v);if(e.url?n.proxy(l,"click",()=>{window.open(e.url),n.emit("artplayerPluginAds:click",e)}):c(l,"display","none"),e.video){const l=u(s,i),a=u(s,t);c(a,"display","none"),e.muted&&(y.muted=!0,c(l,"display","none"),c(a,"display","inline-flex")),n.proxy(s,"click",()=>{y.muted=!y.muted,y.muted?(c(l,"display","none"),c(a,"display","inline-flex")):(c(l,"display","inline-flex"),c(a,"display","none"))})}else c(s,"display","none");const b=u(h,r),w=u(h,d);c(w,"display","none"),n.proxy(h,"click",()=>{n.fullscreen=!n.fullscreen,n.fullscreen?(c(b,"display","inline-flex"),c(w,"display","none")):(c(b,"display","none"),c(w,"display","inline-flex"))}),n.proxy(y,"click",()=>{e.url&&window.open(e.url),n.emit("artplayerPluginAds:click",e)})}(),n.pause(),e.video?(n.proxy(y,"error",A),n.proxy(y,"loadedmetadata",()=>{C(),y.play(),c(g,"display","flex"),c(v,"display","flex"),c(x,"display","none")})):(C(),c(g,"display","flex"),c(v,"display","flex"),c(x,"display","none")),n.on("document:visibilitychange",()=>{document.hidden?P():C()}))}return n.on("ready",()=>{n.once("play",B),n.once("video:playing",B)}),{name:"artplayerPluginAds",skip:A,pause:P,play:C}}}if("undefined"!==typeof document&&!document.getElementById("artplayer-plugin-ads")){const l=document.createElement("style");l.id="artplayer-plugin-ads",l.textContent=".artplayer-plugin-ads{position:absolute;z-index:150;inset:0;width:100%;height:100%;overflow:hidden;font-size:13px;line-height:1;color:#fff;background-color:#000}.artplayer-plugin-ads .artplayer-plugin-ads-html{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-video{width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-timer{display:none;position:absolute;top:10px;right:10px}.artplayer-plugin-ads .artplayer-plugin-ads-timer>div{display:flex;align-items:center;background-color:#00000080;border-radius:15px;margin-left:5px;padding:5px 10px;cursor:pointer}.artplayer-plugin-ads .artplayer-plugin-ads-control{display:none;position:absolute;bottom:10px;right:10px}.artplayer-plugin-ads .artplayer-plugin-ads-control>div{display:flex;align-items:center;background-color:#00000080;border-radius:15px;margin-left:5px;padding:5px 10px;cursor:pointer}.artplayer-plugin-ads .artplayer-plugin-ads-control .art-icon svg{width:20px;height:20px}.artplayer-plugin-ads .artplayer-plugin-ads-lLine truncated
|
||||
const style = ".artplayer-plugin-ads {\n position: absolute;\n z-index: 150;\n left: 0;\n top: 0;\n right: 0;\n bottom: 0;\n width: 100%;\n height: 100%;\n overflow: hidden;\n font-size: 13px;\n line-height: 1;\n color: #fff;\n background-color: #000;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-html {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-video {\n width: 100%;\n height: 100%;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-timer {\n display: none;\n position: absolute;\n top: 10px;\n right: 10px;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-timer > div {\n display: flex;\n align-items: center;\n background-color: rgba(0, 0, 0, 0.5);\n border-radius: 15px;\n margin-left: 5px;\n padding: 5px 10px;\n cursor: pointer;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-control {\n display: none;\n position: absolute;\n bottom: 10px;\n right: 10px;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-control > div {\n display: flex;\n align-items: center;\n background-color: rgba(0, 0, 0, 0.5);\n border-radius: 15px;\n margin-left: 5px;\n padding: 5px 10px;\n cursor: pointer;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-control .art-icon svg {\n width: 20px;\n height: 20px;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n position: absolute;\n left: 0;\n top: 0;\n right: 0;\n bottom: 0;\n width: 100%;\n height: 100%;\n}\n";
|
||||
function checkVersion(art) {
|
||||
const {
|
||||
version,
|
||||
utils: { errorHandle }
|
||||
} = art.constructor;
|
||||
const arr = version.split(".").map(Number);
|
||||
const major = arr[0];
|
||||
const minor = arr[1] / 100;
|
||||
errorHandle(
|
||||
major + minor >= 5,
|
||||
`Artplayer.js@${version} is not compatible the artplayerPluginAds@${artplayerPluginAds.version}. Please update it to version Artplayer.js@5.x.x`
|
||||
);
|
||||
}
|
||||
function artplayerPluginAds(option) {
|
||||
return (art) => {
|
||||
checkVersion(art);
|
||||
const {
|
||||
template: { $player },
|
||||
icons: { volume, volumeClose, fullscreenOn, fullscreenOff, loading },
|
||||
constructor: {
|
||||
validator,
|
||||
utils: { query, append, setStyle }
|
||||
}
|
||||
} = art;
|
||||
option = validator(
|
||||
{
|
||||
html: "",
|
||||
video: "",
|
||||
url: "",
|
||||
playDuration: 5,
|
||||
totalDuration: 10,
|
||||
muted: false,
|
||||
i18n: {
|
||||
close: "关闭广告",
|
||||
countdown: "%s秒",
|
||||
detail: "查看详情",
|
||||
canBeClosed: "%s秒后可关闭广告"
|
||||
},
|
||||
...option
|
||||
},
|
||||
{
|
||||
html: "?string",
|
||||
video: "?string",
|
||||
url: "?string",
|
||||
playDuration: "number",
|
||||
totalDuration: "number",
|
||||
muted: "?boolean",
|
||||
i18n: {
|
||||
close: "string",
|
||||
countdown: "string",
|
||||
detail: "string",
|
||||
canBeClosed: "string"
|
||||
}
|
||||
}
|
||||
);
|
||||
let $ads = null;
|
||||
let $timer = null;
|
||||
let $close = null;
|
||||
let $countdown = null;
|
||||
let $control = null;
|
||||
let $loading = null;
|
||||
let time = 0;
|
||||
let timer = null;
|
||||
let isEnd = false;
|
||||
let isInit = false;
|
||||
let isCanClose = false;
|
||||
function getI18n(val, str) {
|
||||
return str.replace("%s", val);
|
||||
}
|
||||
function skip() {
|
||||
isEnd = true;
|
||||
art.play();
|
||||
if (option.video)
|
||||
$ads.pause();
|
||||
setStyle(art.template.$ads, "display", "none");
|
||||
art.emit("artplayerPluginAds:skip", option);
|
||||
}
|
||||
function play() {
|
||||
if (isEnd)
|
||||
return;
|
||||
timer = setTimeout(() => {
|
||||
time += 1;
|
||||
const playDuration = option.playDuration - time;
|
||||
if (playDuration >= 1) {
|
||||
$close.innerHTML = getI18n(playDuration, option.i18n.canBeClosed);
|
||||
} else {
|
||||
$close.innerHTML = option.i18n.close;
|
||||
if (!isCanClose) {
|
||||
isCanClose = true;
|
||||
}
|
||||
}
|
||||
$countdown.innerHTML = getI18n(option.totalDuration - time, option.i18n.countdown);
|
||||
if (time >= option.totalDuration) {
|
||||
skip();
|
||||
} else {
|
||||
play();
|
||||
}
|
||||
}, 1e3);
|
||||
}
|
||||
function pause() {
|
||||
if (isEnd)
|
||||
return;
|
||||
clearTimeout(timer);
|
||||
}
|
||||
function show() {
|
||||
art.template.$ads = append($player, '<div class="artplayer-plugin-ads"></div>');
|
||||
$ads = append(
|
||||
art.template.$ads,
|
||||
option.video ? `<video class="artplayer-plugin-ads-video" src="${option.video}" loop playsInline></video>` : `<div class="artplayer-plugin-ads-html">${option.html}</div>`
|
||||
);
|
||||
$loading = append(art.template.$ads, '<div class="artplayer-plugin-ads-loading"></div>');
|
||||
append($loading, loading);
|
||||
$timer = append(
|
||||
art.template.$ads,
|
||||
`<div class="artplayer-plugin-ads-timer">
|
||||
<div class="artplayer-plugin-ads-close">${option.playDuration <= 0 ? option.i18n.close : getI18n(option.playDuration, option.i18n.canBeClosed)}</div>
|
||||
<div class="artplayer-plugin-ads-countdown">${getI18n(
|
||||
option.totalDuration,
|
||||
option.i18n.countdown
|
||||
)}</div>
|
||||
</div>`
|
||||
);
|
||||
$close = query(".artplayer-plugin-ads-close", $timer);
|
||||
$countdown = query(".artplayer-plugin-ads-countdown", $timer);
|
||||
if (option.playDuration >= option.totalDuration) {
|
||||
setStyle($close, "display", "none");
|
||||
}
|
||||
art.proxy($close, "click", () => {
|
||||
if (isCanClose) {
|
||||
skip();
|
||||
}
|
||||
});
|
||||
$control = append(
|
||||
art.template.$ads,
|
||||
`<div class="artplayer-plugin-ads-control">
|
||||
<div class="artplayer-plugin-ads-detail">${option.i18n.detail}</div>
|
||||
<div class="artplayer-plugin-ads-muted"></div>
|
||||
<div class="artplayer-plugin-ads-fullscreen"></div>
|
||||
</div>`
|
||||
);
|
||||
const $detail = query(".artplayer-plugin-ads-detail", $control);
|
||||
const $muted = query(".artplayer-plugin-ads-muted", $control);
|
||||
const $fullscreen = query(".artplayer-plugin-ads-fullscreen", $control);
|
||||
if (option.url) {
|
||||
art.proxy($detail, "click", () => {
|
||||
window.open(option.url);
|
||||
art.emit("artplayerPluginAds:click", option);
|
||||
});
|
||||
} else {
|
||||
setStyle($detail, "display", "none");
|
||||
}
|
||||
if (option.video) {
|
||||
const $volume = append($muted, volume);
|
||||
const $volumeClose = append($muted, volumeClose);
|
||||
setStyle($volumeClose, "display", "none");
|
||||
if (option.muted) {
|
||||
$ads.muted = true;
|
||||
setStyle($volume, "display", "none");
|
||||
setStyle($volumeClose, "display", "inline-flex");
|
||||
}
|
||||
art.proxy($muted, "click", () => {
|
||||
$ads.muted = !$ads.muted;
|
||||
if ($ads.muted) {
|
||||
setStyle($volume, "display", "none");
|
||||
setStyle($volumeClose, "display", "inline-flex");
|
||||
} else {
|
||||
setStyle($volume, "display", "inline-flex");
|
||||
setStyle($volumeClose, "display", "none");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setStyle($muted, "display", "none");
|
||||
}
|
||||
const $fullscreenOn = append($fullscreen, fullscreenOn);
|
||||
const $fullscreenOff = append($fullscreen, fullscreenOff);
|
||||
setStyle($fullscreenOff, "display", "none");
|
||||
art.proxy($fullscreen, "click", () => {
|
||||
art.fullscreen = !art.fullscreen;
|
||||
if (art.fullscreen) {
|
||||
setStyle($fullscreenOn, "display", "inline-flex");
|
||||
setStyle($fullscreenOff, "display", "none");
|
||||
} else {
|
||||
setStyle($fullscreenOn, "display", "none");
|
||||
setStyle($fullscreenOff, "display", "inline-flex");
|
||||
}
|
||||
});
|
||||
art.proxy($ads, "click", () => {
|
||||
if (option.url)
|
||||
window.open(option.url);
|
||||
art.emit("artplayerPluginAds:click", option);
|
||||
});
|
||||
}
|
||||
function init() {
|
||||
if (isInit)
|
||||
return;
|
||||
isInit = true;
|
||||
show();
|
||||
art.pause();
|
||||
if (option.video) {
|
||||
art.proxy($ads, "error", skip);
|
||||
art.proxy($ads, "loadedmetadata", () => {
|
||||
play();
|
||||
$ads.play();
|
||||
setStyle($timer, "display", "flex");
|
||||
setStyle($control, "display", "flex");
|
||||
setStyle($loading, "display", "none");
|
||||
});
|
||||
} else {
|
||||
play();
|
||||
setStyle($timer, "display", "flex");
|
||||
setStyle($control, "display", "flex");
|
||||
setStyle($loading, "display", "none");
|
||||
}
|
||||
art.on("document:visibilitychange", () => {
|
||||
if (document.hidden) {
|
||||
pause();
|
||||
} else {
|
||||
play();
|
||||
}
|
||||
});
|
||||
}
|
||||
art.on("ready", () => {
|
||||
art.once("play", init);
|
||||
art.once("video:playing", init);
|
||||
});
|
||||
return {
|
||||
name: "artplayerPluginAds",
|
||||
skip,
|
||||
pause,
|
||||
play
|
||||
};
|
||||
};
|
||||
}
|
||||
if (typeof document !== "undefined") {
|
||||
if (!document.getElementById("artplayer-plugin-ads")) {
|
||||
const $style = document.createElement("style");
|
||||
$style.id = "artplayer-plugin-ads";
|
||||
$style.textContent = style;
|
||||
document.head.appendChild($style);
|
||||
}
|
||||
}
|
||||
export {
|
||||
artplayerPluginAds as default
|
||||
};
|
||||
@@ -4,4 +4,101 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
function t(t={}){return e=>{const{$video:n}=e.template,{createElement:r,addClass:o,setStyles:i}=e.constructor.utils,{blur:a="50px",opacity:l=.5,frequency:s=10,duration:c=.3}=t,u=r("div"),d=((f=u).innerHTML=Array.from({length:9}).fill("<div></div>").join(""),Array.from(f.children));var f,m,g,p;!function(t,e){o(t,"artplayer-plugin-ambilight"),e.parentNode.insertBefore(t,e),i(t,{position:"absolute",top:0,left:0,zIndex:9,inset:0,display:"grid",gridTemplateColumns:"1fr 1fr 1fr",gridTemplateRows:"1fr 1fr 1fr"})}(u,n),m=a,g=l,p=c,d.forEach(t=>{i(t,{opacity:g,filter:`blur(${m})`,transition:`background-color ${p}s ease`})});const y=function(t,n,o){const i=r("canvas"),a=i.getContext("2d");i.width=3,i.height=3;let l=0;return function r(){const i=performance.now();if(i-l<1e3/o||!e.playing)return void(h=requestAnimationFrame(r));l=i;const s=t.videoWidth/3,c=t.videoHeight/3,u=[[0,0],[s,0],[2*s,0],[0,c],[s,c],[2*s,c],[0,2*c],[s,2*c],[2*s,2*c]].map(([e,n])=>function(e,n,r,o){a.drawImage(t,e,n,r,o,0,0,1,1);const[i,l,s]=a.getImageData(0,0,1,1).data;return`rgb(${i}, ${l}, ${s})`}(e,n,s,c));n.forEach((t,e)=>{t.style.backgroundColor=u[e]}),h=requestAnimationFrame(r)}}(n,d,s);let h=null;function v(){h||y()}function b(){h&&(cancelAnimationFrame(h),h=null)}return e.on("ready",v),e.on("destroy",b),{name:"artplayerPluginAmbilight",start:v,stop:b}}}export{t as default};
|
||||
function artplayerPluginAmbilight(option = {}) {
|
||||
return (art) => {
|
||||
const { $video } = art.template;
|
||||
const { createElement, addClass, setStyles } = art.constructor.utils;
|
||||
const { blur = "50px", opacity = 0.5, frequency = 10, duration = 0.3 } = option;
|
||||
const $ambilight = createElement("div");
|
||||
const gridItems = createGridItems($ambilight);
|
||||
setupAmbilight($ambilight, $video);
|
||||
setupGridItems(gridItems, blur, opacity, duration);
|
||||
const updateColors = createColorUpdater($video, gridItems, frequency);
|
||||
let animationFrameId = null;
|
||||
function start() {
|
||||
if (!animationFrameId) {
|
||||
updateColors();
|
||||
}
|
||||
}
|
||||
function stop() {
|
||||
if (animationFrameId) {
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
animationFrameId = null;
|
||||
}
|
||||
}
|
||||
art.on("ready", start);
|
||||
art.on("destroy", stop);
|
||||
return {
|
||||
name: "artplayerPluginAmbilight",
|
||||
start,
|
||||
stop
|
||||
};
|
||||
function createGridItems($ambilight2) {
|
||||
$ambilight2.innerHTML = Array.from({ length: 9 }).fill("<div></div>").join("");
|
||||
return Array.from($ambilight2.children);
|
||||
}
|
||||
function setupAmbilight($ambilight2, $video2) {
|
||||
addClass($ambilight2, "artplayer-plugin-ambilight");
|
||||
$video2.parentNode.insertBefore($ambilight2, $video2);
|
||||
setStyles($ambilight2, {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
zIndex: 9,
|
||||
inset: 0,
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr 1fr 1fr",
|
||||
gridTemplateRows: "1fr 1fr 1fr"
|
||||
});
|
||||
}
|
||||
function setupGridItems(gridItems2, blur2, opacity2, duration2) {
|
||||
gridItems2.forEach(($item) => {
|
||||
setStyles($item, {
|
||||
opacity: opacity2,
|
||||
filter: `blur(${blur2})`,
|
||||
transition: `background-color ${duration2}s ease`
|
||||
});
|
||||
});
|
||||
}
|
||||
function createColorUpdater($video2, gridItems2, frequency2) {
|
||||
const canvas = createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
canvas.width = 3;
|
||||
canvas.height = 3;
|
||||
function getAverageColor(x, y, w, h) {
|
||||
ctx.drawImage($video2, x, y, w, h, 0, 0, 1, 1);
|
||||
const [r, g, b] = ctx.getImageData(0, 0, 1, 1).data;
|
||||
return `rgb(${r}, ${g}, ${b})`;
|
||||
}
|
||||
let lastUpdateTime = 0;
|
||||
return function updateColors2() {
|
||||
const now = performance.now();
|
||||
if (now - lastUpdateTime < 1e3 / frequency2 || !art.playing) {
|
||||
animationFrameId = requestAnimationFrame(updateColors2);
|
||||
return;
|
||||
}
|
||||
lastUpdateTime = now;
|
||||
const w = $video2.videoWidth / 3;
|
||||
const h = $video2.videoHeight / 3;
|
||||
const colors = [
|
||||
[0, 0],
|
||||
[w, 0],
|
||||
[2 * w, 0],
|
||||
[0, h],
|
||||
[w, h],
|
||||
[2 * w, h],
|
||||
[0, 2 * h],
|
||||
[w, 2 * h],
|
||||
[2 * w, 2 * h]
|
||||
].map(([x, y]) => getAverageColor(x, y, w, h));
|
||||
gridItems2.forEach(($item, index) => {
|
||||
$item.style.backgroundColor = colors[index];
|
||||
});
|
||||
animationFrameId = requestAnimationFrame(updateColors2);
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
export {
|
||||
artplayerPluginAmbilight as default
|
||||
};
|
||||
+246
-1
@@ -4,4 +4,249 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
function t(t={}){const{length:e=3,interval:n=100,sampleRate:r=16e3,autoHideTimeout:a=1e4,onAudioChunk:o=()=>null}=t;return t=>{let i=!1,l=null,s=null,u=null,c=null,p=[],d=null,m=!1,y=null,f=null,g=null;const w=t.layers.add({name:"asr",html:""});function h(){w.style.display="none"}function v(t){var n;"string"===typeof t&&(clearTimeout(y),y=setTimeout(h,a),w.style.display="",w.innerHTML=(n=t,n.split(/(?<=[\u3001\u3002\uff01\uff1f!?.])\s*/u).map(t=>t.trim()).filter(Boolean).slice(-e)).map(t=>`<div class="art-asr-line">${t}</div>`).join(""))}async function b(){if(!s&&t.video)try{return s=l.createMediaElementSource(t.video),s}catch(e){console.warn("[artplayerPluginAsr] Direct connection failed:",e)}try{if(f||(f=await async function(t){try{return t.captureStream?t.captureStream():t.mozCaptureStream?t.mozCaptureStream():(console.warn("[artplayerPluginAsr] captureStream not supported"),null)}catch(e){return console.warn("[artplayerPluginAsr] Error capturing stream:",e),null}}(t.video)),f)return g=l.createMediaStreamSource(f),g}catch(e){console.warn("[artplayerPluginAsr] MediaStream fallback failed:",e)}return null}async function x(){i&&(i=!1,clearInterval(d),d=null,u&&(u.disconnect(),u.port.onmessage=null,u=null),c&&(c.disconnect(),c=null),p=[])}async function A(){await x(),g&&(g.disconnect(),g=null),s&&(s.disconnect(),s=null),f&&(f.getTracks().forEach(t=>t.stop()),f=null),l&&(await l.close(),l=null),m=!1}return t.on("video:volumechange",()=>{c&&(c.gain.value=t.volume)}),t.on("play",async function(){if(!i)try{await async function(){return l||(l=new(window.AudioContext||window.webkitAudioContext)({sampleRate:r}),"suspended"===l.state&&await l.resume(),l)}();const t=await b();if(!t)throw new Error("Could not establish audio source");if(!m){const t=(()=>{const t=new Blob(["\n class RecorderProcessor extends AudioWorkletProcessor {\n process(inputs) {\n const input = inputs[0];\n if (input && input[0]) {\n this.port.postMessage(input[0]);\n }\n return true;\n }\n }\n registerProcessor('recorder-processor', RecorderProcessor);\n "],{type:"application/javascript"});return URL.createObjectURL(t)})();await l.audioWorklet.addModule(t),URL.revokeObjectURL(t),m=!0}c=l.createGain(),c.gain.value=1,u=new AudioWorkletNode(l,"recorder-processor"),u.port.onmessage=t=>{p.push(new Float32Array(t.data))},t.connect(u),t.connect(c),c.connect(l.destination);const e=r*(n/1e3);d=setInterval(async()=>{if(0===p.length)return;let t=new Float32Array(0);for(;t.length<e&&p.length;){const e=p.shift(),n=new Float32Array(t.length+e.length);n.set(t,0),n.set(e,t.length),t=n}if(t.length<e)return;const n=function(t){const e=t.length,n=new ArrayBuffer(2*e),r=new DataView(n);for(let a=0;a<e;a++){const e=Math.max(-1,Math.min(1,t[a]));r.setInt16(2*a,e<0?32768*e:32767*e,!0)}return n}(t.slice(0,e)),a=function(t,e){const n=t.byteLength,r=new ArrayBuffer(44+n),a=new DataView(r),o=(t,e)=>{for(let n=0;n<e.length;n++)a.setUint8(t+n,e.charCodeAt(n))};return o(0,"RIFF"),a.setUint32(4,36+n,!0),o(8,"WAVE"),o(12,"fmt "),a.setUint32(16,16,!0),a.setUint16(20,1,!0),a.setUint16(22,1,!0),a.setUint32(24,e,!0),a.setUint32(28,2*e,!0),a.setUint16(32,2,!0),a.setUint16(34,16,!0),o(36,"data"),a.setUint32(40,n,!0),new Uint8Array(r).set(new Uint8Array(t),44),r}(n,r);v(await o({pcm:n,wav:a}))},n),i=!0}catch(t){console.error("[artplayerPluginAsr] Initialization failed:",t),await x()}}),t.on("pause",x),t.on("destroy",A),{name:"artplayerPluginAsr",stop:A,hide:h,append:v}}}if("undefined"!==typeof document&&!document.getElementById("artplayer-plugin-asr")){const t=document.createElement("style");t.id="artplayer-plugin-asr",t.textContent=".art-video-player .art-layer-asr{pointer-events:none!important;position:absolute;z-index:150;inset:0;display:flex;flex-direction:column;justify-content:end;padding:0 2%;font-size:1rem;gap:var(--art-subtitle-gap);padding-bottom:var(--art-subtitle-bottom);transition:padding-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-control-show .art-layer-asr{padding-bottom:calc(var(--art-control-height) + var(--art-subtitle-bottom))}",document.head.appendChild(t)}export{t as default};
|
||||
const style = ".art-video-player .art-layer-asr {\n pointer-events: none !important;\n position: absolute;\n z-index: 150;\n inset: 0px;\n display: flex;\n flex-direction: column;\n justify-content: end;\n padding: 0 2%;\n font-size: 1rem;\n gap: var(--art-subtitle-gap);\n padding-bottom: var(--art-subtitle-bottom);\n transition: padding-bottom var(--art-transition-duration) ease;\n 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;\n}\n.art-video-player.art-control-show .art-layer-asr {\n padding-bottom: calc(var(--art-control-height) + var(--art-subtitle-bottom));\n}\n";
|
||||
function artplayerPluginAsr(option = {}) {
|
||||
const {
|
||||
length = 3,
|
||||
interval = 100,
|
||||
sampleRate = 16e3,
|
||||
autoHideTimeout = 1e4,
|
||||
onAudioChunk = () => null
|
||||
} = option;
|
||||
return (art) => {
|
||||
let started = false;
|
||||
let audioCtx = null;
|
||||
let sourceNode = null;
|
||||
let recorderNode = null;
|
||||
let gainNode = null;
|
||||
let bufferChunks = [];
|
||||
let timer = null;
|
||||
let workletLoaded = false;
|
||||
let hideTimer = null;
|
||||
let mediaStream = null;
|
||||
let mediaStreamSource = null;
|
||||
const $asr = art.layers.add({
|
||||
name: "asr",
|
||||
html: ""
|
||||
});
|
||||
function splitByPunctuation(text) {
|
||||
return text.split(/(?<=[、。!?!?.])\s*/u).map((s) => s.trim()).filter(Boolean).slice(-length);
|
||||
}
|
||||
function hide() {
|
||||
$asr.style.display = "none";
|
||||
}
|
||||
function append(subtitle) {
|
||||
if (typeof subtitle !== "string")
|
||||
return;
|
||||
clearTimeout(hideTimer);
|
||||
hideTimer = setTimeout(hide, autoHideTimeout);
|
||||
$asr.style.display = "";
|
||||
$asr.innerHTML = splitByPunctuation(subtitle).map((line) => `<div class="art-asr-line">${line}</div>`).join("");
|
||||
}
|
||||
const recorderProcessorCode = `
|
||||
class RecorderProcessor extends AudioWorkletProcessor {
|
||||
process(inputs) {
|
||||
const input = inputs[0];
|
||||
if (input && input[0]) {
|
||||
this.port.postMessage(input[0]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
registerProcessor('recorder-processor', RecorderProcessor);
|
||||
`;
|
||||
const createWorkletBlobUrl = () => {
|
||||
const blob = new Blob([recorderProcessorCode], { type: "application/javascript" });
|
||||
return URL.createObjectURL(blob);
|
||||
};
|
||||
function floatTo16BitPCM(float32Array) {
|
||||
const len = float32Array.length;
|
||||
const buffer = new ArrayBuffer(len * 2);
|
||||
const view = new DataView(buffer);
|
||||
for (let i = 0; i < len; i++) {
|
||||
const s = Math.max(-1, Math.min(1, float32Array[i]));
|
||||
view.setInt16(i * 2, s < 0 ? s * 32768 : s * 32767, true);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
function pcmToWav(pcmBuffer, sampleRate2) {
|
||||
const pcmLength = pcmBuffer.byteLength;
|
||||
const wavBuffer = new ArrayBuffer(44 + pcmLength);
|
||||
const view = new DataView(wavBuffer);
|
||||
const writeString = (offset, str) => {
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
view.setUint8(offset + i, str.charCodeAt(i));
|
||||
}
|
||||
};
|
||||
writeString(0, "RIFF");
|
||||
view.setUint32(4, 36 + pcmLength, true);
|
||||
writeString(8, "WAVE");
|
||||
writeString(12, "fmt ");
|
||||
view.setUint32(16, 16, true);
|
||||
view.setUint16(20, 1, true);
|
||||
view.setUint16(22, 1, true);
|
||||
view.setUint32(24, sampleRate2, true);
|
||||
view.setUint32(28, sampleRate2 * 2, true);
|
||||
view.setUint16(32, 2, true);
|
||||
view.setUint16(34, 16, true);
|
||||
writeString(36, "data");
|
||||
view.setUint32(40, pcmLength, true);
|
||||
new Uint8Array(wavBuffer).set(new Uint8Array(pcmBuffer), 44);
|
||||
return wavBuffer;
|
||||
}
|
||||
async function setupAudioContext() {
|
||||
if (audioCtx)
|
||||
return audioCtx;
|
||||
audioCtx = new (window.AudioContext || window.webkitAudioContext)({ sampleRate });
|
||||
if (audioCtx.state === "suspended")
|
||||
await audioCtx.resume();
|
||||
return audioCtx;
|
||||
}
|
||||
async function setupAudioSource() {
|
||||
if (!sourceNode && art.video) {
|
||||
try {
|
||||
sourceNode = audioCtx.createMediaElementSource(art.video);
|
||||
return sourceNode;
|
||||
} catch (err) {
|
||||
console.warn("[artplayerPluginAsr] Direct connection failed:", err);
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (!mediaStream) {
|
||||
mediaStream = await captureAudioFromVideo(art.video);
|
||||
}
|
||||
if (mediaStream) {
|
||||
mediaStreamSource = audioCtx.createMediaStreamSource(mediaStream);
|
||||
return mediaStreamSource;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn("[artplayerPluginAsr] MediaStream fallback failed:", err);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
async function captureAudioFromVideo(videoElement) {
|
||||
try {
|
||||
if (videoElement.captureStream)
|
||||
return videoElement.captureStream();
|
||||
if (videoElement.mozCaptureStream)
|
||||
return videoElement.mozCaptureStream();
|
||||
console.warn("[artplayerPluginAsr] captureStream not supported");
|
||||
return null;
|
||||
} catch (err) {
|
||||
console.warn("[artplayerPluginAsr] Error capturing stream:", err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
async function startCapture() {
|
||||
if (started)
|
||||
return;
|
||||
try {
|
||||
await setupAudioContext();
|
||||
const audioSource = await setupAudioSource();
|
||||
if (!audioSource)
|
||||
throw new Error("Could not establish audio source");
|
||||
if (!workletLoaded) {
|
||||
const blobUrl = createWorkletBlobUrl();
|
||||
await audioCtx.audioWorklet.addModule(blobUrl);
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
workletLoaded = true;
|
||||
}
|
||||
gainNode = audioCtx.createGain();
|
||||
gainNode.gain.value = 1;
|
||||
recorderNode = new AudioWorkletNode(audioCtx, "recorder-processor");
|
||||
recorderNode.port.onmessage = (event) => {
|
||||
bufferChunks.push(new Float32Array(event.data));
|
||||
};
|
||||
audioSource.connect(recorderNode);
|
||||
audioSource.connect(gainNode);
|
||||
gainNode.connect(audioCtx.destination);
|
||||
const CHUNK_SAMPLES = sampleRate * (interval / 1e3);
|
||||
timer = setInterval(async () => {
|
||||
if (bufferChunks.length === 0)
|
||||
return;
|
||||
let accumulated = new Float32Array(0);
|
||||
while (accumulated.length < CHUNK_SAMPLES && bufferChunks.length) {
|
||||
const next = bufferChunks.shift();
|
||||
const tmp = new Float32Array(accumulated.length + next.length);
|
||||
tmp.set(accumulated, 0);
|
||||
tmp.set(next, accumulated.length);
|
||||
accumulated = tmp;
|
||||
}
|
||||
if (accumulated.length < CHUNK_SAMPLES)
|
||||
return;
|
||||
const chunkToSend = accumulated.slice(0, CHUNK_SAMPLES);
|
||||
const pcm = floatTo16BitPCM(chunkToSend);
|
||||
const wav = pcmToWav(pcm, sampleRate);
|
||||
const subtitle = await onAudioChunk({ pcm, wav });
|
||||
append(subtitle);
|
||||
}, interval);
|
||||
started = true;
|
||||
} catch (err) {
|
||||
console.error("[artplayerPluginAsr] Initialization failed:", err);
|
||||
await stopCapture();
|
||||
}
|
||||
}
|
||||
async function stopCapture() {
|
||||
if (!started)
|
||||
return;
|
||||
started = false;
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
if (recorderNode) {
|
||||
recorderNode.disconnect();
|
||||
recorderNode.port.onmessage = null;
|
||||
recorderNode = null;
|
||||
}
|
||||
if (gainNode) {
|
||||
gainNode.disconnect();
|
||||
gainNode = null;
|
||||
}
|
||||
bufferChunks = [];
|
||||
}
|
||||
async function destroy() {
|
||||
await stopCapture();
|
||||
if (mediaStreamSource) {
|
||||
mediaStreamSource.disconnect();
|
||||
mediaStreamSource = null;
|
||||
}
|
||||
if (sourceNode) {
|
||||
sourceNode.disconnect();
|
||||
sourceNode = null;
|
||||
}
|
||||
if (mediaStream) {
|
||||
mediaStream.getTracks().forEach((track) => track.stop());
|
||||
mediaStream = null;
|
||||
}
|
||||
if (audioCtx) {
|
||||
await audioCtx.close();
|
||||
audioCtx = null;
|
||||
}
|
||||
workletLoaded = false;
|
||||
}
|
||||
art.on("video:volumechange", () => {
|
||||
if (gainNode) {
|
||||
gainNode.gain.value = art.volume;
|
||||
}
|
||||
});
|
||||
art.on("play", startCapture);
|
||||
art.on("pause", stopCapture);
|
||||
art.on("destroy", destroy);
|
||||
return {
|
||||
name: "artplayerPluginAsr",
|
||||
stop: destroy,
|
||||
hide,
|
||||
append
|
||||
};
|
||||
};
|
||||
}
|
||||
if (typeof document !== "undefined") {
|
||||
if (!document.getElementById("artplayer-plugin-asr")) {
|
||||
const $style = document.createElement("style");
|
||||
$style.id = "artplayer-plugin-asr";
|
||||
$style.textContent = style;
|
||||
document.head.appendChild($style);
|
||||
}
|
||||
}
|
||||
export {
|
||||
artplayerPluginAsr as default
|
||||
};
|
||||
+86
-1
@@ -4,4 +4,89 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
function e(e){return a=>{let{url:o,offset:n=0,sync:t=.3}=e;const u=new Audio;function c(){if(!a.video||!o)return;const e=a.currentTime+n;Math.abs(u.currentTime-e)>t&&(u.currentTime=e)}return u.preload="auto",o&&(u.src=o),a.on("play",()=>{o&&(c(),u.play().catch(e=>{console.warn(e)}))}),a.on("pause",()=>{u.pause()}),a.on("seek",()=>{c()}),a.on("video:timeupdate",()=>{a.playing&&c()}),a.on("video:ratechange",()=>{u.playbackRate=a.video.playbackRate}),a.on("video:volumechange",()=>{u.volume=a.volume,u.muted=a.muted}),a.on("video:waiting",()=>{u.pause()}),a.on("video:playing",()=>{o&&a.playing&&u.play().catch(e=>{console.warn(e)})}),a.on("destroy",()=>{u.pause(),u.src="",u.load()}),u.volume=a.volume,u.muted=a.muted,u.playbackRate=a.video?.playbackRate||1,{name:"artplayerPluginAudioTrack",audio:u,update:function(e){e.url&&e.url!==o&&(o=e.url,u.src=o,a.playing&&u.play().catch(e=>console.warn(e))),void 0!==e.offset&&(n=e.offset),void 0!==e.sync&&(t=e.sync)}}}}export{e as default};
|
||||
function artplayerPluginAudioTrack(option) {
|
||||
return (art) => {
|
||||
let { url, offset = 0, sync = 0.3 } = option;
|
||||
const audio = new Audio();
|
||||
audio.preload = "auto";
|
||||
if (url) {
|
||||
audio.src = url;
|
||||
}
|
||||
function syncAudio() {
|
||||
if (!art.video || !url)
|
||||
return;
|
||||
const videoTime = art.currentTime;
|
||||
const targetTime = videoTime + offset;
|
||||
if (Math.abs(audio.currentTime - targetTime) > sync) {
|
||||
audio.currentTime = targetTime;
|
||||
}
|
||||
}
|
||||
art.on("play", () => {
|
||||
if (!url)
|
||||
return;
|
||||
syncAudio();
|
||||
audio.play().catch((err) => {
|
||||
console.warn(err);
|
||||
});
|
||||
});
|
||||
art.on("pause", () => {
|
||||
audio.pause();
|
||||
});
|
||||
art.on("seek", () => {
|
||||
syncAudio();
|
||||
});
|
||||
art.on("video:timeupdate", () => {
|
||||
if (art.playing) {
|
||||
syncAudio();
|
||||
}
|
||||
});
|
||||
art.on("video:ratechange", () => {
|
||||
audio.playbackRate = art.video.playbackRate;
|
||||
});
|
||||
art.on("video:volumechange", () => {
|
||||
audio.volume = art.volume;
|
||||
audio.muted = art.muted;
|
||||
});
|
||||
art.on("video:waiting", () => {
|
||||
audio.pause();
|
||||
});
|
||||
art.on("video:playing", () => {
|
||||
if (url && art.playing) {
|
||||
audio.play().catch((err) => {
|
||||
console.warn(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
art.on("destroy", () => {
|
||||
audio.pause();
|
||||
audio.src = "";
|
||||
audio.load();
|
||||
});
|
||||
function update(newOption) {
|
||||
if (newOption.url && newOption.url !== url) {
|
||||
url = newOption.url;
|
||||
audio.src = url;
|
||||
if (art.playing) {
|
||||
audio.play().catch((err) => console.warn(err));
|
||||
}
|
||||
}
|
||||
if (newOption.offset !== void 0) {
|
||||
offset = newOption.offset;
|
||||
}
|
||||
if (newOption.sync !== void 0) {
|
||||
sync = newOption.sync;
|
||||
}
|
||||
}
|
||||
audio.volume = art.volume;
|
||||
audio.muted = art.muted;
|
||||
audio.playbackRate = art.video?.playbackRate || 1;
|
||||
return {
|
||||
name: "artplayerPluginAudioTrack",
|
||||
audio,
|
||||
update
|
||||
};
|
||||
};
|
||||
}
|
||||
export {
|
||||
artplayerPluginAudioTrack as default
|
||||
};
|
||||
+57
-1
@@ -4,4 +4,60 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
function e(e){return async t=>(t.on("video:loadedmetadata",()=>{const n=e.url||t.option.url,o=e.width||160,a=e.number||100,r=e.scale||1;!function({url:e,width:t,number:n},o){const a=document.createElement("video");a.crossOrigin="anonymous",a.src=e,a.onloadedmetadata=()=>{const e=a.duration,r=document.createElement("canvas"),i=r.getContext("2d"),u=Math.floor(t*a.videoHeight/a.videoWidth);r.width=10*t,r.height=u*Math.ceil(n/10);let l=null;!function d(c){r.toBlob(e=>{URL.revokeObjectURL(l),l=URL.createObjectURL(e),o({url:l,height:u})},"image/jpeg"),c>=n||(a.currentTime=e*c/n,a.onseeked=()=>{i.drawImage(a,c%10*t,Math.floor(c/10)*u,t,u),d(c+1)})}(0)}}({url:n,width:o,number:a},e=>{t.thumbnails={...e,column:10,number:a,width:o,scale:r}})}),{name:"artplayerPluginAutoThumbnail"})}export{e as default};
|
||||
function create({ url, width, number }, callback) {
|
||||
const video = document.createElement("video");
|
||||
video.crossOrigin = "anonymous";
|
||||
video.src = url;
|
||||
video.onloadedmetadata = () => {
|
||||
const duration = video.duration;
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
const height = Math.floor(width * video.videoHeight / video.videoWidth);
|
||||
canvas.width = width * 10;
|
||||
canvas.height = height * Math.ceil(number / 10);
|
||||
let blobUrl = null;
|
||||
function seekAndDraw(index) {
|
||||
canvas.toBlob((blob) => {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
blobUrl = URL.createObjectURL(blob);
|
||||
callback({
|
||||
url: blobUrl,
|
||||
height
|
||||
});
|
||||
}, "image/jpeg");
|
||||
if (index >= number)
|
||||
return;
|
||||
video.currentTime = duration * index / number;
|
||||
video.onseeked = () => {
|
||||
ctx.drawImage(video, index % 10 * width, Math.floor(index / 10) * height, width, height);
|
||||
seekAndDraw(index + 1);
|
||||
};
|
||||
}
|
||||
seekAndDraw(0);
|
||||
};
|
||||
}
|
||||
function artplayerPluginAutoThumbnail(option) {
|
||||
return async (art) => {
|
||||
art.on("video:loadedmetadata", () => {
|
||||
const url = option.url || art.option.url;
|
||||
const width = option.width || 160;
|
||||
const number = option.number || 100;
|
||||
const scale = option.scale || 1;
|
||||
create({ url, width, number }, (config) => {
|
||||
art.thumbnails = {
|
||||
...config,
|
||||
column: 10,
|
||||
number,
|
||||
width,
|
||||
scale
|
||||
};
|
||||
});
|
||||
});
|
||||
return {
|
||||
name: "artplayerPluginAutoThumbnail"
|
||||
};
|
||||
};
|
||||
}
|
||||
export {
|
||||
artplayerPluginAutoThumbnail as default
|
||||
};
|
||||
@@ -4,4 +4,156 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
function t(t={}){return r=>{const{$player:e}=r.template,{setStyle:a,append:n,clamp:o,query:i,addClass:p,removeClass:s}=r.constructor.utils;let d=[];const l=r.query(".art-control-progress-inner"),c=n(l,'<div class="art-chapters"></div>'),h=n(l,'<div class="art-chapter-title"></div>');function u({$chapter:t,width:r}){const e=t.dataset.title.trim();if(e){h.textContent=e;const t=h.clientWidth;r<=t/2?a(h,"left",0):r>l.clientWidth-t/2?a(h,"left",l.clientWidth-t+"px"):a(h,"left",r-t/2+"px")}}function g(t=[]){if(d=[],c.textContent="",s(e,"artplayer-plugin-chapter"),Array.isArray(t)&&t.length&&r.duration){t=t.sort((t,r)=>t.start-r.start);for(let e=0;e<t.length;e++){const a=t[e],n=t[e+1];if(a.end===1/0&&(a.end=r.duration),"number"!==typeof a.start||"number"!==typeof a.end||"string"!==typeof a.title)throw new TypeError("Illegal chapter data type");if(a.start<0||a.end>r.duration||a.start>=a.end)throw new Error("Illegal chapter time point");if(n&&a.end>n.start)throw new Error("Illegal chapter time point")}t[0].start>0&&t.unshift({start:0,end:t[0].start,title:""}),t[t.length-1].end<r.duration&&t.push({start:t[t.length-1].end,end:r.duration,title:""});for(let r=0;r<t.length-1;r++)t[r].end!==t[r+1].start&&t.splice(r+1,0,{start:t[r].end,end:t[r+1].start,title:""});d=t.map(t=>{const e=n(c,'\n <div class="art-chapter">\n <div class="art-chapter-inner">\n <div class="art-progress-hover"></div>\n <div class="art-progress-loaded"></div>\n <div class="art-progress-played"></div>\n </div>\n </div>\n '),a=o(t.start,0,r.duration),p=o(t.end,0,r.duration),s=p-a,d=s/r.duration;return e.dataset.start=a,e.dataset.end=p,e.dataset.duration=s,e.dataset.title=t.title.trim(),e.style.width=100*d+"%",{$chapter:e,$hover:i(".art-progress-hover",e),$loaded:i(".art-progress-loaded",e),$played:i(".art-progress-played",e)}}),p(e,"artplayer-plugin-chapter"),r.emit("setBar","loaded",r.loaded||0)}}return r.on("setBar",(t,e)=>{if(d.length)for(let n=0;n<d.length;n++){const{$chapter:o,$loaded:i,$played:p,$hover:s}=d[n],l={hover:s,loaded:i,played:p}[t];if(!l)return;const h=c.clientWidth*e,g=r.duration*e,m=Number.parseFloat(o.dataset.duration),y=Number.parseFloat(o.dataset.start),f=Number.parseFloat(o.dataset.end);if(g<y&&a(l,"width",0),g>f&&a(l,"width","100%"),g>=y&&g<=f){a(l,"width",100*((g-y)/m)+"%"),"hover"===t&&h>0&&u({$chapter:o,width:h})}}}),r.once("video:loadedmetadata",()=>g(t.chapters)),{name:"artplayerPluginChapter",update:({chapters:t})=>g(t)}}}if("undefined"!==typeof document){const t="artplayer-plugin-chapter";let r=document.getElementById(t);r||(r=document.createElement("style"),r.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.appendChild(r)}):(document.head||document.documentElement).appendChild(r)),r.textContent=".artplayer-plugin-chapter .art-control-progress-inner{height:100%!important;background-color:transparent!important}.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-hover,.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-loaded,.artplayer-plugin-chapter .art-control-progress-inner>.art-progress-played{display:none!important}.artplayer-plugin-chapter .art-control-thumbnails{bottom:calc(var(--art-bottom-gap) + 64px)!important}.artplayer-plugin-chapter .art-chapters{position:absolute;z-index:0;inset:0;display:flex;align-items:center;gap:4px;height:100%;transform:scaleY(1.25)}.artplayer-plugin-chapter .art-chapters .art-chapter{display:flex;align-items:center;height:100%}.artplayer-plugin-chapter .art-chapters .art-chapter .art-chapter-inner{position:relative;cursor:pointer;width:100%;height:50%;border-radius:10px;overflow:hidden;transition:height var(--art-transition-duration) ease;background-color:var(--art-progress-color)}.artplayer-plugin-chapter .art-chapters .art-chapter:hover .art-chapter-inner{height:100%}.artplayer-plugin-chapter .art-chapter-title{transform-origin:bottom center;transform:scale(.5);opacity:0;position:absolute;z-index:70;top:-50px;left:0;padding:3px 5px;line-height:1;font-size:14px;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}.artplayer-plugin-chapter.art-progress-hover .art-chapter-title{transform:scale(1);opacity:1}"}export{t as default};
|
||||
const style = ".artplayer-plugin-chapter .art-control-progress-inner {\n height: 100% !important;\n background-color: transparent !important;\n}\n.artplayer-plugin-chapter .art-control-progress-inner > .art-progress-hover,\n.artplayer-plugin-chapter .art-control-progress-inner > .art-progress-loaded,\n.artplayer-plugin-chapter .art-control-progress-inner > .art-progress-played {\n display: none !important;\n}\n.artplayer-plugin-chapter .art-control-thumbnails {\n bottom: calc(var(--art-bottom-gap) + 64px) !important;\n}\n.artplayer-plugin-chapter .art-chapters {\n position: absolute;\n z-index: 0;\n inset: 0;\n display: flex;\n align-items: center;\n gap: 4px;\n height: 100%;\n transform: scaleY(1.25);\n}\n.artplayer-plugin-chapter .art-chapters .art-chapter {\n display: flex;\n align-items: center;\n height: 100%;\n}\n.artplayer-plugin-chapter .art-chapters .art-chapter .art-chapter-inner {\n position: relative;\n cursor: pointer;\n width: 100%;\n height: 50%;\n border-radius: 10px;\n overflow: hidden;\n transition: height var(--art-transition-duration) ease;\n background-color: var(--art-progress-color);\n}\n.artplayer-plugin-chapter .art-chapters .art-chapter:hover .art-chapter-inner {\n height: 100%;\n}\n.artplayer-plugin-chapter .art-chapter-title {\n transform-origin: bottom center;\n transform: scale(0.5);\n opacity: 0;\n position: absolute;\n z-index: 70;\n top: -50px;\n left: 0;\n padding: 3px 5px;\n line-height: 1;\n font-size: 14px;\n border-radius: var(--art-border-radius);\n white-space: nowrap;\n background-color: var(--art-tip-background);\n transition: transform var(--art-transition-duration) ease, opacity var(--art-transition-duration) ease;\n}\n.artplayer-plugin-chapter.art-progress-hover .art-chapter-title {\n transform: scale(1);\n opacity: 1;\n}\n";
|
||||
function artplayerPluginChapter(option = {}) {
|
||||
return (art) => {
|
||||
const { $player } = art.template;
|
||||
const { setStyle, append, clamp, query, addClass, removeClass } = art.constructor.utils;
|
||||
const html = `
|
||||
<div class="art-chapter">
|
||||
<div class="art-chapter-inner">
|
||||
<div class="art-progress-hover"></div>
|
||||
<div class="art-progress-loaded"></div>
|
||||
<div class="art-progress-played"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
let $chapters = [];
|
||||
const $inner = art.query(".art-control-progress-inner");
|
||||
const $control = append($inner, '<div class="art-chapters"></div>');
|
||||
const $title = append($inner, '<div class="art-chapter-title"></div>');
|
||||
function showTitle({ $chapter, width }) {
|
||||
const title = $chapter.dataset.title.trim();
|
||||
if (title) {
|
||||
$title.textContent = title;
|
||||
const titleWidth = $title.clientWidth;
|
||||
if (width <= titleWidth / 2) {
|
||||
setStyle($title, "left", 0);
|
||||
} else if (width > $inner.clientWidth - titleWidth / 2) {
|
||||
setStyle($title, "left", `${$inner.clientWidth - titleWidth}px`);
|
||||
} else {
|
||||
setStyle($title, "left", `${width - titleWidth / 2}px`);
|
||||
}
|
||||
}
|
||||
}
|
||||
function update(chapters = []) {
|
||||
$chapters = [];
|
||||
$control.textContent = "";
|
||||
removeClass($player, "artplayer-plugin-chapter");
|
||||
if (!Array.isArray(chapters))
|
||||
return;
|
||||
if (!chapters.length)
|
||||
return;
|
||||
if (!art.duration)
|
||||
return;
|
||||
chapters = chapters.sort((a, b) => a.start - b.start);
|
||||
for (let i = 0; i < chapters.length; i++) {
|
||||
const chapter = chapters[i];
|
||||
const nextChapter = chapters[i + 1];
|
||||
if (chapter.end === Infinity) {
|
||||
chapter.end = art.duration;
|
||||
}
|
||||
if (typeof chapter.start !== "number" || typeof chapter.end !== "number" || typeof chapter.title !== "string") {
|
||||
throw new TypeError("Illegal chapter data type");
|
||||
}
|
||||
if (chapter.start < 0 || chapter.end > art.duration || chapter.start >= chapter.end) {
|
||||
throw new Error("Illegal chapter time point");
|
||||
}
|
||||
if (nextChapter && chapter.end > nextChapter.start) {
|
||||
throw new Error("Illegal chapter time point");
|
||||
}
|
||||
}
|
||||
if (chapters[0].start > 0) {
|
||||
chapters.unshift({ start: 0, end: chapters[0].start, title: "" });
|
||||
}
|
||||
if (chapters[chapters.length - 1].end < art.duration) {
|
||||
chapters.push({ start: chapters[chapters.length - 1].end, end: art.duration, title: "" });
|
||||
}
|
||||
for (let i = 0; i < chapters.length - 1; i++) {
|
||||
if (chapters[i].end !== chapters[i + 1].start) {
|
||||
chapters.splice(i + 1, 0, {
|
||||
start: chapters[i].end,
|
||||
end: chapters[i + 1].start,
|
||||
title: ""
|
||||
});
|
||||
}
|
||||
}
|
||||
$chapters = chapters.map((chapter) => {
|
||||
const $chapter = append($control, html);
|
||||
const start = clamp(chapter.start, 0, art.duration);
|
||||
const end = clamp(chapter.end, 0, art.duration);
|
||||
const duration = end - start;
|
||||
const percentage = duration / art.duration;
|
||||
$chapter.dataset.start = start;
|
||||
$chapter.dataset.end = end;
|
||||
$chapter.dataset.duration = duration;
|
||||
$chapter.dataset.title = chapter.title.trim();
|
||||
$chapter.style.width = `${percentage * 100}%`;
|
||||
return {
|
||||
$chapter,
|
||||
$hover: query(".art-progress-hover", $chapter),
|
||||
$loaded: query(".art-progress-loaded", $chapter),
|
||||
$played: query(".art-progress-played", $chapter)
|
||||
};
|
||||
});
|
||||
addClass($player, "artplayer-plugin-chapter");
|
||||
art.emit("setBar", "loaded", art.loaded || 0);
|
||||
}
|
||||
art.on("setBar", (type, percentage) => {
|
||||
if (!$chapters.length)
|
||||
return;
|
||||
for (let i = 0; i < $chapters.length; i++) {
|
||||
const { $chapter, $loaded, $played, $hover } = $chapters[i];
|
||||
const $target = {
|
||||
hover: $hover,
|
||||
loaded: $loaded,
|
||||
played: $played
|
||||
}[type];
|
||||
if (!$target)
|
||||
return;
|
||||
const width = $control.clientWidth * percentage;
|
||||
const currentTime = art.duration * percentage;
|
||||
const duration = Number.parseFloat($chapter.dataset.duration);
|
||||
const start = Number.parseFloat($chapter.dataset.start);
|
||||
const end = Number.parseFloat($chapter.dataset.end);
|
||||
if (currentTime < start) {
|
||||
setStyle($target, "width", 0);
|
||||
}
|
||||
if (currentTime > end) {
|
||||
setStyle($target, "width", "100%");
|
||||
}
|
||||
if (currentTime >= start && currentTime <= end) {
|
||||
const percentage2 = (currentTime - start) / duration;
|
||||
setStyle($target, "width", `${percentage2 * 100}%`);
|
||||
if (type === "hover" && width > 0) {
|
||||
showTitle({ $chapter, width });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
art.once("video:loadedmetadata", () => update(option.chapters));
|
||||
return {
|
||||
name: "artplayerPluginChapter",
|
||||
update: ({ chapters }) => update(chapters)
|
||||
};
|
||||
};
|
||||
}
|
||||
if (typeof document !== "undefined") {
|
||||
const id = "artplayer-plugin-chapter";
|
||||
let $style = document.getElementById(id);
|
||||
if (!$style) {
|
||||
$style = document.createElement("style");
|
||||
$style.id = id;
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.head.appendChild($style);
|
||||
});
|
||||
} else {
|
||||
(document.head || document.documentElement).appendChild($style);
|
||||
}
|
||||
}
|
||||
$style.textContent = style;
|
||||
}
|
||||
export {
|
||||
artplayerPluginChapter as default
|
||||
};
|
||||
+170
-1
@@ -4,4 +4,173 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
function e(e){let t=!1,o=null,a=null;const n=e=>{const t=document.querySelector(".art-icon-cast");if(t)switch(e){case"connected":t.style.color="red";break;case"connecting":case"disconnecting":t.style.color="orange";break;default:t.style.color="white"}},c=()=>new Promise((c,s)=>{var i;window.__onGCastApiAvailable=i=>{if(i){const s=window.cast.framework.CastContext.getInstance();s.setOptions({receiverApplicationId:window.chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID,autoJoinPolicy:window.chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED}),s.addEventListener(window.cast.framework.CastContextEventType.SESSION_STATE_CHANGED,t=>{const c=window.cast.framework.SessionState;switch(a=t.sessionState,o=t.session,t.sessionState){case c.NO_SESSION:e.onStateChange?.("disconnected"),n("disconnected");break;case c.SESSION_STARTING:e.onStateChange?.("connecting"),n("connecting");break;case c.SESSION_STARTED:e.onStateChange?.("connected"),n("connected");break;case c.SESSION_ENDING:e.onStateChange?.("disconnecting"),n("disconnecting");break;case c.SESSION_RESUMED:e.onStateChange?.("connected"),n("connected")}}),s.addEventListener(window.cast.framework.CastContextEventType.CAST_STATE_CHANGED,t=>{const o=window.cast.framework.CastState;switch(t.castState){case o.NO_DEVICES_AVAILABLE:e.onCastAvailable?.(!1);break;case o.NOT_CONNECTED:case o.CONNECTING:case o.CONNECTED:e.onCastAvailable?.(!0)}}),t=!0,c()}else s(new Error("Cast API is not available"))},window.chrome&&window.chrome.cast||(i=e.sdk||"https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1",new Promise((e,t)=>{const o=document.createElement("script");o.src=i,o.onload=e,o.onerror=t,document.body.appendChild(o)})).catch(s)}),s=(t,o)=>{const a=e.url||t.option.url,n=new window.chrome.cast.media.MediaInfo(a,e.mimeType||function(e){return{mp4:"video/mp4",webm:"video/webm",ogg:"video/ogg",ogv:"video/ogg",mp3:"audio/mp3",wav:"audio/wav",flv:"video/x-flv",mov:"video/quicktime",avi:"video/x-msvideo",wmv:"video/x-ms-wmv",mpd:"application/dash+xml",m3u8:"application/x-mpegURL"}[e.split("?")[0].split("#")[0].split(".").pop().toLowerCase()]||"application/octet-stream"}(a)),c=new window.chrome.cast.media.LoadRequest(n);o.loadMedia(c).then(()=>{t.notice.show="Casting started",e.onCastStart?.()}).catch(o=>{throw t.notice.show="Error casting media",e.onError?.(o),o})};return async n=>(n.controls.add({name:"chromecast",position:"right",tooltip:"Chromecast",html:`<i class="art-icon art-icon-cast">${e.icon||'<svg height="20" width="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M512 96H64v99c-13-2-26.4-3-40-3H0V96C0 60.7 28.7 32 64 32H512c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H288V456c0-13.6-1-27-3-40H512V96zM24 224c128.1 0 232 103.9 232 232c0 13.3-10.7 24-24 24s-24-10.7-24-24c0-101.6-82.4-184-184-184c-13.3 0-24-10.7-24-24s10.7-24 24-24zm8 192a32 32 0 1 1 0 64 32 32 0 1 1 0-64zM0 344c0-13.3 10.7-24 24-24c75.1 0 136 60.9 136 136c0 13.3-10.7 24-24 24s-24-10.7-24-24c0-48.6-39.4-88-88-88c-13.3 0-24-10.7-24-24z"/></svg>'}</i>`,click:async()=>{if(!t)try{await c()}catch(i){throw n.notice.show="Failed to initialize Cast API",e.onError?.(i),i}const a=window.cast.framework.CastContext.getInstance();if(o)s(n,o);else try{const e=await a.requestSession();s(n,e)}catch(i){throw n.notice.show="Error connecting to cast session",e.onError?.(i),i}}}),{name:"artplayerPluginChromecast",getCastState:()=>a,isCasting:()=>null!==o})}export{e as default};
|
||||
function loadScript(src) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement("script");
|
||||
script.src = src;
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
document.body.appendChild(script);
|
||||
});
|
||||
}
|
||||
function getMimeType(url) {
|
||||
const extension = url.split("?")[0].split("#")[0].split(".").pop().toLowerCase();
|
||||
const mimeTypes = {
|
||||
mp4: "video/mp4",
|
||||
webm: "video/webm",
|
||||
ogg: "video/ogg",
|
||||
ogv: "video/ogg",
|
||||
mp3: "audio/mp3",
|
||||
wav: "audio/wav",
|
||||
flv: "video/x-flv",
|
||||
mov: "video/quicktime",
|
||||
avi: "video/x-msvideo",
|
||||
wmv: "video/x-ms-wmv",
|
||||
mpd: "application/dash+xml",
|
||||
m3u8: "application/x-mpegURL"
|
||||
};
|
||||
return mimeTypes[extension] || "application/octet-stream";
|
||||
}
|
||||
function artplayerPluginChromecast(option) {
|
||||
const DEFAULT_ICON = `<svg height="20" width="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M512 96H64v99c-13-2-26.4-3-40-3H0V96C0 60.7 28.7 32 64 32H512c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H288V456c0-13.6-1-27-3-40H512V96zM24 224c128.1 0 232 103.9 232 232c0 13.3-10.7 24-24 24s-24-10.7-24-24c0-101.6-82.4-184-184-184c-13.3 0-24-10.7-24-24s10.7-24 24-24zm8 192a32 32 0 1 1 0 64 32 32 0 1 1 0-64zM0 344c0-13.3 10.7-24 24-24c75.1 0 136 60.9 136 136c0 13.3-10.7 24-24 24s-24-10.7-24-24c0-48.6-39.4-88-88-88c-13.3 0-24-10.7-24-24z"/></svg>`;
|
||||
const DEFAULT_SDK = "https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1";
|
||||
let isCastInitialized = false;
|
||||
let castSession = null;
|
||||
let castState = null;
|
||||
const updateCastButton = (state) => {
|
||||
const button = document.querySelector(".art-icon-cast");
|
||||
if (button) {
|
||||
switch (state) {
|
||||
case "connected":
|
||||
button.style.color = "red";
|
||||
break;
|
||||
case "connecting":
|
||||
case "disconnecting":
|
||||
button.style.color = "orange";
|
||||
break;
|
||||
case "disconnected":
|
||||
default:
|
||||
button.style.color = "white";
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
const initializeCastApi = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
window.__onGCastApiAvailable = (isAvailable) => {
|
||||
if (isAvailable) {
|
||||
const context = window.cast.framework.CastContext.getInstance();
|
||||
context.setOptions({
|
||||
receiverApplicationId: window.chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID,
|
||||
autoJoinPolicy: window.chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED
|
||||
});
|
||||
context.addEventListener(
|
||||
window.cast.framework.CastContextEventType.SESSION_STATE_CHANGED,
|
||||
(event) => {
|
||||
const SessionState = window.cast.framework.SessionState;
|
||||
castState = event.sessionState;
|
||||
castSession = event.session;
|
||||
switch (event.sessionState) {
|
||||
case SessionState.NO_SESSION:
|
||||
option.onStateChange?.("disconnected");
|
||||
updateCastButton("disconnected");
|
||||
break;
|
||||
case SessionState.SESSION_STARTING:
|
||||
option.onStateChange?.("connecting");
|
||||
updateCastButton("connecting");
|
||||
break;
|
||||
case SessionState.SESSION_STARTED:
|
||||
option.onStateChange?.("connected");
|
||||
updateCastButton("connected");
|
||||
break;
|
||||
case SessionState.SESSION_ENDING:
|
||||
option.onStateChange?.("disconnecting");
|
||||
updateCastButton("disconnecting");
|
||||
break;
|
||||
case SessionState.SESSION_RESUMED:
|
||||
option.onStateChange?.("connected");
|
||||
updateCastButton("connected");
|
||||
break;
|
||||
}
|
||||
}
|
||||
);
|
||||
context.addEventListener(window.cast.framework.CastContextEventType.CAST_STATE_CHANGED, (event) => {
|
||||
const CastState = window.cast.framework.CastState;
|
||||
switch (event.castState) {
|
||||
case CastState.NO_DEVICES_AVAILABLE:
|
||||
option.onCastAvailable?.(false);
|
||||
break;
|
||||
case CastState.NOT_CONNECTED:
|
||||
option.onCastAvailable?.(true);
|
||||
break;
|
||||
case CastState.CONNECTING:
|
||||
case CastState.CONNECTED:
|
||||
option.onCastAvailable?.(true);
|
||||
break;
|
||||
}
|
||||
});
|
||||
isCastInitialized = true;
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error("Cast API is not available"));
|
||||
}
|
||||
};
|
||||
if (!window.chrome || !window.chrome.cast) {
|
||||
loadScript(option.sdk || DEFAULT_SDK).catch(reject);
|
||||
}
|
||||
});
|
||||
};
|
||||
const castVideo = (art, session) => {
|
||||
const url = option.url || art.option.url;
|
||||
const mediaInfo = new window.chrome.cast.media.MediaInfo(url, option.mimeType || getMimeType(url));
|
||||
const request = new window.chrome.cast.media.LoadRequest(mediaInfo);
|
||||
session.loadMedia(request).then(() => {
|
||||
art.notice.show = "Casting started";
|
||||
option.onCastStart?.();
|
||||
}).catch((error) => {
|
||||
art.notice.show = "Error casting media";
|
||||
option.onError?.(error);
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
return async (art) => {
|
||||
art.controls.add({
|
||||
name: "chromecast",
|
||||
position: "right",
|
||||
tooltip: "Chromecast",
|
||||
html: `<i class="art-icon art-icon-cast">${option.icon || DEFAULT_ICON}</i>`,
|
||||
click: async () => {
|
||||
if (!isCastInitialized) {
|
||||
try {
|
||||
await initializeCastApi();
|
||||
} catch (error) {
|
||||
art.notice.show = "Failed to initialize Cast API";
|
||||
option.onError?.(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const context = window.cast.framework.CastContext.getInstance();
|
||||
if (castSession) {
|
||||
castVideo(art, castSession);
|
||||
} else {
|
||||
try {
|
||||
const session = await context.requestSession();
|
||||
castVideo(art, session);
|
||||
} catch (error) {
|
||||
art.notice.show = "Error connecting to cast session";
|
||||
option.onError?.(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return {
|
||||
name: "artplayerPluginChromecast",
|
||||
getCastState: () => castState,
|
||||
isCasting: () => castSession !== null
|
||||
};
|
||||
};
|
||||
}
|
||||
export {
|
||||
artplayerPluginChromecast as default
|
||||
};
|
||||
+49169
-1
File diff suppressed because it is too large.
Load diff
+1561
-1
File diff suppressed because it is too large.
Load diff
+161
-1
@@ -4,4 +4,164 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
function t(t,e){const o=/* @__PURE__ */new Map;return t.filter(t=>{const a=t[e];return void 0===a||!o.has(a)&&o.set(a,1)})}function e(e={}){return o=>{const{$video:a}=o.template,{errorHandle:n}=o.constructor.utils;function i(){n(o.dash.getVideoElement()===a,'Cannot find instance of DASH from "art.dash"'),function(a){const n=a.getBitrateInfoListFor("video");if(!n||!n.length)return;const i=e.quality||{},l=i.auto||"Auto",s=i.title||"Quality",r=i.getName||(t=>`${t.height}p`),c=a.getQualityFor("video"),u=a.getSettings().streaming.abr.autoSwitchBitrate.video,h=u?l:r(n[c]),d=t(n.map(t=>({html:r(t),value:t.qualityIndex,default:c===t.qualityIndex&&!u})),"html").sort((t,e)=>e.value-t.value);d.push({html:l,value:"auto",default:u});const g=t=>("auto"===t.value?a.updateSettings({streaming:{abr:{autoSwitchBitrate:{video:!0}}}}):(a.updateSettings({streaming:{abr:{autoSwitchBitrate:{video:!1}}}}),a.setQualityFor("video",t.value)),o.notice.show=`${s}: ${t.html}`,i.control&&o.controls.check(t),i.setting&&o.setting.check(t),t.html);i.control&&o.controls.update({name:"dash-quality",position:"right",html:h,style:{padding:"0 10px"},selector:d,onSelect:g}),i.setting&&o.setting.update({name:"dash-quality",tooltip:h,html:s,icon:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>',width:200,selector:d,onSelect:g})}(o.dash),function(a){const n=a.getTracksFor("audio");if(!n||!n.length)return;const i=e.audio||{},l=i.auto||"Auto",s=i.title||"Audio",r=i.getName||(t=>t.lang||t.id),c=a.getCurrentTrackFor("audio")||n[0],u=c?r(c):l,h=t(n.map(t=>({html:r(t),value:t,default:c===t})),"html"),d=t=>(a.setCurrentTrack(t.value),o.notice.show=`${s}: ${t.html}`,i.control&&o.controls.check(t),i.setting&&o.setting.check(t),t.html);i.control&&o.controls.update({name:"dash-audio",position:"right",html:u,style:{padding:"0 10px"},selector:h,onSelect:d}),i.setting&&o.setting.update({name:"dash-audio",tooltip:u,html:s,icon:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>',width:200,selector:h,onSelect:d})}(o.dash)}return o.on("ready",i),o.on("restart",i),{name:"artplayerPluginDashControl",update:i}}}export{e as default};
|
||||
const $audio = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>';
|
||||
const $quality = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>';
|
||||
function uniqBy(array, property) {
|
||||
const seen = /* @__PURE__ */ new Map();
|
||||
return array.filter((item) => {
|
||||
const key = item[property];
|
||||
if (key === void 0) {
|
||||
return true;
|
||||
}
|
||||
return !seen.has(key) && seen.set(key, 1);
|
||||
});
|
||||
}
|
||||
function artplayerPluginDashControl(option = {}) {
|
||||
return (art) => {
|
||||
const { $video } = art.template;
|
||||
const { errorHandle } = art.constructor.utils;
|
||||
function updateQuality(dash) {
|
||||
const qualities = dash.getBitrateInfoListFor("video");
|
||||
if (!qualities || !qualities.length)
|
||||
return;
|
||||
const config = option.quality || {};
|
||||
const auto = config.auto || "Auto";
|
||||
const title = config.title || "Quality";
|
||||
const getName = config.getName || ((level) => `${level.height}p`);
|
||||
const currentQuality = dash.getQualityFor("video");
|
||||
const currentAuto = dash.getSettings().streaming.abr.autoSwitchBitrate.video;
|
||||
const defaultHtml = currentAuto ? auto : getName(qualities[currentQuality]);
|
||||
const selector = uniqBy(
|
||||
qualities.map((item) => {
|
||||
return {
|
||||
html: getName(item),
|
||||
value: item.qualityIndex,
|
||||
default: currentQuality === item.qualityIndex && !currentAuto
|
||||
};
|
||||
}),
|
||||
"html"
|
||||
).sort((a, b) => b.value - a.value);
|
||||
selector.push({
|
||||
html: auto,
|
||||
value: "auto",
|
||||
default: currentAuto
|
||||
});
|
||||
const onSelect = (item) => {
|
||||
if (item.value === "auto") {
|
||||
dash.updateSettings({
|
||||
streaming: {
|
||||
abr: {
|
||||
autoSwitchBitrate: {
|
||||
video: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
dash.updateSettings({
|
||||
streaming: {
|
||||
abr: {
|
||||
autoSwitchBitrate: {
|
||||
video: false
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
dash.setQualityFor("video", item.value);
|
||||
}
|
||||
art.notice.show = `${title}: ${item.html}`;
|
||||
if (config.control)
|
||||
art.controls.check(item);
|
||||
if (config.setting)
|
||||
art.setting.check(item);
|
||||
return item.html;
|
||||
};
|
||||
if (config.control) {
|
||||
art.controls.update({
|
||||
name: "dash-quality",
|
||||
position: "right",
|
||||
html: defaultHtml,
|
||||
style: { padding: "0 10px" },
|
||||
selector,
|
||||
onSelect
|
||||
});
|
||||
}
|
||||
if (config.setting) {
|
||||
art.setting.update({
|
||||
name: "dash-quality",
|
||||
tooltip: defaultHtml,
|
||||
html: title,
|
||||
icon: $quality,
|
||||
width: 200,
|
||||
selector,
|
||||
onSelect
|
||||
});
|
||||
}
|
||||
}
|
||||
function updateAudio(dash) {
|
||||
const audioTracks = dash.getTracksFor("audio");
|
||||
if (!audioTracks || !audioTracks.length)
|
||||
return;
|
||||
const config = option.audio || {};
|
||||
const auto = config.auto || "Auto";
|
||||
const title = config.title || "Audio";
|
||||
const getName = config.getName || ((track) => track.lang || track.id);
|
||||
const currentTrack = dash.getCurrentTrackFor("audio") || audioTracks[0];
|
||||
const defaultHtml = currentTrack ? getName(currentTrack) : auto;
|
||||
const selector = uniqBy(
|
||||
audioTracks.map((item) => {
|
||||
return {
|
||||
html: getName(item),
|
||||
value: item,
|
||||
default: currentTrack === item
|
||||
};
|
||||
}),
|
||||
"html"
|
||||
);
|
||||
const onSelect = (item) => {
|
||||
dash.setCurrentTrack(item.value);
|
||||
art.notice.show = `${title}: ${item.html}`;
|
||||
if (config.control)
|
||||
art.controls.check(item);
|
||||
if (config.setting)
|
||||
art.setting.check(item);
|
||||
return item.html;
|
||||
};
|
||||
if (config.control) {
|
||||
art.controls.update({
|
||||
name: "dash-audio",
|
||||
position: "right",
|
||||
html: defaultHtml,
|
||||
style: { padding: "0 10px" },
|
||||
selector,
|
||||
onSelect
|
||||
});
|
||||
}
|
||||
if (config.setting) {
|
||||
art.setting.update({
|
||||
name: "dash-audio",
|
||||
tooltip: defaultHtml,
|
||||
html: title,
|
||||
icon: $audio,
|
||||
width: 200,
|
||||
selector,
|
||||
onSelect
|
||||
});
|
||||
}
|
||||
}
|
||||
function update() {
|
||||
errorHandle(art.dash.getVideoElement() === $video, 'Cannot find instance of DASH from "art.dash"');
|
||||
updateQuality(art.dash);
|
||||
updateAudio(art.dash);
|
||||
}
|
||||
art.on("ready", update);
|
||||
art.on("restart", update);
|
||||
return {
|
||||
name: "artplayerPluginDashControl",
|
||||
update
|
||||
};
|
||||
};
|
||||
}
|
||||
export {
|
||||
artplayerPluginDashControl as default
|
||||
};
|
||||
+188
-1
@@ -4,4 +4,191 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
function e(e={}){const t={width:480,height:270,fallbackToVideoPiP:!0,placeholder:"Playing in Document Picture-in-Picture",...e};return e=>{const n="documentPictureInPicture"in window&&"function"===typeof window.documentPictureInPicture?.requestWindow,{proxy:i,icons:o,i18n:r,template:{$player:c},constructor:{utils:{append:l,tooltip:d,addClass:a,removeClass:p,sleep:u}}}=e,s={win:null,originalParent:null,originalNext:null,currentDoc:null,placeholder:null};async function m(){if(!n&&t.fallbackToVideoPiP)return e.pip=!0,void console.warn("[artplayer-plugin-document-pip] Document Picture-in-Picture is not supported, falling back to Video Picture-in-Picture");if(!s.win)try{const n=await window.documentPictureInPicture.requestWindow({width:t.width,height:t.height});s.win=n,s.currentDoc=n.document,s.placeholder=document.createElement("div"),s.placeholder.className="artplayer-document-pip-placeholder",s.placeholder.style.cssText="display:flex;justify-content:center;align-items:center;width:100%;height:100%;",s.placeholder.textContent=t.placeholder,s.originalParent=c.parentNode,s.originalNext=c.nextSibling,s.originalParent.insertBefore(s.placeholder,s.originalNext);const i=n.document.createElement("div");i.id="__art_dpip_root",n.document.body.appendChild(i),function(e){const t=e.createElement("style");if(t.textContent="\n html, body { margin:0; padding:0; width:100%; height:100%; background:#000; overflow:hidden; }\n #__art_dpip_root { position:absolute; inset:0; display:flex; }\n #__art_dpip_root > * { width:100% !important; height:100% !important; }\n ",e.head.appendChild(t),!e.querySelector('meta[name="viewport"]')){const t=e.createElement("meta");t.name="viewport",t.content="width=device-width,initial-scale=1",e.head.appendChild(t)}try{document.querySelectorAll('link[rel="stylesheet"]').forEach(t=>{const n=e.createElement("link");n.rel="stylesheet",n.href=t.href,t.media&&(n.media=t.media),t.crossOrigin&&(n.crossOrigin=t.crossOrigin),t.referrerPolicy&&(n.referrerPolicy=t.referrerPolicy),e.head.appendChild(n)})}catch{}try{document.querySelectorAll("style").forEach(t=>{const n=e.createElement("style");n.textContent=t.textContent,e.head.appendChild(n)})}catch{}}(n.document);const o=n.document.adoptNode(c);i.appendChild(o),s.currentDoc=n.document;const r=()=>e.resize?.(),l=()=>h();n.addEventListener("resize",r),n.addEventListener("pagehide",l),n.addEventListener("unload",l),s.cleanup=()=>{n.removeEventListener("resize",r),n.removeEventListener("pagehide",l),n.removeEventListener("unload",l)},a(c,"artplayer-document-pip"),e.events.bindGlobalEvents?.(),e.emit("document-pip",!0),await u(100),e.emit("resize")}catch(i){e.notice.show="Document Picture-in-Picture open failed",console.warn("[artplayer-plugin-document-pip] open failed:",i)}}async function h(){if(s.win)try{s.cleanup?.(),function(){if(!s.placeholder)return;const e=s.placeholder.ownerDocument||document,t=e.adoptNode(c);s.originalParent.insertBefore(t,s.placeholder),s.placeholder.remove(),s.placeholder=null,s.currentDoc=e}(),s.win.close(),s.win=null,p(c,"artplayer-document-pip"),e.events.bindGlobalEvents?.(),e.emit("document-pip",!1),await u(100),e.emit("resize")}catch(t){e.notice.show="Document Picture-in-Picture close failed",console.warn("[artplayer-plugin-document-pip] close failed:",t)}}function y(){s.win?h():m()}return e.controls.add({name:"document-pip",position:"right",index:40,tooltip:e.i18n.get("PIP Mode"),mounted:t=>{l(t,o.pip),i(t,"click",y),e.on("document-pip",e=>{d(t,r.get(e?"Exit PIP Mode":"PIP Mode"))})}}),e.on("destroy",h),{name:"artplayerPluginDocumentPip",get isSupported(){return n},get isActive(){return!!s.win},open:m,close:h,toggle:y}}}if("undefined"!==typeof document){const e="artplayer-plugin-document-pip";let t=document.getElementById(e);t||(t=document.createElement("style"),t.id=e,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.appendChild(t)}):(document.head||document.documentElement).appendChild(t)),t.textContent=".artplayer-document-pip.art-video-player{width:100%!important;height:100%!important}"}export{e as default};
|
||||
const style = ".artplayer-document-pip.art-video-player {\n width: 100% !important;\n height: 100% !important;\n}\n";
|
||||
function artplayerPluginDocumentPip(userOptions = {}) {
|
||||
const options = {
|
||||
width: 480,
|
||||
height: 270,
|
||||
fallbackToVideoPiP: true,
|
||||
placeholder: `Playing in Document Picture-in-Picture`,
|
||||
...userOptions
|
||||
};
|
||||
return (art) => {
|
||||
const isSupported = "documentPictureInPicture" in window && typeof window.documentPictureInPicture?.requestWindow === "function";
|
||||
const { proxy, icons, i18n, template: { $player }, constructor: { utils: { append, tooltip, addClass, removeClass, sleep } } } = art;
|
||||
const state = {
|
||||
win: null,
|
||||
originalParent: null,
|
||||
originalNext: null,
|
||||
currentDoc: null,
|
||||
placeholder: null
|
||||
};
|
||||
function copyStylesTo(targetDoc) {
|
||||
const base = targetDoc.createElement("style");
|
||||
base.textContent = `
|
||||
html, body { margin:0; padding:0; width:100%; height:100%; background:#000; overflow:hidden; }
|
||||
#__art_dpip_root { position:absolute; inset:0; display:flex; }
|
||||
#__art_dpip_root > * { width:100% !important; height:100% !important; }
|
||||
`;
|
||||
targetDoc.head.appendChild(base);
|
||||
if (!targetDoc.querySelector('meta[name="viewport"]')) {
|
||||
const meta = targetDoc.createElement("meta");
|
||||
meta.name = "viewport";
|
||||
meta.content = "width=device-width,initial-scale=1";
|
||||
targetDoc.head.appendChild(meta);
|
||||
}
|
||||
try {
|
||||
document.querySelectorAll('link[rel="stylesheet"]').forEach((link) => {
|
||||
const clone = targetDoc.createElement("link");
|
||||
clone.rel = "stylesheet";
|
||||
clone.href = link.href;
|
||||
if (link.media)
|
||||
clone.media = link.media;
|
||||
if (link.crossOrigin)
|
||||
clone.crossOrigin = link.crossOrigin;
|
||||
if (link.referrerPolicy)
|
||||
clone.referrerPolicy = link.referrerPolicy;
|
||||
targetDoc.head.appendChild(clone);
|
||||
});
|
||||
} catch {
|
||||
}
|
||||
try {
|
||||
document.querySelectorAll("style").forEach((style2) => {
|
||||
const clone = targetDoc.createElement("style");
|
||||
clone.textContent = style2.textContent;
|
||||
targetDoc.head.appendChild(clone);
|
||||
});
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
async function open() {
|
||||
if (!isSupported && options.fallbackToVideoPiP) {
|
||||
art.pip = true;
|
||||
console.warn("[artplayer-plugin-document-pip] Document Picture-in-Picture is not supported, falling back to Video Picture-in-Picture");
|
||||
return;
|
||||
}
|
||||
if (state.win)
|
||||
return;
|
||||
try {
|
||||
const pipWin = await window.documentPictureInPicture.requestWindow({
|
||||
width: options.width,
|
||||
height: options.height
|
||||
});
|
||||
state.win = pipWin;
|
||||
state.currentDoc = pipWin.document;
|
||||
state.placeholder = document.createElement("div");
|
||||
state.placeholder.className = "artplayer-document-pip-placeholder";
|
||||
state.placeholder.style.cssText = `display:flex;justify-content:center;align-items:center;width:100%;height:100%;`;
|
||||
state.placeholder.textContent = options.placeholder;
|
||||
state.originalParent = $player.parentNode;
|
||||
state.originalNext = $player.nextSibling;
|
||||
state.originalParent.insertBefore(state.placeholder, state.originalNext);
|
||||
const root = pipWin.document.createElement("div");
|
||||
root.id = "__art_dpip_root";
|
||||
pipWin.document.body.appendChild(root);
|
||||
copyStylesTo(pipWin.document);
|
||||
const adopted = pipWin.document.adoptNode($player);
|
||||
root.appendChild(adopted);
|
||||
state.currentDoc = pipWin.document;
|
||||
const onResize = () => art.resize?.();
|
||||
const onClose = () => close();
|
||||
pipWin.addEventListener("resize", onResize);
|
||||
pipWin.addEventListener("pagehide", onClose);
|
||||
pipWin.addEventListener("unload", onClose);
|
||||
state.cleanup = () => {
|
||||
pipWin.removeEventListener("resize", onResize);
|
||||
pipWin.removeEventListener("pagehide", onClose);
|
||||
pipWin.removeEventListener("unload", onClose);
|
||||
};
|
||||
addClass($player, "artplayer-document-pip");
|
||||
art.events.bindGlobalEvents?.();
|
||||
art.emit("document-pip", true);
|
||||
await sleep(100);
|
||||
art.emit("resize");
|
||||
} catch (err) {
|
||||
art.notice.show = "Document Picture-in-Picture open failed";
|
||||
console.warn("[artplayer-plugin-document-pip] open failed:", err);
|
||||
}
|
||||
}
|
||||
function restoreToOriginalDocument() {
|
||||
if (!state.placeholder)
|
||||
return;
|
||||
const backDoc = state.placeholder.ownerDocument || document;
|
||||
const adoptedBack = backDoc.adoptNode($player);
|
||||
state.originalParent.insertBefore(adoptedBack, state.placeholder);
|
||||
state.placeholder.remove();
|
||||
state.placeholder = null;
|
||||
state.currentDoc = backDoc;
|
||||
}
|
||||
async function close() {
|
||||
if (!state.win)
|
||||
return;
|
||||
try {
|
||||
state.cleanup?.();
|
||||
restoreToOriginalDocument();
|
||||
state.win.close();
|
||||
state.win = null;
|
||||
removeClass($player, "artplayer-document-pip");
|
||||
art.events.bindGlobalEvents?.();
|
||||
art.emit("document-pip", false);
|
||||
await sleep(100);
|
||||
art.emit("resize");
|
||||
} catch (err) {
|
||||
art.notice.show = "Document Picture-in-Picture close failed";
|
||||
console.warn("[artplayer-plugin-document-pip] close failed:", err);
|
||||
}
|
||||
}
|
||||
function toggle() {
|
||||
if (state.win) {
|
||||
close();
|
||||
} else {
|
||||
open();
|
||||
}
|
||||
}
|
||||
art.controls.add({
|
||||
name: "document-pip",
|
||||
position: "right",
|
||||
index: 40,
|
||||
tooltip: art.i18n.get("PIP Mode"),
|
||||
mounted: ($control) => {
|
||||
append($control, icons.pip);
|
||||
proxy($control, "click", toggle);
|
||||
art.on("document-pip", (value) => {
|
||||
tooltip($control, i18n.get(value ? "Exit PIP Mode" : "PIP Mode"));
|
||||
});
|
||||
}
|
||||
});
|
||||
art.on("destroy", close);
|
||||
return {
|
||||
name: "artplayerPluginDocumentPip",
|
||||
get isSupported() {
|
||||
return isSupported;
|
||||
},
|
||||
get isActive() {
|
||||
return !!state.win;
|
||||
},
|
||||
open,
|
||||
close,
|
||||
toggle
|
||||
};
|
||||
};
|
||||
}
|
||||
if (typeof document !== "undefined") {
|
||||
const id = "artplayer-plugin-document-pip";
|
||||
let $style = document.getElementById(id);
|
||||
if (!$style) {
|
||||
$style = document.createElement("style");
|
||||
$style.id = id;
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.head.appendChild($style);
|
||||
});
|
||||
} else {
|
||||
(document.head || document.documentElement).appendChild($style);
|
||||
}
|
||||
}
|
||||
$style.textContent = style;
|
||||
}
|
||||
export {
|
||||
artplayerPluginDocumentPip as default
|
||||
};
|
||||
+137
-1
@@ -4,4 +4,140 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
function t(t,e){const l=/* @__PURE__ */new Map;return t.filter(t=>{const o=t[e];return void 0===o||!l.has(o)&&l.set(o,1)})}function e(e={}){return l=>{const{$video:o}=l.template,{errorHandle:n}=l.constructor.utils;function a(){n(l.hls?.media===o,'Cannot find instance of HLS from "art.hls"'),function(o){if(!o.levels.length)return;const n=e.quality||{},a=n.auto||"Auto",c=n.title||"Quality",s=n.getName||(t=>t.name||`${t.height}P`),i=o.levels[o.currentLevel],r=i?s(i):a,u=t(o.levels.map((t,e)=>({html:s(t,e),value:e,default:o.currentLevel===e})),"html").sort((t,e)=>e.value-t.value);u.push({html:a,value:-1,default:-1===o.currentLevel});const h=t=>(o.currentLevel=t.value,l.notice.show=`${c}: ${t.html}`,n.control&&l.controls.check(t),n.setting&&l.setting.check(t),t.html);n.control&&l.controls.update({name:"hls-quality",position:"right",html:r,style:{padding:"0 10px"},selector:u,onSelect:h}),n.setting&&l.setting.update({name:"hls-quality",tooltip:r,html:c,icon:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>',width:200,selector:u,onSelect:h})}(l.hls),function(o){if(!o.audioTracks.length)return;const n=e.audio||{},a=n.auto||"Auto",c=n.title||"Audio",s=n.getName||(t=>t.name||t.lang||t.language),i=o.audioTracks[o.audioTrack],r=i?s(i):a,u=t(o.audioTracks.map((t,e)=>({html:s(t,e),value:t.id,default:o.audioTrack===t.id})),"html"),h=t=>(o.audioTrack=t.value,l.notice.show=`${c}: ${t.html}`,n.control&&l.controls.check(t),n.setting&&l.setting.check(t),t.html);n.control&&l.controls.update({name:"hls-audio",position:"right",html:r,style:{padding:"0 10px"},selector:u,onSelect:h}),n.setting&&l.setting.update({name:"hls-audio",tooltip:r,html:c,icon:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>',width:200,selector:u,onSelect:h})}(l.hls)}return l.on("ready",a),l.on("restart",a),{name:"artplayerPluginHlsControl",update:a}}}export{e as default};
|
||||
const $audio = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>';
|
||||
const $quality = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>';
|
||||
function uniqBy(array, property) {
|
||||
const seen = /* @__PURE__ */ new Map();
|
||||
return array.filter((item) => {
|
||||
const key = item[property];
|
||||
if (key === void 0) {
|
||||
return true;
|
||||
}
|
||||
return !seen.has(key) && seen.set(key, 1);
|
||||
});
|
||||
}
|
||||
function artplayerPluginHlsControl(option = {}) {
|
||||
return (art) => {
|
||||
const { $video } = art.template;
|
||||
const { errorHandle } = art.constructor.utils;
|
||||
function updateQuality(hls) {
|
||||
if (!hls.levels.length)
|
||||
return;
|
||||
const config = option.quality || {};
|
||||
const auto = config.auto || "Auto";
|
||||
const title = config.title || "Quality";
|
||||
const getName = config.getName || ((level) => level.name || `${level.height}P`);
|
||||
const defaultLevel = hls.levels[hls.currentLevel];
|
||||
const defaultHtml = defaultLevel ? getName(defaultLevel) : auto;
|
||||
const selector = uniqBy(
|
||||
hls.levels.map((item, index) => {
|
||||
return {
|
||||
html: getName(item, index),
|
||||
value: index,
|
||||
default: hls.currentLevel === index
|
||||
};
|
||||
}),
|
||||
"html"
|
||||
).sort((a, b) => b.value - a.value);
|
||||
selector.push({
|
||||
html: auto,
|
||||
value: -1,
|
||||
default: hls.currentLevel === -1
|
||||
});
|
||||
const onSelect = (item) => {
|
||||
hls.currentLevel = item.value;
|
||||
art.notice.show = `${title}: ${item.html}`;
|
||||
if (config.control)
|
||||
art.controls.check(item);
|
||||
if (config.setting)
|
||||
art.setting.check(item);
|
||||
return item.html;
|
||||
};
|
||||
if (config.control) {
|
||||
art.controls.update({
|
||||
name: "hls-quality",
|
||||
position: "right",
|
||||
html: defaultHtml,
|
||||
style: { padding: "0 10px" },
|
||||
selector,
|
||||
onSelect
|
||||
});
|
||||
}
|
||||
if (config.setting) {
|
||||
art.setting.update({
|
||||
name: "hls-quality",
|
||||
tooltip: defaultHtml,
|
||||
html: title,
|
||||
icon: $quality,
|
||||
width: 200,
|
||||
selector,
|
||||
onSelect
|
||||
});
|
||||
}
|
||||
}
|
||||
function updateAudio(hls) {
|
||||
if (!hls.audioTracks.length)
|
||||
return;
|
||||
const config = option.audio || {};
|
||||
const auto = config.auto || "Auto";
|
||||
const title = config.title || "Audio";
|
||||
const getName = config.getName || ((track) => track.name || track.lang || track.language);
|
||||
const defaultTrack = hls.audioTracks[hls.audioTrack];
|
||||
const defaultHtml = defaultTrack ? getName(defaultTrack) : auto;
|
||||
const selector = uniqBy(
|
||||
hls.audioTracks.map((item, index) => {
|
||||
return {
|
||||
html: getName(item, index),
|
||||
value: item.id,
|
||||
default: hls.audioTrack === item.id
|
||||
};
|
||||
}),
|
||||
"html"
|
||||
);
|
||||
const onSelect = (item) => {
|
||||
hls.audioTrack = item.value;
|
||||
art.notice.show = `${title}: ${item.html}`;
|
||||
if (config.control)
|
||||
art.controls.check(item);
|
||||
if (config.setting)
|
||||
art.setting.check(item);
|
||||
return item.html;
|
||||
};
|
||||
if (config.control) {
|
||||
art.controls.update({
|
||||
name: "hls-audio",
|
||||
position: "right",
|
||||
html: defaultHtml,
|
||||
style: { padding: "0 10px" },
|
||||
selector,
|
||||
onSelect
|
||||
});
|
||||
}
|
||||
if (config.setting) {
|
||||
art.setting.update({
|
||||
name: "hls-audio",
|
||||
tooltip: defaultHtml,
|
||||
html: title,
|
||||
icon: $audio,
|
||||
width: 200,
|
||||
selector,
|
||||
onSelect
|
||||
});
|
||||
}
|
||||
}
|
||||
function update() {
|
||||
errorHandle(art.hls?.media === $video, 'Cannot find instance of HLS from "art.hls"');
|
||||
updateQuality(art.hls);
|
||||
updateAudio(art.hls);
|
||||
}
|
||||
art.on("ready", update);
|
||||
art.on("restart", update);
|
||||
return {
|
||||
name: "artplayerPluginHlsControl",
|
||||
update
|
||||
};
|
||||
};
|
||||
}
|
||||
export {
|
||||
artplayerPluginHlsControl as default
|
||||
};
|
||||
@@ -4,4 +4,575 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
var e=Object.defineProperty,t=(t,s,i)=>((t,s,i)=>s in t?e(t,s,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[s]=i)(t,"symbol"!==typeof s?s+"":s,i);typeof HTMLVideoElement<"u"&&!("requestVideoFrameCallback"in HTMLVideoElement.prototype)&&"getVideoPlaybackQuality"in HTMLVideoElement.prototype&&(HTMLVideoElement.prototype._rvfcpolyfillmap={},HTMLVideoElement.prototype.requestVideoFrameCallback=function(e){const t=performance.now(),s=this.getVideoPlaybackQuality(),i=this.mozPresentedFrames||this.mozPaintedFrames||s.totalVideoFrames-s.droppedVideoFrames,a=(r,n)=>{const o=this.getVideoPlaybackQuality(),h=this.mozPresentedFrames||this.mozPaintedFrames||o.totalVideoFrames-o.droppedVideoFrames;if(h>i){const i=this.mozFrameDelay||o.totalFrameDelay-s.totalFrameDelay||0,a=n-r;e(n,{presentationTime:n+1e3*i,expectedDisplayTime:n+a,width:this.videoWidth,height:this.videoHeight,mediaTime:Math.max(0,this.currentTime||0)+a/1e3,presentedFrames:h,processingDuration:i}),delete this._rvfcpolyfillmap[t]}else this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>a(n,e))};return this._rvfcpolyfillmap[t]=requestAnimationFrame(e=>a(t,e)),t},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(e){cancelAnimationFrame(this._rvfcpolyfillmap[e]),delete this._rvfcpolyfillmap[e]});const s={bt709:"BT709",bt470bg:"BT601",smpte170m:"BT601"},i={BT601:{BT709:"1.0863 -0.0723 -0.014 0 0 0.0965 0.8451 0.0584 0 0 -0.0141 -0.0277 1.0418"},BT709:{BT601:"0.9137 0.0784 0.0079 0 0 -0.1049 1.1722 -0.0671 0 0 0.0096 0.0322 0.9582"},FCC:{BT709:"1.0873 -0.0736 -0.0137 0 0 0.0974 0.8494 0.0531 0 0 -0.0127 -0.0251 1.0378",BT601:"1.001 -0.0008 -0.0002 0 0 0.0009 1.005 -0.006 0 0 0.0013 0.0027 0.996"},SMPTE240M:{BT709:"0.9993 0.0006 0.0001 0 0 -0.0004 0.9812 0.0192 0 0 -0.0034 -0.0114 1.0148",BT601:"0.913 0.0774 0.0096 0 0 -0.1051 1.1508 -0.0456 0 0 0.0063 0.0207 0.973"}},a=class e extends EventTarget{constructor(t){if(super(),!globalThis.Worker)throw this.destroy("Worker not supported");if(!t)throw this.destroy("No options provided");this._loaded=new Promise(e=>{this._init=e});const s=e._test();if(this._onDemandRender="requestVideoFrameCallback"in HTMLVideoElement.prototype&&(t.onDemandRender??!0),this._offscreenRender="transferControlToOffscreen"in HTMLCanvasElement.prototype&&!t.canvas&&(t.offscreenRender??!0),this.timeOffset=t.timeOffset||0,this._video=t.video,this._videoHeight=0,this._videoWidth=0,this._videoColorSpace=null,this._canvas=t.canvas,this._video&&!this._canvas)this._canvasParent=document.createElement("div"),this._canvasParent.className="JASSUB",this._canvasParent.style.position="relative",this._canvas=this._createCanvas(),this._video.insertAdjacentElement("afterend",this._canvasParent);else if(!this._canvas)throw this.destroy("Don't know where to render: you should give video or canvas in options.");if(this._bufferCanvas=document.createElement("canvas"),this._bufferCtx=this._bufferCanvas.getContext("2d"),!this._bufferCtx)throw this.destroy("Canvas rendering not supported");this._canvasctrl=this._offscreenRender?this._canvas.transferControlToOffscreen():this._canvas,this._ctx=!this._offscreenRender&&this._canvasctrl.getContext("2d"),this._lastRenderTime=0,this.debug=!!t.debug,this.prescaleFactor=t.prescaleFactor||1,this.prescaleHeightLimit=t.prescaleHeightLimit||1080,this.maxRenderHeight=t.maxRenderHeight||0,this._boundResize=this.resize.bind(this),this._boundTimeUpdate=this._timeupdate.bind(this),this._boundSetRate=this.setRate.bind(this),this._boundUpdateColorSpace=this._updateColorSpace.bind(this),this._video&&this.setVideo(t.video),this._onDemandRender&&(this.busy=!1,this._lastDemandTime=null),this._worker=new Worker(t.workerUrl||"jassub-worker.js"),this._worker.onmessage=e=>this._onmessage(e),this._worker.onerror=e=>this._error(e),s.then(()=>{this._worker.postMessage({target:"init",wasmUrl:e._supportsSIMD&&t.modernWasmUrl?t.modernWasmUrl:t.wasmUrl??"jassub-worker.wasm",legacyWasmUrl:t.legacyWasmUrl??"jassub-worker.wasm.js",asyncRender:typeof createImageBitmap<"u"&&(t.asyncRender??!0),onDemandRender:this._onDemandRender,width:this._canvasctrl.width||0,height:this._canvasctrl.height||0,blendMode:t.blendMode||"js",subUrl:t.subUrl,subContent:t.subContent||null,fonts:t.fonts||[],availableFonts:t.availableFonts||{"liberation sans":"./default.woff2"},fallbackFont:t.fallbackFont||"liberation sans",debug:this.debug,targetFps:t.targetFps||24,dropAllAnimations:t.dropAllAnimations,dropAllBlur:t.dropAllBlur,libassMemoryLimit:t.libassMemoryLimit||0,libassGlyphLimit:t.libassGlyphLimit||0,useLocalFonts:typeof queryLocalFonts<"u"&&(t.useLocalFonts??!0),hasBitmapBug:e._hasBitmapBug}),!0===this._offscreenRender&&this.sendMessage("offscreenCanvas",null,[this._canvasctrl])})}_createCanvas(){return this._canvas=document.createElement("canvas"),this._canvas.style.display="block",this._canvas.style.position="absolute",this._canvas.style.pointerEvents="none",this._canvasParent.appendChild(this._canvas),this._canvas}static _testSIMD(){if(null===e._suppLine truncated
|
||||
var __defProp = Object.defineProperty;
|
||||
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
||||
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
||||
typeof HTMLVideoElement < "u" && !("requestVideoFrameCallback" in HTMLVideoElement.prototype) && "getVideoPlaybackQuality" in HTMLVideoElement.prototype && (HTMLVideoElement.prototype._rvfcpolyfillmap = {}, HTMLVideoElement.prototype.requestVideoFrameCallback = function(c) {
|
||||
const e = performance.now(), t = this.getVideoPlaybackQuality(), s = this.mozPresentedFrames || this.mozPaintedFrames || t.totalVideoFrames - t.droppedVideoFrames, r = (n, a) => {
|
||||
const i = this.getVideoPlaybackQuality(), h = this.mozPresentedFrames || this.mozPaintedFrames || i.totalVideoFrames - i.droppedVideoFrames;
|
||||
if (h > s) {
|
||||
const d = this.mozFrameDelay || i.totalFrameDelay - t.totalFrameDelay || 0, l = a - n;
|
||||
c(a, {
|
||||
presentationTime: a + d * 1e3,
|
||||
expectedDisplayTime: a + l,
|
||||
width: this.videoWidth,
|
||||
height: this.videoHeight,
|
||||
mediaTime: Math.max(0, this.currentTime || 0) + l / 1e3,
|
||||
presentedFrames: h,
|
||||
processingDuration: d
|
||||
}), delete this._rvfcpolyfillmap[e];
|
||||
} else
|
||||
this._rvfcpolyfillmap[e] = requestAnimationFrame((d) => r(a, d));
|
||||
};
|
||||
return this._rvfcpolyfillmap[e] = requestAnimationFrame((n) => r(e, n)), e;
|
||||
}, HTMLVideoElement.prototype.cancelVideoFrameCallback = function(c) {
|
||||
cancelAnimationFrame(this._rvfcpolyfillmap[c]), delete this._rvfcpolyfillmap[c];
|
||||
});
|
||||
const _ = {
|
||||
bt709: "BT709",
|
||||
// these might not be exactly correct? oops?
|
||||
bt470bg: "BT601",
|
||||
// alias BT.601 PAL... whats the difference?
|
||||
smpte170m: "BT601"
|
||||
// alias BT.601 NTSC... whats the difference?
|
||||
}, f = {
|
||||
BT601: {
|
||||
BT709: "1.0863 -0.0723 -0.014 0 0 0.0965 0.8451 0.0584 0 0 -0.0141 -0.0277 1.0418"
|
||||
},
|
||||
BT709: {
|
||||
BT601: "0.9137 0.0784 0.0079 0 0 -0.1049 1.1722 -0.0671 0 0 0.0096 0.0322 0.9582"
|
||||
},
|
||||
FCC: {
|
||||
BT709: "1.0873 -0.0736 -0.0137 0 0 0.0974 0.8494 0.0531 0 0 -0.0127 -0.0251 1.0378",
|
||||
BT601: "1.001 -0.0008 -0.0002 0 0 0.0009 1.005 -0.006 0 0 0.0013 0.0027 0.996"
|
||||
},
|
||||
SMPTE240M: {
|
||||
BT709: "0.9993 0.0006 0.0001 0 0 -0.0004 0.9812 0.0192 0 0 -0.0034 -0.0114 1.0148",
|
||||
BT601: "0.913 0.0774 0.0096 0 0 -0.1051 1.1508 -0.0456 0 0 0.0063 0.0207 0.973"
|
||||
}
|
||||
};
|
||||
const _o = class _o extends EventTarget {
|
||||
/**
|
||||
* @param {Object} options Settings object.
|
||||
* @param {HTMLVideoElement} options.video Video to use as target for rendering and event listeners. Optional if canvas is specified instead.
|
||||
* @param {HTMLCanvasElement} [options.canvas=HTMLCanvasElement] Canvas to use for manual handling. Not required if video is specified.
|
||||
* @param {'js'|'wasm'} [options.blendMode='js'] Which image blending mode to use. WASM will perform better on lower end devices, JS will perform better if the device and browser supports hardware acceleration.
|
||||
* @param {Boolean} [options.asyncRender=true] Whether or not to use async rendering, which offloads the CPU by creating image bitmaps on the GPU.
|
||||
* @param {Boolean} [options.offscreenRender=true] Whether or not to render things fully on the worker, greatly reduces CPU usage.
|
||||
* @param {Boolean} [options.onDemandRender=true] Whether or not to render subtitles as the video player decodes renders, rather than predicting which frame the player is on using events.
|
||||
* @param {Number} [options.targetFps=24] Target FPS to render subtitles at. Ignored when onDemandRender is enabled.
|
||||
* @param {Number} [options.timeOffset=0] Subtitle time offset in seconds.
|
||||
* @param {Boolean} [options.debug=false] Whether or not to print debug information.
|
||||
* @param {Number} [options.prescaleFactor=1.0] Scale down (< 1.0) the subtitles canvas to improve performance at the expense of quality, or scale it up (> 1.0).
|
||||
* @param {Number} [options.prescaleHeightLimit=1080] The height in pixels beyond which the subtitles canvas won't be prescaled.
|
||||
* @param {Number} [options.maxRenderHeight=0] The maximum rendering height in pixels of the subtitles canvas. Beyond this subtitles will be upscaled by the browser.
|
||||
* @param {Boolean} [options.dropAllAnimations=false] Attempt to discard all animated tags. Enabling this may severly mangle complex subtitles and should only be considered as an last ditch effort of uncertain success for hardware otherwise incapable of displaing anything. Will not reliably work with manually edited or allocated events.
|
||||
* @param {Boolean} [options.dropAllBlur=false] The holy grail of performance gains. If heavy TS lags a lot, disabling this will make it ~x10 faster. This drops blur from all added subtitle tracks making most text and backgrounds look sharper, this is way less intrusive than dropping all animations, while still offering major performance gains.
|
||||
* @param {String} [options.workerUrl='jassub-worker.js'] The URL of the worker.
|
||||
* @param {String} [options.wasmUrl='jassub-worker.wasm'] The URL of the worker WASM.
|
||||
* @param {String} [options.legacyWasmUrl='jassub-worker.wasm.js'] The URL of the worker WASM. Only loaded if the browser doesn't support WASM.
|
||||
* @param {String} options.modernWasmUrl The URL of the modern worker WASM. This includes faster ASM instructions, but is only supported by newer browsers, disabled if the URL isn't defined.
|
||||
* @param {String} [options.subUrl=options.subContent] The URL of the subtitle file to play.
|
||||
* @param {String} [options.subContent=options.subUrl] The content of the subtitle file to play.
|
||||
* @param {String[]|Uint8Array[]} [options.fonts] An array of links or Uint8Arrays to the fonts used in the subtitle. If Uint8Array is used the array is copied, not referenced. This forces all the fonts in this array to be loaded by the renderer, regardless of if they are used.
|
||||
* @param {Object} [options.availableFonts={'liberation sans': './default.woff2'}] Object with all available fonts - Key is font family in lower case, value is link or Uint8Array: { arial: '/font1.ttf' }. These fonts are selectively loaded if detected as used in the current subtitle track.
|
||||
* @param {String} [options.fallbackFont='liberation sans'] The font family key of the fallback font in availableFonts to use if the other font for the style is missing special glyphs or unicode.
|
||||
* @param {Boolean} [options.useLocalFonts=false] If the Local Font Access API is enabled [chrome://flags/#font-access], the library will query for permissions to use local fonts and use them if any are missing. The permission can be queried beforehand using navigator.permissions.request({ name: 'local-fonts' }).
|
||||
* @param {Number} [options.libassMemoryLimit] libass bitmap cache memory limit in MiB (approximate).
|
||||
* @param {Number} [options.libassGlyphLimit] libass glyph cache memory limit in MiB (approximate).
|
||||
*/
|
||||
constructor(e) {
|
||||
if (super(), !globalThis.Worker)
|
||||
throw this.destroy("Worker not supported");
|
||||
if (!e)
|
||||
throw this.destroy("No options provided");
|
||||
this._loaded = /** @type {Promise<void>} */
|
||||
new Promise((s) => {
|
||||
this._init = s;
|
||||
});
|
||||
const t = _o._test();
|
||||
if (this._onDemandRender = "requestVideoFrameCallback" in HTMLVideoElement.prototype && (e.onDemandRender ?? true), this._offscreenRender = "transferControlToOffscreen" in HTMLCanvasElement.prototype && !e.canvas && (e.offscreenRender ?? true), this.timeOffset = e.timeOffset || 0, this._video = e.video, this._videoHeight = 0, this._videoWidth = 0, this._videoColorSpace = null, this._canvas = e.canvas, this._video && !this._canvas)
|
||||
this._canvasParent = document.createElement("div"), this._canvasParent.className = "JASSUB", this._canvasParent.style.position = "relative", this._canvas = this._createCanvas(), this._video.insertAdjacentElement("afterend", this._canvasParent);
|
||||
else if (!this._canvas)
|
||||
throw this.destroy("Don't know where to render: you should give video or canvas in options.");
|
||||
if (this._bufferCanvas = document.createElement("canvas"), this._bufferCtx = this._bufferCanvas.getContext("2d"), !this._bufferCtx)
|
||||
throw this.destroy("Canvas rendering not supported");
|
||||
this._canvasctrl = this._offscreenRender ? this._canvas.transferControlToOffscreen() : this._canvas, this._ctx = !this._offscreenRender && this._canvasctrl.getContext("2d"), this._lastRenderTime = 0, this.debug = !!e.debug, this.prescaleFactor = e.prescaleFactor || 1, this.prescaleHeightLimit = e.prescaleHeightLimit || 1080, this.maxRenderHeight = e.maxRenderHeight || 0, this._boundResize = this.resize.bind(this), this._boundTimeUpdate = this._timeupdate.bind(this), this._boundSetRate = this.setRate.bind(this), this._boundUpdateColorSpace = this._updateColorSpace.bind(this), this._video && this.setVideo(e.video), this._onDemandRender && (this.busy = false, this._lastDemandTime = null), this._worker = new Worker(e.workerUrl || "jassub-worker.js"), this._worker.onmessage = (s) => this._onmessage(s), this._worker.onerror = (s) => this._error(s), t.then(() => {
|
||||
this._worker.postMessage({
|
||||
target: "init",
|
||||
wasmUrl: _o._supportsSIMD && e.modernWasmUrl ? e.modernWasmUrl : e.wasmUrl ?? "jassub-worker.wasm",
|
||||
legacyWasmUrl: e.legacyWasmUrl ?? "jassub-worker.wasm.js",
|
||||
asyncRender: typeof createImageBitmap < "u" && (e.asyncRender ?? true),
|
||||
onDemandRender: this._onDemandRender,
|
||||
width: this._canvasctrl.width || 0,
|
||||
height: this._canvasctrl.height || 0,
|
||||
blendMode: e.blendMode || "js",
|
||||
subUrl: e.subUrl,
|
||||
subContent: e.subContent || null,
|
||||
fonts: e.fonts || [],
|
||||
availableFonts: e.availableFonts || { "liberation sans": "./default.woff2" },
|
||||
fallbackFont: e.fallbackFont || "liberation sans",
|
||||
debug: this.debug,
|
||||
targetFps: e.targetFps || 24,
|
||||
dropAllAnimations: e.dropAllAnimations,
|
||||
dropAllBlur: e.dropAllBlur,
|
||||
libassMemoryLimit: e.libassMemoryLimit || 0,
|
||||
libassGlyphLimit: e.libassGlyphLimit || 0,
|
||||
// @ts-ignore
|
||||
useLocalFonts: typeof queryLocalFonts < "u" && (e.useLocalFonts ?? true),
|
||||
hasBitmapBug: _o._hasBitmapBug
|
||||
}), this._offscreenRender === true && this.sendMessage("offscreenCanvas", null, [this._canvasctrl]);
|
||||
});
|
||||
}
|
||||
_createCanvas() {
|
||||
return this._canvas = document.createElement("canvas"), this._canvas.style.display = "block", this._canvas.style.position = "absolute", this._canvas.style.pointerEvents = "none", this._canvasParent.appendChild(this._canvas), this._canvas;
|
||||
}
|
||||
static _testSIMD() {
|
||||
if (_o._supportsSIMD === null)
|
||||
try {
|
||||
_o._supportsSIMD = WebAssembly.validate(Uint8Array.of(0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 123, 3, 2, 1, 0, 10, 10, 1, 8, 0, 65, 0, 253, 15, 253, 98, 11));
|
||||
} catch {
|
||||
_o._supportsSIMD = false;
|
||||
}
|
||||
}
|
||||
static async _testImageBugs() {
|
||||
if (_o._hasBitmapBug !== null)
|
||||
return;
|
||||
const e = document.createElement("canvas"), t = e.getContext("2d", { willReadFrequently: true });
|
||||
if (!t)
|
||||
throw new Error("Canvas rendering not supported");
|
||||
if (typeof ImageData.prototype.constructor == "function")
|
||||
try {
|
||||
new ImageData(new Uint8ClampedArray([0, 0, 0, 0]), 1, 1);
|
||||
} catch {
|
||||
console.log("Detected that ImageData is not constructable despite browser saying so"), self.ImageData = function(h, d, l) {
|
||||
const m = t.createImageData(d, l);
|
||||
return h && m.data.set(h), m;
|
||||
};
|
||||
}
|
||||
const s = document.createElement("canvas"), r = s.getContext("2d", { willReadFrequently: true });
|
||||
if (!r)
|
||||
throw new Error("Canvas rendering not supported");
|
||||
e.width = s.width = 1, e.height = s.height = 1, t.clearRect(0, 0, 1, 1), r.clearRect(0, 0, 1, 1);
|
||||
const n = r.getImageData(0, 0, 1, 1).data;
|
||||
t.putImageData(new ImageData(new Uint8ClampedArray([0, 255, 0, 0]), 1, 1), 0, 0), r.drawImage(e, 0, 0);
|
||||
const a = r.getImageData(0, 0, 1, 1).data;
|
||||
if (_o._hasAlphaBug = n[1] !== a[1], _o._hasAlphaBug && console.log("Detected a browser having issue with transparent pixels, applying workaround"), typeof createImageBitmap < "u") {
|
||||
const i = new Uint8ClampedArray([255, 0, 255, 0, 255]).subarray(1, 5);
|
||||
r.drawImage(await createImageBitmap(new ImageData(i, 1)), 0, 0);
|
||||
const { data: h } = r.getImageData(0, 0, 1, 1);
|
||||
_o._hasBitmapBug = false;
|
||||
for (const [d, l] of h.entries())
|
||||
if (Math.abs(i[d] - l) > 15) {
|
||||
_o._hasBitmapBug = true, console.log("Detected a browser having issue with partial bitmaps, applying workaround");
|
||||
break;
|
||||
}
|
||||
} else
|
||||
_o._hasBitmapBug = false;
|
||||
e.remove(), s.remove();
|
||||
}
|
||||
static async _test() {
|
||||
_o._testSIMD(), await _o._testImageBugs();
|
||||
}
|
||||
/**
|
||||
* Resize the canvas to given parameters. Auto-generated if values are ommited.
|
||||
* @param {Number} [width=0]
|
||||
* @param {Number} [height=0]
|
||||
* @param {Number} [top=0]
|
||||
* @param {Number} [left=0]
|
||||
* @param {Boolean} [force=false]
|
||||
*/
|
||||
resize(e = 0, t = 0, s = 0, r = 0, n = this._video?.paused) {
|
||||
if ((!e || !t) && this._video) {
|
||||
const a = this._getVideoPosition();
|
||||
let i = null;
|
||||
if (this._videoWidth) {
|
||||
const h = this._video.videoWidth / this._videoWidth, d = this._video.videoHeight / this._videoHeight;
|
||||
i = this._computeCanvasSize((a.width || 0) / h, (a.height || 0) / d);
|
||||
} else
|
||||
i = this._computeCanvasSize(a.width || 0, a.height || 0);
|
||||
e = i.width, t = i.height, this._canvasParent && (s = a.y - (this._canvasParent.getBoundingClientRect().top - this._video.getBoundingClientRect().top), r = a.x), this._canvas.style.width = a.width + "px", this._canvas.style.height = a.height + "px";
|
||||
}
|
||||
this._canvas.style.top = s + "px", this._canvas.style.left = r + "px", n && this.busy === false ? this.busy = true : n = false, this.sendMessage("canvas", { width: e, height: t, videoWidth: this._videoWidth || this._video.videoWidth, videoHeight: this._videoHeight || this._video.videoHeight, force: n });
|
||||
}
|
||||
_getVideoPosition(e = this._video.videoWidth, t = this._video.videoHeight) {
|
||||
const s = e / t, { offsetWidth: r, offsetHeight: n } = this._video, a = r / n;
|
||||
e = r, t = n, a > s ? e = Math.floor(n * s) : t = Math.floor(r / s);
|
||||
const i = (r - e) / 2, h = (n - t) / 2;
|
||||
return { width: e, height: t, x: i, y: h };
|
||||
}
|
||||
_computeCanvasSize(e = 0, t = 0) {
|
||||
const s = this.prescaleFactor <= 0 ? 1 : this.prescaleFactor, r = self.devicePixelRatio || 1;
|
||||
if (t <= 0 || e <= 0)
|
||||
e = 0, t = 0;
|
||||
else {
|
||||
const n = s < 1 ? -1 : 1;
|
||||
let a = t * r;
|
||||
n * a * s <= n * this.prescaleHeightLimit ? a *= s : n * a < n * this.prescaleHeightLimit && (a = this.prescaleHeightLimit), this.maxRenderHeight > 0 && a > this.maxRenderHeight && (a = this.maxRenderHeight), e *= a / t, t = a;
|
||||
}
|
||||
return { width: e, height: t };
|
||||
}
|
||||
_timeupdate({ type: e }) {
|
||||
const s = {
|
||||
seeking: true,
|
||||
waiting: true,
|
||||
playing: false
|
||||
}[e];
|
||||
s != null && (this._playstate = s), this.setCurrentTime(this._video.paused || this._playstate, this._video.currentTime + this.timeOffset);
|
||||
}
|
||||
/**
|
||||
* Change the video to use as target for event listeners.
|
||||
* @param {HTMLVideoElement} video
|
||||
*/
|
||||
setVideo(e) {
|
||||
e instanceof HTMLVideoElement ? (this._removeListeners(), this._video = e, this._onDemandRender ? this._video.requestVideoFrameCallback(this._handleRVFC.bind(this)) : (this._playstate = e.paused, e.addEventListener("timeupdate", this._boundTimeUpdate, false), e.addEventListener("progress", this._boundTimeUpdate, false), e.addEventListener("waiting", this._boundTimeUpdate, false), e.addEventListener("seeking", this._boundTimeUpdate, false), e.addEventListener("playing", this._boundTimeUpdate, false), e.addEventListener("ratechange", this._boundSetRate, false), e.addEventListener("resize", this._boundResize, false)), "VideoFrame" in window && (e.addEventListener("loadedmetadata", this._boundUpdateColorSpace, false), e.readyState > 2 && this._updateColorSpace()), e.videoWidth > 0 && this.resize(), typeof ResizeObserver < "u" && (this._ro || (this._ro = new ResizeObserver(() => this.resize())), this._ro.observe(e))) : this._error("Video element invalid!");
|
||||
}
|
||||
runBenchmark() {
|
||||
this.sendMessage("runBenchmark");
|
||||
}
|
||||
/**
|
||||
* Overwrites the current subtitle content.
|
||||
* @param {String} url URL to load subtitles from.
|
||||
*/
|
||||
setTrackByUrl(e) {
|
||||
this.sendMessage("setTrackByUrl", { url: e }), this._reAttachOffscreen(), this._ctx && (this._ctx.filter = "none");
|
||||
}
|
||||
/**
|
||||
* Overwrites the current subtitle content.
|
||||
* @param {String} content Content of the ASS file.
|
||||
*/
|
||||
setTrack(e) {
|
||||
this.sendMessage("setTrack", { content: e }), this._reAttachOffscreen(), this._ctx && (this._ctx.filter = "none");
|
||||
}
|
||||
/**
|
||||
* Free currently used subtitle track.
|
||||
*/
|
||||
freeTrack() {
|
||||
this.sendMessage("freeTrack");
|
||||
}
|
||||
/**
|
||||
* Sets the playback state of the media.
|
||||
* @param {Boolean} isPaused Pause/Play subtitle playback.
|
||||
*/
|
||||
setIsPaused(e) {
|
||||
this.sendMessage("video", { isPaused: e });
|
||||
}
|
||||
/**
|
||||
* Sets the playback rate of the media [speed multiplier].
|
||||
* @param {Number} rate Playback rate.
|
||||
*/
|
||||
setRate(e) {
|
||||
this.sendMessage("video", { rate: e });
|
||||
}
|
||||
/**
|
||||
* Sets the current time, playback state and rate of the subtitles.
|
||||
* @param {Boolean} [isPaused] Pause/Play subtitle playback.
|
||||
* @param {Number} [currentTime] Time in seconds.
|
||||
* @param {Number} [rate] Playback rate.
|
||||
*/
|
||||
setCurrentTime(e, t, s) {
|
||||
this.sendMessage("video", { isPaused: e, currentTime: t, rate: s, colorSpace: this._videoColorSpace });
|
||||
}
|
||||
/**
|
||||
* @typedef {Object} ASS_Event
|
||||
* @property {Number} Start Start Time of the Event, in 0:00:00:00 format ie. Hrs:Mins:Secs:hundredths. This is the time elapsed during script playback at which the text will appear onscreen. Note that there is a single digit for the hours!
|
||||
* @property {Number} Duration End Time of the Event, in 0:00:00:00 format ie. Hrs:Mins:Secs:hundredths. This is the time elapsed during script playback at which the text will disappear offscreen. Note that there is a single digit for the hours!
|
||||
* @property {String} Style Style name. If it is "Default", then your own *Default style will be subtituted.
|
||||
* @property {String} Name Character name. This is the name of the character who speaks the dialogue. It is for information only, to make the script is easier to follow when editing/timing.
|
||||
* @property {Number} MarginL 4-figure Left Margin override. The values are in pixels. All zeroes means the default margins defined by the style are used.
|
||||
* @property {Number} MarginR 4-figure Right Margin override. The values are in pixels. All zeroes means the default margins defined by the style are used.
|
||||
* @property {Number} MarginV 4-figure Bottom Margin override. The values are in pixels. All zeroes means the default margins defined by the style are used.
|
||||
* @property {String} Effect Transition Effect. This is either empty, or contains information for one of the three transition effects implemented in SSA v4.x
|
||||
* @property {String} Text Subtitle Text. This is the actual text which will be displayed as a subtitle onscreen. Everything after the 9th comma is treated as the subtitle text, so it can include commas.
|
||||
* @property {Number} ReadOrder Number in order of which to read this event.
|
||||
* @property {Number} Layer Z-index overlap in which to render this event.
|
||||
* @property {Number} _index (Internal) index of the event.
|
||||
*/
|
||||
/**
|
||||
* Create a new ASS event directly.
|
||||
* @param {ASS_Event} event
|
||||
*/
|
||||
createEvent(e) {
|
||||
this.sendMessage("createEvent", { event: e });
|
||||
}
|
||||
/**
|
||||
* Overwrite the data of the event with the specified index.
|
||||
* @param {ASS_Event} event
|
||||
* @param {Number} index
|
||||
*/
|
||||
setEvent(e, t) {
|
||||
this.sendMessage("setEvent", { event: e, index: t });
|
||||
}
|
||||
/**
|
||||
* Remove the event with the specified index.
|
||||
* @param {Number} index
|
||||
*/
|
||||
removeEvent(e) {
|
||||
this.sendMessage("removeEvent", { index: e });
|
||||
}
|
||||
/**
|
||||
* Get all ASS events.
|
||||
* @param {function(Error|null, ASS_Event): void} callback Function to callback when worker returns the events.
|
||||
*/
|
||||
getEvents(e) {
|
||||
this._fetchFromWorker({
|
||||
target: "getEvents"
|
||||
}, (t, { events: s }) => {
|
||||
e(t, s);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Set a style override.
|
||||
* @param {ASS_Style} style
|
||||
*/
|
||||
styleOverride(e) {
|
||||
this.sendMessage("styleOverride", { style: e });
|
||||
}
|
||||
/**
|
||||
* Disable style override.
|
||||
*/
|
||||
disableStyleOverride() {
|
||||
this.sendMessage("disableStyleOverride");
|
||||
}
|
||||
/**
|
||||
* @typedef {Object} ASS_Style
|
||||
* @property {String} Name The name of the Style. Case sensitive. Cannot include commas.
|
||||
* @property {String} FontName The fontname as used by Windows. Case-sensitive.
|
||||
* @property {Number} FontSize Font size.
|
||||
* @property {Number} PrimaryColour A long integer BGR (blue-green-red) value. ie. the byte order in the hexadecimal equivelent of this number is BBGGRR
|
||||
* @property {Number} SecondaryColour A long integer BGR (blue-green-red) value. ie. the byte order in the hexadecimal equivelent of this number is BBGGRR
|
||||
* @property {Number} OutlineColour A long integer BGR (blue-green-red) value. ie. the byte order in the hexadecimal equivelent of this number is BBGGRR
|
||||
* @property {Number} BackColour This is the colour of the subtitle outline or shadow, if these are used. A long integer BGR (blue-green-red) value. ie. the byte order in the hexadecimal equivelent of this number is BBGGRR.
|
||||
* @property {Number} Bold This defines whether text is bold (true) or not (false). -1 is True, 0 is False. This is independant of the Italic attribute - you can have have text which is both bold and italic.
|
||||
* @property {Number} Italic Italic. This defines whether text is italic (true) or not (false). -1 is True, 0 is False. This is independant of the bold attribute - you can have have text which is both bold and italic.
|
||||
* @property {Number} Underline -1 or 0
|
||||
* @property {Number} StrikeOut -1 or 0
|
||||
* @property {Number} ScaleX Modifies the width of the font. [percent]
|
||||
* @property {Number} ScaleY Modifies the height of the font. [percent]
|
||||
* @property {Number} Spacing Extra space between characters. [pixels]
|
||||
* @property {Number} Angle The origin of the rotation is defined by the alignment. Can be a floating point number. [degrees]
|
||||
* @property {Number} BorderStyle 1=Outline + drop shadow, 3=Opaque box
|
||||
* @property {Number} Outline If BorderStyle is 1, then this specifies the width of the outline around the text, in pixels. Values may be 0, 1, 2, 3 or 4.
|
||||
* @property {Number} Shadow If BorderStyle is 1, then this specifies the depth of the drop shadow behind the text, in pixels. Values may be 0, 1, 2, 3 or 4. Drop shadow is always used in addition to an outline - SSA will force an outline of 1 pixel if no outline width is given.
|
||||
* @property {Number} Alignment This sets how text is "justified" within the Left/Right onscreen margins, and also the vertical placing. Values may be 1=Left, 2=Centered, 3=Right. Add 4 to the value for a "Toptitle". Add 8 to the value for a "Midtitle". eg. 5 = left-justified toptitle
|
||||
* @property {Number} MarginL This defines the Left Margin in pixels. It is the distance from the left-hand edge of the screen.The three onscreen margins (MarginL, MarginR, MarginV) define areas in which the subtitle text will be displayed.
|
||||
* @property {Number} MarginR This defines the Right Margin in pixels. It is the distance from the right-hand edge of the screen. The three onscreen margins (MarginL, MarginR, MarginV) define areas in which the subtitle text will be displayed.
|
||||
* @property {Number} MarginV This defines the vertical Left Margin in pixels. For a subtitle, it is the distance from the bottom of the screen. For a toptitle, it is the distance from the top of the screen. For a midtitle, the value is ignored - the text will be vertically centred.
|
||||
* @property {Number} Encoding This specifies the font character set or encoding and on multi-lingual Windows installations it provides access to characters used in multiple than one languages. It is usually 0 (zero) for English (Western, ANSI) Windows.
|
||||
* @property {Number} treat_fontname_as_pattern
|
||||
* @property {Number} Blur
|
||||
* @property {Number} Justify
|
||||
*/
|
||||
/**
|
||||
* Create a new ASS style directly.
|
||||
* @param {ASS_Style} style
|
||||
*/
|
||||
createStyle(e) {
|
||||
this.sendMessage("createStyle", { style: e });
|
||||
}
|
||||
/**
|
||||
* Overwrite the data of the style with the specified index.
|
||||
* @param {ASS_Style} style
|
||||
* @param {Number} index
|
||||
*/
|
||||
setStyle(e, t) {
|
||||
this.sendMessage("setStyle", { style: e, index: t });
|
||||
}
|
||||
/**
|
||||
* Remove the style with the specified index.
|
||||
* @param {Number} index
|
||||
*/
|
||||
removeStyle(e) {
|
||||
this.sendMessage("removeStyle", { index: e });
|
||||
}
|
||||
/**
|
||||
* Get all ASS styles.
|
||||
* @param {function(Error|null, ASS_Style): void} callback Function to callback when worker returns the styles.
|
||||
*/
|
||||
getStyles(e) {
|
||||
this._fetchFromWorker({
|
||||
target: "getStyles"
|
||||
}, (t, { styles: s }) => {
|
||||
e(t, s);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Adds a font to the renderer.
|
||||
* @param {String|Uint8Array} font Font to add.
|
||||
*/
|
||||
addFont(e) {
|
||||
this.sendMessage("addFont", { font: e });
|
||||
}
|
||||
/**
|
||||
* Changes the font family of the default font, this font needs to be previously added via addFont or fonts array on construction.
|
||||
* @param {String} font Font family to change to.
|
||||
*/
|
||||
setDefaultFont(e) {
|
||||
this.sendMessage("defaultFont", { font: e });
|
||||
}
|
||||
_sendLocalFont(e) {
|
||||
try {
|
||||
queryLocalFonts().then((t) => {
|
||||
const s = t?.find((r) => r.fullName.toLowerCase() === e);
|
||||
s && s.blob().then((r) => {
|
||||
r.arrayBuffer().then((n) => {
|
||||
this.addFont(new Uint8Array(n));
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch (t) {
|
||||
console.warn("Local fonts API:", t);
|
||||
}
|
||||
}
|
||||
_getLocalFont({ font: e }) {
|
||||
try {
|
||||
navigator?.permissions?.query ? navigator.permissions.query({ name: "local-fonts" }).then((t) => {
|
||||
t.state === "granted" && this._sendLocalFont(e);
|
||||
}) : this._sendLocalFont(e);
|
||||
} catch (t) {
|
||||
console.warn("Local fonts API:", t);
|
||||
}
|
||||
}
|
||||
_unbusy() {
|
||||
this._lastDemandTime ? this._demandRender(this._lastDemandTime) : this.busy = false;
|
||||
}
|
||||
_handleRVFC(e, { mediaTime: t, width: s, height: r }) {
|
||||
if (this._destroyed)
|
||||
return null;
|
||||
this.busy ? this._lastDemandTime = { mediaTime: t, width: s, height: r } : (this.busy = true, this._demandRender({ mediaTime: t, width: s, height: r })), this._video.requestVideoFrameCallback(this._handleRVFC.bind(this));
|
||||
}
|
||||
_demandRender({ mediaTime: e, width: t, height: s }) {
|
||||
this._lastDemandTime = null, (t !== this._videoWidth || s !== this._videoHeight) && (this._videoWidth = t, this._videoHeight = s, this.resize()), this.sendMessage("demand", { time: e + this.timeOffset });
|
||||
}
|
||||
// if we're using offscreen render, we can't use ctx filters, so we can't use a transfered canvas
|
||||
_detachOffscreen() {
|
||||
if (!this._offscreenRender || this._ctx)
|
||||
return null;
|
||||
this._canvas.remove(), this._createCanvas(), this._canvasctrl = this._canvas, this._ctx = this._canvasctrl.getContext("2d"), this.sendMessage("detachOffscreen"), this.busy = false, this.resize(0, 0, 0, 0, true);
|
||||
}
|
||||
// if the video or track changed, we need to re-attach the offscreen canvas
|
||||
_reAttachOffscreen() {
|
||||
if (!this._offscreenRender || !this._ctx)
|
||||
return null;
|
||||
this._canvas.remove(), this._createCanvas(), this._canvasctrl = this._canvas.transferControlToOffscreen(), this._ctx = false, this.sendMessage("offscreenCanvas", null, [this._canvasctrl]), this.resize(0, 0, 0, 0, true);
|
||||
}
|
||||
_updateColorSpace() {
|
||||
this._video.requestVideoFrameCallback(() => {
|
||||
try {
|
||||
const e = new VideoFrame(this._video);
|
||||
this._videoColorSpace = _[e.colorSpace.matrix], e.close(), this.sendMessage("getColorSpace");
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Veryify the color spaces for subtitles and videos, then apply filters to correct the color of subtitles.
|
||||
* @param {Object} options
|
||||
* @param {String} options.subtitleColorSpace Subtitle color space. One of: BT601 BT709 SMPTE240M FCC
|
||||
* @param {String=} options.videoColorSpace Video color space. One of: BT601 BT709
|
||||
*/
|
||||
_verifyColorSpace({ subtitleColorSpace: e, videoColorSpace: t = this._videoColorSpace }) {
|
||||
!e || !t || e !== t && (this._detachOffscreen(), this._ctx.filter = `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='f'><feColorMatrix type='matrix' values='${f[e][t]} 0 0 0 0 0 1 0'/></filter></svg>#f")`);
|
||||
}
|
||||
_render({ images: e, asyncRender: t, times: s, width: r, height: n, colorSpace: a }) {
|
||||
this._unbusy(), this.debug && (s.IPCTime = Date.now() - s.JSRenderTime), (this._canvasctrl.width !== r || this._canvasctrl.height !== n) && (this._canvasctrl.width = r, this._canvasctrl.height = n, this._verifyColorSpace({ subtitleColorSpace: a })), this._ctx.clearRect(0, 0, this._canvasctrl.width, this._canvasctrl.height);
|
||||
for (const i of e)
|
||||
i.image && (t ? (this._ctx.drawImage(i.image, i.x, i.y), i.image.close()) : (this._bufferCanvas.width = i.w, this._bufferCanvas.height = i.h, this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(i.image)), i.w, i.h), 0, 0), this._ctx.drawImage(this._bufferCanvas, i.x, i.y)));
|
||||
if (this.debug) {
|
||||
s.JSRenderTime = Date.now() - s.JSRenderTime - s.IPCTime;
|
||||
let i = 0;
|
||||
const h = s.bitmaps || e.length;
|
||||
delete s.bitmaps;
|
||||
for (const d in s)
|
||||
i += s[d];
|
||||
console.log("Bitmaps: " + h + " Total: " + (i | 0) + "ms", s);
|
||||
}
|
||||
}
|
||||
_fixAlpha(e) {
|
||||
if (_o._hasAlphaBug)
|
||||
for (let t = 3; t < e.length; t += 4)
|
||||
e[t] = e[t] > 1 ? e[t] : 1;
|
||||
return e;
|
||||
}
|
||||
_ready() {
|
||||
this._init(), this.dispatchEvent(new CustomEvent("ready"));
|
||||
}
|
||||
/**
|
||||
* Send data and execute function in the worker.
|
||||
* @param {String} target Target function.
|
||||
* @param {Object} [data] Data for function.
|
||||
* @param {Transferable[]} [transferable] Array of transferables.
|
||||
*/
|
||||
async sendMessage(e, t = {}, s) {
|
||||
await this._loaded, s ? this._worker.postMessage({
|
||||
target: e,
|
||||
transferable: s,
|
||||
...t
|
||||
}, [...s]) : this._worker.postMessage({
|
||||
target: e,
|
||||
...t
|
||||
});
|
||||
}
|
||||
_fetchFromWorker(e, t) {
|
||||
try {
|
||||
const s = e.target, r = setTimeout(() => {
|
||||
a(new Error("Error: Timeout while try to fetch " + s));
|
||||
}, 5e3), n = ({ data: i }) => {
|
||||
i.target === s && (t(null, i), this._worker.removeEventListener("message", n), this._worker.removeEventListener("error", a), clearTimeout(r));
|
||||
}, a = (i) => {
|
||||
t(i), this._worker.removeEventListener("message", n), this._worker.removeEventListener("error", a), clearTimeout(r);
|
||||
};
|
||||
this._worker.addEventListener("message", n), this._worker.addEventListener("error", a), this._worker.postMessage(e);
|
||||
} catch (s) {
|
||||
this._error(s);
|
||||
}
|
||||
}
|
||||
_console({ content: e, command: t }) {
|
||||
console[t].apply(console, JSON.parse(e));
|
||||
}
|
||||
_onmessage({ data: e }) {
|
||||
this["_" + e.target] && this["_" + e.target](e);
|
||||
}
|
||||
_error(e) {
|
||||
const t = e instanceof Error ? e : e instanceof ErrorEvent ? e.error : new Error(e), s = e instanceof Event ? new ErrorEvent(e.type, e) : new ErrorEvent("error", { error: t });
|
||||
return this.dispatchEvent(s), console.error(t), t;
|
||||
}
|
||||
_removeListeners() {
|
||||
this._video && (this._ro && this._ro.unobserve(this._video), this._ctx && (this._ctx.filter = "none"), this._video.removeEventListener("timeupdate", this._boundTimeUpdate), this._video.removeEventListener("progress", this._boundTimeUpdate), this._video.removeEventListener("waiting", this._boundTimeUpdate), this._video.removeEventListener("seeking", this._boundTimeUpdate), this._video.removeEventListener("playing", this._boundTimeUpdate), this._video.removeEventListener("ratechange", this._boundSetRate), this._video.removeEventListener("resize", this._boundResize), this._video.removeEventListener("loadedmetadata", this._boundUpdateColorSpace));
|
||||
}
|
||||
/**
|
||||
* Destroy the object, worker, listeners and all data.
|
||||
* @param {String|Error} [err] Error to throw when destroying.
|
||||
*/
|
||||
destroy(e) {
|
||||
return e && (e = this._error(e)), this._video && this._canvasParent && this._video.parentNode?.removeChild(this._canvasParent), this._destroyed = true, this._removeListeners(), this.sendMessage("destroy"), this._worker?.terminate(), e;
|
||||
}
|
||||
};
|
||||
// test support for WASM, ImageData, alphaBug, but only once, on init so it doesn't run when first running the page
|
||||
/** @type {boolean|null} */
|
||||
__publicField(_o, "_supportsSIMD", null);
|
||||
/** @type {boolean|null} */
|
||||
__publicField(_o, "_hasAlphaBug", null);
|
||||
/** @type {boolean|null} */
|
||||
__publicField(_o, "_hasBitmapBug", null);
|
||||
let o = _o;
|
||||
function artplayerPluginJassub(option) {
|
||||
return (art) => {
|
||||
const instance = new o({
|
||||
video: art.video,
|
||||
...option
|
||||
});
|
||||
instance._canvasParent.style.zIndex = 20;
|
||||
art.on("destroy", () => {
|
||||
instance.destroy();
|
||||
});
|
||||
return {
|
||||
name: "artplayerPluginJassub",
|
||||
instance
|
||||
};
|
||||
};
|
||||
}
|
||||
export {
|
||||
artplayerPluginJassub as default
|
||||
};
|
||||
+828
-1
@@ -4,4 +4,831 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
var e={direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center"},t=function(t){t||(t={"&":"&","<":"<",">":">","&lrm":"\u200e","&rlm":"\u200f"," ":"\xa0"}),this.entities=t,this.parse=function(a,s){a=a.replace(/\0/g,"\ufffd");var r=Date.now(),o=0,l=a.split(/\r\n|\r|\n/),c=!1,f=[],u=[],d=[];function p(e,t){d.push({message:e,line:o+1,col:t})}var m=l[o],g=m.length,h="WEBVTT",v=0,b=6;for("\ufeff"===m[0]&&(v=1,b+=1),(g<b||m.indexOf(h)!==0+v||g>b&&" "!==m[b]&&"\t"!==m[b])&&p('No valid signature. (File needs to start with "WEBVTT".)'),o++;""!=l[o]&&void 0!=l[o];){if(p("No blank line after the signature."),-1!=l[o].indexOf("--\x3e")){c=!0;break}o++}for(;void 0!=l[o];){for(var x;!c&&""==l[o];)o++;if(!c&&void 0==l[o])break;x=Object.assign({},e,{id:"",startTime:0,endTime:0,pauseOnExit:!1,direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center",text:"",tree:null});var T=!0;if(-1==l[o].indexOf("--\x3e")){if(x.id=l[o],/^NOTE($|[ \t])/.test(x.id)){for(o++;""!=l[o]&&void 0!=l[o];)-1!=l[o].indexOf("--\x3e")&&p("Cannot have timestamp in a comment."),o++;continue}if(/^STYLE($|[ \t])/.test(x.id)){var y=[],w=!1;for(o++;""!=l[o]&&void 0!=l[o];)-1!=l[o].indexOf("--\x3e")&&(p("Cannot have timestamp in a style block."),w=!0),y.push(l[o]),o++;if(u.length){p("Style blocks cannot appear after the first cue.");continue}w||f.push(y.join("\n"));continue}if(""==l[++o]||void 0==l[o]){p("Cue identifier cannot be standalone.");continue}if(-1==l[o].indexOf("--\x3e")){T=!1,p("Cue identifier needs to be followed by timestamp.");continue}}c=!1;var O=new n(l[o],p),L=0;if(u.length>0&&(L=u[u.length-1].startTime),!T||O.parse(x,L)){for(o++;""!=l[o]&&void 0!=l[o];){if(-1!=l[o].indexOf("--\x3e")){p("Blank line missing before cue."),c=!0;break}""!=x.text&&(x.text+="\n"),x.text+=l[o],o++}var I=new i(x.text,p,s,t);x.tree=I.parse(x.startTime,x.endTime),u.push(x)}else for(x=null,o++;""!=l[o]&&void 0!=l[o];){if(-1!=l[o].indexOf("--\x3e")){c=!0;break}o++}}return u.sort(function(e,t){return e.startTime<t.startTime?-1:e.startTime>t.startTime?1:e.endTime>t.endTime?-1:e.endTime<t.endTime?1:0}),{cues:u,errors:d,time:Date.now()-r,styles:f}}},n=function(e,t){var n=/[\u0020\t\f]/,i=/[^\u0020\t\f]/,a=(e=e,0),s=function(e){t(e,a+1)};function r(t){for(;void 0!=e[a]&&t.test(e[a]);)a++}function o(t){for(var n="";void 0!=e[a]&&t.test(e[a]);)n+=e[a],a++;return n}function l(){var t,n,i,r,l="minutes";if(void 0!=e[a])if(/\d/.test(e[a]))if(((t=o(/\d/)).length>2||parseInt(t,10)>59)&&(l="hours"),":"==e[a])if(a++,2==(n=o(/\d/)).length){if("hours"==l||":"==e[a]){if(":"!=e[a])return void s("No seconds found or minutes is greater than 59.");if(a++,2!=(i=o(/\d/)).length)return void s("Must be exactly two digits.")}else{if(2!=t.length)return void s("Must be exactly two digits.");i=n,n=t,t="0"}if("."==e[a])if(a++,3==(r=o(/\d/)).length)if(parseInt(n,10)>59)s("You cannot have more than 59 minutes.");else{if(!(parseInt(i,10)>59))return 60*parseInt(t,10)*60+60*parseInt(n,10)+parseInt(i,10)+parseInt(r,10)/1e3;s("You cannot have more than 59 seconds.")}else s("Milliseconds must be given in three digits.");else s('No decimal separator (".") found.')}else s("Must be exactly two digits.");else s("No time unit separator found.");else s("Timestamp must start with a character in the range 0-9.");else s("No timestamp found.")}this.parse=function(t,o){if(r(n),t.startTime=l(),void 0!=t.startTime)if(t.startTime<o&&s("Start timestamp is not greater than or equal to start timestamp of previous cue."),i.test(e[a])&&s("Timestamp not separated from '--\x3e' by whitespace."),r(n),"-"==e[a])if(a++,"-"==e[a])if(a++,">"==e[a]){if(a++,i.test(e[a])&&s("'--\x3e' not separated from timestamp by whitespace."),r(n),t.endTime=l(),void 0!=t.endTime)return t.endTime<=t.startTime&&s("End timestamp is not greater than start timestamp."),i.test(e[a]),r(n),function(e,t){for(var i=e.split(n),a=[],r=0;r<i.length;r++)if(""!=i[r]){var o=i[r].indexOf(":"),l=i[r].slice(0,o),c=i[r].slice(o+1);if(-1!=a.indexOf(l)&&s("Duplicate setting."),a.push(l),""==c)return void s("No value for setting defined.");if("vertical"==l){if("rl"!=c&&"lr"!=c){s("Writing direction can only be set to 'rl' or 'rl'.");continue}t.direction=c}else if("line"==l){if(/,/.test(c)){var f=c.split(",");c=f[0];var u=f[1]}if(!/^[-\d](\d*)(\.\d+)?%?$/.test(c)){s("Line position takes a number or percentage.");continue}if(-1!=c.indexOf("-",1)){s("Line position can only have '-' at the start.");continue}if(-1!=c.indexOf("%")&&c.indexOf("%")!=c.length-1){s("Line position can only have '%' at the end.");continue}if("-"==c[0]&&"%"==c[c.length-1]){s("Line position cannot be a negative percentage.");continue}var d=c,p=!1;if("%"==c[c.length-1]&&(p=!0,d=c.slice(0,c.length-1),parseInt(c,10)>100)){s("Line position cannot be >100%.");continue}if(""===d||isNaN(d)||!isFinite(d)){s("Line position needsLine truncated
|
||||
var defaultCueSettings = {
|
||||
direction: "horizontal",
|
||||
snapToLines: true,
|
||||
linePosition: "auto",
|
||||
lineAlign: "start",
|
||||
textPosition: "auto",
|
||||
positionAlign: "auto",
|
||||
size: 100,
|
||||
alignment: "center"
|
||||
};
|
||||
var WebVTTParser = function(entities) {
|
||||
if (!entities) {
|
||||
entities = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
"&lrm": "",
|
||||
"&rlm": "",
|
||||
" ": " "
|
||||
};
|
||||
}
|
||||
this.entities = entities;
|
||||
this.parse = function(input, mode) {
|
||||
input = input.replace(/\0/g, "�");
|
||||
var NEWLINE = /\r\n|\r|\n/, startTime = Date.now(), linePos = 0, lines = input.split(NEWLINE), alreadyCollected = false, styles = [], cues = [], errors = [];
|
||||
function err(message, col) {
|
||||
errors.push({ message, line: linePos + 1, col });
|
||||
}
|
||||
var line = lines[linePos], lineLength = line.length, signature = "WEBVTT", bom = 0, signature_length = signature.length;
|
||||
if (line[0] === "\uFEFF") {
|
||||
bom = 1;
|
||||
signature_length += 1;
|
||||
}
|
||||
if (lineLength < signature_length || line.indexOf(signature) !== 0 + bom || lineLength > signature_length && line[signature_length] !== " " && line[signature_length] !== " ") {
|
||||
err('No valid signature. (File needs to start with "WEBVTT".)');
|
||||
}
|
||||
linePos++;
|
||||
while (lines[linePos] != "" && lines[linePos] != void 0) {
|
||||
err("No blank line after the signature.");
|
||||
if (lines[linePos].indexOf("-->") != -1) {
|
||||
alreadyCollected = true;
|
||||
break;
|
||||
}
|
||||
linePos++;
|
||||
}
|
||||
while (lines[linePos] != void 0) {
|
||||
var cue;
|
||||
while (!alreadyCollected && lines[linePos] == "") {
|
||||
linePos++;
|
||||
}
|
||||
if (!alreadyCollected && lines[linePos] == void 0) break;
|
||||
cue = Object.assign({}, defaultCueSettings, {
|
||||
id: "",
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
pauseOnExit: false,
|
||||
direction: "horizontal",
|
||||
snapToLines: true,
|
||||
linePosition: "auto",
|
||||
lineAlign: "start",
|
||||
textPosition: "auto",
|
||||
positionAlign: "auto",
|
||||
size: 100,
|
||||
alignment: "center",
|
||||
text: "",
|
||||
tree: null
|
||||
});
|
||||
var parseTimings = true;
|
||||
if (lines[linePos].indexOf("-->") == -1) {
|
||||
cue.id = lines[linePos];
|
||||
if (/^NOTE($|[ \t])/.test(cue.id)) {
|
||||
linePos++;
|
||||
while (lines[linePos] != "" && lines[linePos] != void 0) {
|
||||
if (lines[linePos].indexOf("-->") != -1) err("Cannot have timestamp in a comment.");
|
||||
linePos++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (/^STYLE($|[ \t])/.test(cue.id)) {
|
||||
var style = [];
|
||||
var invalid = false;
|
||||
linePos++;
|
||||
while (lines[linePos] != "" && lines[linePos] != void 0) {
|
||||
if (lines[linePos].indexOf("-->") != -1) {
|
||||
err("Cannot have timestamp in a style block.");
|
||||
invalid = true;
|
||||
}
|
||||
style.push(lines[linePos]);
|
||||
linePos++;
|
||||
}
|
||||
if (cues.length) {
|
||||
err("Style blocks cannot appear after the first cue.");
|
||||
continue;
|
||||
}
|
||||
if (!invalid) {
|
||||
styles.push(style.join("\n"));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
linePos++;
|
||||
if (lines[linePos] == "" || lines[linePos] == void 0) {
|
||||
err("Cue identifier cannot be standalone.");
|
||||
continue;
|
||||
}
|
||||
if (lines[linePos].indexOf("-->") == -1) {
|
||||
parseTimings = false;
|
||||
err("Cue identifier needs to be followed by timestamp.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
alreadyCollected = false;
|
||||
var timings = new WebVTTCueTimingsAndSettingsParser(lines[linePos], err);
|
||||
var previousCueStart = 0;
|
||||
if (cues.length > 0) {
|
||||
previousCueStart = cues[cues.length - 1].startTime;
|
||||
}
|
||||
if (parseTimings && !timings.parse(cue, previousCueStart)) {
|
||||
cue = null;
|
||||
linePos++;
|
||||
while (lines[linePos] != "" && lines[linePos] != void 0) {
|
||||
if (lines[linePos].indexOf("-->") != -1) {
|
||||
alreadyCollected = true;
|
||||
break;
|
||||
}
|
||||
linePos++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
linePos++;
|
||||
while (lines[linePos] != "" && lines[linePos] != void 0) {
|
||||
if (lines[linePos].indexOf("-->") != -1) {
|
||||
err("Blank line missing before cue.");
|
||||
alreadyCollected = true;
|
||||
break;
|
||||
}
|
||||
if (cue.text != "") cue.text += "\n";
|
||||
cue.text += lines[linePos];
|
||||
linePos++;
|
||||
}
|
||||
var cuetextparser = new WebVTTCueTextParser(cue.text, err, mode, entities);
|
||||
cue.tree = cuetextparser.parse(cue.startTime, cue.endTime);
|
||||
cues.push(cue);
|
||||
}
|
||||
cues.sort(function(a, b) {
|
||||
if (a.startTime < b.startTime) return -1;
|
||||
if (a.startTime > b.startTime) return 1;
|
||||
if (a.endTime > b.endTime) return -1;
|
||||
if (a.endTime < b.endTime) return 1;
|
||||
return 0;
|
||||
});
|
||||
return { cues, errors, time: Date.now() - startTime, styles };
|
||||
};
|
||||
};
|
||||
var WebVTTCueTimingsAndSettingsParser = function(line, errorHandler) {
|
||||
var SPACE = /[\u0020\t\f]/, NOSPACE = /[^\u0020\t\f]/, line = line, pos = 0, err = function(message) {
|
||||
errorHandler(message, pos + 1);
|
||||
};
|
||||
function skip(pattern) {
|
||||
while (line[pos] != void 0 && pattern.test(line[pos])) {
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
function collect(pattern) {
|
||||
var str = "";
|
||||
while (line[pos] != void 0 && pattern.test(line[pos])) {
|
||||
str += line[pos];
|
||||
pos++;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
function timestamp() {
|
||||
var units = "minutes", val1, val2, val3, val4;
|
||||
if (line[pos] == void 0) {
|
||||
err("No timestamp found.");
|
||||
return;
|
||||
}
|
||||
if (!/\d/.test(line[pos])) {
|
||||
err("Timestamp must start with a character in the range 0-9.");
|
||||
return;
|
||||
}
|
||||
val1 = collect(/\d/);
|
||||
if (val1.length > 2 || parseInt(val1, 10) > 59) {
|
||||
units = "hours";
|
||||
}
|
||||
if (line[pos] != ":") {
|
||||
err("No time unit separator found.");
|
||||
return;
|
||||
}
|
||||
pos++;
|
||||
val2 = collect(/\d/);
|
||||
if (val2.length != 2) {
|
||||
err("Must be exactly two digits.");
|
||||
return;
|
||||
}
|
||||
if (units == "hours" || line[pos] == ":") {
|
||||
if (line[pos] != ":") {
|
||||
err("No seconds found or minutes is greater than 59.");
|
||||
return;
|
||||
}
|
||||
pos++;
|
||||
val3 = collect(/\d/);
|
||||
if (val3.length != 2) {
|
||||
err("Must be exactly two digits.");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (val1.length != 2) {
|
||||
err("Must be exactly two digits.");
|
||||
return;
|
||||
}
|
||||
val3 = val2;
|
||||
val2 = val1;
|
||||
val1 = "0";
|
||||
}
|
||||
if (line[pos] != ".") {
|
||||
err('No decimal separator (".") found.');
|
||||
return;
|
||||
}
|
||||
pos++;
|
||||
val4 = collect(/\d/);
|
||||
if (val4.length != 3) {
|
||||
err("Milliseconds must be given in three digits.");
|
||||
return;
|
||||
}
|
||||
if (parseInt(val2, 10) > 59) {
|
||||
err("You cannot have more than 59 minutes.");
|
||||
return;
|
||||
}
|
||||
if (parseInt(val3, 10) > 59) {
|
||||
err("You cannot have more than 59 seconds.");
|
||||
return;
|
||||
}
|
||||
return parseInt(val1, 10) * 60 * 60 + parseInt(val2, 10) * 60 + parseInt(val3, 10) + parseInt(val4, 10) / 1e3;
|
||||
}
|
||||
function parseSettings(input, cue) {
|
||||
var settings = input.split(SPACE), seen = [];
|
||||
for (var i = 0; i < settings.length; i++) {
|
||||
if (settings[i] == "") continue;
|
||||
var index = settings[i].indexOf(":"), setting = settings[i].slice(0, index), value = settings[i].slice(index + 1);
|
||||
if (seen.indexOf(setting) != -1) {
|
||||
err("Duplicate setting.");
|
||||
}
|
||||
seen.push(setting);
|
||||
if (value == "") {
|
||||
err("No value for setting defined.");
|
||||
return;
|
||||
}
|
||||
if (setting == "vertical") {
|
||||
if (value != "rl" && value != "lr") {
|
||||
err("Writing direction can only be set to 'rl' or 'rl'.");
|
||||
continue;
|
||||
}
|
||||
cue.direction = value;
|
||||
} else if (setting == "line") {
|
||||
if (/,/.test(value)) {
|
||||
var comp = value.split(",");
|
||||
value = comp[0];
|
||||
var lineAlign = comp[1];
|
||||
}
|
||||
if (!/^[-\d](\d*)(\.\d+)?%?$/.test(value)) {
|
||||
err("Line position takes a number or percentage.");
|
||||
continue;
|
||||
}
|
||||
if (value.indexOf("-", 1) != -1) {
|
||||
err("Line position can only have '-' at the start.");
|
||||
continue;
|
||||
}
|
||||
if (value.indexOf("%") != -1 && value.indexOf("%") != value.length - 1) {
|
||||
err("Line position can only have '%' at the end.");
|
||||
continue;
|
||||
}
|
||||
if (value[0] == "-" && value[value.length - 1] == "%") {
|
||||
err("Line position cannot be a negative percentage.");
|
||||
continue;
|
||||
}
|
||||
var numVal = value;
|
||||
var isPercent = false;
|
||||
if (value[value.length - 1] == "%") {
|
||||
isPercent = true;
|
||||
numVal = value.slice(0, value.length - 1);
|
||||
if (parseInt(value, 10) > 100) {
|
||||
err("Line position cannot be >100%.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (numVal === "" || isNaN(numVal) || !isFinite(numVal)) {
|
||||
err("Line position needs to be a number");
|
||||
continue;
|
||||
}
|
||||
if (lineAlign !== void 0) {
|
||||
if (!["start", "center", "end"].includes(lineAlign)) {
|
||||
err("Line alignment needs to be one of start, center or end");
|
||||
continue;
|
||||
}
|
||||
cue.lineAlign = lineAlign;
|
||||
}
|
||||
cue.snapToLines = !isPercent;
|
||||
cue.linePosition = parseFloat(numVal);
|
||||
if (parseFloat(numVal).toString() !== numVal) {
|
||||
cue.nonSerializable = true;
|
||||
}
|
||||
} else if (setting == "position") {
|
||||
if (/,/.test(value)) {
|
||||
var comp = value.split(",");
|
||||
value = comp[0];
|
||||
var positionAlign = comp[1];
|
||||
}
|
||||
if (value[value.length - 1] != "%") {
|
||||
err("Text position must be a percentage.");
|
||||
continue;
|
||||
}
|
||||
if (parseInt(value, 10) > 100 || parseInt(value, 10) < 0) {
|
||||
err("Text position needs to be between 0 and 100%.");
|
||||
continue;
|
||||
}
|
||||
numVal = value.slice(0, value.length - 1);
|
||||
if (numVal === "" || isNaN(numVal) || !isFinite(numVal)) {
|
||||
err("Line position needs to be a number");
|
||||
continue;
|
||||
}
|
||||
if (positionAlign !== void 0) {
|
||||
if (!["line-left", "center", "line-right"].includes(positionAlign)) {
|
||||
err("Position alignment needs to be one of line-left, center or line-right");
|
||||
continue;
|
||||
}
|
||||
cue.positionAlign = positionAlign;
|
||||
}
|
||||
cue.textPosition = parseFloat(numVal);
|
||||
} else if (setting == "size") {
|
||||
if (value[value.length - 1] != "%") {
|
||||
err("Size must be a percentage.");
|
||||
continue;
|
||||
}
|
||||
if (parseInt(value, 10) > 100) {
|
||||
err("Size cannot be >100%.");
|
||||
continue;
|
||||
}
|
||||
var size = value.slice(0, value.length - 1);
|
||||
if (size === void 0 || size === "" || isNaN(size)) {
|
||||
err("Size needs to be a number");
|
||||
size = 100;
|
||||
continue;
|
||||
} else {
|
||||
size = parseFloat(size);
|
||||
if (size < 0 || size > 100) {
|
||||
err("Size needs to be between 0 and 100%.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
cue.size = size;
|
||||
} else if (setting == "align") {
|
||||
var alignValues = ["start", "center", "end", "left", "right"];
|
||||
if (alignValues.indexOf(value) == -1) {
|
||||
err("Alignment can only be set to one of " + alignValues.join(", ") + ".");
|
||||
continue;
|
||||
}
|
||||
cue.alignment = value;
|
||||
} else {
|
||||
err("Invalid setting.");
|
||||
}
|
||||
}
|
||||
}
|
||||
this.parse = function(cue, previousCueStart) {
|
||||
skip(SPACE);
|
||||
cue.startTime = timestamp();
|
||||
if (cue.startTime == void 0) {
|
||||
return;
|
||||
}
|
||||
if (cue.startTime < previousCueStart) {
|
||||
err("Start timestamp is not greater than or equal to start timestamp of previous cue.");
|
||||
}
|
||||
if (NOSPACE.test(line[pos])) {
|
||||
err("Timestamp not separated from '-->' by whitespace.");
|
||||
}
|
||||
skip(SPACE);
|
||||
if (line[pos] != "-") {
|
||||
err("No valid timestamp separator found.");
|
||||
return;
|
||||
}
|
||||
pos++;
|
||||
if (line[pos] != "-") {
|
||||
err("No valid timestamp separator found.");
|
||||
return;
|
||||
}
|
||||
pos++;
|
||||
if (line[pos] != ">") {
|
||||
err("No valid timestamp separator found.");
|
||||
return;
|
||||
}
|
||||
pos++;
|
||||
if (NOSPACE.test(line[pos])) {
|
||||
err("'-->' not separated from timestamp by whitespace.");
|
||||
}
|
||||
skip(SPACE);
|
||||
cue.endTime = timestamp();
|
||||
if (cue.endTime == void 0) {
|
||||
return;
|
||||
}
|
||||
if (cue.endTime <= cue.startTime) {
|
||||
err("End timestamp is not greater than start timestamp.");
|
||||
}
|
||||
if (NOSPACE.test(line[pos])) ;
|
||||
skip(SPACE);
|
||||
parseSettings(line.substring(pos), cue);
|
||||
return true;
|
||||
};
|
||||
this.parseTimestamp = function() {
|
||||
var ts = timestamp();
|
||||
if (line[pos] != void 0) {
|
||||
err("Timestamp must not have trailing characters.");
|
||||
return;
|
||||
}
|
||||
return ts;
|
||||
};
|
||||
};
|
||||
var WebVTTCueTextParser = function(line, errorHandler, mode, entities) {
|
||||
this.entities = entities;
|
||||
var self = this;
|
||||
var line = line, pos = 0, err = function(message) {
|
||||
if (mode == "metadata") return;
|
||||
errorHandler(message, pos + 1);
|
||||
};
|
||||
this.parse = function(cueStart, cueEnd) {
|
||||
function removeCycles(tree) {
|
||||
const cyclelessTree = { ...tree };
|
||||
if (tree.children) {
|
||||
cyclelessTree.children = tree.children.map(removeCycles);
|
||||
}
|
||||
if (cyclelessTree.parent) {
|
||||
delete cyclelessTree.parent;
|
||||
}
|
||||
return cyclelessTree;
|
||||
}
|
||||
var result = { children: [] }, current = result, timestamps = [];
|
||||
function attach(token2) {
|
||||
current.children.push({
|
||||
type: "object",
|
||||
name: token2[1],
|
||||
classes: token2[2],
|
||||
children: [],
|
||||
parent: current
|
||||
});
|
||||
current = current.children[current.children.length - 1];
|
||||
}
|
||||
function inScope(name2) {
|
||||
var node = current;
|
||||
while (node) {
|
||||
if (node.name == name2) return true;
|
||||
node = node.parent;
|
||||
}
|
||||
return;
|
||||
}
|
||||
while (line[pos] != void 0) {
|
||||
var token = nextToken();
|
||||
if (token[0] == "text") {
|
||||
current.children.push({ type: "text", value: token[1], parent: current });
|
||||
} else if (token[0] == "start tag") {
|
||||
if (mode == "chapters") err("Start tags not allowed in chapter title text.");
|
||||
var name = token[1];
|
||||
if (name != "v" && name != "lang" && token[3] != "") {
|
||||
err("Only <v> and <lang> can have an annotation.");
|
||||
}
|
||||
if (name == "c" || name == "i" || name == "b" || name == "u" || name == "ruby") {
|
||||
attach(token);
|
||||
} else if (name == "rt" && current.name == "ruby") {
|
||||
attach(token);
|
||||
} else if (name == "v") {
|
||||
if (inScope("v")) {
|
||||
err("<v> cannot be nested inside itself.");
|
||||
}
|
||||
attach(token);
|
||||
current.value = token[3];
|
||||
if (!token[3]) {
|
||||
err("<v> requires an annotation.");
|
||||
}
|
||||
} else if (name == "lang") {
|
||||
attach(token);
|
||||
current.value = token[3];
|
||||
} else {
|
||||
err("Incorrect start tag.");
|
||||
}
|
||||
} else if (token[0] == "end tag") {
|
||||
if (mode == "chapters") err("End tags not allowed in chapter title text.");
|
||||
if (token[1] == current.name) {
|
||||
current = current.parent;
|
||||
} else if (token[1] == "ruby" && current.name == "rt") {
|
||||
current = current.parent.parent;
|
||||
} else {
|
||||
err("Incorrect end tag.");
|
||||
}
|
||||
} else if (token[0] == "timestamp") {
|
||||
if (mode == "chapters") err("Timestamp not allowed in chapter title text.");
|
||||
var timings = new WebVTTCueTimingsAndSettingsParser(token[1], err), timestamp = timings.parseTimestamp();
|
||||
if (timestamp != void 0) {
|
||||
if (timestamp <= cueStart || timestamp >= cueEnd) {
|
||||
err("Timestamp must be between start timestamp and end timestamp.");
|
||||
}
|
||||
if (timestamps.length > 0 && timestamps[timestamps.length - 1] >= timestamp) {
|
||||
err("Timestamp must be greater than any previous timestamp.");
|
||||
}
|
||||
current.children.push({ type: "timestamp", value: timestamp, parent: current });
|
||||
timestamps.push(timestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (current.parent) {
|
||||
if (current.name != "v") {
|
||||
err("Required end tag missing.");
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
return removeCycles(result);
|
||||
};
|
||||
function nextToken() {
|
||||
var state = "data", result = "", buffer = "", classes = [];
|
||||
while (line[pos - 1] != void 0 || pos == 0) {
|
||||
var c = line[pos];
|
||||
if (state == "data") {
|
||||
if (c == "&") {
|
||||
buffer = c;
|
||||
state = "escape";
|
||||
} else if (c == "<" && result == "") {
|
||||
state = "tag";
|
||||
} else if (c == "<" || c == void 0) {
|
||||
return ["text", result];
|
||||
} else {
|
||||
result += c;
|
||||
}
|
||||
} else if (state == "escape") {
|
||||
if (c == "<" || c == void 0) {
|
||||
err("Incorrect escape.");
|
||||
let m;
|
||||
if (m = buffer.match(/^&#([0-9]+)$/)) {
|
||||
result += String.fromCharCode(m[1]);
|
||||
} else {
|
||||
if (self.entities[buffer]) {
|
||||
result += self.entities[buffer];
|
||||
} else {
|
||||
result += buffer;
|
||||
}
|
||||
}
|
||||
return ["text", result];
|
||||
} else if (c == "&") {
|
||||
err("Incorrect escape.");
|
||||
result += buffer;
|
||||
buffer = c;
|
||||
} else if (/[a-z#0-9]/i.test(c)) {
|
||||
buffer += c;
|
||||
} else if (c == ";") {
|
||||
let m;
|
||||
if (m = buffer.match(/^&#(x?[0-9]+)$/)) {
|
||||
result += String.fromCharCode("0" + m[1]);
|
||||
} else if (self.entities[buffer + c]) {
|
||||
result += self.entities[buffer + c];
|
||||
} else if (m = Object.keys(entities).find((n) => buffer.startsWith(n))) {
|
||||
result += self.entities[m] + buffer.slice(m.length) + c;
|
||||
} else {
|
||||
err("Incorrect escape.");
|
||||
result += buffer + ";";
|
||||
}
|
||||
state = "data";
|
||||
} else {
|
||||
err("Incorrect escape.");
|
||||
result += buffer + c;
|
||||
state = "data";
|
||||
}
|
||||
} else if (state == "tag") {
|
||||
if (c == " " || c == "\n" || c == "\f" || c == " ") {
|
||||
state = "start tag annotation";
|
||||
} else if (c == ".") {
|
||||
state = "start tag class";
|
||||
} else if (c == "/") {
|
||||
state = "end tag";
|
||||
} else if (/\d/.test(c)) {
|
||||
result = c;
|
||||
state = "timestamp tag";
|
||||
} else if (c == ">" || c == void 0) {
|
||||
if (c == ">") {
|
||||
pos++;
|
||||
}
|
||||
return ["start tag", "", [], ""];
|
||||
} else {
|
||||
result = c;
|
||||
state = "start tag";
|
||||
}
|
||||
} else if (state == "start tag") {
|
||||
if (c == " " || c == "\f" || c == " ") {
|
||||
state = "start tag annotation";
|
||||
} else if (c == "\n") {
|
||||
buffer = c;
|
||||
state = "start tag annotation";
|
||||
} else if (c == ".") {
|
||||
state = "start tag class";
|
||||
} else if (c == ">" || c == void 0) {
|
||||
if (c == ">") {
|
||||
pos++;
|
||||
}
|
||||
return ["start tag", result, [], ""];
|
||||
} else {
|
||||
result += c;
|
||||
}
|
||||
} else if (state == "start tag class") {
|
||||
if (c == " " || c == "\f" || c == " ") {
|
||||
if (buffer) {
|
||||
classes.push(buffer);
|
||||
}
|
||||
buffer = "";
|
||||
state = "start tag annotation";
|
||||
} else if (c == "\n") {
|
||||
if (buffer) {
|
||||
classes.push(buffer);
|
||||
}
|
||||
buffer = c;
|
||||
state = "start tag annotation";
|
||||
} else if (c == ".") {
|
||||
if (buffer) {
|
||||
classes.push(buffer);
|
||||
}
|
||||
buffer = "";
|
||||
} else if (c == ">" || c == void 0) {
|
||||
if (c == ">") {
|
||||
pos++;
|
||||
}
|
||||
if (buffer) {
|
||||
classes.push(buffer);
|
||||
}
|
||||
return ["start tag", result, classes, ""];
|
||||
} else {
|
||||
buffer += c;
|
||||
}
|
||||
} else if (state == "start tag annotation") {
|
||||
if (c == ">" || c == void 0) {
|
||||
if (c == ">") {
|
||||
pos++;
|
||||
}
|
||||
buffer = buffer.split(/[\u0020\t\f\r\n]+/).filter(function(item) {
|
||||
if (item) return true;
|
||||
}).join(" ");
|
||||
return ["start tag", result, classes, buffer];
|
||||
} else {
|
||||
buffer += c;
|
||||
}
|
||||
} else if (state == "end tag") {
|
||||
if (c == ">" || c == void 0) {
|
||||
if (c == ">") {
|
||||
pos++;
|
||||
}
|
||||
return ["end tag", result];
|
||||
} else {
|
||||
result += c;
|
||||
}
|
||||
} else if (state == "timestamp tag") {
|
||||
if (c == ">" || c == void 0) {
|
||||
if (c == ">") {
|
||||
pos++;
|
||||
}
|
||||
return ["timestamp", result];
|
||||
} else {
|
||||
result += c;
|
||||
}
|
||||
} else {
|
||||
err("Never happens.");
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
};
|
||||
var WebVTTSerializer = function() {
|
||||
function serializeTimestamp(seconds) {
|
||||
const ms = ("00" + (seconds - Math.floor(seconds)).toFixed(3) * 1e3).slice(-3);
|
||||
let h = 0, m = 0, s = 0;
|
||||
if (seconds >= 3600) {
|
||||
h = Math.floor(seconds / 3600);
|
||||
}
|
||||
m = Math.floor((seconds - 3600 * h) / 60);
|
||||
s = Math.floor(seconds - 3600 * h - 60 * m);
|
||||
return (h ? h + ":" : "") + ("" + m).padStart(2, "0") + ":" + ("" + s).padStart(2, "0") + "." + ms;
|
||||
}
|
||||
function serializeCueSettings(cue) {
|
||||
var result = "";
|
||||
const nonDefaultSettings = Object.keys(defaultCueSettings).filter((s) => cue[s] !== defaultCueSettings[s]);
|
||||
if (nonDefaultSettings.includes("direction")) {
|
||||
result += " vertical:" + cue.direction;
|
||||
}
|
||||
if (nonDefaultSettings.includes("alignment")) {
|
||||
result += " align:" + cue.alignment;
|
||||
}
|
||||
if (nonDefaultSettings.includes("size")) {
|
||||
result += " size:" + cue.size + "%";
|
||||
}
|
||||
if (nonDefaultSettings.includes("lineAlign") || nonDefaultSettings.includes("linePosition")) {
|
||||
result += " line:" + cue.linePosition + (cue.snapToLines ? "" : "%") + (cue.lineAlign && cue.lineAlign != defaultCueSettings.lineAlign ? "," + cue.lineAlign : "");
|
||||
}
|
||||
if (nonDefaultSettings.includes("textPosition") || nonDefaultSettings.includes("positionAlign")) {
|
||||
result += " position:" + cue.textPosition + "%" + (cue.positionAlign && cue.positionAlign !== defaultCueSettings.positionAlign ? "," + cue.positionAlign : "");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function serializeTree(tree) {
|
||||
var result = "";
|
||||
for (var i = 0; i < tree.length; i++) {
|
||||
var node = tree[i];
|
||||
if (node.type == "text") {
|
||||
result += node.value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
} else if (node.type == "object") {
|
||||
result += "<" + node.name;
|
||||
if (node.classes) {
|
||||
for (var y = 0; y < node.classes.length; y++) {
|
||||
result += "." + node.classes[y];
|
||||
}
|
||||
}
|
||||
if (node.value) {
|
||||
result += " " + node.value;
|
||||
}
|
||||
result += ">";
|
||||
if (node.children) result += serializeTree(node.children);
|
||||
result += "</" + node.name + ">";
|
||||
} else if (node.type == "timestamp") {
|
||||
result += "<" + serializeTimestamp(node.value) + ">";
|
||||
} else {
|
||||
result += "<" + node.value + ">";
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function serializeCue(cue) {
|
||||
return (cue.id !== void 0 ? cue.id + "\n" : "") + serializeTimestamp(cue.startTime) + " --> " + serializeTimestamp(cue.endTime) + serializeCueSettings(cue) + "\n" + serializeTree(cue.tree.children) + "\n\n";
|
||||
}
|
||||
function serializeStyle(style) {
|
||||
return "STYLE\n" + style + "\n\n";
|
||||
}
|
||||
this.serialize = function(cues, styles) {
|
||||
var result = "WEBVTT\n\n";
|
||||
if (styles) {
|
||||
for (var i = 0; i < styles.length; i++) {
|
||||
result += serializeStyle(styles[i]);
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < cues.length; i++) {
|
||||
result += serializeCue(cues[i]);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
};
|
||||
async function loadVtt(option, { getExt, srtToVtt, assToVtt }) {
|
||||
const response = await fetch(option.url);
|
||||
const buffer = await response.arrayBuffer();
|
||||
const decoder = new TextDecoder(option.encoding || "utf-8");
|
||||
const text = decoder.decode(buffer);
|
||||
switch (option.type || getExt(option.url)) {
|
||||
case "srt": {
|
||||
return srtToVtt(text);
|
||||
}
|
||||
case "ass": {
|
||||
return assToVtt(text);
|
||||
}
|
||||
case "vtt": {
|
||||
return text;
|
||||
}
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
function mergeTrees(trees) {
|
||||
const parser = new WebVTTParser();
|
||||
const result = parser.parse("", "metadata");
|
||||
for (let i = 0; i < trees.length; i++) {
|
||||
const tree = trees[i];
|
||||
if (!tree.updated) {
|
||||
tree.updated = true;
|
||||
for (let j = 0; j < tree.cues.length; j++) {
|
||||
const cue = tree.cues[j];
|
||||
for (let k = 0; k < cue.tree.children.length; k++) {
|
||||
const children = cue.tree.children[k];
|
||||
children.value = `<div class="art-subtitle-${tree.name}">${children.value}</div>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
result.cues.push(...tree.cues);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function artplayerPluginMultipleSubtitles({ subtitles = [] }) {
|
||||
return async (art) => {
|
||||
const { unescape, getExt, srtToVtt, assToVtt } = art.constructor.utils;
|
||||
const parser = new WebVTTParser();
|
||||
const seri = new WebVTTSerializer();
|
||||
const vtts = await Promise.all(
|
||||
subtitles.map((option) => {
|
||||
return loadVtt(option, { getExt, srtToVtt, assToVtt });
|
||||
})
|
||||
);
|
||||
const trees = vtts.map((vtt, index) => {
|
||||
const tree = parser.parse(vtt, "metadata");
|
||||
tree.url = subtitles[index].url;
|
||||
tree.name = subtitles[index].name;
|
||||
return tree;
|
||||
});
|
||||
let lastUrl = "";
|
||||
function setTracks(trees2 = []) {
|
||||
const tree = mergeTrees(trees2);
|
||||
const vtt = seri.serialize(tree.cues);
|
||||
URL.revokeObjectURL(lastUrl);
|
||||
const url = URL.createObjectURL(new Blob([vtt], { type: "text/vtt" }));
|
||||
lastUrl = url;
|
||||
art.option.subtitle.escape = false;
|
||||
art.subtitle.init({
|
||||
...art.option.subtitle,
|
||||
url,
|
||||
type: "vtt",
|
||||
onVttLoad: unescape
|
||||
});
|
||||
}
|
||||
setTracks(trees);
|
||||
return {
|
||||
name: "multipleSubtitles",
|
||||
tracks(names = []) {
|
||||
return setTracks(names.map((name) => trees.find((tree) => tree.name === name)));
|
||||
},
|
||||
reset() {
|
||||
return setTracks(trees);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
export {
|
||||
artplayerPluginMultipleSubtitles as default
|
||||
};
|
||||
+546
-1
@@ -4,4 +4,549 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
var t=null,e=function(){t=null};function i(t,e,i){return e&&function(t,e){for(var i=0;i<e.length;i++){var n=e[i];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}(t.prototype,e),t}function n(){return n=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var i=arguments[e];for(var n in i)Object.prototype.hasOwnProperty.call(i,n)&&(t[n]=i[n])}return t},n.apply(this,arguments)}function s(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t,o(t,e)}function r(t){return(r=Object.setPrototypeOf?Object.getPrototypeOf:function(t){return t.__proto__||Object.getPrototypeOf(t)})(t)}function o(t,e){return(o=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t})(t,e)}function a(t,e,i){return(a=function(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch(t){return!1}}()?Reflect.construct:function(t,e,i){var n=[null];n.push.apply(n,e);var s=new(Function.bind.apply(t,n));return i&&o(s,i.prototype),s}).apply(null,arguments)}function h(t){var e="function"==typeof Map?/* @__PURE__ */new Map:void 0;return h=function(t){if(null===t||-1===Function.toString.call(t).indexOf("[native code]"))return t;if("function"!=typeof t)throw new TypeError("Super expression must either be null or a function");if(void 0!==e){if(e.has(t))return e.get(t);e.set(t,i)}function i(){return a(t,arguments,r(this).constructor)}return i.prototype=Object.create(t.prototype,{constructor:{value:i,enumerable:!1,writable:!0,configurable:!0}}),o(i,t)},h(t)}function d(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function u(t,e){(null==e||e>t.length)&&(e=t.length);for(var i=0,n=new Array(e);i<e;i++)n[i]=t[i];return n}var l=0;function c(t){return"__private_"+l+++"_"+t}function v(t,e){if(!Object.prototype.hasOwnProperty.call(t,e))throw new TypeError("attempted to use private field on non-instance");return t}var f={};f.CustomEvent="function"==typeof CustomEvent?CustomEvent:function(t){return e[t]=new e("").constructor[t],e;function e(t,e){e||(e={});var i=document.createEvent("CustomEvent");return i.initCustomEvent(t,!!e.bubbles,!!e.cancelable,e.detail),i}}("prototype");var p,E,y,A=f.CustomEvent,m=c("mediaElement"),b=c("currentTime"),g=c("enabled"),O=function(){function t(t){Object.defineProperty(this,m,{writable:!0,value:void 0}),Object.defineProperty(this,b,{writable:!0,value:void 0}),Object.defineProperty(this,g,{writable:!0,value:void 0}),this.seeking=void 0,v(this,m)[m]=t,v(this,b)[b]=0,v(this,g)[g]=!1,this.seeking=!1,this.t=this.t.bind(this),this.i=this.i.bind(this),this.h=this.h.bind(this),this.enable()}var e=t.prototype;return e.i=function(){this.seeking=!0},e.h=function(){this.seeking=!1},e.t=function(){var t,e;this.seeking||null!=(t=v(this,m)[m])&&t.paused||(v(this,b)[b]=null==(e=v(this,m)[m])?void 0:e.currentTime)},e.enable=function(){var t,e,i;v(this,g)[g]||(null==(t=v(this,m)[m])||t.addEventListener("seeking",this.i),null==(e=v(this,m)[m])||e.addEventListener("seeked",this.h),null==(i=v(this,m)[m])||i.addEventListener("timeupdate",this.t),v(this,g)[g]=!0)},e.disable=function(){var t,e,i;v(this,g)[g]&&(null==(t=v(this,m)[m])||t.removeEventListener("seeking",this.i),null==(e=v(this,m)[m])||e.removeEventListener("seeked",this.h),null==(i=v(this,m)[m])||i.removeEventListener("timeupdate",this.t),v(this,g)[g]=!1)},e.play=function(){var t=this;return new Promise(function(e){var i;e(null==(i=v(t,m)[m])?void 0:i.play())})},e.pause=function(){var t;null==(t=v(this,m)[m])||t.pause()},e.reset=function(){v(this,b)[b]=0,v(this,g)[g]=!1,this.seeking=!1,this.enable()},e.destroy=function(){v(this,b)[b]=0,v(this,g)[g]=!1,this.seeking=!1,this.disable(),v(this,m)[m]=void 0},i(t,[{key:"enabled",get:function(){return v(this,g)[g]}},{key:"currentTime",get:function(){return v(this,b)[b]}},{key:"duration",get:function(){var t;return null==(t=v(this,m)[m])?void 0:t.duration}},{key:"muted",get:function(){var t;return null==(t=v(this,m)[m])?void 0:t.muted}},{key:"volume",get:function(){var t;return null==(t=v(this,m)[m])?void 0:t.volume}}]),t}(),w=function(){function t(){this.delegate=document.createDocumentFragment()}var e=t.prototype;return e.addEventListener=function(){this.delegate.addEventListener.apply(this.delegate,[].slice.call(arguments))},e.dispatchEvent=function(){return this.delegate.dispatchEvent.apply(this.delegate,[].slice.call(arguments))},e.removeEventListener=function(){return this.delegate.removeEventListener.apply(this.delegate,[].slice.call(arguments))},t}(),R=["abort","canplay","canplaythrough","durationchange","emptied","ended","error","loadeddata","loadedmetadata","loadstart","pause","play","playing","progress","ratechange","seeked","seeking","stalled","suspend","timeupdate","volumechange","waiting"];(y=p||(p={})).MEDIA_STALine truncated
|
||||
var t = null, i = function() {
|
||||
t = null;
|
||||
}, e = function() {
|
||||
var e2 = window;
|
||||
return e2.google && e2.google.ima ? Promise.resolve(e2.google.ima) : t || ((t = new Promise(function(t2, i2) {
|
||||
var e3 = document.createElement("script");
|
||||
e3.async = true, e3.src = "https://imasdk.googleapis.com/js/sdkloader/ima3.js", e3.onload = t2, e3.onerror = i2, document.body.appendChild(e3);
|
||||
}).then(function() {
|
||||
return e2.google.ima;
|
||||
})).then(i).catch(i), t);
|
||||
};
|
||||
function n(t2, i2) {
|
||||
for (var e2 = 0; e2 < i2.length; e2++) {
|
||||
var n2 = i2[e2];
|
||||
n2.enumerable = n2.enumerable || false, n2.configurable = true, "value" in n2 && (n2.writable = true), Object.defineProperty(t2, n2.key, n2);
|
||||
}
|
||||
}
|
||||
function s(t2, i2, e2) {
|
||||
return i2 && n(t2.prototype, i2), t2;
|
||||
}
|
||||
function h() {
|
||||
return h = Object.assign || function(t2) {
|
||||
for (var i2 = 1; i2 < arguments.length; i2++) {
|
||||
var e2 = arguments[i2];
|
||||
for (var n2 in e2) Object.prototype.hasOwnProperty.call(e2, n2) && (t2[n2] = e2[n2]);
|
||||
}
|
||||
return t2;
|
||||
}, h.apply(this, arguments);
|
||||
}
|
||||
function r(t2, i2) {
|
||||
t2.prototype = Object.create(i2.prototype), t2.prototype.constructor = t2, a(t2, i2);
|
||||
}
|
||||
function o(t2) {
|
||||
return o = Object.setPrototypeOf ? Object.getPrototypeOf : function(t3) {
|
||||
return t3.__proto__ || Object.getPrototypeOf(t3);
|
||||
}, o(t2);
|
||||
}
|
||||
function a(t2, i2) {
|
||||
return a = Object.setPrototypeOf || function(t3, i3) {
|
||||
return t3.__proto__ = i3, t3;
|
||||
}, a(t2, i2);
|
||||
}
|
||||
function u() {
|
||||
if ("undefined" == typeof Reflect || !Reflect.construct) return false;
|
||||
if (Reflect.construct.sham) return false;
|
||||
if ("function" == typeof Proxy) return true;
|
||||
try {
|
||||
return Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function() {
|
||||
})), true;
|
||||
} catch (t2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function c(t2, i2, e2) {
|
||||
return c = u() ? Reflect.construct : function(t3, i3, e3) {
|
||||
var n2 = [null];
|
||||
n2.push.apply(n2, i3);
|
||||
var s2 = new (Function.bind.apply(t3, n2))();
|
||||
return e3 && a(s2, e3.prototype), s2;
|
||||
}, c.apply(null, arguments);
|
||||
}
|
||||
function d(t2) {
|
||||
var i2 = "function" == typeof Map ? /* @__PURE__ */ new Map() : void 0;
|
||||
return d = function(t3) {
|
||||
if (null === t3 || -1 === Function.toString.call(t3).indexOf("[native code]")) return t3;
|
||||
if ("function" != typeof t3) throw new TypeError("Super expression must either be null or a function");
|
||||
if (void 0 !== i2) {
|
||||
if (i2.has(t3)) return i2.get(t3);
|
||||
i2.set(t3, e2);
|
||||
}
|
||||
function e2() {
|
||||
return c(t3, arguments, o(this).constructor);
|
||||
}
|
||||
return e2.prototype = Object.create(t3.prototype, { constructor: { value: e2, enumerable: false, writable: true, configurable: true } }), a(e2, t3);
|
||||
}, d(t2);
|
||||
}
|
||||
function l(t2) {
|
||||
if (void 0 === t2) throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
|
||||
return t2;
|
||||
}
|
||||
function f(t2, i2) {
|
||||
(null == i2 || i2 > t2.length) && (i2 = t2.length);
|
||||
for (var e2 = 0, n2 = new Array(i2); e2 < i2; e2++) n2[e2] = t2[e2];
|
||||
return n2;
|
||||
}
|
||||
var v = 0;
|
||||
function b(t2) {
|
||||
return "__private_" + v++ + "_" + t2;
|
||||
}
|
||||
function w(t2, i2) {
|
||||
if (!Object.prototype.hasOwnProperty.call(t2, i2)) throw new TypeError("attempted to use private field on non-instance");
|
||||
return t2;
|
||||
}
|
||||
var m = {};
|
||||
m.CustomEvent = "function" == typeof CustomEvent ? CustomEvent : (function(t2) {
|
||||
return i2[t2] = new i2("").constructor[t2], i2;
|
||||
function i2(t3, i3) {
|
||||
i3 || (i3 = {});
|
||||
var e2 = document.createEvent("CustomEvent");
|
||||
return e2.initCustomEvent(t3, !!i3.bubbles, !!i3.cancelable, i3.detail), e2;
|
||||
}
|
||||
})("prototype");
|
||||
var p, y, O = m.CustomEvent, j = b("mediaElement"), g = b("currentTime"), A = b("enabled"), k = (function() {
|
||||
function t2(t3) {
|
||||
Object.defineProperty(this, j, { writable: true, value: void 0 }), Object.defineProperty(this, g, { writable: true, value: void 0 }), Object.defineProperty(this, A, { writable: true, value: void 0 }), this.seeking = void 0, w(this, j)[j] = t3, w(this, g)[g] = 0, w(this, A)[A] = false, this.seeking = false, this.t = this.t.bind(this), this.i = this.i.bind(this), this.h = this.h.bind(this), this.enable();
|
||||
}
|
||||
var i2 = t2.prototype;
|
||||
return i2.i = function() {
|
||||
this.seeking = true;
|
||||
}, i2.h = function() {
|
||||
this.seeking = false;
|
||||
}, i2.t = function() {
|
||||
var t3, i3;
|
||||
this.seeking || null != (t3 = w(this, j)[j]) && t3.paused || (w(this, g)[g] = null == (i3 = w(this, j)[j]) ? void 0 : i3.currentTime);
|
||||
}, i2.enable = function() {
|
||||
var t3, i3, e2;
|
||||
w(this, A)[A] || (null == (t3 = w(this, j)[j]) || t3.addEventListener("seeking", this.i), null == (i3 = w(this, j)[j]) || i3.addEventListener("seeked", this.h), null == (e2 = w(this, j)[j]) || e2.addEventListener("timeupdate", this.t), w(this, A)[A] = true);
|
||||
}, i2.disable = function() {
|
||||
var t3, i3, e2;
|
||||
w(this, A)[A] && (null == (t3 = w(this, j)[j]) || t3.removeEventListener("seeking", this.i), null == (i3 = w(this, j)[j]) || i3.removeEventListener("seeked", this.h), null == (e2 = w(this, j)[j]) || e2.removeEventListener("timeupdate", this.t), w(this, A)[A] = false);
|
||||
}, i2.play = function() {
|
||||
var t3 = this;
|
||||
return new Promise(function(i3) {
|
||||
var e2;
|
||||
i3(null == (e2 = w(t3, j)[j]) ? void 0 : e2.play());
|
||||
});
|
||||
}, i2.pause = function() {
|
||||
var t3;
|
||||
null == (t3 = w(this, j)[j]) || t3.pause();
|
||||
}, i2.reset = function() {
|
||||
w(this, g)[g] = 0, w(this, A)[A] = false, this.seeking = false, this.enable();
|
||||
}, i2.destroy = function() {
|
||||
w(this, g)[g] = 0, w(this, A)[A] = false, this.seeking = false, this.disable(), w(this, j)[j] = void 0;
|
||||
}, s(t2, [{ key: "enabled", get: function() {
|
||||
return w(this, A)[A];
|
||||
} }, { key: "currentTime", get: function() {
|
||||
return w(this, g)[g];
|
||||
} }, { key: "duration", get: function() {
|
||||
var t3;
|
||||
return null == (t3 = w(this, j)[j]) ? void 0 : t3.duration;
|
||||
} }, { key: "muted", get: function() {
|
||||
var t3;
|
||||
return null == (t3 = w(this, j)[j]) ? void 0 : t3.muted;
|
||||
} }, { key: "volume", get: function() {
|
||||
var t3;
|
||||
return null == (t3 = w(this, j)[j]) ? void 0 : t3.volume;
|
||||
} }]), t2;
|
||||
})(), C = (function() {
|
||||
function t2() {
|
||||
this.delegate = document.createDocumentFragment();
|
||||
}
|
||||
var i2 = t2.prototype;
|
||||
return i2.addEventListener = function() {
|
||||
this.delegate.addEventListener.apply(this.delegate, [].slice.call(arguments));
|
||||
}, i2.dispatchEvent = function() {
|
||||
return this.delegate.dispatchEvent.apply(this.delegate, [].slice.call(arguments));
|
||||
}, i2.removeEventListener = function() {
|
||||
return this.delegate.removeEventListener.apply(this.delegate, [].slice.call(arguments));
|
||||
}, t2;
|
||||
})(), P = ["abort", "canplay", "canplaythrough", "durationchange", "emptied", "ended", "error", "loadeddata", "loadedmetadata", "loadstart", "pause", "play", "playing", "progress", "ratechange", "seeked", "seeking", "stalled", "suspend", "timeupdate", "volumechange", "waiting"];
|
||||
!(function(t2) {
|
||||
t2.MEDIA_START = "MediaStart", t2.MEDIA_IMPRESSION = "MediaImpression", t2.MEDIA_STOP = "MediaStop", t2.MEDIA_CUE_POINTS_CHANGE = "MediaCuePointsChange", t2.MEDIA_RESUMED = "MediaResumed";
|
||||
})(p || (p = {})), (function(t2) {
|
||||
t2.AD_ERROR = "AdError", t2.AD_BUFFERING = "AdBuffering", t2.LOADED = "AdLoaded", t2.IMPRESSION = "AdImpression", t2.STARTED = "AdStarted", t2.FIRST_QUARTILE = "AdFirstQuartile", t2.MIDPOINT = "AdMidpoint", t2.THIRD_QUARTILE = "AdThirdQuartile", t2.AD_PROGRESS = "AdProgress", t2.COMPLETE = "AdComplete", t2.CLICK = "AdClick", t2.PAUSED = "AdPaused", t2.RESUMED = "AdResumed", t2.SKIPPED = "AdSkipped", t2.SKIPPABLE_STATE_CHANGED = "AdSkippableStateChanged", t2.VOLUME_CHANGED = "AdVolumeChanged", t2.VOLUME_MUTED = "AdMuted", t2.AD_METADATA = "AdMetadata", t2.AD_BREAK_READY = "AdBreakReady", t2.CONTENT_PAUSE_REQUESTED = "AdContentPauseRequested", t2.CONTENT_RESUME_REQUESTED = "AdContentResumeRequested", t2.ALL_ADS_COMPLETED = "AdAllAdsCompleted", t2.DURATION_CHANGE = "AdDurationChange", t2.INTERACTION = "AdInteraction", t2.LINEAR_CHANGED = "AdLinearChanged", t2.LOG = "AdLog", t2.USER_CLOSE = "AdUserClose", t2.AD_CAN_PLAY = "AdCanPlay", t2.EXPANDED_CHANGED = "AdExpandedChanged", t2.VIEWABLE_IMPRESSION = "AdViewableImpression";
|
||||
})(y || (y = {}));
|
||||
var R = h({}, y, p), E = function() {
|
||||
this.disableCustomPlaybackForIOS10Plus = false, this.autoResize = true, this.clickTrackingElement = void 0;
|
||||
}, T = (function(t2) {
|
||||
function i2() {
|
||||
var i3;
|
||||
return (i3 = t2.call.apply(t2, [this].concat([].slice.call(arguments))) || this).errorCode = void 0, i3.innerError = void 0, i3.type = void 0, i3.vastErrorCode = void 0, i3;
|
||||
}
|
||||
return r(i2, t2), i2;
|
||||
})(d(Error));
|
||||
T.ERROR_CODE_ADS_MANAGER_LOADED_TIMEOUT = 9e3, T.ERROR_CODE_REQUEST_ADS_TIMEOUT = 9001;
|
||||
var S = b("mediaElement"), M = b("adElement"), x = b("customPlayhead"), I = b("adsRenderingSettings"), B = b("ima"), L = b("adDisplayContainer"), D = b("adsManager"), W = b("width"), _ = b("height"), q = b("adsLoader"), F = b("playerOptions"), N = b("resizeObserver"), V = b("currentAd"), G = b("loadedAd"), Q = b("mediaStartTriggered"), U = b("mediaImpressionTriggered"), z = b("mediaInActivation"), Z = b("customPlaybackTimeAdjustedOnEnded"), $ = b("cuePoints"), H = b("adCurrentTime"), J = b("adDuration"), K = b("startAdCallback"), X = b("adsManagerLoadedTimeout"), Y = b("requestAdsTimeout"), tt = b("wasExternallyPaused"), it = b("lastNonZeroAdVolume"), et = b("activatePromise"), nt = (function(t2) {
|
||||
function i2(i3, e3, n2, s2, h2) {
|
||||
var r2;
|
||||
void 0 === s2 && (s2 = new i3.AdsRenderingSettings()), void 0 === h2 && (h2 = new E()), r2 = t2.call(this) || this, Object.defineProperty(l(r2), S, { writable: true, value: void 0 }), Object.defineProperty(l(r2), M, { writable: true, value: void 0 }), Object.defineProperty(l(r2), x, { writable: true, value: void 0 }), Object.defineProperty(l(r2), I, { writable: true, value: void 0 }), Object.defineProperty(l(r2), B, { writable: true, value: void 0 }), Object.defineProperty(l(r2), L, { writable: true, value: void 0 }), Object.defineProperty(l(r2), D, { writable: true, value: void 0 }), Object.defineProperty(l(r2), W, { writable: true, value: void 0 }), Object.defineProperty(l(r2), _, { writable: true, value: void 0 }), Object.defineProperty(l(r2), q, { writable: true, value: void 0 }), Object.defineProperty(l(r2), F, { writable: true, value: void 0 }), Object.defineProperty(l(r2), N, { writable: true, value: void 0 }), Object.defineProperty(l(r2), V, { writable: true, value: void 0 }), Object.defineProperty(l(r2), G, { writable: true, value: void 0 }), Object.defineProperty(l(r2), Q, { writable: true, value: false }), Object.defineProperty(l(r2), U, { writable: true, value: false }), Object.defineProperty(l(r2), z, { writable: true, value: false }), Object.defineProperty(l(r2), Z, { writable: true, value: false }), Object.defineProperty(l(r2), $, { writable: true, value: [] }), Object.defineProperty(l(r2), H, { writable: true, value: void 0 }), Object.defineProperty(l(r2), J, { writable: true, value: void 0 }), Object.defineProperty(l(r2), K, { writable: true, value: void 0 }), Object.defineProperty(l(r2), X, { writable: true, value: void 0 }), Object.defineProperty(l(r2), Y, { writable: true, value: void 0 }), Object.defineProperty(l(r2), tt, { writable: true, value: false }), Object.defineProperty(l(r2), it, { writable: true, value: 1 }), Object.defineProperty(l(r2), et, { writable: true, value: Promise.resolve() }), w(l(r2), S)[S] = e3, w(l(r2), M)[M] = n2, w(l(r2), B)[B] = i3, w(l(r2), F)[F] = h2, w(l(r2), I)[I] = s2, w(l(r2), I)[I].restoreCustomPlaybackStateOnAdBreakComplete = true, h2.disableCustomPlaybackForIOS10Plus && !w(l(r2), S)[S].hasAttribute("playsinline") && w(l(r2), S)[S].setAttribute("playsinline", ""), w(l(r2), B)[B].settings.setDisableCustomPlaybackForIOS10Plus(h2.disableCustomPlaybackForIOS10Plus), w(l(r2), x)[x] = new k(w(l(r2), S)[S]), r2.o = r2.o.bind(l(r2)), P.forEach(function(t3) {
|
||||
w(l(r2), S)[S].addEventListener(t3, r2.o);
|
||||
}), r2.u = r2.u.bind(l(r2)), r2.l = r2.l.bind(l(r2));
|
||||
var o2 = w(l(r2), S)[S], a2 = o2.offsetHeight, u2 = o2.offsetWidth;
|
||||
return w(l(r2), W)[W] = u2, w(l(r2), _)[_] = a2, h2.autoResize && window.ResizeObserver && (w(l(r2), N)[N] = new window.ResizeObserver(function(t3) {
|
||||
return r2.v(t3);
|
||||
}), w(l(r2), N)[N].observe(w(l(r2), S)[S])), r2;
|
||||
}
|
||||
r(i2, t2);
|
||||
var e2 = i2.prototype;
|
||||
return e2.activate = function() {
|
||||
var t3 = this;
|
||||
if (!w(this, Q)[Q] && !w(this, z)[z] && (w(this, z)[z] = true, w(this, S)[S].paused)) {
|
||||
var i3 = function() {
|
||||
return w(t3, S)[S].pause(), new Promise(function(i4) {
|
||||
setTimeout(function() {
|
||||
w(t3, z)[z] = false, i4();
|
||||
}, 1);
|
||||
});
|
||||
};
|
||||
w(this, et)[et] = new Promise(function(i4) {
|
||||
return i4(w(t3, S)[S].play());
|
||||
}).then(i3).catch(i3);
|
||||
}
|
||||
}, e2.playAds = function(t3) {
|
||||
this.m(t3);
|
||||
}, e2.loadAds = function(t3, i3) {
|
||||
this.m(t3, false, i3);
|
||||
}, e2.p = function() {
|
||||
var t3 = this;
|
||||
return w(this, et)[et].then(function() {
|
||||
return new Promise(function(i3) {
|
||||
return i3(w(t3, S)[S].play());
|
||||
});
|
||||
});
|
||||
}, e2.m = function(t3, i3, e3) {
|
||||
var n2 = this;
|
||||
void 0 === i3 && (i3 = true), this.reset(), this.O(), w(this, B)[B].settings.setAutoPlayAdBreaks(i3), w(this, S)[S].ended && (w(this, x)[x].reset(), w(this, S)[S].currentTime = 0), w(this, K)[K] = e3, t3.linearAdSlotWidth = w(this, W)[W], t3.linearAdSlotHeight = w(this, _)[_], t3.nonLinearAdSlotWidth = w(this, W)[W], t3.nonLinearAdSlotHeight = w(this, _)[_], null == t3.contentDuration && (t3.contentDuration = -3), w(this, X)[X] = window.setTimeout(function() {
|
||||
var t4 = new T("No adsManagerLoadedEvent within 5000ms.");
|
||||
t4.errorCode = T.ERROR_CODE_ADS_MANAGER_LOADED_TIMEOUT, n2.j(t4);
|
||||
}, 5e3), w(this, Y)[Y] = window.setTimeout(function() {
|
||||
var t4 = new T("No response for ads-request within 10000ms.");
|
||||
t4.errorCode = T.ERROR_CODE_REQUEST_ADS_TIMEOUT, n2.j(t4);
|
||||
}, 1e4), w(this, q)[q].requestAds(t3);
|
||||
}, e2.O = function() {
|
||||
w(this, L)[L] = new (w(this, B))[B].AdDisplayContainer(w(this, M)[M], w(this, F)[F].disableCustomPlaybackForIOS10Plus ? void 0 : w(this, S)[S], w(this, F)[F].clickTrackingElement), w(this, q)[q] = new (w(this, B))[B].AdsLoader(w(this, L)[L]), w(this, q)[q].addEventListener(w(this, B)[B].AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, this.u, false), w(this, q)[q].addEventListener(w(this, B)[B].AdErrorEvent.Type.AD_ERROR, this.l, false);
|
||||
}, e2.skipAd = function() {
|
||||
w(this, D)[D] && w(this, D)[D].skip();
|
||||
}, e2.discardAdBreak = function() {
|
||||
w(this, D)[D] && w(this, D)[D].discardAdBreak();
|
||||
}, e2.play = function() {
|
||||
var t3 = this;
|
||||
w(this, tt)[tt] = false, !w(this, x)[x].enabled && w(this, D)[D] ? w(this, D)[D].resume() : w(this, X)[X] || this.p().then(function() {
|
||||
}).catch(function() {
|
||||
t3.dispatchEvent(new O("pause"));
|
||||
});
|
||||
}, e2.pause = function() {
|
||||
w(this, tt)[tt] = true, !w(this, x)[x].enabled && w(this, D)[D] ? w(this, D)[D].pause() : w(this, S)[S].pause();
|
||||
}, e2.g = function(t3) {
|
||||
w(this, $)[$] = [].concat(t3), this.dispatchEvent(new O(R.MEDIA_CUE_POINTS_CHANGE, { detail: { cuePoints: [].concat(w(this, $)[$]) } }));
|
||||
}, e2.A = function(t3) {
|
||||
var i3 = this.cuePoints.indexOf(t3);
|
||||
i3 > -1 && (w(this, $)[$].splice(i3, 1), this.g(w(this, $)[$]));
|
||||
}, e2.resizeAd = function(t3, i3) {
|
||||
w(this, W)[W] = t3, w(this, _)[_] = i3, w(this, D)[D] && w(this, D)[D].resize(t3, i3, this.k()), w(this, M)[M].style.width = t3 + "px", w(this, M)[M].style.height = i3 + "px";
|
||||
}, e2.reset = function(t3) {
|
||||
void 0 === t3 && (t3 = false), t3 && (w(this, U)[U] = false, w(this, Q)[Q] = false, w(this, Z)[Z] = false), this.C(), w(this, $)[$] = [], w(this, tt)[tt] = false, w(this, K)[K] = void 0, w(this, D)[D] && w(this, D)[D].destroy(), w(this, D)[D] = void 0, w(this, B)[B].settings.setAutoPlayAdBreaks(true), t3 && this.P(), w(this, M)[M].style.display = "none";
|
||||
}, e2.P = function() {
|
||||
w(this, q)[q] && (w(this, q)[q].removeEventListener(w(this, B)[B].AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, this.u, false), w(this, q)[q].removeEventListener(w(this, B)[B].AdErrorEvent.Type.AD_ERROR, this.l, false), w(this, q)[q].destroy()), w(this, L)[L] && w(this, L)[L].destroy();
|
||||
}, e2.destroy = function() {
|
||||
var t3, i3, e3, n2, s2 = this;
|
||||
this.reset(true), w(this, x)[x].destroy(), P.forEach(function(t4) {
|
||||
w(s2, S)[S].removeEventListener(t4, s2.o);
|
||||
}), null == (t3 = w(this, q)[q]) || t3.removeEventListener(w(this, B)[B].AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, this.u, false), null == (i3 = w(this, L)[L]) || i3.destroy(), null == (e3 = w(this, q)[q]) || e3.destroy(), w(this, U)[U] = false, w(this, Z)[Z] = false, w(this, Q)[Q] = false, null == (n2 = w(this, N)[N]) || n2.disconnect();
|
||||
}, e2.isCustomPlaybackUsed = function() {
|
||||
return false === w(this, B)[B].settings.getDisableCustomPlaybackForIOS10Plus() && !w(this, M)[M].querySelector("video");
|
||||
}, e2.C = function() {
|
||||
window.clearTimeout(w(this, X)[X]), window.clearTimeout(w(this, Y)[Y]), w(this, X)[X] = void 0, w(this, Y)[Y] = void 0, w(this, V)[V] = void 0, w(this, H)[H] = void 0, w(this, J)[J] = void 0, w(this, M)[M].style.display = "none", w(this, M)[M].classList.remove("nonlinear"), w(this, D)[D] && this.R();
|
||||
}, e2.o = function(t3) {
|
||||
var i3 = this;
|
||||
if (w(this, x)[x].enabled || "volumechange" === t3.type) {
|
||||
if ("timeupdate" === t3.type) {
|
||||
if (w(this, D)[D]) {
|
||||
var e3 = w(this, D)[D].getCuePoints().filter(function(t4) {
|
||||
return t4 >= 0 && t4 < w(i3, x)[x].currentTime;
|
||||
}).pop();
|
||||
this.A(e3);
|
||||
}
|
||||
!w(this, U)[U] && w(this, Q)[Q] && w(this, S)[S].currentTime >= 0.5 && (this.dispatchEvent(new O(R.MEDIA_IMPRESSION)), w(this, U)[U] = true);
|
||||
}
|
||||
if ("play" !== t3.type || w(this, Q)[Q] || w(this, z)[z] || (this.dispatchEvent(new O(R.MEDIA_START)), w(this, Q)[Q] = true), "ended" === t3.type && (this.isCustomPlaybackUsed() && w(this, S)[S].currentTime === w(this, S)[S].duration && w(this, $)[$].indexOf(-1) > -1 && (w(this, S)[S].currentTime = w(this, S)[S].duration - 1e-5, w(this, Z)[Z] = true), w(this, q)[q].contentComplete(), w(this, D)[D] || this.T()), !window.ResizeObserver && w(this, F)[F].autoResize && "loadedmetadata" === t3.type) {
|
||||
var n2 = w(this, S)[S], s2 = n2.offsetHeight, h2 = n2.offsetWidth;
|
||||
w(this, W)[W] = h2, w(this, _)[_] = s2, this.R();
|
||||
}
|
||||
w(this, z)[z] && "volumechange" !== t3.type || this.dispatchEvent(new O(t3.type));
|
||||
}
|
||||
}, e2.S = function(t3) {
|
||||
var i3 = this, e3 = w(this, B)[B].AdEvent;
|
||||
switch (t3.type) {
|
||||
case e3.Type.LOADED:
|
||||
var n2 = t3.getAd();
|
||||
w(this, K)[K] && 0 === w(this, $)[$].length ? w(this, K)[K]({ ad: n2, start: function() {
|
||||
i3.M(), w(i3, K)[K] = void 0;
|
||||
}, startWithoutReset: function() {
|
||||
i3.M();
|
||||
} }) : w(this, G)[G] = n2;
|
||||
break;
|
||||
case e3.Type.AD_BREAK_READY:
|
||||
this.C(), w(this, K)[K] ? (w(this, K)[K]({ ad: w(this, G)[G], adBreakTime: t3.getAdData().adBreakTime, start: function() {
|
||||
i3.M(), w(i3, K)[K] = void 0;
|
||||
}, startWithoutReset: function() {
|
||||
i3.M();
|
||||
} }), w(this, G)[G] = void 0) : this.M();
|
||||
break;
|
||||
case e3.Type.STARTED:
|
||||
var s2 = w(this, V)[V] = t3.getAd();
|
||||
s2.getAdPodInfo().getAdPosition() > 1 && w(this, D)[D].setVolume(w(this, D)[D].getVolume()), w(this, M)[M].classList.remove("nonlinear"), this.R(), s2.isLinear() ? (w(this, x)[x].disable(), w(this, J)[J] = s2.getDuration(), w(this, H)[H] = 0) : (w(this, M)[M].classList.add("nonlinear"), this.I()), w(this, M)[M].style.display = "", w(this, tt)[tt] && (w(this, tt)[tt] = false, w(this, D)[D].pause());
|
||||
break;
|
||||
case e3.Type.ALL_ADS_COMPLETED:
|
||||
if (w(this, Z)[Z]) return;
|
||||
this.isCustomPlaybackUsed() && Boolean(w(this, V)[V]) && -1 !== w(this, V)[V].getAdPodInfo().getTimeOffset() && this.I(), this.reset();
|
||||
break;
|
||||
case e3.Type.CONTENT_PAUSE_REQUESTED:
|
||||
this.C(), w(this, V)[V] = t3.getAd(), w(this, M)[M].style.display = "", w(this, S)[S].pause(), this.R(), w(this, V)[V] && this.A(w(this, V)[V].getAdPodInfo().getTimeOffset()), w(this, D)[D].setVolume(w(this, S)[S].muted ? 0 : w(this, S)[S].volume), w(this, x)[x].disable(), w(this, J)[J] = w(this, V)[V].getDuration(), w(this, H)[H] = 0;
|
||||
break;
|
||||
case e3.Type.CONTENT_RESUME_REQUESTED:
|
||||
var h2 = Boolean(w(this, V)[V]);
|
||||
if (h2) {
|
||||
var r2 = w(this, D)[D].getVolume();
|
||||
w(this, S)[S].muted = 0 === r2, w(this, S)[S].volume = w(this, it)[it];
|
||||
}
|
||||
w(this, Z)[Z] && (w(this, S)[S].currentTime = w(this, S)[S].duration + 1, w(this, Z)[Z] = false), w(this, S)[S].ended ? (this.reset(), this.T()) : this.C(), h2 && (w(this, tt)[tt] = false, this.I());
|
||||
break;
|
||||
case e3.Type.AD_METADATA:
|
||||
this.g(w(this, D)[D].getCuePoints()), -1 === w(this, $)[$].indexOf(0) && (w(this, K)[K] ? w(this, K)[K]({ start: function() {
|
||||
i3.I(), w(i3, K)[K] = void 0;
|
||||
}, startWithoutReset: function() {
|
||||
i3.I();
|
||||
} }) : this.I());
|
||||
break;
|
||||
case e3.Type.AD_PROGRESS:
|
||||
var o2 = t3.getAdData();
|
||||
w(this, H)[H] = o2.currentTime, w(this, J)[J] = o2.duration;
|
||||
break;
|
||||
case e3.Type.LOG:
|
||||
var a2 = t3.getAdData();
|
||||
w(this, K)[K] ? w(this, K)[K]({ start: function() {
|
||||
i3.I(), w(i3, K)[K] = void 0;
|
||||
}, startWithoutReset: function() {
|
||||
i3.I();
|
||||
} }) : a2.adError && !w(this, V)[V] && this.I();
|
||||
break;
|
||||
case e3.Type.VOLUME_CHANGED:
|
||||
var u2 = w(this, D)[D].getVolume();
|
||||
u2 > 0 && (w(this, it)[it] = u2);
|
||||
}
|
||||
}, e2.l = function(t3) {
|
||||
this.j(this.B(t3));
|
||||
}, e2.u = function(t3) {
|
||||
var i3 = this, e3 = w(this, B)[B], n2 = e3.AdEvent, s2 = e3.AdErrorEvent.Type.AD_ERROR;
|
||||
window.clearTimeout(w(this, X)[X]);
|
||||
var h2 = w(this, D)[D] = t3.getAdsManager(w(this, x)[x], w(this, I)[I]);
|
||||
Object.keys(n2.Type).forEach(function(t4) {
|
||||
h2.addEventListener(n2.Type[t4], function(e4) {
|
||||
if (i3.S(e4), R[t4]) {
|
||||
var n3 = ["LOG", "AD_PROGRESS"].indexOf(t4) > -1;
|
||||
i3.dispatchEvent(new O(R[t4], { detail: { ad: e4.getAd() || w(i3, V)[V], adData: n3 ? e4.getAdData() : {} } }));
|
||||
}
|
||||
});
|
||||
}), h2.addEventListener(s2, function(t4) {
|
||||
return i3.j(i3.B(t4));
|
||||
});
|
||||
try {
|
||||
var r2;
|
||||
h2.init(w(this, W)[W], w(this, _)[_], this.k()), h2.setVolume(w(this, S)[S].muted ? 0 : w(this, S)[S].volume), null == (r2 = w(this, L)[L]) || r2.initialize(), w(this, K)[K] || this.M();
|
||||
} catch (t4) {
|
||||
this.j(new T(t4.message));
|
||||
}
|
||||
}, e2.M = function() {
|
||||
if (w(this, Q)[Q] || (this.dispatchEvent(new O(R.MEDIA_START)), w(this, Q)[Q] = true), w(this, D)[D]) try {
|
||||
w(this, D)[D].start();
|
||||
} catch (t3) {
|
||||
this.j(new T(t3.message));
|
||||
}
|
||||
else this.I();
|
||||
}, e2.T = function() {
|
||||
var t3 = this;
|
||||
setTimeout(function() {
|
||||
w(t3, U)[U] = false, w(t3, Q)[Q] = false, w(t3, Z)[Z] = false, t3.dispatchEvent(new O(R.MEDIA_STOP));
|
||||
}, 1);
|
||||
}, e2.v = function(t3) {
|
||||
for (var i3, e3 = (function(t4, i4) {
|
||||
var e4 = "undefined" != typeof Symbol && t4[Symbol.iterator] || t4["@@iterator"];
|
||||
if (e4) return (e4 = e4.call(t4)).next.bind(e4);
|
||||
if (Array.isArray(t4) || (e4 = (function(t5, i5) {
|
||||
if (t5) {
|
||||
if ("string" == typeof t5) return f(t5, i5);
|
||||
var e5 = Object.prototype.toString.call(t5).slice(8, -1);
|
||||
return "Object" === e5 && t5.constructor && (e5 = t5.constructor.name), "Map" === e5 || "Set" === e5 ? Array.from(t5) : "Arguments" === e5 || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(e5) ? f(t5, i5) : void 0;
|
||||
}
|
||||
})(t4))) {
|
||||
e4 && (t4 = e4);
|
||||
var n3 = 0;
|
||||
return function() {
|
||||
return n3 >= t4.length ? { done: true } : { done: false, value: t4[n3++] };
|
||||
};
|
||||
}
|
||||
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
||||
})(t3); !(i3 = e3()).done; ) {
|
||||
var n2 = i3.value;
|
||||
n2.contentBoxSize && 1 === n2.contentBoxSize.length ? (w(this, W)[W] = n2.contentBoxSize[0].inlineSize, w(this, _)[_] = n2.contentBoxSize[0].blockSize) : n2.contentBoxSize && n2.contentBoxSize.inlineSize ? (w(this, W)[W] = n2.contentBoxSize.inlineSize, w(this, _)[_] = n2.contentBoxSize.blockSize) : (w(this, W)[W] = n2.contentRect.width, w(this, _)[_] = n2.contentRect.height);
|
||||
}
|
||||
this.R();
|
||||
}, e2.R = function() {
|
||||
if (w(this, F)[F].autoResize && w(this, D)[D]) {
|
||||
var t3 = w(this, V)[V], i3 = this.k();
|
||||
t3 && !t3.isLinear() ? t3 && (t3.getWidth() > w(this, W)[W] || t3.getHeight() > w(this, _)[_] ? this.resizeAd(w(this, W)[W], w(this, _)[_]) : (w(this, D)[D].resize(t3.getWidth(), t3.getHeight() + 20, i3), w(this, M)[M].style.width = t3.getWidth() + "px", w(this, M)[M].style.height = t3.getHeight() + 20 + "px")) : this.resizeAd(w(this, W)[W], w(this, _)[_]);
|
||||
}
|
||||
}, e2.k = function() {
|
||||
return document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement || w(this, S)[S].webkitDisplayingFullscreen ? w(this, B)[B].ViewMode.FULLSCREEN : w(this, B)[B].ViewMode.NORMAL;
|
||||
}, e2.I = function() {
|
||||
var t3 = this;
|
||||
w(this, M)[M].style.display = "none", w(this, S)[S].ended || (w(this, x)[x].enable(), w(this, tt)[tt] ? (w(this, S)[S].pause(), this.dispatchEvent(new O("pause"))) : (this.p().then(function() {
|
||||
}).catch(function() {
|
||||
t3.dispatchEvent(new O("pause"));
|
||||
}), this.dispatchEvent(new O("play")), this.dispatchEvent(new O(R.MEDIA_RESUMED))), w(this, tt)[tt] = false);
|
||||
}, e2.B = function(t3) {
|
||||
var i3 = t3.getError(), e3 = new T(i3.getMessage());
|
||||
return e3.type = i3.getType(), e3.errorCode = i3.getErrorCode(), e3.vastErrorCode = i3.getVastErrorCode && i3.getVastErrorCode(), e3.innerError = i3.getInnerError(), e3;
|
||||
}, e2.j = function(t3) {
|
||||
var i3 = this;
|
||||
this.dispatchEvent(new O(R.AD_ERROR, { detail: { error: t3 } })), this.C(), w(this, K)[K] ? w(this, K)[K]({ start: function() {
|
||||
i3.I(), w(i3, K)[K] = void 0;
|
||||
}, startWithoutReset: function() {
|
||||
i3.I();
|
||||
} }) : this.I();
|
||||
}, s(i2, [{ key: "volume", get: function() {
|
||||
return !w(this, x)[x].enabled && w(this, D)[D] ? w(this, D)[D].getVolume() : w(this, S)[S].volume;
|
||||
}, set: function(t3) {
|
||||
!w(this, x)[x].enabled && w(this, D)[D] && w(this, D)[D].setVolume(t3), w(this, S)[S].volume = t3;
|
||||
} }, { key: "muted", get: function() {
|
||||
return !w(this, x)[x].enabled && w(this, D)[D] ? 0 === w(this, D)[D].getVolume() : w(this, S)[S].muted;
|
||||
}, set: function(t3) {
|
||||
!w(this, x)[x].enabled && w(this, D)[D] && w(this, D)[D].setVolume(t3 ? 0 : w(this, it)[it]), w(this, S)[S].muted = t3;
|
||||
} }, { key: "currentTime", get: function() {
|
||||
return void 0 !== w(this, H)[H] ? w(this, H)[H] : w(this, S)[S].currentTime;
|
||||
}, set: function(t3) {
|
||||
w(this, x)[x].enabled && (w(this, S)[S].currentTime = t3);
|
||||
} }, { key: "duration", get: function() {
|
||||
return void 0 !== w(this, J)[J] ? w(this, J)[J] : w(this, S)[S].duration;
|
||||
} }, { key: "cuePoints", get: function() {
|
||||
return [].concat(w(this, $)[$]);
|
||||
} }]), i2;
|
||||
})(C);
|
||||
function artplayerPluginVast(callback) {
|
||||
return async (art) => {
|
||||
const { template, constructor } = art;
|
||||
const { createElement, setStyles } = constructor.utils;
|
||||
const { $video, $player } = template;
|
||||
await e();
|
||||
const google = window.google;
|
||||
const ima = google.ima;
|
||||
const adsRenderingSettings = new ima.AdsRenderingSettings();
|
||||
adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true;
|
||||
adsRenderingSettings.enablePreloading = true;
|
||||
const playerOptions = new E();
|
||||
let isAdPlaying = false;
|
||||
let imaPlayer = null;
|
||||
let $container = null;
|
||||
function createContainer() {
|
||||
const container = createElement("div");
|
||||
const id = `art-vast-${Date.now()}`;
|
||||
container.id = id;
|
||||
setStyles(container, {
|
||||
position: "absolute",
|
||||
inset: "0",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
zIndex: "150",
|
||||
backgroundColor: "black",
|
||||
display: "none",
|
||||
pointerEvents: "auto"
|
||||
});
|
||||
return container;
|
||||
}
|
||||
function initPlayer() {
|
||||
if (imaPlayer)
|
||||
return imaPlayer;
|
||||
$container = createContainer();
|
||||
$player.appendChild($container);
|
||||
imaPlayer = new nt(ima, $video, $container, adsRenderingSettings, playerOptions);
|
||||
imaPlayer.addEventListener("AdContentPauseRequested", () => {
|
||||
isAdPlaying = true;
|
||||
$container.style.display = "block";
|
||||
});
|
||||
imaPlayer.addEventListener("AdContentResumeRequested", () => {
|
||||
isAdPlaying = false;
|
||||
$container.style.display = "none";
|
||||
});
|
||||
imaPlayer.addEventListener("AdStarted", () => {
|
||||
isAdPlaying = true;
|
||||
$container.style.display = "block";
|
||||
});
|
||||
imaPlayer.addEventListener("AdError", (event) => {
|
||||
console.error("VAST Ad Error:", event.detail);
|
||||
isAdPlaying = false;
|
||||
$container.style.display = "none";
|
||||
});
|
||||
return imaPlayer;
|
||||
}
|
||||
function destroyPlayer() {
|
||||
if (imaPlayer?.destroy)
|
||||
imaPlayer.destroy();
|
||||
if ($container?.parentNode)
|
||||
$container.parentNode.removeChild($container);
|
||||
$container = null;
|
||||
imaPlayer = null;
|
||||
}
|
||||
function playUrl(url, config = {}) {
|
||||
if (isAdPlaying)
|
||||
return;
|
||||
if (!imaPlayer)
|
||||
initPlayer();
|
||||
const request = new ima.AdsRequest();
|
||||
request.adTagUrl = url;
|
||||
for (const key in config) {
|
||||
request[key] = config[key];
|
||||
}
|
||||
imaPlayer.playAds(request);
|
||||
}
|
||||
function playRes(res, config = {}) {
|
||||
if (isAdPlaying)
|
||||
return;
|
||||
if (!imaPlayer)
|
||||
initPlayer();
|
||||
const request = new ima.AdsRequest();
|
||||
request.adsResponse = res;
|
||||
for (const key in config) {
|
||||
request[key] = config[key];
|
||||
}
|
||||
imaPlayer.playAds(request);
|
||||
}
|
||||
if (typeof callback === "function") {
|
||||
await callback({
|
||||
art,
|
||||
playUrl,
|
||||
playRes,
|
||||
init: initPlayer,
|
||||
ima,
|
||||
adsRenderingSettings,
|
||||
playerOptions,
|
||||
get imaPlayer() {
|
||||
return imaPlayer;
|
||||
},
|
||||
get container() {
|
||||
return $container;
|
||||
}
|
||||
});
|
||||
}
|
||||
return {
|
||||
name: "artplayerPluginVast",
|
||||
destroy: destroyPlayer
|
||||
};
|
||||
};
|
||||
}
|
||||
export {
|
||||
artplayerPluginVast as default
|
||||
};
|
||||
+118
-1
@@ -4,4 +4,121 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
function t(t){const n=t.split("."),e=n[0].split(":")||[],l=(i=n[1]||"0",o=3,s="0",i.length>o?String(i):((o-=i.length)>s.length&&(s+=s.repeat(o/s.length)),String(i)+s.slice(0,o)));var i,o,s;const r=Number(l)/1e3;return 3600*Number(e[e.length-3]||0)+60*Number(e[e.length-2]||0)+Number(e[e.length-1]||0)+r}function n(n){return async e=>{const{constructor:{utils:{setStyle:l,isMobile:i,addClass:o}},template:{$progress:s}}=e;let r=null;const a=await async function(n=""){const e=(await(await fetch(n)).text()).split(/[\n\r]/g).filter(t=>t.trim()),l=[];for(let i=1;i<e.length;i+=2){const o=e[i],s=e[i+1];if(!s.trim())continue;const r=/((?:\d{2}:)?(?:\d{2}:)?\d{2}(?:.\d{3})?) ?--> ?((?:\d{2}:)?(?:\d{2}:)?\d{2}(?:.\d{3})?)/,a=o.match(r),c=/(.*)#(\w{4})=(.*)/,u=s.match(c),d=Math.floor(t(a[1])),p=Math.floor(t(a[2]));let h=u[1];if(!/^\/|(?:https?|ftp|file):\/\//i.test(h)){const t=n.split("/");t.pop(),t.push(h),h=t.join("/")}const f={start:d,end:p,url:h},g=u[2].split(""),m=u[3].split(",");for(let t=0;t<g.length;t++)f[g[t]]=m[t];l.push(f)}return l}(n.vtt);return e.controls.add({name:"vtt-thumbnail",position:"top",index:20,style:n.style||{},mounted(t){o(t,"art-control-thumbnails"),e.on("setBar",async(n,o,c)=>{const u="played"===n&&c&&i;if("hover"===n||u){const n=s.clientWidth*o,c=o*e.duration;l(t,"display","flex");const d=a.find(t=>c>=t.start&&c<=t.end);if(!d)return l(t,"display","none");n>0&&n<s.clientWidth?function(t,n,e){l(t,"backgroundImage",`url(${n.url})`),l(t,"height",`${n.h}px`),l(t,"width",`${n.w}px`),l(t,"backgroundPosition",`-${n.x}px -${n.y}px`),e<=n.w/2?l(t,"left",0):e>s.clientWidth-n.w/2?l(t,"left",s.clientWidth-n.w+"px"):l(t,"left",e-n.w/2+"px")}(t,d,n):i||l(t,"display","none"),u&&(clearTimeout(r),r=setTimeout(()=>{l(t,"display","none")},500))}})}}),{name:"artplayerPluginVttThumbnail"}}}export{n as default};
|
||||
function padEnd(str, targetLength, padString) {
|
||||
if (str.length > targetLength) {
|
||||
return String(str);
|
||||
} else {
|
||||
targetLength = targetLength - str.length;
|
||||
if (targetLength > padString.length) {
|
||||
padString += padString.repeat(targetLength / padString.length);
|
||||
}
|
||||
return String(str) + padString.slice(0, targetLength);
|
||||
}
|
||||
}
|
||||
function t2d(time) {
|
||||
const arr = time.split(".");
|
||||
const left = arr[0].split(":") || [];
|
||||
const right = padEnd(arr[1] || "0", 3, "0");
|
||||
const ms = Number(right) / 1e3;
|
||||
const h = Number(left[left.length - 3] || 0) * 3600;
|
||||
const m = Number(left[left.length - 2] || 0) * 60;
|
||||
const s = Number(left[left.length - 1] || 0);
|
||||
return h + m + s + ms;
|
||||
}
|
||||
async function getVttArray(vttUrl = "") {
|
||||
const vttString = await (await fetch(vttUrl)).text();
|
||||
const lines = vttString.split(/[\n\r]/g).filter((item) => item.trim());
|
||||
const vttArray = [];
|
||||
for (let i = 1; i < lines.length; i += 2) {
|
||||
const time = lines[i];
|
||||
const text = lines[i + 1];
|
||||
if (!text.trim())
|
||||
continue;
|
||||
const timeReg = /((?:\d{2}:)?(?:\d{2}:)?\d{2}(?:.\d{3})?) ?--> ?((?:\d{2}:)?(?:\d{2}:)?\d{2}(?:.\d{3})?)/;
|
||||
const timeMatch = time.match(timeReg);
|
||||
const textReg = /(.*)#(\w{4})=(.*)/;
|
||||
const textMatch = text.match(textReg);
|
||||
const start = Math.floor(t2d(timeMatch[1]));
|
||||
const end = Math.floor(t2d(timeMatch[2]));
|
||||
let url = textMatch[1];
|
||||
const isAbsoluteUrl = /^\/|(?:https?|ftp|file):\/\//i.test(url);
|
||||
if (!isAbsoluteUrl) {
|
||||
const urlArr = vttUrl.split("/");
|
||||
urlArr.pop();
|
||||
urlArr.push(url);
|
||||
url = urlArr.join("/");
|
||||
}
|
||||
const result = { start, end, url };
|
||||
const keys = textMatch[2].split("");
|
||||
const values = textMatch[3].split(",");
|
||||
for (let j = 0; j < keys.length; j++) {
|
||||
result[keys[j]] = values[j];
|
||||
}
|
||||
vttArray.push(result);
|
||||
}
|
||||
return vttArray;
|
||||
}
|
||||
function artplayerPluginVttThumbnail(option) {
|
||||
return async (art) => {
|
||||
const {
|
||||
constructor: {
|
||||
utils: { setStyle, isMobile, addClass }
|
||||
},
|
||||
template: { $progress }
|
||||
} = art;
|
||||
let timer = null;
|
||||
const thumbnails = await getVttArray(option.vtt);
|
||||
function showThumbnails($control, find, width) {
|
||||
setStyle($control, "backgroundImage", `url(${find.url})`);
|
||||
setStyle($control, "height", `${find.h}px`);
|
||||
setStyle($control, "width", `${find.w}px`);
|
||||
setStyle($control, "backgroundPosition", `-${find.x}px -${find.y}px`);
|
||||
if (width <= find.w / 2) {
|
||||
setStyle($control, "left", 0);
|
||||
} else if (width > $progress.clientWidth - find.w / 2) {
|
||||
setStyle($control, "left", `${$progress.clientWidth - find.w}px`);
|
||||
} else {
|
||||
setStyle($control, "left", `${width - find.w / 2}px`);
|
||||
}
|
||||
}
|
||||
art.controls.add({
|
||||
name: "vtt-thumbnail",
|
||||
position: "top",
|
||||
index: 20,
|
||||
style: option.style || {},
|
||||
mounted($control) {
|
||||
addClass($control, "art-control-thumbnails");
|
||||
art.on("setBar", async (type, percentage, event) => {
|
||||
const isMobileDroging = type === "played" && event && isMobile;
|
||||
if (type === "hover" || isMobileDroging) {
|
||||
const width = $progress.clientWidth * percentage;
|
||||
const second = percentage * art.duration;
|
||||
setStyle($control, "display", "flex");
|
||||
const find = thumbnails.find((item) => second >= item.start && second <= item.end);
|
||||
if (!find)
|
||||
return setStyle($control, "display", "none");
|
||||
if (width > 0 && width < $progress.clientWidth) {
|
||||
showThumbnails($control, find, width);
|
||||
} else {
|
||||
if (!isMobile) {
|
||||
setStyle($control, "display", "none");
|
||||
}
|
||||
}
|
||||
if (isMobileDroging) {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
setStyle($control, "display", "none");
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return {
|
||||
name: "artplayerPluginVttThumbnail"
|
||||
};
|
||||
};
|
||||
}
|
||||
export {
|
||||
artplayerPluginVttThumbnail as default
|
||||
};
|
||||
+118
-1
@@ -4,4 +4,121 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
function e(e){return t=>{const{constructor:n}=t,{createElement:i}=n.utils,o=i("canvas"),a=i("video"),r=o.getContext("2d");let c=null;const d={};for(const e in o)"function"===typeof o[e]&&(d[e]=o[e].bind(o));for(const e in a)e in o||Object.defineProperty(o,e,{get(){const t=a[e];return"function"===typeof t?t.bind(a):t},set(t){a[e]=t},configurable:!0,enumerable:!0});for(const e in d)o[e]=function(...t){return e in d?d[e](...t):a[e](...t)};return function(){const{events:e}=n.config;setTimeout(()=>{e.forEach(e=>{t.proxy(a,e,e=>{t.emit(`video:${e.type}`,e)})})})}(),t.on("video:loadedmetadata",()=>{o.width=a.videoWidth,o.height=a.videoHeight}),t.on("video:play",()=>{cancelAnimationFrame(c),u()}),t.on("video:pause",()=>{cancelAnimationFrame(c)}),t.on("resize",()=>{!function(){const e=t.template?.$player;if(!e||t.option.autoSize)return;const n=a.videoWidth/a.videoHeight,i=e.clientWidth,r=e.clientHeight;let c,d;i/r>n?(d=r,c=d*n):(c=i,d=c/n),o.width=c,o.height=d;const s=(i-c)/2,u=(r-d)/2;Object.assign(o.style,{padding:`${u}px ${s}px`})}(),s()}),t.on("destroy",()=>{cancelAnimationFrame(c)}),o;async function s(){try{if("undefined"!==typeof createImageBitmap){const e=await createImageBitmap(a);r.drawImage(e,0,0,o.width,o.height),e.close()}else r.drawImage(a,0,0,o.width,o.height);e&&e(r,a),t.emit("artplayerProxyCanvas:draw",r,a)}catch(n){t.emit("artplayerProxyCanvas:error",n)}}async function u(){await s(),c=requestAnimationFrame(u)}}}export{e as default};
|
||||
function artplayerProxyCanvas(callback) {
|
||||
return (art) => {
|
||||
const { constructor } = art;
|
||||
const { createElement } = constructor.utils;
|
||||
const canvas = createElement("canvas");
|
||||
const video = createElement("video");
|
||||
const ctx = canvas.getContext("2d");
|
||||
let animationId = null;
|
||||
const originalCanvasMethods = {};
|
||||
for (const prop in canvas) {
|
||||
if (typeof canvas[prop] === "function") {
|
||||
originalCanvasMethods[prop] = canvas[prop].bind(canvas);
|
||||
}
|
||||
}
|
||||
for (const prop in video) {
|
||||
if (!(prop in canvas)) {
|
||||
Object.defineProperty(canvas, prop, {
|
||||
get() {
|
||||
const value = video[prop];
|
||||
return typeof value === "function" ? value.bind(video) : value;
|
||||
},
|
||||
set(value) {
|
||||
video[prop] = value;
|
||||
},
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const prop in originalCanvasMethods) {
|
||||
canvas[prop] = function(...args) {
|
||||
if (prop in originalCanvasMethods) {
|
||||
return originalCanvasMethods[prop](...args);
|
||||
}
|
||||
return video[prop](...args);
|
||||
};
|
||||
}
|
||||
setupEventListeners();
|
||||
setupArtPlayerEvents();
|
||||
return canvas;
|
||||
function setupEventListeners() {
|
||||
const { events } = constructor.config;
|
||||
setTimeout(() => {
|
||||
events.forEach((event) => {
|
||||
art.proxy(video, event, (event2) => {
|
||||
art.emit(`video:${event2.type}`, event2);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
async function draw() {
|
||||
try {
|
||||
if (typeof createImageBitmap !== "undefined") {
|
||||
const bitmap = await createImageBitmap(video);
|
||||
ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
|
||||
bitmap.close();
|
||||
} else {
|
||||
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
callback && callback(ctx, video);
|
||||
art.emit("artplayerProxyCanvas:draw", ctx, video);
|
||||
} catch (error) {
|
||||
art.emit("artplayerProxyCanvas:error", error);
|
||||
}
|
||||
}
|
||||
async function animation() {
|
||||
await draw();
|
||||
animationId = requestAnimationFrame(animation);
|
||||
}
|
||||
function resize() {
|
||||
const player = art.template?.$player;
|
||||
if (!player || art.option.autoSize)
|
||||
return;
|
||||
const aspectRatio = video.videoWidth / video.videoHeight;
|
||||
const containerWidth = player.clientWidth;
|
||||
const containerHeight = player.clientHeight;
|
||||
const containerRatio = containerWidth / containerHeight;
|
||||
let canvasWidth, canvasHeight;
|
||||
if (containerRatio > aspectRatio) {
|
||||
canvasHeight = containerHeight;
|
||||
canvasWidth = canvasHeight * aspectRatio;
|
||||
} else {
|
||||
canvasWidth = containerWidth;
|
||||
canvasHeight = canvasWidth / aspectRatio;
|
||||
}
|
||||
canvas.width = canvasWidth;
|
||||
canvas.height = canvasHeight;
|
||||
const paddingLeft = (containerWidth - canvasWidth) / 2;
|
||||
const paddingTop = (containerHeight - canvasHeight) / 2;
|
||||
Object.assign(canvas.style, {
|
||||
padding: `${paddingTop}px ${paddingLeft}px`
|
||||
});
|
||||
}
|
||||
function setupArtPlayerEvents() {
|
||||
art.on("video:loadedmetadata", () => {
|
||||
canvas.width = video.videoWidth;
|
||||
canvas.height = video.videoHeight;
|
||||
});
|
||||
art.on("video:play", () => {
|
||||
cancelAnimationFrame(animationId);
|
||||
animation();
|
||||
});
|
||||
art.on("video:pause", () => {
|
||||
cancelAnimationFrame(animationId);
|
||||
});
|
||||
art.on("resize", () => {
|
||||
resize();
|
||||
draw();
|
||||
});
|
||||
art.on("destroy", () => {
|
||||
cancelAnimationFrame(animationId);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
export {
|
||||
artplayerProxyCanvas as default
|
||||
};
|
||||
+15371
-1
File diff suppressed because it is too large.
Load diff
+131
-1
@@ -4,4 +4,134 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
class e{static get iframe(){return window.top!==window}static postMessage({type:t,data:s,id:i=0}){if(!e.iframe)throw new Error('The "ArtplayerToolIframe.postMessage" method can only be used in iframe');window.parent.postMessage({type:t,data:s,id:i},"*")}static async onMessage(t){if(!e.iframe)throw new Error('The "ArtplayerToolIframe.onMessage" method can only be used in iframe');const{type:s,data:i,id:a}=t.data;if("commit"===s)try{if(i.match(/\bresolve\((.*?)\)/)){const t=`return new Promise(function(resolve){\n${i}\n})`,s=await new Function(t)();e.postMessage({type:"response",data:s,id:a})}else{const t=new Function(i)();e.postMessage({type:"response",data:t,id:a})}}catch(o){throw e.postMessage({type:"error",data:o.message,id:a}),o}}static inject(){if(!e.iframe)throw new Error('The "ArtplayerToolIframe.inject" method can only be used in iframe');e.postMessage({type:"inject"}),window.addEventListener("message",e.onMessage)}constructor({iframe:e,url:t}){if(e instanceof HTMLIFrameElement===!1)throw new TypeError('"option.iframe" needs to be a HTMLIFrameElement');if("string"!==typeof t)throw new TypeError('"option.url" needs to be a string');this.url=t,this.$iframe=e,this.promises={},this.injected=!1,this.destroyed=!1,this.messageCallback=()=>null,this.onMessage=this.onMessage.bind(this),window.addEventListener("message",this.onMessage),this.$iframe.src=this.url}onMessage(e){const{type:t,data:s,id:i}=e.data;if("inject"===t)this.injected=!0;this.promises[i]&&("error"===t?this.promises[i].reject(new Error(s)):this.promises[i].resove(s),delete this.promises[i]),this.messageCallback&&this.messageCallback({type:t,data:s})}postMessage({type:e,data:t}){return new Promise((s,i)=>{(function a(){if(this.destroyed)i(new Error("The instance has been destroyed"));else if(this.injected){const a=Date.now();this.promises[a]={resove:s,reject:i},this.$iframe.contentWindow.postMessage({type:e,data:t,id:a},"*")}else setTimeout(a.bind(this),200)}).call(this)})}commit(e){if("function"!==typeof e)throw new TypeError('"commit.callback" needs to be a function');const t=e.toString(),s=t.substring(t.indexOf("{")+1,t.lastIndexOf("}"));return this.postMessage({type:"commit",data:s})}message(e){if("function"!==typeof e)throw new TypeError('"message.callback" needs to be a function');this.messageCallback=e}destroy(){this.destroyed=!0,window.removeEventListener("message",this.onMessage)}}export{e as default};
|
||||
class ArtplayerToolIframe {
|
||||
static get iframe() {
|
||||
return window.top !== window;
|
||||
}
|
||||
static postMessage({ type, data, id = 0 }) {
|
||||
if (!ArtplayerToolIframe.iframe) {
|
||||
throw new Error('The "ArtplayerToolIframe.postMessage" method can only be used in iframe');
|
||||
}
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type,
|
||||
data,
|
||||
id
|
||||
},
|
||||
"*"
|
||||
);
|
||||
}
|
||||
static async onMessage(event) {
|
||||
if (!ArtplayerToolIframe.iframe) {
|
||||
throw new Error('The "ArtplayerToolIframe.onMessage" method can only be used in iframe');
|
||||
}
|
||||
const { type, data, id } = event.data;
|
||||
switch (type) {
|
||||
case "commit":
|
||||
try {
|
||||
if (data.match(/\bresolve\((.*?)\)/)) {
|
||||
const string = `return new Promise(function(resolve){
|
||||
${data}
|
||||
})`;
|
||||
const result = await new Function(string)();
|
||||
ArtplayerToolIframe.postMessage({ type: "response", data: result, id });
|
||||
} else {
|
||||
const result = new Function(data)();
|
||||
ArtplayerToolIframe.postMessage({ type: "response", data: result, id });
|
||||
}
|
||||
} catch (error) {
|
||||
ArtplayerToolIframe.postMessage({ type: "error", data: error.message, id });
|
||||
throw error;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
static inject() {
|
||||
if (!ArtplayerToolIframe.iframe) {
|
||||
throw new Error('The "ArtplayerToolIframe.inject" method can only be used in iframe');
|
||||
}
|
||||
ArtplayerToolIframe.postMessage({ type: "inject" });
|
||||
window.addEventListener("message", ArtplayerToolIframe.onMessage);
|
||||
}
|
||||
constructor({ iframe, url }) {
|
||||
if (iframe instanceof HTMLIFrameElement === false) {
|
||||
throw new TypeError('"option.iframe" needs to be a HTMLIFrameElement');
|
||||
}
|
||||
if (typeof url !== "string") {
|
||||
throw new TypeError('"option.url" needs to be a string');
|
||||
}
|
||||
this.url = url;
|
||||
this.$iframe = iframe;
|
||||
this.promises = {};
|
||||
this.injected = false;
|
||||
this.destroyed = false;
|
||||
this.messageCallback = () => null;
|
||||
this.onMessage = this.onMessage.bind(this);
|
||||
window.addEventListener("message", this.onMessage);
|
||||
this.$iframe.src = this.url;
|
||||
}
|
||||
onMessage(event) {
|
||||
const { type, data, id } = event.data;
|
||||
switch (type) {
|
||||
case "inject":
|
||||
this.injected = true;
|
||||
break;
|
||||
}
|
||||
if (this.promises[id]) {
|
||||
if (type === "error") {
|
||||
this.promises[id].reject(new Error(data));
|
||||
} else {
|
||||
this.promises[id].resove(data);
|
||||
}
|
||||
delete this.promises[id];
|
||||
}
|
||||
if (this.messageCallback) {
|
||||
this.messageCallback({ type, data });
|
||||
}
|
||||
}
|
||||
postMessage({ type, data }) {
|
||||
return new Promise((resove, reject) => {
|
||||
(function loop() {
|
||||
if (this.destroyed) {
|
||||
reject(new Error("The instance has been destroyed"));
|
||||
} else {
|
||||
if (this.injected) {
|
||||
const id = Date.now();
|
||||
this.promises[id] = { resove, reject };
|
||||
this.$iframe.contentWindow.postMessage(
|
||||
{
|
||||
type,
|
||||
data,
|
||||
id
|
||||
},
|
||||
"*"
|
||||
);
|
||||
} else {
|
||||
setTimeout(loop.bind(this), 200);
|
||||
}
|
||||
}
|
||||
}).call(this);
|
||||
});
|
||||
}
|
||||
commit(callback) {
|
||||
if (typeof callback !== "function") {
|
||||
throw new TypeError('"commit.callback" needs to be a function');
|
||||
}
|
||||
const callbackString = callback.toString();
|
||||
const bodyString = callbackString.substring(callbackString.indexOf("{") + 1, callbackString.lastIndexOf("}"));
|
||||
return this.postMessage({ type: "commit", data: bodyString });
|
||||
}
|
||||
message(callback) {
|
||||
if (typeof callback !== "function") {
|
||||
throw new TypeError('"message.callback" needs to be a function');
|
||||
}
|
||||
this.messageCallback = callback;
|
||||
}
|
||||
destroy() {
|
||||
this.destroyed = true;
|
||||
window.removeEventListener("message", this.onMessage);
|
||||
}
|
||||
}
|
||||
export {
|
||||
ArtplayerToolIframe as default
|
||||
};
|
||||
@@ -4,4 +4,261 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
class t{on(t,e,i){const n=this.e||(this.e={});return(n[t]||(n[t]=[])).push({fn:e,ctx:i}),this}once(t,e,i){const n=this;function o(...s){n.off(t,o),e.apply(i,s)}return o._=e,this.on(t,o,i)}emit(t,...e){const i=((this.e||(this.e={}))[t]||[]).slice();for(let n=0;n<i.length;n+=1)i[n].fn.apply(i[n].ctx,e);return this}off(t,e){const i=this.e||(this.e={}),n=i[t],o=[];if(n&&e)for(let s=0,r=n.length;s<r;s+=1)n[s].fn!==e&&n[s].fn._!==e&&o.push(n[s]);return o.length?i[t]=o:delete i[t],this}}function e(t,e,i){return Math.max(Math.min(t,Math.max(e,i)),Math.min(e,i))}class i extends t{constructor(t={}){super(),this.processing=!1,this.option={},this.setup(Object.assign({},i.DEFAULTS,t)),this.video=i.creatVideo(),this.duration=0,this.inputChange=this.inputChange.bind(this),this.ondrop=this.ondrop.bind(this),this.option.fileInput.addEventListener("change",this.inputChange),this.option.fileInput.addEventListener("dragover",i.ondragover),this.option.fileInput.addEventListener("drop",i.ondrop)}static get DEFAULTS(){return{number:60,width:160,height:90,column:10,begin:0,end:Number.NaN}}static ondragover(t){t.preventDefault()}ondrop(t){t.preventDefault();const e=t.dataTransfer.files[0];this.loadVideo(e)}setup(t={}){this.option=Object.assign({},this.option,t);const{fileInput:i,number:n,width:o,column:s}=this.option;if(this.errorHandle(i instanceof Element,"The 'fileInput' is not a Element"),"INPUT"!==i.tagName||"file"!==i.type){i.style.position="relative";const t=document.createElement("input");t.type="file",t.style.position="absolute",t.style.width="100%",t.style.height="100%",t.style.left="0",t.style.top="0",t.style.right="0",t.style.bottom="0",t.style.opacity="0",i.appendChild(t),this.option.fileInput=t}return["number","width","column","begin","end"].forEach(t=>{this.errorHandle("number"===typeof this.option[t],`The '${t}' is not a number`)}),this.option.number=e(n,10,1e3),this.option.width=e(o,10,1e3),this.option.column=e(s,1,1e3),this}static creatVideo(){const t=document.createElement("video");return t.style.position="absolute",t.style.top="-9999px",t.style.left="-9999px",t.muted=!0,t.controls=!0,document.body.appendChild(t),t}inputChange(t){const e=this.option.fileInput.files[0];this.loadVideo(e),t.target.value=""}loadVideo(t){if(t){const e=this.video.canPlayType(t.type);this.errorHandle("maybe"===e||"probably"===e,`Playback of this file format is not supported: ${t.type}`);const i=URL.createObjectURL(t);this.videoUrl=i,this.file=t,this.emit("file",this.file),this.video.src=i,this.emit("video",this.video)}}start(){if(!this.video.duration)return(t=1e3,new Promise(e=>setTimeout(e,t))).then(()=>this.start());var t;const{width:i,number:n,begin:o,end:s}=this.option,r=this.video.videoHeight/this.video.videoWidth*i;this.option.height=r,this.option.begin=e(o,0,this.video.duration),this.option.end=e(s||this.video.duration,o,this.video.duration),this.errorHandle(this.option.end>this.option.begin,"End time must be greater than the start time"),this.duration=this.option.end-this.option.begin,this.density=n/this.duration,this.errorHandle(this.file&&this.video,"Please select the video file first"),this.errorHandle(!this.processing,"There is currently a task in progress, please wait a moment..."),this.errorHandle(this.density<=1,`The preview density cannot be greater than 1, but got ${this.density}`);const h=this.creatScreenshotDate(),a=this.creatCanvas(),l=a.getContext("2d");this.emit("canvas",a);const d=h.map((t,e)=>()=>new Promise(o=>{this.video.oncanplay=()=>{l.drawImage(this.video,t.x,t.y,i,r),a.toBlob(t=>{this.thumbnailUrl&&URL.revokeObjectURL(this.thumbnailUrl),this.thumbnailUrl=URL.createObjectURL(t),this.emit("update",this.thumbnailUrl,(e+1)/n),this.video.oncanplay=null,o()})},this.video.currentTime=t.time}));return this.processing=!0,(p=d,p.reduce((t,e)=>t.then(e),Promise.resolve())).then(()=>{this.processing=!1,this.emit("done")}).catch(t=>{throw this.processing=!1,this.emit("error",t.message),t});var p}creatScreenshotDate(){const{number:t,width:e,height:i,column:n,begin:o}=this.option,s=this.duration/t,r=[o+s];for(;r.length<t;){const t=r[r.length-1];r.push(t+s)}return r.map((t,o)=>({time:t-s/2,x:o%n*e,y:Math.floor(o/n)*i}))}creatCanvas(){const{number:t,width:e,height:i,column:n}=this.option,o=document.createElement("canvas"),s=o.getContext("2d");return o.width=e*n,o.height=Math.ceil(t/n)*i+30,s.fillStyle="black",s.fillRect(0,0,o.width,o.height),s.font="14px Georgia",s.fillStyle="#fff",s.fillText(`From: https://artplayer.org/, Number: ${t}, Width: ${e}, Height: ${i}, Column: ${n}`,10,o.height-11),o}download(){this.errorHandle(this.file&&this.thumbnailUrl,"Download does not seem to be ready, please create preview first"),this.errorHandle(!this.processing,"There is currently a task in progress, please wait a moment...");const t=document.createElement("a"),e=`${function(t){const e=t.split(".");return e.pop(),e.join(".")}(this.file.name)}.png`;return t.download=e,t.href=this.thumbnailUrl,document.body.appendChild(t),t.click(),docuLine truncated
|
||||
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;
|
||||
}
|
||||
}
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
function runPromisesInSeries(ps) {
|
||||
return ps.reduce((p, next) => p.then(next), Promise.resolve());
|
||||
}
|
||||
function getFileName(name) {
|
||||
const nameArray = name.split(".");
|
||||
nameArray.pop();
|
||||
return nameArray.join(".");
|
||||
}
|
||||
function clamp(num, a, b) {
|
||||
return Math.max(Math.min(num, Math.max(a, b)), Math.min(a, b));
|
||||
}
|
||||
class ArtplayerToolThumbnail extends Emitter {
|
||||
constructor(option = {}) {
|
||||
super();
|
||||
this.processing = false;
|
||||
this.option = {};
|
||||
this.setup(Object.assign({}, ArtplayerToolThumbnail.DEFAULTS, option));
|
||||
this.video = ArtplayerToolThumbnail.creatVideo();
|
||||
this.duration = 0;
|
||||
this.inputChange = this.inputChange.bind(this);
|
||||
this.ondrop = this.ondrop.bind(this);
|
||||
this.option.fileInput.addEventListener("change", this.inputChange);
|
||||
this.option.fileInput.addEventListener("dragover", ArtplayerToolThumbnail.ondragover);
|
||||
this.option.fileInput.addEventListener("drop", ArtplayerToolThumbnail.ondrop);
|
||||
}
|
||||
static get DEFAULTS() {
|
||||
return {
|
||||
number: 60,
|
||||
width: 160,
|
||||
height: 90,
|
||||
column: 10,
|
||||
begin: 0,
|
||||
end: Number.NaN
|
||||
};
|
||||
}
|
||||
static ondragover(event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
ondrop(event) {
|
||||
event.preventDefault();
|
||||
const file = event.dataTransfer.files[0];
|
||||
this.loadVideo(file);
|
||||
}
|
||||
setup(option = {}) {
|
||||
this.option = Object.assign({}, this.option, option);
|
||||
const { fileInput, number, width, column } = this.option;
|
||||
this.errorHandle(fileInput instanceof Element, "The 'fileInput' is not a Element");
|
||||
if (!(fileInput.tagName === "INPUT" && fileInput.type === "file")) {
|
||||
fileInput.style.position = "relative";
|
||||
const newFileInput = document.createElement("input");
|
||||
newFileInput.type = "file";
|
||||
newFileInput.style.position = "absolute";
|
||||
newFileInput.style.width = "100%";
|
||||
newFileInput.style.height = "100%";
|
||||
newFileInput.style.left = "0";
|
||||
newFileInput.style.top = "0";
|
||||
newFileInput.style.right = "0";
|
||||
newFileInput.style.bottom = "0";
|
||||
newFileInput.style.opacity = "0";
|
||||
fileInput.appendChild(newFileInput);
|
||||
this.option.fileInput = newFileInput;
|
||||
}
|
||||
["number", "width", "column", "begin", "end"].forEach((item) => {
|
||||
this.errorHandle(typeof this.option[item] === "number", `The '${item}' is not a number`);
|
||||
});
|
||||
this.option.number = clamp(number, 10, 1e3);
|
||||
this.option.width = clamp(width, 10, 1e3);
|
||||
this.option.column = clamp(column, 1, 1e3);
|
||||
return this;
|
||||
}
|
||||
static creatVideo() {
|
||||
const video = document.createElement("video");
|
||||
video.style.position = "absolute";
|
||||
video.style.top = "-9999px";
|
||||
video.style.left = "-9999px";
|
||||
video.muted = true;
|
||||
video.controls = true;
|
||||
document.body.appendChild(video);
|
||||
return video;
|
||||
}
|
||||
inputChange(event) {
|
||||
const file = this.option.fileInput.files[0];
|
||||
this.loadVideo(file);
|
||||
event.target.value = "";
|
||||
}
|
||||
loadVideo(file) {
|
||||
if (file) {
|
||||
const canPlayType = this.video.canPlayType(file.type);
|
||||
this.errorHandle(
|
||||
canPlayType === "maybe" || canPlayType === "probably",
|
||||
`Playback of this file format is not supported: ${file.type}`
|
||||
);
|
||||
const videoUrl = URL.createObjectURL(file);
|
||||
this.videoUrl = videoUrl;
|
||||
this.file = file;
|
||||
this.emit("file", this.file);
|
||||
this.video.src = videoUrl;
|
||||
this.emit("video", this.video);
|
||||
}
|
||||
}
|
||||
start() {
|
||||
if (!this.video.duration)
|
||||
return sleep(1e3).then(() => this.start());
|
||||
const { width, number, begin, end } = this.option;
|
||||
const height = this.video.videoHeight / this.video.videoWidth * width;
|
||||
this.option.height = height;
|
||||
this.option.begin = clamp(begin, 0, this.video.duration);
|
||||
this.option.end = clamp(end || this.video.duration, begin, this.video.duration);
|
||||
this.errorHandle(this.option.end > this.option.begin, `End time must be greater than the start time`);
|
||||
this.duration = this.option.end - this.option.begin;
|
||||
this.density = number / this.duration;
|
||||
this.errorHandle(this.file && this.video, "Please select the video file first");
|
||||
this.errorHandle(!this.processing, "There is currently a task in progress, please wait a moment...");
|
||||
this.errorHandle(this.density <= 1, `The preview density cannot be greater than 1, but got ${this.density}`);
|
||||
const screenshotDate = this.creatScreenshotDate();
|
||||
const canvas = this.creatCanvas();
|
||||
const context2D = canvas.getContext("2d");
|
||||
this.emit("canvas", canvas);
|
||||
const promiseList = screenshotDate.map((item, index) => () => {
|
||||
return new Promise((resolve) => {
|
||||
this.video.oncanplay = () => {
|
||||
context2D.drawImage(this.video, item.x, item.y, width, height);
|
||||
canvas.toBlob((blob) => {
|
||||
if (this.thumbnailUrl) {
|
||||
URL.revokeObjectURL(this.thumbnailUrl);
|
||||
}
|
||||
this.thumbnailUrl = URL.createObjectURL(blob);
|
||||
this.emit("update", this.thumbnailUrl, (index + 1) / number);
|
||||
this.video.oncanplay = null;
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
this.video.currentTime = item.time;
|
||||
});
|
||||
});
|
||||
this.processing = true;
|
||||
return runPromisesInSeries(promiseList).then(() => {
|
||||
this.processing = false;
|
||||
this.emit("done");
|
||||
}).catch((err) => {
|
||||
this.processing = false;
|
||||
this.emit("error", err.message);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
creatScreenshotDate() {
|
||||
const { number, width, height, column, begin } = this.option;
|
||||
const timeGap = this.duration / number;
|
||||
const timePoints = [begin + timeGap];
|
||||
while (timePoints.length < number) {
|
||||
const last = timePoints[timePoints.length - 1];
|
||||
timePoints.push(last + timeGap);
|
||||
}
|
||||
return timePoints.map((item, index) => ({
|
||||
time: item - timeGap / 2,
|
||||
x: index % column * width,
|
||||
y: Math.floor(index / column) * height
|
||||
}));
|
||||
}
|
||||
creatCanvas() {
|
||||
const { number, width, height, column } = this.option;
|
||||
const canvas = document.createElement("canvas");
|
||||
const context2D = canvas.getContext("2d");
|
||||
canvas.width = width * column;
|
||||
canvas.height = Math.ceil(number / column) * height + 30;
|
||||
context2D.fillStyle = "black";
|
||||
context2D.fillRect(0, 0, canvas.width, canvas.height);
|
||||
context2D.font = "14px Georgia";
|
||||
context2D.fillStyle = "#fff";
|
||||
context2D.fillText(
|
||||
`From: https://artplayer.org/, Number: ${number}, Width: ${width}, Height: ${height}, Column: ${column}`,
|
||||
10,
|
||||
canvas.height - 11
|
||||
);
|
||||
return canvas;
|
||||
}
|
||||
download() {
|
||||
this.errorHandle(
|
||||
this.file && this.thumbnailUrl,
|
||||
"Download does not seem to be ready, please create preview first"
|
||||
);
|
||||
this.errorHandle(!this.processing, "There is currently a task in progress, please wait a moment...");
|
||||
const elink = document.createElement("a");
|
||||
const name = `${getFileName(this.file.name)}.png`;
|
||||
elink.download = name;
|
||||
elink.href = this.thumbnailUrl;
|
||||
document.body.appendChild(elink);
|
||||
elink.click();
|
||||
document.body.removeChild(elink);
|
||||
this.emit("download", name);
|
||||
return this;
|
||||
}
|
||||
errorHandle(condition, msg) {
|
||||
if (!condition) {
|
||||
this.emit("error", msg);
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
destroy() {
|
||||
this.option.fileInput.removeEventListener("change", this.inputChange);
|
||||
this.option.fileInput.removeEventListener("dragover", ArtplayerToolThumbnail.ondragover);
|
||||
this.option.fileInput.removeEventListener("drop", ArtplayerToolThumbnail.ondrop);
|
||||
document.body.removeChild(this.video);
|
||||
if (this.videoUrl) {
|
||||
URL.revokeObjectURL(this.videoUrl);
|
||||
}
|
||||
if (this.thumbnailUrl) {
|
||||
URL.revokeObjectURL(this.thumbnailUrl);
|
||||
}
|
||||
this.emit("destroy");
|
||||
}
|
||||
}
|
||||
export {
|
||||
ArtplayerToolThumbnail as default
|
||||
};
|
||||
+5006
-1
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user