// ==UserScript==
// @name 视频网站自动网页全屏|倍速播放
// @namespace http://tampermonkey.net/
// @version 2.7.1
// @author Feny
// @description 支持哔哩哔哩、B站直播、腾讯视频、优酷视频、爱奇艺、芒果TV、搜狐视频、AcFun弹幕网自动网页全屏;快捷键切换:全屏(F)、网页全屏(P)、下一个视频(N)、弹幕开关(D);支持任意视频倍速播放,提示记忆倍速;B站播放完自动退出网页全屏和取消连播。
// @license GPL-3.0-only
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAqdJREFUWEftl91LFFEYxp/3jB9ESZjtSl51F1RUSgRCF/kHlF1IhiFhF65dqEQkBUErdJMStBukGwQre2NZUiCRqUiURkW65mIfqGUFsW6Ii0jY7p4Tc3Rqd5zaGVldAudynve8z28e3jMzh5Dmi1R/V0vQyRRWxgWG6x22SrcnOAhQcQIbwVtXba8y1EANSpS1xzJin5c/Dz+jRDPvGWoErwRw35zuh8ChpcXXFjbwi9k/WADA9viGgovGnxtFs6EmcApMvCdBA3oIIirl4N8NNQngmRYJiwTOE7EHHLERAmXFawQ6AdCQkRbjsZIMUvIFoV0HMSsEDjCgSK8tJqAHAEDAMWLKLOexx8tiVVDEhLLVQAtzRPcwKOUANSWCw1/rsBe6PcFz8dpfAdTFgtF+EmIvBG7pID7mZNl2zkVCFQbahzqHfYerddpNhFpdsnfqauzl8ZoEuO4JXdIKOefynnZlimxXhBbqjTZL/el8pzrAVjTGmKh12Bq1ddJs974abQDXfFMuAhQ6EodwDTHWAf6/BAoK8nD0cDEKtuVhyD+OzvvLXnyWJshyApedJ1F65M9n4tlAAF5fL168fGfJWCu2DDA61GpodLvjCdp8vfjyNWQJJGUAquvMzBzafD0yEc65KZCUAmiOo4FPEqS753VSiFUB0FxbPF244en6J8SqAoTD8zhYcjZ9AP6RCVRWNacHYPD5GJqudmBi8tvaAkxNBeUuuNv5NOkAqgUpm4FIJCrfA+r0z4bnTZmvCKCv+wrsts0JBg8fvZLGY28NfoqToFhOoOJ4CS40lMu2I28mpXFP37DpJ9YXWgZQG+Tm5mBL7qakA2aGakUAZhqbrVkH0BLoB34fzcyml5K6pd/yaicRlQlgV0q6mmwitMOpyfpVKfsFya4w73cz9xQAAAAASUVORK5CYII=
// @homepage https://github.com/xFeny/UserScript/tree/main/monkey-web-fullscreen
// @include *://pages.iqiyi.com/p/zy/*
// @include *://www.ezdmw.site/Index/video/*
// @include *://player.ezdmw.com/danmuku/*
// @include *://*bimiacg*.net/*/play*
// @include *://acgfta.com/play*
// @include *://ppoft.com/play*
// @match *://tv.sohu.com/v/*
// @match *://www.mgtv.com/b/*
// @match *://www.acfun.cn/v/*
// @match *://www.iqiyi.com/v_*
// @match *://v.pptv.com/show/*
// @match *://v.qq.com/x/page/*
// @match *://v.douyu.com/show/*
// @match *://v.qq.com/x/cover/*
// @match *://live.bilibili.com/*
// @match *://v.youku.com/video?*
// @match *://haokan.baidu.com/v?*
// @match *://live.acfun.cn/live/*
// @match *://www.acfun.cn/bangumi/*
// @match *://www.bilibili.com/list/*
// @match *://www.bilibili.com/video/*
// @match *://www.bilibili.com/*/play/*
// @match *://v.qq.com/live/p/newtopic/*
// @match *://www.bilibili.com/festival/*
// @grant GM_addStyle
// @grant GM_addValueChangeListener
// @grant GM_getValue
// @grant GM_info
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_unregisterMenuCommand
// @grant unsafeWindow
// @note *://*/*
// ==/UserScript==
(e=>{if(typeof GM_addStyle=="function"){GM_addStyle(e);return}const t=document.createElement("style");t.textContent=e,document.head.append(t)})(' @charset "UTF-8";.showToast{color:#fff!important;font-size:13.5px!important;padding:5px 15px!important;border-radius:5px!important;position:absolute!important;z-index:2147483647!important;font-weight:400!important;transition:opacity .3s ease-in;background:#000000bf!important}#bilibili-player .bpx-player-toast-wrap,#bilibili-player .bpx-player-cmd-dm-wrap,#bilibili-player .bpx-player-dialog-wrap,.live-room-app #sidebar-vm,.live-room-app #prehold-nav-vm,.live-room-app #shop-popover-vm,.login-tip{display:none!important}.monkey-auto-web-fullScreen ul,.monkey-auto-web-fullScreen li{font-size:24px;list-style-type:none}.monkey-auto-web-fullScreen input{width:24px;height:24px;vertical-align:bottom}.monkey-auto-web-fullScreen label{display:flex;align-items:center;justify-content:space-between;padding-top:15px} ');
(function () {
'use strict';
const positions = Object.freeze({
bottomLeft: "bottom: 17%; left: 10px;",
bottomRight: "bottom: 17%; right: 10px;",
center: "top: 50%; left: 50%; transform: translate(-50%, -50%);"
});
const ONE_SECOND = 1e3;
const constants = Object.freeze({
EMPTY: "",
DEF_PLAY_RATE: 1,
MAX_PLAY_RATE: 16,
ONE_SEC: ONE_SECOND,
SHOW_TOAST_TIME: ONE_SECOND * 5,
SHOW_TOAST_POSITION: positions.bottomLeft,
MSG_SOURCE: "SCRIPTS_AUTO_WEB_FULLSCREEN",
QQ_VID_REG: /v.qq.com\/x/,
ACFUN_VID_REG: /acfun.cn\/v/,
IQIYI_VID_REG: /iqiyi.com\/v_*/,
BILI_VID_REG: /bilibili.com\/video/,
SYMBOL: Object.freeze({
ADD: "+",
SUBTRACT: "-",
MULTIPLY: "×",
DIVIDE: "÷"
})
});
var _GM_addValueChangeListener = /* @__PURE__ */ (() => typeof GM_addValueChangeListener != "undefined" ? GM_addValueChangeListener : void 0)();
var _GM_getValue = /* @__PURE__ */ (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)();
var _GM_info = /* @__PURE__ */ (() => typeof GM_info != "undefined" ? GM_info : void 0)();
var _GM_registerMenuCommand = /* @__PURE__ */ (() => typeof GM_registerMenuCommand != "undefined" ? GM_registerMenuCommand : void 0)();
var _GM_setValue = /* @__PURE__ */ (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)();
var _GM_unregisterMenuCommand = /* @__PURE__ */ (() => typeof GM_unregisterMenuCommand != "undefined" ? GM_unregisterMenuCommand : void 0)();
var _unsafeWindow = /* @__PURE__ */ (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)();
const { ONE_SEC: ONE_SEC$2, MSG_SOURCE: MSG_SOURCE$1 } = constants;
const Tools = {
isTopWin: () => window.top === window,
isNumber: (str) => /^[0-9]$/.test(str),
scrollTop: (top) => _unsafeWindow.top.scrollTo({ top }),
query: (selector, context) => (context || document).querySelector(selector),
querys: (selector, context) => (context || document).querySelectorAll(selector),
validDuration: (video) => !isNaN(video.duration) && video.duration !== Infinity,
triggerClick: (ele) => ele?.dispatchEvent(new MouseEvent("click", { bubbles: true })),
postMessage: (win = null, data) => win?.postMessage({ source: MSG_SOURCE$1, ...data }, "*"),
isVisible: (ele) => !!(ele?.offsetWidth || ele?.offsetHeight || ele?.getClientRects().length),
log: (...data) => console.log(...["%c======= 脚本日志 =======\n\n", "color:green;font-size:14px;", ...data, "\n\n"]),
alert: (...data) => window.alert(data.join(" ")),
getFrames() {
return this.querys("iframe:not([src=''], [src='#'], [id='buffer'], [id='install'])");
},
postMsgToFrames(data) {
this.getFrames().forEach((iframe) => this.postMessage(iframe.contentWindow, data));
},
debounce(fn, delay = ONE_SEC$2) {
let timer;
return function() {
if (timer) clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, arguments), delay);
};
},
getElementRect(element) {
return element?.getBoundingClientRect();
},
getElementCenterPoint(element) {
const { top, left, width, height } = this.getElementRect(element);
return { centerX: left + width / 2, centerY: top + height / 2 };
},
isPointInElementRect(pointX, pointY, element) {
const { top, left, right, bottom } = this.getElementRect(element);
return pointX >= left && pointX <= right && pointY >= top && pointY <= bottom;
},
triggerMousemove(element) {
const offsetWidth = element.offsetWidth;
const { centerY } = this.getElementCenterPoint(element);
for (let clientX = 0; clientX < offsetWidth; clientX += 10) {
this.dispatchMousemove(element, clientX, centerY);
}
},
dispatchMousemove(element, clientX, clientY) {
const dict = { clientX, clientY, bubbles: true };
const mousemove = new MouseEvent("mousemove", dict);
element.dispatchEvent(mousemove);
},
triggerHoverEvent(element) {
if (!element) return;
this.log("网页全屏元素:", element);
const { centerX, centerY } = this.getElementCenterPoint(element);
const dict = { clientX: centerX, clientY: centerY, bubbles: true };
const mouseover = new MouseEvent("mouseover", dict);
element?.dispatchEvent(mouseover);
},
triggerEscapeEvent() {
const dict = { key: "Escape", keyCode: 27, bubbles: true };
const keydown = new KeyboardEvent("keydown", dict);
document.body?.dispatchEvent(keydown);
},
createObserver(target, callback) {
target = target instanceof HTMLElement ? target : this.query(target);
const options = { attributes: true, childList: true, subtree: true };
const observer = new MutationObserver(callback);
observer.observe(target, options);
return observer;
},
closest(element, selector, maxLevel = 3) {
let curLevel = 0;
while (element && curLevel < maxLevel) {
if (element.matches(selector)) return element;
element = element.parentElement;
curLevel++;
}
return null;
},
findSiblingInParent(element, selector, maxLevel = 3) {
let curLevel = 0;
let curParent = element.parentElement;
while (curParent && curLevel < maxLevel) {
const sibs = curParent.children;
for (let sib of sibs) {
if (sib !== element && sib.matches(selector)) return sib;
}
curParent = curParent.parentElement;
curLevel++;
}
return null;
},
hasSiblings(element) {
return element.parentElement.children.length > 1;
},
extractNumbers(str) {
const numbers = str.match(/\d+/g);
return numbers ? numbers.map(Number) : [];
},
compareUrls(url1, url2) {
const parseUrl = (url) => {
const parsed = new URL(url);
return parsed.host + parsed.pathname + parsed.search + parsed.hash;
};
return parseUrl(url1) === parseUrl(url2);
}
};
const { EMPTY: EMPTY$2, QQ_VID_REG, BILI_VID_REG, IQIYI_VID_REG, ACFUN_VID_REG } = constants;
const matches = _GM_info.script.matches.filter((match) => match !== "*://*/*").map((match) => new RegExp(match.replace(/\*/g, "\\S+")));
const webSite = {
isDouyu: () => location.host === "v.douyu.com",
isBili: () => BILI_VID_REG.test(location.href),
isTencent: () => QQ_VID_REG.test(location.href),
isIqiyi: () => IQIYI_VID_REG.test(location.href),
isAcFun: () => ACFUN_VID_REG.test(location.href),
isLivePage: () => location.href.includes("live"),
isBiliLive: () => location.host === "live.bilibili.com",
inMatches: () => matches.some((matche) => matche.test(location.href.replace(location.search, EMPTY$2)))
};
const selectorConfig = {
"live.bilibili.com": { webfull: "#businessContainerElement" },
"www.bilibili.com": { webfull: "div[aria-label='网页全屏']", next: ".bpx-player-ctrl-next" },
"live.acfun.cn": { full: ".fullscreen-screen", webfull: ".fullscreen-web", danmaku: ".danmaku-enabled" },
"tv.sohu.com": { full: ".x-fullscreen-btn", webfull: ".x-pagefs-btn", danmaku: ".tm-tmbtn", next: ".x-next-btn" },
"haokan.baidu.com": { full: ".art-icon-fullscreen", webfull: ".art-control-fullscreenWeb", next: ".art-control-next" },
"www.iqiyi.com": { full: ".iqp-btn-fullscreen", webfull: ".iqp-btn-webscreen", danmaku: "#barrage_switch", next: ".iqp-btn-next" },
"www.mgtv.com": { full: ".fullscreenBtn i", webfull: ".webfullscreenBtn i", danmaku: "div[class*='danmuSwitch']", next: ".icon-next" },
"v.qq.com": { full: ".txp_btn_fullscreen", webfull: "div[aria-label='网页全屏']", danmaku: ".barrage-switch", next: ".txp_btn_next_u" },
"v.pptv.com": { full: ".w-zoom-container > div", webfull: ".w-expand-container > div", danmaku: ".w-barrage", next: ".w-next-container" },
"www.acfun.cn": { full: ".fullscreen-screen", webfull: ".fullscreen-web", danmaku: ".danmaku-enabled", next: ".btn-next-part .control-btn" },
"v.youku.com": { full: "#fullscreen-icon", webfull: "#webfullscreen-icon", danmaku: "div[class*='switch-img_12hDa turn-']", next: ".kui-next-icon-0" }
};
const douyu = {
getRoot() {
return document.querySelector("demand-video").shadowRoot;
},
getControllerBar() {
return this.getRoot().querySelector("#demandcontroller-bar").shadowRoot;
},
getVideo() {
return this.getRoot().querySelector("video");
},
play() {
this.getControllerBar().querySelector(".ControllerBarPlay")?.click();
},
pause() {
this.getControllerBar().querySelector(".ControllerBarStop")?.click();
},
getWebfullIcon() {
return this.getControllerBar().querySelector(".ControllerBar-PageFull-Icon");
},
getFullIcon() {
return this.getControllerBar().querySelector(".ControllerBar-WindowFull-Icon");
},
getDanmakuIcon() {
return document.querySelector("demand-player-extension").shadowRoot.querySelector(".BarrageSwitch-icon");
},
addStyle() {
const root = this.getRoot();
let style = root.querySelector("style");
if (style) return;
style = document.createElement("style");
style.textContent = `
.showToast {
color: #fff !important;
font-size: 13.5px !important;
padding: 5px 15px !important;
border-radius: 5px !important;
position: absolute !important;
z-index: 2147483647 !important;
font-weight: normal !important;
transition: opacity 500ms ease-in;
background: rgba(0, 0, 0, 0.75) !important;
}
`;
root.prepend(style);
}
};
const VideoListenerHandler = {
loadedmetadata() {
this.volume = 1;
this.isToastShown = false;
this.isWebFullScreen = false;
},
loadeddata() {
this.volume = 1;
this.isToastShown = false;
this.isWebFullScreen = false;
},
timeupdate() {
if (isNaN(this.duration)) return;
App.changeVideoInfo(this);
App.experimentWebFullScreen(this);
App.currVideoUseCachePlayRate(this);
},
canplay() {
webSite.isDouyu() ? douyu.play() : this.play();
},
play() {
this.isEnded = false;
App.webFullScreen(this);
},
ended() {
this.isEnded = true;
this.isToastShown = false;
if (!webSite.isBili() && !webSite.isAcFun()) return;
const pod = Tools.query(".video-pod");
const pods = Tools.querys('.video-pod .switch-btn:not(.on), .video-pod__item:last-of-type[data-scrolled="true"]');
if (!pod || !!pods.length) App.exitWebFullScreen();
}
};
const { EMPTY: EMPTY$1, ONE_SEC: ONE_SEC$1, SHOW_TOAST_TIME, SHOW_TOAST_POSITION } = constants;
const App = {
init() {
this.setupVisibleListener();
this.setupKeydownListener();
this.setupMutationObserver();
this.setupUrlChangeListener();
this.setupMouseMoveListener();
},
isLive() {
return webSite.isLivePage() || this.videoInfo?.isLive;
},
normalWebsite() {
return !this.videoInfo;
},
getVideo() {
if (webSite.isDouyu()) return douyu.getVideo();
return Tools.query("video:not([loop]):not([src=''])") || Tools.query("video:not([loop])");
},
getElement() {
if (webSite.isDouyu()) return douyu.getWebfullIcon();
return document.querySelector(selectorConfig[location.host]?.webfull);
},
getVideoIframe() {
if (!this.videoInfo?.frameSrc) return null;
const url = new URL(this.videoInfo.frameSrc);
const src = decodeURI(url.pathname + url.search);
return Tools.query(`iframe[src*="${src}"]`);
},
setupVisibleListener() {
window.addEventListener("visibilitychange", () => {
window.top.focus();
if (this.normalWebsite()) return;
const video = this.isLive() ? this.getVideo() : this.video;
if (!video || video?.isEnded || !Tools.isVisible(video)) return;
document.hidden ? video?.pause() : video?.play();
});
},
setupUrlChangeListener() {
const _wr = (method) => {
const original = history[method];
history[method] = function() {
original.apply(history, arguments);
window.dispatchEvent(new Event(method));
};
};
const handler = Tools.debounce(() => this.setupMutationObserver());
["popstate", "pushState", "replaceState"].forEach((t) => _wr(t) & window.addEventListener(t, handler));
},
setupMutationObserver() {
const observer = Tools.createObserver(document.body, () => {
const video = this.getVideo();
this.element = this.getElement();
if (video?.play && !!video.offsetWidth) this.setupVideoListener();
if (!webSite.inMatches() && this.video) return observer.disconnect();
if (!video?.play || !this.element || !this.webFullScreen(video)) return;
observer.disconnect();
this.biliLiveExtras();
this.webSiteLoginObserver();
});
setTimeout(() => observer.disconnect(), ONE_SEC$1 * 10);
},
video: null,
videoBoundListeners: [],
setupVideoListener() {
const video = this.getVideo();
this.addVideoEvtListener(video);
this.healthCurrentVideo();
},
addVideoEvtListener(video) {
this.video = video;
this.setVideoInfo(video);
this.removeVideoEvtListener();
for (const type of Object.keys(VideoListenerHandler)) {
const handler = VideoListenerHandler[type];
this.video.addEventListener(type, handler);
this.videoBoundListeners.push([this.video, type, handler]);
}
},
removeVideoEvtListener() {
this.videoBoundListeners.forEach((listener) => {
const [target, type, handler] = listener;
target.removeEventListener(type, handler);
});
this.videoBoundListeners = [];
},
healthCurrentVideo() {
if (this.healthID) clearInterval(this.healthID);
this.healthID = setInterval(() => this.getPlayingVideo(), ONE_SEC$1);
},
getPlayingVideo() {
const videos = Tools.querys("video");
for (const video of videos) {
const isWiderThanWindow = video.offsetWidth > window.innerWidth;
if (this.video === video || video.paused || !Tools.validDuration(video) || isWiderThanWindow) continue;
return this.addVideoEvtListener(video);
}
},
setVideoInfo(video) {
const videoInfo = { ...Tools.getElementCenterPoint(video), isLive: video.duration === Infinity };
this.setParentVideoInfo(videoInfo);
},
setParentVideoInfo(videoInfo) {
this.videoInfo = videoInfo;
if (Tools.isTopWin()) return this.setupScriptMenuCommand();
videoInfo.frameSrc = location.href;
Tools.postMessage(window.parent, { videoInfo });
},
changeVideoInfo(video) {
if (!this.videoInfo) return;
const isLive = video.duration === Infinity;
if (this.videoInfo.isLive === isLive) return;
this.videoInfo.isLive = isLive;
this.setParentVideoInfo(this.videoInfo);
},
setupMouseMoveListener() {
if (this.isSetupMouseMoveListener) return;
const delay = ONE_SEC$1 * 2;
this.isSetupMouseMoveListener = true;
let timer = setTimeout(this.showOrHideCursor, delay);
document.addEventListener("mousemove", (event) => {
if (!event.isTrusted) return;
clearTimeout(timer);
const target = event.target;
this.showOrHideCursor(false);
timer = setTimeout(this.showOrHideCursor, delay);
if (this.video === target || !(target instanceof HTMLVideoElement)) return;
this.addVideoEvtListener(target);
});
},
showOrHideCursor(isHide = true) {
Tools.querys(":is(video, iframe)").forEach((ele) => {
isHide ? ele.style.cursor = "none" : ele.style.cursor = EMPTY$1;
});
if (!isHide) return;
const mouseleave = new MouseEvent("mouseleave");
Tools.querys("body *").forEach((ele) => ele.dispatchEvent(mouseleave));
},
showToast(content, duration = SHOW_TOAST_TIME) {
if (webSite.isDouyu()) douyu.addStyle();
const el = document.createElement("div");
if (content instanceof HTMLElement) el.appendChild(content);
if (Object.is(typeof content, typeof EMPTY$1)) el.textContent = content;
el.setAttribute("class", "showToast");
el.setAttribute("style", SHOW_TOAST_POSITION);
const videoParent = this.video?.parentElement;
const videoContainer = this.getVideoContainer();
const target = this.video === videoContainer ? videoParent : videoContainer;
Tools.query(".showToast", target)?.remove();
target?.appendChild(el);
setTimeout(() => {
el.style.opacity = 0;
setTimeout(() => el.remove(), ONE_SEC$1 / 3);
}, duration);
}
};
const setStorage = function(value) {
_GM_setValue(this.name, value);
};
const getStorage = function(defaultValue) {
return _GM_getValue(this.name, defaultValue);
};
const storage = {
CACHED_PLAY_RATE: Object.freeze({
name: "FENY_SCRIPTS_V_PLAYBACK_RATE",
set(value) {
localStorage.setItem(this.name, value);
},
get() {
return localStorage.getItem(this.name);
}
}),
PLAY_RATE_STEP: Object.freeze({
name: "PLAY_RATE_STEP",
set: setStorage,
get() {
return Number.parseFloat(getStorage.bind(this, 0.25)());
}
}),
CLOSE_PLAY_RATE: Object.freeze({
name: "CLOSE_PLAY_RATE",
set: setStorage,
get() {
return getStorage.bind(this, false)();
}
}),
VIDEO_FASTFORWARD_DURATION: Object.freeze({
name: "VIDEO_FASTFORWARD_DURATION",
set: setStorage,
get() {
return Number.parseInt(getStorage.bind(this, 30)());
}
}),
VIDEO_TIME_STEP: Object.freeze({
name: "VIDEO_TIME_STEP",
set: setStorage,
get() {
return Number.parseInt(getStorage.bind(this, 5)());
}
}),
CLOSE_AUTO_WEB_FULL_SCREEN: Object.freeze({
name: "CLOSE_AUTO_WEB_FULL_SCREEN",
set: setStorage,
get() {
return getStorage.bind(this, false)();
}
}),
OVERRIDE_KEYBOARD: Object.freeze({
name: "OVERRIDE_KEYBOARD",
set: setStorage,
get() {
return getStorage.bind(this, false)();
}
})
};
const eventCode = Object.freeze({
KeyA: "KeyA",
KeyD: "KeyD",
KeyF: "KeyF",
KeyN: "KeyN",
KeyP: "KeyP",
KeyS: "KeyS",
KeyZ: "KeyZ",
Space: "Space",
ArrowLeft: "ArrowLeft",
ArrowRight: "ArrowRight",
NumpadAdd: "NumpadAdd",
NumpadSubtract: "NumpadSubtract"
});
const { VIDEO_TIME_STEP: VIDEO_TIME_STEP$1, VIDEO_FASTFORWARD_DURATION: VIDEO_FASTFORWARD_DURATION$1 } = storage;
const { EMPTY, SYMBOL: SYMBOL$1, MSG_SOURCE } = constants;
const KeydownHandler = {
preventDefault(event) {
const overrideKey = [eventCode.Space, eventCode.ArrowLeft, eventCode.ArrowRight];
const isOverrideKey = this.isOverrideKeyboard() && overrideKey.includes(event.code);
const isNumberKey = Tools.isNumber(event.key) && !this.isClosedPlayRate();
if (!isNumberKey && !isOverrideKey) return;
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
},
setupKeydownListener() {
window.addEventListener("keyup", (event) => this.preventDefault(event), true);
window.addEventListener("keydown", (event) => this.keydownHandler.call(this, event), true);
window.addEventListener("message", (event) => {
const { data } = event;
if (!data?.source || !data.source.includes(MSG_SOURCE)) return;
if (data?.topWinInfo) this.topWinInfo = data.topWinInfo;
if (data?.defaultPlayRate) this.defaultPlayRate();
if (data?.videoInfo) return this.setParentVideoInfo(data.videoInfo);
this.processEvent(data);
});
},
keydownHandler(event) {
if (this.normalWebsite()) return;
let key = event.key.toUpperCase();
const { code, target, shiftKey } = event;
if (["INPUT", "TEXTAREA", "DEMAND-SEARCH-BOX"].includes(target.tagName)) return;
if (!Object.keys(eventCode).includes(code) && !Tools.isNumber(key)) return;
this.preventDefault(event);
if (eventCode.Space === code) key = eventCode.Space.toUpperCase();
if (shiftKey && eventCode.NumpadAdd === code) key = SYMBOL$1.MULTIPLY;
if (shiftKey && eventCode.NumpadSubtract === code) key = SYMBOL$1.DIVIDE;
if (!Tools.isTopWin() && (eventCode.KeyP === code || eventCode.KeyN === code)) {
return Tools.postMessage(window.top, { key });
}
this.processEvent({ key });
},
processEvent(data) {
if (!this.video) Tools.postMsgToFrames(data);
if (data?.key) this.execHotKeyActions(data.key);
},
execHotKeyActions(key) {
if (this.normalWebsite()) return;
const keyMapping = this.getKeyMapping();
if (keyMapping[key]) return keyMapping[key]();
if (Tools.isNumber(key)) this.setPlayRate(key);
},
getKeyMapping() {
return {
Z: () => this.defaultPlayRate(),
A: () => this.adjustPlayRate(SYMBOL$1.ADD),
S: () => this.adjustPlayRate(SYMBOL$1.SUBTRACT),
[SYMBOL$1.ADD]: () => this.adjustPlayRate(SYMBOL$1.ADD),
[SYMBOL$1.DIVIDE]: () => this.adjustPlayRate(SYMBOL$1.DIVIDE),
[SYMBOL$1.SUBTRACT]: () => this.adjustPlayRate(SYMBOL$1.SUBTRACT),
[SYMBOL$1.MULTIPLY]: () => this.adjustPlayRate(SYMBOL$1.MULTIPLY),
N: () => webSite.inMatches() ? this.triggerIconElement("next") : this.switchNextEpisode(),
F: () => webSite.isDouyu() ? douyu.getFullIcon().click() : this.triggerIconElement("full", 0),
D: () => webSite.isDouyu() ? douyu.getDanmakuIcon().click() : this.triggerIconElement("danmaku", 3),
ARROWLEFT: () => this.isOverrideKeyboard() ? this.adjustVideoTime(SYMBOL$1.SUBTRACT) : null,
ARROWRIGHT: () => this.isOverrideKeyboard() ? this.adjustVideoTime() : null,
0: () => this.adjustVideoTime(VIDEO_FASTFORWARD_DURATION$1.get()),
P: () => {
if (!webSite.inMatches()) return this.enhance();
if (webSite.isDouyu()) return douyu.getWebfullIcon().click();
webSite.isBiliLive() ? this.biliLiveWebFullScreen() : this.triggerIconElement("webfull");
},
SPACE: () => {
if (!this.video || !this.isOverrideKeyboard()) return;
if (webSite.isDouyu()) return this.video.paused ? douyu.play() : douyu.pause();
this.video.paused ? this.video.play() : this.video.pause();
}
};
},
triggerIconElement(name, index) {
if (!webSite.inMatches()) return;
if (webSite.isBiliLive()) return this.getBiliLiveIcons()?.[index]?.click();
Tools.query(selectorConfig[location.host]?.[name])?.click();
Tools.triggerMousemove(this.video);
},
adjustVideoTime(second = VIDEO_TIME_STEP$1.get(), _symbol) {
if (!this.video || !Tools.validDuration(this.video)) return;
if (_symbol && ![SYMBOL$1.ADD, SYMBOL$1.SUBTRACT].includes(_symbol)) return;
if (Object.is(typeof second, typeof EMPTY) && !_symbol) {
_symbol = second;
second = VIDEO_TIME_STEP$1.get();
}
second = Object.is(SYMBOL$1.SUBTRACT, _symbol) ? -second : second;
const currentTime = this.video.currentTime + second;
this.video.currentTime = Math.max(0, currentTime);
}
};
const {
PLAY_RATE_STEP: PLAY_RATE_STEP$1,
CLOSE_PLAY_RATE,
VIDEO_TIME_STEP,
OVERRIDE_KEYBOARD,
CLOSE_AUTO_WEB_FULL_SCREEN,
VIDEO_FASTFORWARD_DURATION
} = storage;
const CLOSE_OTHER_WEBSITES_AUTO = "CLOSE_OTHER_WEBSITES_AUTO_";
const MenuCommandHandler = {
isClosedPlayRate: () => CLOSE_PLAY_RATE.get(),
isOverrideKeyboard: () => OVERRIDE_KEYBOARD.get(),
isClosedAuto: () => CLOSE_AUTO_WEB_FULL_SCREEN.get(),
isClosedOtherWebsiteAuto() {
const host = Tools.isTopWin() ? location.host : this.topWinInfo.host;
return _GM_getValue(CLOSE_OTHER_WEBSITES_AUTO + host, true);
},
setupScriptMenuCommand() {
if (!Tools.isTopWin()) return;
this.registerMenuCommand();
this.setupCommandChangeListener();
const topWinInfo = this.topWinInfo = { innerWidth, host: location.host };
Tools.postMsgToFrames({ topWinInfo });
},
registerMenuCommand() {
this.registerClosePlayRate();
this.registerPlayRateCommand();
this.registerVideoTimeCommand();
this.registerFastforwardCommand();
this.registerCloseAutoFullCommand();
this.registerOverrideKeyboardCommand();
this.registerCloseAutoExperimentCommand();
},
setupCommandChangeListener() {
if (this.isSetupCommandChangeListener) return;
const handler = () => this.registerMenuCommand();
[
CLOSE_PLAY_RATE.name,
OVERRIDE_KEYBOARD.name,
CLOSE_AUTO_WEB_FULL_SCREEN.name,
CLOSE_OTHER_WEBSITES_AUTO + window.top.location.host
].forEach((key) => _GM_addValueChangeListener(key, handler));
this.isSetupCommandChangeListener = true;
},
registerClosePlayRate() {
const isClose = this.isClosedPlayRate();
const title = isClose ? "启用倍速功能" : "禁用倍速功能";
_GM_unregisterMenuCommand(this.close_play_rate_command_id);
if (this.isLive()) return;
this.close_play_rate_command_id = _GM_registerMenuCommand(title, () => {
CLOSE_PLAY_RATE.set(!isClose);
if (!isClose) Tools.postMessage(window, { defaultPlayRate: true });
});
},
registerPlayRateCommand() {
const title = "设置倍速步进";
_GM_unregisterMenuCommand(this.play_rate_command_id);
if (this.isLive() || this.isClosedPlayRate()) return;
this.play_rate_command_id = _GM_registerMenuCommand(title, () => {
const input = prompt(title, PLAY_RATE_STEP$1.get());
if (!isNaN(input) && Number.parseFloat(input)) PLAY_RATE_STEP$1.set(input);
});
},
registerVideoTimeCommand() {
_GM_unregisterMenuCommand(this.video_time_command_id);
if (this.isLive() || !this.isOverrideKeyboard()) return;
const title = "设置快进/退秒数";
this.video_time_command_id = _GM_registerMenuCommand(title, () => {
const input = prompt(title, VIDEO_TIME_STEP.get());
if (!isNaN(input) && Number.parseInt(input)) VIDEO_TIME_STEP.set(input);
});
},
registerFastforwardCommand() {
_GM_unregisterMenuCommand(this.fastforward_command_id);
if (this.isLive()) return;
const title = "设置零键快进秒数";
this.fastforward_command_id = _GM_registerMenuCommand(title, () => {
const input = prompt(title, VIDEO_FASTFORWARD_DURATION.get());
if (!isNaN(input) && Number.parseInt(input)) VIDEO_FASTFORWARD_DURATION.set(input);
});
},
registerCloseAutoFullCommand() {
if (!webSite.inMatches()) return;
const isClose = this.isClosedAuto();
const title = isClose ? "启用自动网页全屏" : "禁用自动网页全屏";
_GM_unregisterMenuCommand(this.close_auto_command_id);
this.close_auto_command_id = _GM_registerMenuCommand(title, () => CLOSE_AUTO_WEB_FULL_SCREEN.set(!isClose));
},
registerOverrideKeyboardCommand() {
const isOverride = this.isOverrideKeyboard();
const title = isOverride ? "禁用 空格 ◀▶ 键控制" : "启用 空格 ◀▶ 键控制";
_GM_unregisterMenuCommand(this.override_keyboard_command_id);
this.override_keyboard_command_id = _GM_registerMenuCommand(title, () => OVERRIDE_KEYBOARD.set(!isOverride));
},
registerCloseAutoExperimentCommand() {
if (webSite.inMatches()) return;
const videos = Array.from(Tools.querys("video")).filter((video) => !isNaN(video));
if (videos.length > 1) return;
const isClose = this.isClosedOtherWebsiteAuto();
const title = isClose ? "此站点启用自动网页全屏" : "此站点禁用自动网页全屏";
_GM_unregisterMenuCommand(this.close_experiment_command_id);
this.close_experiment_command_id = _GM_registerMenuCommand(title, () => {
_GM_setValue(CLOSE_OTHER_WEBSITES_AUTO + location.host, !isClose);
});
}
};
const { ONE_SEC } = constants;
const WebSiteLoginHandler = {
webSiteLoginObserver() {
this.handleIqyLogin();
this.handleBiliLogin();
this.handleTencentLogin();
},
loginObserver(target, matches2, clickTarget) {
return Tools.createObserver(target, (mutations, observer) => {
mutations.forEach((mutation) => {
if (mutation.addedNodes.length === 0) return;
mutation.addedNodes.forEach((node) => {
if (node.nodeType !== Node.ELEMENT_NODE) return;
if (!node.matches(matches2)) return;
Tools.query(clickTarget)?.click();
observer.disconnect();
});
});
});
},
handleTencentLogin() {
if (!webSite.isTencent()) return;
const selector = ".main-login-wnd-module_close-button__mt9WU";
this.loginObserver("#login_win", selector, selector);
},
handleIqyLogin() {
if (!webSite.isIqiyi()) return;
const selector = ".simple-buttons_close_btn__6N7HD";
this.loginObserver("#qy_pca_login_root", selector, selector);
Tools.createObserver(".cd-time", () => {
const selector2 = ":is([id*='mask-layer'], #modal-vip-cashier-scope)";
Tools.querys(selector2).forEach((el) => el.remove());
Tools.query(".simple-buttons_close_btn__6N7HD")?.click();
const adTime = Tools.query(".public-time");
if (adTime.style.display === "none") return;
if (this.video.currentTime !== this.video.duration) return;
Tools.querys("*:not(.public-vip)", adTime).forEach((el) => el.click());
});
},
handleBiliLogin() {
if (!webSite.isBili()) return;
if (document.cookie.includes("DedeUserID")) return _unsafeWindow.player?.requestQuality(80);
setTimeout(() => {
_unsafeWindow.__BiliUser__.isLogin = true;
_unsafeWindow.__BiliUser__.cache.data.isLogin = true;
_unsafeWindow.__BiliUser__.cache.data.mid = Date.now();
}, ONE_SEC * 3);
}
};
const WebFullScreenHandler = {
webFullScreen(video) {
const w = video?.offsetWidth || 0;
if (Object.is(0, w)) return false;
if (this.isClosedAuto()) return true;
if (w >= window.innerWidth) return true;
if (!webSite.isBiliLive()) return Tools.triggerClick(this.element);
return this.biliLiveWebFullScreen();
},
biliLiveWebFullScreen() {
const control = this.getBiliLiveIcons();
if (control.length === 0) return false;
Tools.scrollTop(70);
const el = Tools.query(":is(.lite-room, #player-ctnr)", _unsafeWindow.top.document);
if (el) Tools.scrollTop(Tools.getElementRect(el)?.top || 0);
return Tools.triggerClick(control[1]);
},
exitWebFullScreen() {
if (window.innerWidth === this.video.offsetWidth) this.getElement()?.click();
const cancelButton = Tools.query(".bpx-player-ending-related-item-cancel");
if (cancelButton) setTimeout(() => cancelButton.click(), 100);
},
biliLiveExtras() {
_unsafeWindow.top?.livePlayer?.volume(100);
_unsafeWindow.top?.livePlayer?.switchQuality("10000");
localStorage.setItem("FULLSCREEN-GIFT-PANEL-SHOW", 0);
document.body.classList.add("hide-asida-area", "hide-aside-area");
},
getBiliLiveIcons() {
const video = this.getVideo();
if (!video) return [];
Tools.triggerMousemove(video);
return Tools.querys("#web-player-controller-wrap-el .right-area .icon");
},
experimentWebFullScreen(video) {
if (webSite.inMatches() || video.isWebFullScreen || !this.topWinInfo || this.isClosedOtherWebsiteAuto()) return;
if (video.offsetWidth === this.topWinInfo.innerWidth) return video.isWebFullScreen = true;
window.top.focus();
video.isWebFullScreen = true;
Tools.postMessage(window.top, { key: "P" });
}
};
const SwitchEpisodeHandler = {
switchPrevEpisode: function() {
this.handleEpisodeChange(this.getPrevEpisode);
},
switchNextEpisode: function() {
this.handleEpisodeChange(this.getNextEpisode);
},
handleEpisodeChange(getTargetEpisode) {
if (!Tools.isTopWin()) return;
let curEpisode = this.getCurrentEpisode();
if (!curEpisode) curEpisode = this.getCurrentEpisodeFromAllLink();
const targetEpisode = getTargetEpisode.call(this, curEpisode);
this.jumpToEpisodeNumber(targetEpisode);
},
getCurrentEpisode() {
const ele = this.getCurrentEpisodeLinkElement();
return this.getCurrentEpisodeContainer(ele);
},
getCurrentEpisodeLinkElement() {
const href = location.href;
const path = location.pathname;
const lastPath = path.substring(path.lastIndexOf("/") + 1);
const links = Array.from(Tools.querys(`:is(a[href*="${path}"], a[href*="${lastPath}"])`));
if (links.length == 1) return links.shift();
const filter = [
"h1",
"header",
"[id*='history']",
"[class*='lishi']",
"[class*='record']",
"[class*='history']",
"[class*='tab-item']"
];
return links.find((link) => {
const linkUrl = new URL(link.href);
if (Tools.closest(link, `:is(${filter})`, 5)) return false;
if (!Object.is(href, link.href) && !Object.is(path, linkUrl.pathname)) return false;
return !!this.getEpisodeNumber(link);
});
},
getEpisodeNumber(element) {
return Tools.extractNumbers(element.innerText).shift();
},
getPrevEpisode(element) {
const curNumber = this.getEpisodeNumber(element);
return this.getEpisodeNumberContainer(element, curNumber - 1);
},
getNextEpisode(element) {
const curNumber = this.getEpisodeNumber(element);
return this.getEpisodeNumberContainer(element, curNumber + 1);
},
getEpisodeNumberContainer(element, targetNumber) {
const allEpisode = this.getAllEpisodeElement(element);
for (const episode of allEpisode) {
if (element === episode) continue;
const episodeNumber = Tools.extractNumbers(episode.innerText).shift();
if (targetNumber === episodeNumber) return episode;
}
},
getAllEpisodeElement(element) {
const tagName = element.tagName;
const sibling = Tools.findSiblingInParent(element, tagName);
const children = Array.from(sibling.parentElement.children);
return children.filter((ele) => ele.tagName === tagName);
},
jumpToEpisodeNumber(element) {
const stack = [element];
while (stack.length > 0) {
const currentElement = stack.pop();
if (!(currentElement instanceof HTMLElement)) continue;
if (currentElement instanceof HTMLAnchorElement) {
currentElement.click();
}
const children = Array.from(currentElement.children).reverse();
for (const child of children) {
stack.push(child);
}
}
},
getCurrentEpisodeContainer(element) {
while (element) {
const parentEle = element.parentElement;
const hasLink = Tools.querys(element.tagName, parentEle).filter((el) => el !== element);
const hasSiblings = Tools.hasSiblings(element);
if (hasSiblings && !!hasLink.length) return element;
element = parentEle;
}
return element;
},
getCurrentEpisodeFromAllLink() {
const path = location.pathname;
const lastPath = path.substring(path.lastIndexOf("/") + 1);
const curNumber = [...Tools.extractNumbers(lastPath)].join("");
for (const link of Tools.querys("a")) {
const attrs = link.attributes;
for (const attr of attrs) {
const attrNumbers = [...Tools.extractNumbers(attr.value)].join("");
if (attrNumbers === curNumber) return link;
}
}
}
};
const ScriptsEnhanceHandler = {
enhance() {
if (!this.videoInfo) return;
const ele = this.getHoverElement();
Tools.triggerHoverEvent(ele);
Tools.triggerEscapeEvent();
this.backupTrigger();
},
backupTrigger() {
if (!this.video || this.videoInfo.frameSrc) return;
let oldWidth = this.video.oldWidth;
let newWidth = this.video.offsetWidth;
if (!Object.is(oldWidth, newWidth)) return;
Tools.query("#playerControlBtn")?.click();
if (!Object.is(newWidth, this.video.offsetWidth)) return;
Tools.query("#playerControlBtn")?.click();
},
getHoverElement() {
if (this.video) return this.getVideoContainer();
const iframe = this.getVideoIframe();
if (iframe) return iframe;
const { centerX, centerY } = this.videoInfo;
const iframes = Tools.getFrames();
for (const element of iframes) {
if (!Tools.isVisible(element)) continue;
if (Tools.isPointInElementRect(centerX, centerY, element)) return element;
}
},
getVideoContainer() {
const video = this.video;
video.oldWidth = video.offsetWidth;
const parentEle = video?.parentElement;
const control = this.getVideoControls(video);
const player = this.getVideoPlayer(parentEle);
const videoContainer = player || control?.parentElement;
if (!videoContainer) return video;
if (this.videoInfo.frameSrc) return videoContainer;
const videoWidth = video.offsetWidth;
const wrapWidth = videoContainer.offsetWidth;
return wrapWidth !== videoWidth ? video : videoContainer;
},
getVideoPlayer(target) {
const selector = [
".video-js",
'[class*="wrap"]',
'[class*="video"]',
'[class*="Player"]',
'[class*="player"]',
'[aria-label="视频播放器"]',
'[aria-label="Video Player"]'
];
return Tools.closest(target, `:is(${selector})`);
},
getVideoControls(element) {
return Tools.findSiblingInParent(element, ['[class*="Control"]', '[class*="control"]']);
}
};
const { PLAY_RATE_STEP, CACHED_PLAY_RATE } = storage;
const { SYMBOL, DEF_PLAY_RATE, MAX_PLAY_RATE } = constants;
const strategy = {
[SYMBOL.MULTIPLY]: (playRate) => playRate * 2,
[SYMBOL.DIVIDE]: (playRate) => playRate / 2,
[SYMBOL.ADD]: (playRate) => playRate + PLAY_RATE_STEP.get(),
[SYMBOL.SUBTRACT]: (playRate) => playRate - PLAY_RATE_STEP.get()
};
const VideoPlaybackRateHandler = {
checkUsable() {
if (!this.video) return false;
if (webSite.isLivePage()) return false;
if (this.isClosedPlayRate()) return false;
if (!Tools.validDuration(this.video)) return false;
return true;
},
toFixed(playRate) {
playRate = Number.parseFloat(playRate);
return playRate.toFixed(2).replace(/\.?0+$/, "");
},
setPlayRate(playRate, show = true) {
if (!this.checkUsable()) return;
this.video.playbackRate = this.toFixed(playRate);
if (show) this.playRateToast();
this.cachePlayRate();
},
adjustPlayRate(_symbol) {
if (!this.checkUsable()) return;
let playRate = this.video.playbackRate;
playRate = strategy[_symbol](playRate);
playRate = Math.max(PLAY_RATE_STEP.get(), playRate);
playRate = Math.min(MAX_PLAY_RATE, playRate);
this.setPlayRate(playRate);
},
defaultPlayRate() {
if (!this.video) return;
this.video.playbackRate = DEF_PLAY_RATE;
if (this.isClosedPlayRate()) return;
this.cachePlayRate();
this.showToast("已恢复正常倍速播放");
},
currVideoUseCachePlayRate(video) {
if (this.isClosedPlayRate()) return;
if (!webSite.isIqiyi()) video.isToastShown = false;
const playRate = this.getCachePlayRate();
if (video.playbackRate === playRate) return;
this.setPlayRate(playRate, !video.isToastShown);
video.isToastShown = true;
},
cachePlayRate() {
CACHED_PLAY_RATE.set(this.video.playbackRate);
},
getCachePlayRate: () => Number.parseFloat(CACHED_PLAY_RATE.get() || DEF_PLAY_RATE),
playRateToast() {
const span = document.createElement("span");
span.appendChild(document.createTextNode("正在以"));
const child = span.cloneNode(true);
child.textContent = `${this.video.playbackRate}x`;
child.setAttribute("style", "margin:0 3px!important;color:#ff6101!important;");
span.appendChild(child);
span.appendChild(document.createTextNode("倍速播放"));
this.showToast(span);
}
};
const logicHandlers = [
{ handler: KeydownHandler },
{ handler: MenuCommandHandler },
{ handler: WebSiteLoginHandler },
{ handler: WebFullScreenHandler },
{ handler: SwitchEpisodeHandler },
{ handler: VideoPlaybackRateHandler },
{ handler: ScriptsEnhanceHandler }
];
logicHandlers.forEach(({ handler }) => {
for (const key of Object.keys(handler)) {
const method = handler[key];
method instanceof Function ? App[key] = method.bind(App) : App[key] = method;
}
});
App.init();
_unsafeWindow.MONKEY_WEB_FULLSCREEN = App;
})();