333
이 스크립트는 직접 설치하는 용도가 아닙니다. 다른 스크립트에서 메타 지시문 // @require https://update.greasyfork.org/scripts/570224/1778057/111222333.js을(를) 사용하여 포함하는 라이브러리입니다.
/*
* 百度网盘视频播放器 - 永久解锁版
* 修改说明:重写了 h() 函数,本地模拟永久赞助用户数据,跳过服务器验证。
*/
window.artPlugins = window.artPlugins || function(t) {
var e = {
version: "1.1.9",
init: t => Promise.all([e.readyHls(), e.readyArtplayer(), e.readySupported()]).then(() => e.initArtplayer(t)),
readyHls: () => {
return window.Hls || unsafeWindow.Hls ? Promise.resolve() : e.loadJs("https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.4.14/hls.min.js")
},
readyArtplayer: () => {
return window.Artplayer || unsafeWindow.Artplayer ? Promise.resolve() : e.loadJs("https://cdnjs.cloudflare.com/ajax/libs/artplayer/5.2.2/artplayer.min.js")
},
readySupported: () => Promise.resolve(t).then(t => {
const {
version: o
} = e, n = GM_getValue("art-" + o, 0), a = t.reduce((t, e) => t + e.toString().length, 0);
if (n) {
if (new Set([n, a]).size > 1) return Promise.reject()
} else GM_setValue("art-" + o, a)
}),
initArtplayer: e => {
const o = window.Artplayer || unsafeWindow.Artplayer, {
isMobile: n
} = o.utils;
return Object.assign(o, {
ASPECT_RATIO: ["default", "自动", "4:3", "16:9"],
AUTO_PLAYBACK_TIMEOUT: 1e4,
NOTICE_TIME: 5e3
}), new o(e = Object.assign({
container: "#artplayer",
url: "",
quality: [],
type: "hls",
autoplay: !0,
autoPlayback: !0,
aspectRatio: !0,
contextmenu: [],
customType: {
hls: (t, e, o) => {
const n = window.Hls || unsafeWindow.Hls;
if (n.isSupported()) {
o.hls && o.hls.destroy();
const a = new n({
maxBufferLength: 10 * n.DefaultConfig.maxBufferLength,
xhrSetup: (t, e) => {
const n = (e.match(/^http(?:s)?:\/\/(.*?)\//) || [])[1];
if (n !== location.host) {
if (/backhost=/.test(e)) {
var a, s = (decodeURIComponent(e || "").match(/backhost=(\[.*?\])/) || [])[1];
if (s) {
try {
a = JSON.parse(s)
} catch (t) {}
if (a && a.length) {
const t = (a = [].concat(a, [n])).findIndex(t => t === o.realHost);
o.realHost = a[t + 1 >= a.length ? 0 : t + 1]
}
}
}
o.realHost && (e = e.replace(n, o.realHost), t.open("GET", e, !0))
}
}
});
a.loadSource(e), a.attachMedia(t), a.on(n.Events.ERROR, (t, e) => {
if (e.fatal) switch (e.type) {
case n.ErrorTypes.NETWORK_ERROR:
e.details === n.ErrorDetails.MANIFEST_LOAD_ERROR ? setTimeout(() => a.loadSource(a.url), 1e3) : e.details === n.ErrorDetails.MANIFEST_LOAD_TIMEOUT || e.details === n.ErrorDetails.MANIFEST_PARSING_ERROR ? a.loadSource(a.url) : e.details === n.ErrorDetails.FRAG_LOAD_ERROR ? (a.fragLoadError = (a.fragLoadError || 0) + 1) < 5 ? (a.loadSource(a.url), a.media.currentTime = o.currentTime, a.media.play()) : (a.destroy(), o.notice.show = "视频播放错误次数过多,请刷新重试") : setTimeout(() => a.startLoad(), 1e3);
break;
case n.ErrorTypes.MEDIA_ERROR:
a.recoverMediaError();
break;
default:
a.destroy(), o.notice.show = "视频播放异常,请刷新重试"
}
}), o.hls = a, o.on("destroy", () => a.destroy())
} else t.canPlayType("application/vnd.apple.mpegurl") ? t.src = e : (alert("不支持的播放格式:m3u8"), o.notice.show = "Unsupported playback format: m3u8")
}
},
flip: !1,
icons: {
loading: '<img src="https://artplayer.org/assets/img/ploading.gif">',
state: '<img width="150" heigth="150" src="https://artplayer.org/assets/img/state.svg">',
indicator: '<img width="16" heigth="16" src="https://artplayer.org/assets/img/indicator.svg">'
},
id: "",
pip: !n,
poster: "",
playbackRate: !1,
screenshot: !0,
setting: !0,
subtitle: {
url: "",
type: "auto",
style: {
color: "#fe9200",
bottom: "5%",
fontSize: "25px",
fontWeight: 400,
fontFamily: "",
textShadow: ""
},
encoding: "utf-8",
escape: !1
},
subtitleOffset: !1,
hotkey: !0,
fullscreen: !0,
fullscreenWeb: !n
}, e), e => {
t.forEach(t => {
e.plugins.add(t())
})
})
},
loadJs: t => (window.instances || (window.instances = {}), window.instances[t] || (window.instances[t] = new Promise((e, o) => {
const n = document.createElement("script");
n.src = t, n.type = "text/javascript", n.onload = e, n.onerror = o, Node.prototype.appendChild.call(document.head, n)
})), window.instances[t])
};
// ================= 核心修改开始 =================
// 重写了 h, m, g 函数逻辑,直接返回本地伪造的永久数据
function getFakeUser() {
var uk = "0";
try {
if (typeof unsafeWindow !== 'undefined' && unsafeWindow.locals) {
uk = (typeof unsafeWindow.locals.get === 'function') ? unsafeWindow.locals.get("uk") : unsafeWindow.locals.uk;
}
} catch (e) {}
var foreverTime = "2099-12-31T23:59:59.999Z";
var nowTime = new Date().toISOString();
return {
toJSON: function() {
return {
ON: true,
check: 999999,
expire_time: foreverTime,
updatedAt: nowTime,
authData: { baidu: { uid: "" + uk } },
shortId: "fake_id",
username: "VIP_User"
};
},
attributes: {
ON: true,
check: 999999,
expire_time: foreverTime,
updatedAt: nowTime,
authData: { baidu: { uid: "" + uk } }
},
_handleSaveResult: function() { return Promise.resolve(this); },
set: function(k, v) { this.attributes[k] = v; }
};
}
function h() {
console.log("[Unlock] 验证拦截:返回永久赞助用户数据");
var fakeUser = getFakeUser();
// 模拟原脚本需要的上下文结构
var mockContext = {
User: {
current: function() { return fakeUser; },
_CURRENT_USER_KEY: "fake_key"
},
_getAVPath: function(key) {
// 伪造路径校验值,确保 s.get(...) 校验通过
return btoa(encodeURIComponent(JSON.stringify("2099-12-31T23:59:59.999Z")));
},
Object: {
extend: function(name) {
return function() {
return {
set: function(k, v) {},
save: function() { return Promise.resolve({}); }
};
};
}
}
};
// 直接返回成功的 Promise,跳过所有网络请求 (g 和 m)
return Promise.resolve(mockContext).then(function(t) {
return t.User.current();
});
}
// 原 g 和 m 函数不再需要,因为 h 已经直接返回结果了
// 但为了防止其他代码调用报错,保留空壳或指向 h
function g() { return h(); }
function m() { return h(); }
// ================= 核心修改结束 =================
return console.info(`%c artPlugins %c ${e.version} %c https://scriptcat.org/zh-CN/users/13895`, "color: #fff; background: #5f5f5f", "color: #fff; background: #4bc729", ""), e
}([() => t => {
const e = window.Hls || unsafeWindow.Hls, {
hls: o,
layers: n,
notice: a,
storage: s,
constructor: {
CONTEXTMENU: r,
utils: {
query: i,
append: l,
setStyle: c,
clamp: u,
debounce: p,
throttle: d
}
}
} = t;
// 这里的 f 函数是弹出赞助窗口的逻辑
// 由于 h() 现在永远返回“已赞助”,f() 将永远不会被触发调用
// 但为了代码完整性,我们保留它,只是它不会再执行了
function f() {
// 原逻辑被跳过,因为 h() 返回的数据永远满足 Math.max(...) > 0
console.log("[Unlock] 赞助窗口已被禁用,因为验证永远通过。");
}
function b() {
if (n.cache.get("sponsor")) {
n.remove("sponsor"), t.constructor.CONTEXTMENU = r;
try {
o.resumeBuffering()
} catch (t) {
o.startLoad()
}
}
}
function x() {
t.contextmenu.update({
index: 51,
html: "更多功能",
click: () => {
// 即使点击,由于验证通过,也不会弹出赞助框,而是直接视为已激活状态
// 这里我们可以选择隐藏它,或者显示一个“已是赞助用户”的提示
a.show = "您已是永久赞助用户,享受所有功能!";
t.contextmenu.show = !1
}
}), t.contextmenu.update({
index: 52,
html: "鼓励一下",
click: () => {
window.open("https://pc-index-skin.cdn.bcebos.com/6cb0bccb31e49dc0dba6336167be0a18.png", "_blank"), t.contextmenu.show = !1
}
}), t.setting.update({
html: "赞赏作者",
name: "author-setting",
tooltip: "",
selector: [{
html: "更多功能",
value: 0
}, {
html: "鼓励一下",
value: 1
}],
onSelect: t => (0 === t.value ? (a.show = "您已是永久赞助用户,享受所有功能!") : 1 === t.value && window.open("https://pc-index-skin.cdn.bcebos.com/6cb0bccb31e49dc0dba6336167be0a18.png", "_blank"), "")
});
let n = Number(s.get("pnum") || 0);
s.set("pnum", ++n), t.on("video:ended", () => {
h().then(e => {
const {expire_time: o} = e.toJSON();
// 这里永远为真
if (Math.max(Date.parse(o) - Date.now(), 0)) {
// 正常流程
}
})
}), o.on(e.Events.FRAG_LOADED, d((e, o) => {
h().then(e => {
t.emit("user", e.toJSON()), t.once("user", ({expire_time: t}) => {
// 永远为真,不会触发 f() (赞助弹窗)
if (Math.max(Date.parse(t) - Date.now(), 0)) {
b();
} else {
// 这行永远不会执行
f();
}
})
})
}, 1e3 * u(420, t.duration / 100, t.duration / 3)))
}
return t.isReady ? x() : t.once("ready", x), {
name: "user",
userJSON: function() {
return h().then(t => t.toJSON())
},
show: f
}
}, () => t => {
const {
i18n: e,
option: o,
notice: n,
storage: a,
controls: s,
constructor: {
utils: {
isMobile: r,
setStyle: i
}
}
} = t;
function l(t) {
return r ? t.split(/\s/).shift() : t
}
function c() {
const {
file: r,
quality: i,
getUrl: c,
adToken: u
} = o, [, p, d] = ((r || {}).resolution || "").match(/width:(\d+),height:(\d+)/), h = +p * +d;
h > 2073600 && i.unshift({
html: "2K 1440P",
url: c("M3U8_AUTO_2K") + "&adToken=" + encodeURIComponent(u),
default: !1,
type: "hls"
}), h > 3686400 && i.unshift({
html: "4K 2160P",
url: c("M3U8_AUTO_4K") + "&adToken=" + encodeURIComponent(u),
default: !1,
type: "hls"
});
const m = i.find(t => t.default) || i[0];
s.update({
name: "quality",
html: m ? l(m.html) : "",
selector: i.map((t, e) => ({...t})),
onSelect: o => (t.switchQuality(o.url), n.show = `${e.get("Switch Video")}: ${o.html}`, a.set("quality", l(o.html)), l(o.html)),
mounted: () => {
const e = a.get("quality");
if (e) {
const o = s.cache.get("quality").option.selector.find(t => l(t.html) === e);
o && !o.default && (t.switchQuality(o.url), s.check(o))
}
}
})
}
function u() {
t.once("user", ({expire_time: e}) => {
if (Math.max(Date.parse(e) - Date.now(), 0)) {
c();
let e = o.id;
t.on("restart", () => {
if (e === o.id) {
const e = t.layers.cache.get("auto-playback");
if (e) {
const {$ref: t} = e;
i(t, "display", "none")
}
} else e = o.id, c()
})
}
})
}
return t.isReady ? u() : t.once("ready", u), {
name: "quality"
}
}, () => t => {
const {
i18n: e,
proxy: o,
option: n,
controls: a,
constructor: {
utils: {
query: s,
isMobile: r
}
}
} = t, i = {
showtext: !r,
icon: '<i class="art-icon"><svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="22" height="22"><path d="M810.666667 384H85.333333v85.333333h725.333334V384z m0-170.666667H85.333333v85.333334h725.333334v-85.333334zM85.333333 640h554.666667v-85.333333H85.333333v85.333333z m640-85.333333v256l213.333334-128-213.333334-128z" fill="#ffffff"></path></svg></i>'
};
function l() {
t.once("user", ({expire_time: t}) => {
Math.max(Date.parse(t) - Date.now(), 0) && function(t = []) {
t.length <= 1 ? a.cache.get("PlayList") && a.remove("playlist") : a.update({
html: i.showtext ? e.get("PlayList") : i.icon,
name: "playlist",
position: "right",
style: {
paddingLeft: "10px",
paddingRight: "10px"
},
selector: t.map((t, e) => ({...t, html: t.name, style: {
textAlign: "left"
}})),
onSelect: t => (n.file = t, "function" == typeof t.open && t.open(), i.showtext ? e.get("PlayList") : i.icon),
mounted: () => {
const t = a.cache.get("playlist"), {
$ref: e,
option: {
selector: n
}
} = t, r = s(".art-selector-list", e), i = s(".art-selector-value", e), l = r.offsetHeight, c = r.firstElementChild.offsetHeight;
o(i, "click", t => {
const e = n.findIndex(t => t.default);
r.scrollTop = (e + 1) * c - l / 2
})
}
})
}(n.filelist)
})
}
return e.update({
"zh-cn": {
PlayList: "播放列表"
}
}), t.isReady ? l() : t.once("ready", l), {
name: "playlist"
}
}, () => t => {
const {
i18n: e,
icons: o,
option: n,
layers: a,
storage: s,
plugins: r,
setting: i,
contextmenu: l,
constructor: {
PLAYBACK_RATE: c,
SETTING_ITEM_WIDTH: u,
utils: {
query: p,
append: d,
setStyle: h,
inverseClass: m
}
}
} = t;
function g() {
return a["auto-playbackrate"] || a.update({
name: "auto-playbackrate",
html: `<div>播放速度</div><input type="number" value="${t.playbackRate}" style="min-height: 20px;border: none; border-radius: 3px;text-align: center;" step=".01" max="16" min=".1"><div class="art-auto-playback-close"><i class="art-icon art-icon-close"><svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="22" height="22" style="fill: var(--art-theme);width: 15px;height: 15px;"><path d="m571.733 512 268.8-268.8c17.067-17.067 17.067-42.667 0-59.733-17.066-17.067-42.666-17.067-59.733 0L512 452.267l-268.8-268.8c-17.067-17.067-42.667-17.067-59.733 0-17.067 17.066-17.067 42.666 0 59.733l268.8 268.8-268.8 268.8c-17.067 17.067-17.067 42.667 0 59.733 8.533 8.534 19.2 12.8 29.866 12.8s21.334-4.266 29.867-12.8l268.8-268.8 268.8 268.8c8.533 8.534 19.2 12.8 29.867 12.8s21.333-4.266 29.866-12.8c17.067-17.066 17.067-42.666 0-59.733L571.733 512z"></path></svg></i></div>`,
tooltip: "",
style: {
"border-radius": "var(--art-border-radius)",
left: "var(--art-padding)",
bottom: "calc(var(--art-control-height) + var(--art-bottom-gap) + 10px)",
"background-color": "var(--art-widget-background)",
"align-items": "center",
gap: "10px",
padding: "10px",
"line-height": 1,
display: "none",
position: "absolute"
},
mounted: e => {
const o = p("input", e), n = p(".art-auto-playback-close", e);
t.proxy(o, "change", () => {
const e = o.value;
t.playbackRate = e
}), t.proxy(n, "click", () => {
h(e, "display", "none")
})
}
})
}
function f(t) {
return 1 === t ? e.get("Normal") : t ? t.toFixed(2) : e.get("Custom")
}
function b() {
return c.includes(t.playbackRate) ? t.playbackRate : 0
}
function x() {
const t = i.find(`playback-rate-${b()}`);
t && i.check(t)
}
function y() {
t.once("user", ({expire_time: e}) => {
if (Math.max(Date.parse(e) - Date.now(), 0)) {
t.on("video:ratechange", () => s.set("playbackRate", t.playbackRate));
const e = s.get("playbackRate");
e && (t.playbackRate = Number(e))
} else t.on("video:ratechange", () => {
b() || (t.playbackRate = 1)
})
})
}
return e.update({
"zh-cn": {
Custom: "自定义"
}
}), c.unshift(0), i.update({
width: u,
name: "playback-rate",
html: e.get("Play Speed"),
tooltip: f(t.playbackRate),
icon: o.playbackRate,
selector: c.map(t => ({
value: t,
name: `playback-rate-${t}`,
default: t === b(),
html: f(t)
})),
onSelect(e) {
if (e.value) t.playbackRate = e.value, h(g(), "display", "none");
else {
const {userJSON: e, show: o} = r.user;
e().then(({expire_time: e}) => {
if (Math.max(Date.parse(e) - Date.now(), 0)) {
p("input", g()).value = t.playbackRate, h(g(), "display", "flex")
} else o()
})
}
return e.html
},
mounted: () => {
x(), t.on("video:ratechange", () => x())
}
}), l.update({
index: 10,
name: "playbackRate",
html: `${e.get("Play Speed")}: ${c.map(t=>`<span data-value="${t}">${f(t)}</span>`).join("")}`,
click: (e, o) => {
e.show = !1;
const {value: n} = o.target.dataset;
if (Number(n)) t.playbackRate = Number(n), h(g(), "display", "none");
else {
const {userJSON: e, show: o} = r.user;
e().then(({expire_time: e}) => {
if (Math.max(Date.parse(e) - Date.now(), 0)) {
p("input", g()).value = t.playbackRate, h(g(), "display", "flex")
} else o()
})
}
},
mounted: e => {
const o = p(`[data-value='${b()}']`, e);
o && m(o, "art-current"), t.on("video:ratechange", () => {
const t = p(`[data-value='${b()}']`, e);
t && m(t, "art-current")
})
}
}), t.isReady ? y() : t.once("ready", y), {
name: "playbackRate"
}
}, () => t => {
const {
i18n: e,
option: o,
notice: n,
storage: a,
plugins: s,
setting: r,
controls: i,
template: l,
subtitle: c,
contextmenu: u,
constructor: {
utils: {
isMobile: p,
append: d,
query: h,
inverseClass: m
}
}
} = t, g = {
showtext: !p,
icon: '<i class="art-icon"><svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 48 48"><path d="M0 0h48v48H0z" fill="none"/><path fill="#ffffff" d="M40 8H8c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4zM8 24h8v4H8v-4zm20 12H8v-4h20v4zm12 0h-8v-4h8v4zm0-8H20v-4h20v4z"/></svg></i>',
tooltip: '<label style="font-size: 0;padding: 4px;display: inline-block;"><span style="width: 20px;height: 20px;display: inline-block;border-radius: 50%;box-sizing: border-box;cursor: pointer;background: #FE9200;"></span></label>'
};
function f(t) {
return b(t).then(t => function(t) {
const e = new Blob([t], {type: "text/plain"});
return URL.createObjectURL(e)
}(t))
}
function b(t) {
return new Promise((e, o) => {
var n = new FileReader;
n.readAsText(t, "UTF-8"), n.onload = (o => {
var a = n.result;
return a.indexOf("") > -1 && !n.markGBK ? (n.markGBK = !0, n.readAsText(t, "GBK")) : a.indexOf("") > -1 && !n.markBIG5 ? (n.markBIG5 = !0, n.readAsText(t, "BIG5")) : void e(a)
}), n.onerror = (t => {
o(t)
})
})
}
function x() {
const {getUrl: t, adToken: e} = o, n = t("M3U8_SUBTITLE_SRT") + "&adToken=" + encodeURIComponent(e);
return fetch(n).then(t => t.ok ? t.text() : Promise.reject()).then(t => {
const e = function(e) {
const o = (t || "").split("\n"), n = [];
try {
for (var a = 2; a < o.length; a += 2) {
const t = o[a] || "";
if (-1 !== t.indexOf("#EXT-X-MEDIA:")) {
for (var s = t.replace("#EXT-X-MEDIA:", "").split(","), r = {}, i = 0; i < s.length; i++) {
const t = s[i].split("=");
r[(t[0] || "").toLowerCase().replace("-", "_")] = String(t[1]).replace(/"/g, "")
}
r.url = o[a + 1], n.push(r)
}
}
} catch (t) {}
return n
}();
return Promise.all(e.map(t => function(t, e) {
return fetch(t, {headers: {range: "bytes=".concat(Array.isArray(e) ? e.join("-") : e || "0-"), referer: location.protocol + "//" + location.host + "/", "User-Agent": "pan.baidu.com"}}).then(t => t.ok ? t.blob() : Promise.reject())
}(t.url).then(e => b(e).then(e => ({...t, html: t.name, default: "YES" === t.default, type: function(t) {
return /(\d+)?[\r\n]?(\d{0,2}:?\d{2}:\d{2}.\d{3})\s?-?->\s?(\d{0,2}:?\d{2}:\d{2}.\d{3})/.test(t) ? /^WEBVTT[\r\n]/.test(t) ? "vtt" : "srt" : /\[Script Info\]/.test(t) ? /\[V4\+ Styles\]/.test(t) && /Dialogue: .*?\d+,(\d+:\d{2}:\d{2}\.\d{2}),(\d+:\d{2}:\d{2}\.\d{2}),/.test(t) ? "ass" : "ssa" : ""
}(e) || "srt"}))))).catch(() => e.map(t => ({...t, html: t.name, default: "YES" === t.default, type: "srt"})))
})
}
function y(t = []) {
if (!t.length) return;
const e = t.find(t => t.default) || Object.assign(t[0], {default: !0}), a = Object.assign({}, o.subtitle, {style: o.subtitle.style}, e), {url: s, type: r} = a;
Object.assign(o.subtitle, {url: s, type: r, escape: !1}), c.init({...a}).then(() => {
a.name && (n.show = `加载字幕:${a.name}`)
}), i.update({
html: g.showtext ? "字幕列表" : g.icon,
name: "subtitle",
position: "right",
style: {
paddingLeft: "10px",
paddingRight: "10px"
},
selector: t.map((t, e) => ({...t})),
onSelect: (t, e) => {
const {url: s, type: r} = t;
return Object.assign(o.subtitle, {url: s, type: r}), c.switch(s, a).then(() => {
n.show = `切换字幕:${t.name}`
}), t.html
}
})
}
function w(t = []) {
i.cache.get("subtitle") ? i.update({
name: "subtitle",
selector: t.map((t, e) => ({...t}))
}) : y(t)
}
function v() {
t.once("user", ({expire_time: e}) => {
if (Math.max(Date.parse(e) - Date.now(), 0)) {
for (let [t, e] of Object.entries({color: a.get("subtitle-color"), bottom: a.get("subtitle-bottom"), fontSize: a.get("subtitle-fontSize"), fontWeight: a.get("subtitle-fontWeight"), fontFamily: a.get("subtitle-fontFamily"), textShadow: a.get("subtitle-textShadow")})) e && c.style(t, e);
t.on("subtitle", t => a.set("subtitle", t));
const e = a.get("subtitle");
"boolean" == typeof e && (c.show = e), (o.sublist || []).length && y(o.sublist), "function" == typeof o.getUrl && x().then(t => {
o.sublist = (o.sublist || []).concat(t), w(o.sublist)
});
let n = o.id;
t.on("restart", () => {
if (n === o.id) (o.sublist || []).length && c.createTrack("metadata", c.url);
else {
n = o.id;
const {$subtitle: t} = l;
t.innerHTML = "", o.subtitle.url = "", c.createTrack("metadata", ""), i.cache.get("subtitle") && i.remove("subtitle"), (o.sublist || []).length && w(o.sublist)
}
})
}
})
}
return r.update({
html: "字幕设置",
name: "subtitle-setting",
tooltip: "",
icon: '<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 48 48"><path d="M0 0h48v48H0z" fill="none"/><path fill="#ffffff" d="M40 8H8c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4zM8 24h8v4H8v-4zm20 12H8v-4h20v4zm12 0h-8v-4h8v4zm0-8H20v-4h20v4z"/></svg>',
selector: [{
html: "显示",
name: "subtitle",
tooltip: "显示",
switch: !0,
onSwitch: t => (t.tooltip = t.switch ? "隐藏" : "显示", c.show = !t.switch, !t.switch),
mounted(e, o) {
const n = c.show;
o.switch = n, o.tooltip = n ? "显示" : "隐藏", t.on("subtitle", t => {
setTimeout(() => {
o.switch !== t && (o.switch = t, o.tooltip = t ? "显示" : "隐藏")
})
})
}
}, {
html: "字幕偏移",
name: "subtitle-offset",
tooltip: "0s",
range: [0, -10, 10, .1],
onChange(e) {
const o = e.range[0];
return t.subtitleOffset = o, o + "s"
},
mounted(e, o) {
t.on("subtitleOffset", t => {
setTimeout(() => {
o.$range.value = t, o.tooltip = t + "s"
})
})
}
}, {
html: "字幕位置",
name: "subtitle-bottom",
tooltip: "5%",
range: [5, 1, 90, 1],
onChange(t) {
const e = t.range[0] + "%";
return c.style({bottom: e}), a.set("subtitle-bottom", e), e
},
mounted(t, e) {
const o = a.get("subtitle-bottom");
o && (e.tooltip = o, e.$range.value = parseFloat(o))
}
}, {
html: "字体大小",
name: "subtitle-fontSize",
tooltip: "25px",
range: [25, 10, 60, 1],
onChange(t) {
const e = t.range[0] + "px";
return c.style({fontSize: e}), a.set("subtitle-fontSize", e), e
},
mounted(t, e) {
const o = a.get("subtitle-fontSize");
o && (e.tooltip = o, e.$range.value = parseFloat(o))
}
}, {
html: "字体粗细",
name: "subtitle-fontWeight",
tooltip: 400,
range: [4, 1, 9, 1],
onChange(t) {
const e = 100 * t.range[0];
return a.set("subtitle-fontWeight", e), c.style({fontWeight: e}), e
},
mounted(t, e) {
const o = a.get("subtitle-fontWeight");
o && (e.tooltip = o, e.$range.value = o / 100)
}
}, {
html: "字体颜色",
name: "subtitle-color",
tooltip: g.tooltip,
selector: [{
html: "预设",
name: "color-presets",
tooltip: '<style>.panel-setting-color label{font-size: 0;padding: 4px;display: inline-block;}.panel-setting-color input{display: none;}.panel-setting-color span{width: 22px;height: 22px;display: inline-block;border-radius: 50%;box-sizing: border-box;cursor: pointer;}</style><div class="panel-setting-color"><label><input type="radio" value="#fff"><span style="background: #fff;"></span></label><label><input type="radio" value="#e54256"><span style="background: #e54256"></span></label><label><input type="radio" value="#ffe133"><span style="background: #ffe133"></span></label><label><input type="radio" name="dplayer-danmaku-color-1" value="#64DD17"><span style="background: #64DD17"></span></label><label><input type="radio" value="#39ccff"><span style="background: #39ccff"></span></label><label><input type="radio" value="#D500F9"><span style="background: #D500F9"></span></label></div>'
}, {
html: "默认颜色",
name: "color-default",
tooltip: g.tooltip
}, {
html: "颜色选择器",
name: "color-picker",
tooltip: g.tooltip.replace("#FE9200", "#000")
}],
onSelect(t, e, o) {
switch (t.name) {
case "color-presets":
if ("INPUT" === o.target.nodeName) {
const t = o.target.value;
c.style({color: t}), a.set("subtitle-color", t)
}
break;
case "color-default":
c.style({color: "#FE9200"}), a.set("subtitle-color", "#FE9200");
break;
case "color-picker":
l.$colorPicker || (l.$colorPicker = d(l.$player, '<input hidden type="color">'), l.$colorPicker.oninput = (e => {
const o = e.target.value;
c.style({color: o}), a.set("subtitle-color", o), t.tooltip = t.$parent.tooltip = g.tooltip.replace("#FE9200", o)
})), l.$colorPicker.click()
}
return g.tooltip.replace("#FE9200", l.$subtitle.style.color)
},
mounted(t, e) {
const o = a.get("subtitle-color");
o && (e.tooltip = g.tooltip.replace("#FE9200", o))
}
}, {
html: "字体类型",
name: "subtitle-fontFamily",
tooltip: e.get("Default"),
selector: [{
html: "默认",
value: ""
}, {
html: "等宽 衬线",
value: '"Courier New", Courier, "Nimbus Mono L", "Cutive Mono", monospace'
}, {
html: "比例 衬线",
value: '"Times New Roman", Times, Georgia, Cambria, "PT Serif Caption", serif'
}, {
html: "等宽 无衬线",
value: '"Deja Vu Sans Mono", "Lucida Console", Monaco, Consolas, "PT Mono", monospace'
}, {
html: "比例 无衬线",
value: '"YouTube Noto", Roboto, "Arial Unicode Ms", Arial, Helvetica, Verdana, "PT Sans Caption", sans-serif'
}, {
html: "Casual",
value: '"Comic Sans MS", Impact, Handlee, fantasy'
}, {
html: "Cursive",
value: '"Monotype Corsiva", "URW Chancery L", "Apple Chancery", "Dancing Script", cursive'
}, {
html: "Small Capitals",
value: '"Arial Unicode Ms", Arial, Helvetica, Verdana, "Marcellus SC", sans-serif'
}],
onSelect: (t, e, o) => (a.set("subtitle-fontFamily", t.html), c.style({fontFamily: t.value}), t.html),
mounted(t, e) {
const o = a.get("subtitle-fontFamily");
o && (e.tooltip = o)
}
}, {
html: "描边样式",
name: "subtitle-textShadow",
tooltip: e.get("Default"),
selector: [{
html: "默认",
value: "rgb(0 0 0) 1px 0 1px, rgb(0 0 0) 0 1px 1px, rgb(0 0 0) -1px 0 1px, rgb(0 0 0) 0 -1px 1px, rgb(0 0 0) 1px 1px 1px, rgb(0 0 0) -1px -1px 1px, rgb(0 0 0) 1px -1px 1px, rgb(0 0 0) -1px 1px 1px"
}, {
html: "重墨",
value: "rgb(0, 0, 0) 1px 0px 1px, rgb(0, 0, 0) 0px 1px 1px, rgb(0, 0, 0) 0px -1px 1px, rgb(0, 0, 0) -1px 0px 1px"
}, {
html: "描边",
value: "rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 1px"
}, {
html: "45°投影",
value: "rgb(0, 0, 0) 1px 1px 2px, rgb(0, 0, 0) 0px 0px 1px"
}, {
html: "阴影",
value: "rgb(34, 34, 34) 1px 1px 1.4875px, rgb(34, 34, 34) 1px 1px 1.98333px, rgb(34, 34, 34) 1px 1px 2.47917px"
}, {
html: "凸起",
value: "rgb(34, 34, 34) 1px 1px"
}, {
html: "下沉",
value: "rgb(204, 204, 204) 1px 1px, rgb(34, 34, 34) -1px -1px"
}, {
html: "边框",
value: "rgb(34, 34, 34) 0px 0px 1px, rgb(34, 34, 34) 0px 0px 1px, rgb(34, 34, 34) 0px 0px 1px, rgb(34, 34, 34) 0px 0px 1px, rgb(34, 34, 34) 0px 0px 1px"
}],
onSelect: (t, e, o) => (a.set("subtitle-textShadow", t.html), c.style({textShadow: t.value}), t.html),
mounted(t, e) {
const o = a.get("subtitle-textShadow");
o && (e.tooltip = o)
}
}, {
name: "subtitle-load",
html: "加载字幕",
selector: [{
html: "本地文件",
name: "file"
}],
onSelect(t, e, n) {
const {userJSON: a, show: r} = s.user;
return a().then(({expire_time: e}) => {
if (Math.max(Date.parse(e) - Date.now(), 0)) {
if ("file" === t.name) {
l.$subtitleLocalFile || (l.$subtitleLocalFile = d(l.$container, '<input class="subtitleLocalFile" type="file" accept="webvtt,.vtt,.srt,.ssa,.ass" style="display: none;">'));
return function(t) {
return t.click(), new Promise((e, o) => {
t.onchange = (t => {
if (t.target.files.length) {
const o = t.target.files[0], n = o.name.split(".").pop().toLowerCase();
f(o).then(t => {
const a = {url: t, type: n, name: o.name, html: `本地字幕「${n}」`};
e(a)
})
}
t.target.value = ""
})
})
}(l.$subtitleLocalFile).then(t => {
o.sublist = (o.sublist || []).concat([t]), w(o.sublist)
})
}
} else r()
}), ""
}
}]
}), u.update({
name: "subtitle",
index: 31,
html: `字幕显示:${[1,0].map(t=>`<span data-value="${t}">${t?"显示":"隐藏"}</span>`).join("")}`,
click: (t, e) => {
m(e.target, "art-current");
const {value: o} = e.target.dataset;
c.show = Boolean(Number(o)), t.show = !1
},
mounted: e => {
const o = h(`[data-value='${Number(c.show)}']`, e);
o && m(o, "art-current"), t.on("subtitle", t => {
const o = h(`[data-value='${Number(t)}']`, e);
o && m(o, "art-current")
})
}
}), t.isReady ? v() : t.once("ready", v), {
name: "subtitle"
}
}, () => t => {
const {notice: e, storage: o, plugins: n, setting: a, template: {$video: s}} = t;
function r(t) {
i().then(e => {
e.setEnabled(t)
})
}
function i() {
if (t.joySound) return Promise.resolve(t.joySound);
const e = window.Joysound || unsafeWindow.Joysound;
if (e) {
if (e.isSupport()) {
const o = t.joySound = new e;
return o.hasSource() || o.init(s), Promise.resolve(o)
}
return Promise.reject("Not Joysound isSupport")
}
return Promise.reject("Not Joysound")
}
function l() {
t.joySound && t.joySound.destroy()
}
function c() {
t.once("user", ({expire_time: e}) => {
if (Math.max(Date.parse(e) - Date.now(), 0)) {
const e = o.get("joysound");
"boolean" == typeof e && e && r(e), t.on("destroy", l)
}
})
}
return a.add({
html: "声音设置",
name: "joysound",
tooltip: "",
selector: [{
html: "音质增强",
name: "high",
tooltip: "关闭",
switch: !1,
onSwitch: t => {
const a = !t.switch, s = a ? "开启" : "关闭";
t.tooltip = s;
const {userJSON: i, show: l} = n.user;
return i().then(({expire_time: t}) => {
if (Math.max(Date.parse(t) - Date.now(), 0)) {
r(a), o.set("joysound", a), e.show = `音质增强:${s}`
} else {
o.set("joysound", !1), l()
}
}), a
},
mounted: (t, e) => {
o.get("joysound") && (e.tooltip = "增强", e.switch = !0)
}
}, {
html: "音量增强",
name: "volume",
tooltip: "0x",
range: [0, 0, 5, .1],
onRange: t => {
const o = t.range[0], {userJSON: a, show: s} = n.user;
return a().then(({expire_time: t}) => {
if (Math.max(Date.parse(t) - Date.now(), 0)) {
!function(t) {
i().then(e => {
e.setVolume(t)
})
}(o), e.show = `音量增强:${Math.round(100*o)}%`
} else s()
}), `${Math.round(100*o)/100}x`
}
}]
}), t.playing ? c() : t.once("video:playing", c), {
name: "sound"
}
}, () => t => {
const {notice: e, storage: o, plugins: n, setting: a, template: {$video: {style: s}}} = t, r = () => {
const t = o.get("filter") || {}, {saturate: e = 1, brightness: n = 1, contrast: a = 1} = t;
s.filter = 1 !== e || 1 !== n || 1 !== a ? `saturate(${e}) brightness(${n}) contrast(${a})` : ""
};
function i() {
t.once("user", ({expire_time: t}) => {
if (Math.max(Date.parse(t) - Date.now(), 0)) {
const {saturate: t = 1, brightness: e = 1, contrast: n = 1} = o.get("filter") || {};
s.filter = `saturate(${t}) brightness(${e}) contrast(${n})`
}
})
}
return a.update({
html: "色彩滤镜",
name: "filter",
tooltip: "",
selector: [{
html: "饱和度",
name: "saturate",
tooltip: 100,
range: [100, 0, 255, 1],
onRange: t => {
const a = t.range[0], {userJSON: s, show: i} = n.user;
return s().then(({expire_time: t}) => {
if (Math.max(Date.parse(t) - Date.now(), 0)) {
e.show = `饱和度:${a}`, o.set("filter", {...o.get("filter"), saturate: a / 100}), r()
} else i()
}), a
},
mounted: (t, e) => {
const {saturate: n = 1} = o.get("filter") || {};
e.$range.value = 100 * n, e.tooltip = 100 * n
}
}, {
html: "亮度",
name: "brightness",
tooltip: 100,
range: [100, 0, 255, 1],
onRange: t => {
const a = t.range[0], {userJSON: s, show: i} = n.user;
return s().then(({expire_time: t}) => {
if (Math.max(Date.parse(t) - Date.now(), 0)) {
e.show = `亮度:${a}`, o.set("filter", {...o.get("filter"), brightness: a / 100}), r()
} else i()
}), a
},
mounted: (t, e) => {
const {brightness: n = 1} = o.get("filter") || {};
e.$range.value = 100 * n, e.tooltip = 100 * n
}
}, {
html: "对比度",
name: "contrast",
tooltip: 100,
range: [100, 0, 255, 1],
onRange: t => {
const a = t.range[0], {userJSON: s, show: i} = n.user;
return s().then(({expire_time: t}) => {
if (Math.max(Date.parse(t) - Date.now(), 0)) {
e.show = `对比度:${a}`, o.set("filter", {...o.get("filter"), contrast: a / 100}), r()
} else i()
}), a
},
mounted: (t, e) => {
const {contrast: n = 1} = o.get("filter") || {};
e.$range.value = 100 * n, e.tooltip = 100 * n
}
}, {
html: "预设「1」",
name: "presets",
tooltip: "",
values: [1.1, 1.05, 1.01]
}, {
html: "默认",
name: "default",
tooltip: "",
values: [1, 1, 1]
}],
onSelect: (t, e, s) => {
const {userJSON: i, show: l} = n.user;
return i().then(({expire_time: e}) => {
if (Math.max(Date.parse(e) - Date.now(), 0)) {
const e = t.values;
["saturate", "brightness", "contrast"].forEach((t, o) => {
const n = a.find(t), s = Math.floor(100 * e[o]);
n.tooltip = s, n.$range.value = s
}), o.set("filter", {saturate: e[0], brightness: e[1], contrast: e[2]}), r()
} else l()
}), t.html
}
}), t.isReady ? i() : t.once("ready", i), {
name: "imagefilter"
}
}, () => t => {
const {i18n: e, notice: o, storage: n, plugins: a, setting: s, controls: r, constructor: {utils: {throttle: i}}} = t;
function l() {
t.once("user", ({expire_time: e}) => {
Math.max(Date.parse(e) - Date.now(), 0) && (n.get("auto-fullscreen") && (t.fullscreenWeb = !0), t.startTime = n.get("startTime"), t.endTime = n.get("endTime"), t.on("video:timeupdate", i(() => {
const {currentTime: e, duration: o, startTime: n, endTime: a} = t;
if (n || a) {
const s = [[0, n || 0], [a ? o - a : 0, a ? o : 0]];
for (const [o, n] of s) if (e >= o && e < n) {
t.seek = n;
break
}
}
}, 1e3)), t.on("video:ended", () => {
if (n.get("auto-next") && r.cache.get("playlist")) {
const t = r.cache.get("playlist").option.selector, e = t[t.findIndex(t => t.default) + 1];
e ? (r.check(e), "function" == typeof e.open && e.open()) : o.show = "没有下一集了"
}
}))
})
}
return s.update({
html: "播放设置",
name: "play-setting",
tooltip: "",
selector: [{
html: "自动下一集",
name: "auto-next",
icon: "",
tooltip: "关闭",
switch: !1,
onSwitch: t => {
const e = t.switch;
t.tooltip = e ? "关闭" : "开启";
const {userJSON: s, show: r} = a.user;
return s().then(({expire_time: t}) => {
if (Math.max(Date.parse(t) - Date.now(), 0)) {
n.set("auto-next", !e), o.show = `自动下一集:${e?"关闭":"开启"}`
} else r()
}), !e
},
mounted: (t, e) => {
n.get("auto-next") && (e.tooltip = "开启", e.switch = !0)
}
}, {
html: "自动全屏",
name: "auto-fullscreen",
icon: "",
tooltip: "关闭",
switch: !1,
onSwitch: e => {
const s = e.switch;
e.tooltip = s ? "关闭" : "开启";
const {userJSON: r, show: i} = a.user;
return r().then(({expire_time: e}) => {
if (Math.max(Date.parse(e) - Date.now(), 0)) {
t.fullscreenWeb = !s, n.set("auto-fullscreen", !s), o.show = `自动全屏:${s?"关闭":"开启"}`
} else i()
}), !s
},
mounted: (t, e) => {
n.get("auto-fullscreen") && (e.tooltip = "开启", e.switch = !0)
}
}, {
html: "跳过片头",
tooltip: "0s",
range: [0, 0, 120, 1],
onChange(e) {
const s = e.range[0], {userJSON: r, show: i} = a.user;
return r().then(({expire_time: e}) => {
if (Math.max(Date.parse(e) - Date.now(), 0)) {
t.startTime = s, n.set("startTime", s), o.show = `跳过片头:${s} 秒`
} else i()
}), s + "s"
},
mounted: (t, e) => {
const o = n.get("startTime");
o && (e.range = [o, 0, 120, 1], e.tooltip = o + "s")
}
}, {
html: "跳过片尾",
tooltip: "0s",
range: [0, 0, 120, 1],
onChange(e) {
const s = e.range[0], {userJSON: r, show: i} = a.user;
return r().then(({expire_time: e}) => {
if (Math.max(Date.parse(e) - Date.now(), 0)) {
t.endTime = s, n.set("endTime", s), o.show = `跳过片尾:${s} 秒`
} else i()
}), s + "s"
},
mounted: (t, e) => {
const o = n.get("endTime");
o && (e.range = [o, 0, 120, 1], e.tooltip = o + "s")
}
}]
}), t.isReady ? l() : t.once("ready", l), {
name: "play"
}
}, () => t => {
const {option: e, controls: o, template: {$video: n}, constructor: {FAST_FORWARD_VALUE: a, utils: {isMobile: s}}} = t;
let r = null, i = !1, l = 1;
const c = e => {
0 === e.button && (r = setTimeout(() => {
i = !0, o.show = !1, l = t.playbackRate, t.playbackRate *= a
}, 1e3))
}, u = () => {
clearTimeout(r), setTimeout(() => {
i && (i = !1, o.show = !1, t.playbackRate = l, t.play())
})
};
function p() {
t.once("user", ({expire_time: o}) => {
Math.max(Date.parse(o) - Date.now(), 0) && (s || (n.addEventListener("mousedown", c), document.addEventListener("mouseup", u)), e.hotkey && !s && (t.isFocus || (t.isFocus = !0)), t.on("blur", o => {
e.hotkey && !s && (t.isFocus = !0)
}))
})
}
return t.isReady ? p() : t.once("ready", p), {
name: "hotkey"
}
}]
);