Greasy Fork is available in English.
快捷键:P-网页全屏,Enter-全屏;支持侧边点击切换网页全屏;支持自动网页全屏
当前为
// ==UserScript==
// @name 视频网页全屏
// @namespace npm/vite-plugin-monkey
// @version 3.8.4
// @author Feny
// @description 快捷键:P-网页全屏,Enter-全屏;支持侧边点击切换网页全屏;支持自动网页全屏
// @license GPL-3.0-only
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAABNNJREFUeF7tm09oHFUcx79vNtkElgQDwYMgaUJulVhIwXoIFAvtQRAPXnopQXZfchD00ksPKoh4KUQJksx7C4UUihREcvFQhB70UMmuYhXBGMRDmrAJ+Wf+2MSwP/kNb5fNZrPzkt3ZzCT7IIfszve93+/z+/NmZmcEzvkQ59x/HAIgpXwI4AoRrRHR9XQ6nWNIUsonAF6zAHZfKXWLj0smk72O4/xloQER3dZa3zW6m47jPLDRAbihlHrEx46MjHxORO9b6DYB/EJE6gAAKSWViR8ppW7wZ8PDwy/E4/E/ALzot4AQ4q7rureNUdeI6Ds/DX9PRLe01vcN8DsAPrXQ5WKx2PWJiYmnRjcN4C0LnXdIEYCU8ihhMaKpVOqSEOJnm8lLIyqlTALQNrrSiEopWcPaqoOInra3tw+Nj4//YyD8CuAVP105gGcAXqokKo2olPJtAN/YTF4WUY4mR9Vv5IQQQ67r/mmc4ey55icCUMzW0dHRC/l8/jcACT9daQaUp/8BbVlEPwAw5je5+X5IKfWDKYcHRHTTT8cRFUJcVkr9Z0rvJwC9fjoApf3nquM4j/001gBMjb6jtf7aOGPbcHL5fP5yOp2eNxG1babFiB6n9EqzNZVKDQsh7lWDcCwAPJFxJmucsWo4HFGt9ausMen5o00zLY3ocUoPwHtKqS+NjR8D+OgoCMcGACD3/Pnzi1NTUysmPb+3bDjFiCaTSav0NEZ/opT60DhjXXpCiDdd1/3WZOs9Ihqu2N8KH1bYAo/MnNKImvTkGvdtOACUUmqEJ7ZJz4IBQoh3Xdf1UllKyb2HQfgOIcRF13V/N+s9FkJcLRedJAO8OYhoWmvNOwIbdZyd4Y7W+jOb9Cwz9g2llNfUpJS8C3lr+4xcIpHoHRsb+9eUHusvlGpODMBA+EJr7UVDSmmdnrwTaK2/8kvPcuccx+mdnJz825QeO3PJj4AQ4onruq/zcZVKr3kt4EfwrH9fzICZmZlDDeKsO8/+FQFkMpmqZ4JnFUYTQCGyzQxolkCzBzSb4Fnt9NX8au4CjdoFOjo6EI/HEYvFsLW1hZ2dnVAkXOAZ0NLSgv7+fiQSB6+Wt7e3kcvlsLa2dqogAgfQ19eHrq6uI51kAAyCgZzGCBzAwMAAWltbfX1jCEtLS9jb2/M9tp4HBA5gcHDQ2l52niEwjEaNUAEoON3I/hBKAAUQjegPoQZQABFkf4gEAAYRVH+IDICg+kPkABRALC4uYmFhoebNIrIA2PN6QIg0gM3NTczOztaUBZEGwJ5ns97vtCcekQawsbGBubm5EzvPwkgDOLc9YH9/H/Pz81hZWakp+pHMgOXlZe9iaXd3t2bnIwVgfX3du1Lkzl/PEfoewLfOOOKrq6v19Ls4V2gBcJ2z4/xHFNwd+1ACqHedV0udUAEIqs5PFYDNPcGg6/xUAVS7K9yoOj9VALx4T08Puru7i3bk83nvJKae+/lJt4jAe0DBsM7OTrS1tXn/8r0+jn4YRsMAhMHZSjY0ARSoNB+RaT4i03xEJrgT7rB2wEbcEQqx755pxV0gm80+I6KKL02F3Yla7Ct9VnhaCGH9vl0ti4ZJe+Bx+fO4FR56XyCTyTwkoitCiJfDFKk621L51dk6LxKJ6c79GyP/A7T+4JsF5qmXAAAAAElFTkSuQmCC
// @match *://*/*
// @grant GM_addStyle
// @grant GM_addValueChangeListener
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_unregisterMenuCommand
// @grant unsafeWindow
// @run-at document-start
// ==/UserScript==
(n=>{const o=Symbol("styleAdded"),t=document.createElement("style");t.textContent=n,window.gmStyle=t,document.addEventListener("addStyle",r=>{const{shadowRoot:e}=r.detail;e[o]||e instanceof Document||(e.prepend(t.cloneNode(!0)),e[o]=!0)}),(GM_addStyle??(()=>document.head.append(t.cloneNode(!0))))(n)})(' @charset "UTF-8";::part(webFullscreen),[part*=webFullscreen],body[part*=webFullscreen] [part*=webFullscreen]{top:0!important;left:0!important;margin:0!important;padding:0!important;zoom:normal!important;border:none!important;width:100vw!important;height:100vh!important;position:fixed!important;transform:none!important;max-width:none!important;max-height:none!important;border-radius:0!important;transition:none!important;z-index:2147483646!important;background-color:#000!important;flex-direction:column!important;overflow:hidden!important;display:flex!important}[part*=webFullscreen]~*:not(.monkey-web-fullscreen){display:none!important}[part*=webFullscreen] video,body[part*=webFullscreen] [part*=webFullscreen] video{top:0!important;left:0!important;width:100vw!important;border:none!important;height:clamp(100vh - 100%,100vh,100%)!important;object-fit:contain!important}.video-edge-click{cursor:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAAAXNSR0IArs4c6QAAAaBJREFUSEutlL8vBEEcxT9fpxSdVtQkGqqrHP8CoqDXSZTC7VpcRa3SCBJEclcgEQmNShDFKVRXKSj8atmv7Gbvsrd2Zydhus2+75v33rwZwXKpx0UAlTIlmxGxAQUY9dCI2GrGCvRn4tCu8CJLTCadmBTrCgcoPfGY2hRHgAmEwyR5FnHWzK8osoDq0hdm7NJoujEJSc24NdDJgCzwkHbAWqGfL+pp7kIBWa1QjyEpc2NqjQlj3QrbWjZxucQty3CHsMU3u+LylreRRDdqJAa8xmdaXB7D/rp00cFngmgPZUccTrM2SCNu4FNKnP42ykwKSdCQbaAqZe7i/3OjCFWvUsTnKsf+GUKVb2ri8mRFHJIvc48wmJct8AHMZdetQn+8w+oxC2xaEAeQdfMFgeFml7VCD188G4hfgRpKVRxq1lc6euECxYHy+LpEOKHAcdyh9SMU5TyGcN5GqyyKw1rSSTux4dlsPTzLXCEUo+93fEbF5dZIbHMw6jEPbIRY5UgcxtPmrOvWUuzQS4E60IUyJQ77/0IcZe0C3eKE6lPXDznkqgSwYj+tAAAAAElFTkSuQmCC),pointer!important;left:0!important;top:6%!important;width:25px!important;height:70%!important;position:absolute!important;z-index:2147483647!important;background-color:transparent!important;user-select:none!important;opacity:0!important}.video-edge-click.right{right:0!important;left:auto!important} ');
(function () {
'use strict';
const isElement = (node) => node instanceof Element;
const isDocument = (node) => node instanceof Document;
const getSRoot = (node) => node?._shadowRoot ?? node?.shadowRoot ?? null;
function* getShadowRoots(node, deep = false) {
if (!node || !isElement(node) && !isDocument(node)) return;
if (isElement(node) && getSRoot(node)) yield getSRoot(node);
const doc = isDocument(node) ? node : node.getRootNode({ composed: true });
if (!doc.createTreeWalker) return;
let currentNode;
const toWalk = [node];
while (currentNode = toWalk.pop()) {
const walker = doc.createTreeWalker(currentNode, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_DOCUMENT_FRAGMENT, {
acceptNode: (child) => isElement(child) && getSRoot(child) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
});
let walkerNode = walker.nextNode();
while (walkerNode) {
const shadowRoot = getSRoot(walkerNode);
if (isElement(walkerNode) && shadowRoot) {
if (deep) toWalk.push(shadowRoot);
yield shadowRoot;
}
walkerNode = walker.nextNode();
}
}
return;
}
function querySelector(selector, subject = document) {
const immediate = subject.querySelector(selector);
if (immediate) return immediate;
const shadowRoots = [...getShadowRoots(subject, true)];
for (const root of shadowRoots) {
const match = root.querySelector(selector);
if (match) return match;
}
return null;
}
function querySelectorAll(selector, subject = document) {
const results = [...subject.querySelectorAll(selector)];
const shadowRoots = [...getShadowRoots(subject, true)];
for (const root of shadowRoots) {
results.push(...root.querySelectorAll(selector));
}
return results;
}
const Consts = Object.freeze({
P: "P",
EMPTY: "",
HALF_SEC: 500,
ONE_SEC: 1e3,
webFull: "webFullscreen",
MSG_SOURCE: "SCRIPTS_VIDEO_FULLSCREEN"
});
const Tools = {
isTopWin: () => window.top === window,
scrollTop: (top) => window.scrollTo({ top }),
getElementRect: (el) => el?.getBoundingClientRect(),
microTask: (callback) => Promise.resolve().then(callback),
query: (selector, context) => querySelector(selector, context),
querys: (selector, context) => querySelectorAll(selector, context),
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
postMessage: (win, data) => win?.postMessage({ source: Consts.MSG_SOURCE, ...data }, "*"),
getIFrames: () => querySelectorAll("iframe:not([src=''], [src='#'], [id='buffer'], [id='install'])"),
isVisible: (el) => !!(el && getComputedStyle(el).visibility !== "hidden" && (el.offsetWidth || el.offsetHeight)),
preventDefault: (event) => event.preventDefault() & event.stopPropagation() & event.stopImmediatePropagation(),
emitEvent: (type, detail = {}) => document.dispatchEvent(new CustomEvent(type, { detail })),
isInputable: (el) => ["INPUT", "TEXTAREA"].includes(el?.tagName) || el?.isContentEditable,
sendToIFrames(data) {
this.getIFrames().forEach((iframe) => this.postMessage(iframe?.contentWindow, data));
},
freqTimes: /* @__PURE__ */ new Map(),
isThrottle(key = "throttle", gap = 300) {
const now = Date.now();
const last = this.freqTimes.get(key) ?? 0;
const diff = now - last;
return diff >= gap ? this.freqTimes.set(key, now) && false : true;
},
limitCountMap: /* @__PURE__ */ new Map(),
isOverLimit(key = "default", maxCount = 5) {
const count = this.limitCountMap.get(key) ?? 0;
if (count < maxCount) return this.limitCountMap.set(key, count + 1) && false;
return true;
},
resetLimit(...keys) {
const keyList = keys.length > 0 ? keys : ["default"];
keyList.forEach((key) => this.limitCountMap.set(key, 0));
},
getCenterPoint(element) {
if (!element) return { centerX: 0, centerY: 0 };
const { top, left, width, height } = this.getElementRect(element);
return { centerX: left + width / 2, centerY: top + height / 2 };
},
pointInElement(pointX, pointY, element) {
if (!element) return false;
const { top, left, right, bottom } = this.getElementRect(element);
return pointX >= left && pointX <= right && pointY >= top && pointY <= bottom;
},
getParent(element) {
if (!element) return null;
const parent = element.parentNode;
if (parent instanceof ShadowRoot) return parent.host;
return parent === document ? null : parent;
},
getParents(element, withSelf = false, maxLevel = Infinity) {
const parents = withSelf && element ? [element] : [];
for (let current = element, level = 0; current && level < maxLevel; level++) {
current = this.getParent(current);
current && parents.unshift(current);
}
return parents;
},
getParts: (node) => node.getAttribute("part")?.split(/\s+/) ?? [],
setPart(node, value) {
if (!isElement(node)) return;
node.setAttribute("part", [.../* @__PURE__ */ new Set([...this.getParts(node), value])].join(" "));
},
delPart(node, value) {
if (!isElement(node)) return;
const parts = this.getParts(node).filter((v) => v !== value);
node.setAttribute("part", parts.join(" "));
},
safeHTML(htmlStr) {
if (!window.trustedTypes?.createPolicy) return htmlStr;
const policy = trustedTypes.defaultPolicy ?? trustedTypes.createPolicy("default", { createHTML: (input) => input });
return policy.createHTML(htmlStr);
},
cloneAttrs(source, target, ...attrs) {
attrs.flat().forEach((attr) => {
const value = source.getAttribute(attr);
if (value) target.setAttribute(attr, value);
});
},
cloneStyle(source, target, ...names) {
const computedStyle = window.getComputedStyle(source);
names.flat().forEach((name) => {
const value = computedStyle.getPropertyValue(name);
if (value) target.style.setProperty(name, value);
});
},
setStyle(eles, prop, val, priority) {
if (!eles || !prop) return;
const fn = val ? "setProperty" : "removeProperty";
[].concat(eles).forEach((el) => el?.style?.[fn]?.(prop, val, priority));
},
isAttached(el) {
if (!el) return false;
const root = el.getRootNode?.();
return el.isConnected && (!root || !(root instanceof ShadowRoot) || root.host.isConnected);
}
};
class VideoEnhancer {
static hackAttachShadow() {
if (Element.prototype.__attachShadow) return;
Element.prototype.__attachShadow = Element.prototype.attachShadow;
Element.prototype.attachShadow = function(options) {
if (this._shadowRoot) return this._shadowRoot;
const shadowRoot = this._shadowRoot = this.__attachShadow.call(this, options);
VideoEnhancer.detectShadowVideoElement();
return shadowRoot;
};
Element.prototype.attachShadow.toString = () => Element.prototype.__attachShadow.toString();
}
static detectShadowVideoElement() {
if (Tools.isThrottle("shadow", 100)) return;
const videos = Tools.querys("video:not([received])");
if (!videos.length) return;
videos.forEach((video) => {
const root = video.getRootNode();
if (!(root instanceof ShadowRoot)) return;
Tools.emitEvent("shadow-video", { video });
Tools.emitEvent("addStyle", { shadowRoot: root });
});
}
}
VideoEnhancer.hackAttachShadow();
const Listen = {
noVideo: () => !window.videoInfo && !window.topWin,
isBackgroundVideo: (video) => video?.muted && video?.loop,
getVideo: () => Tools.querys(":is(video, fake-video):not([loop])").find(Tools.isVisible),
init(isNonFirst = false) {
this.body = document.body;
this.setupKeydownListener();
this.setupMouseMoveListener();
this.setupFullscreenListener();
this.setupVideoListeners();
if (isNonFirst) return;
this.setupDocumentObserver();
this.observeFullscreenChange();
this.setupIgnoreUrlsChangeListener();
this.setupShadowVideoListeners();
},
setupDocumentObserver() {
new MutationObserver(() => {
if (this.body === document.body) return;
this.init(true), document.head.append(gmStyle.cloneNode(true));
}).observe(document, { childList: true });
},
setupFullscreenListener() {
document.addEventListener("fullscreenchange", () => {
Tools.postMessage(window.top, { isFullscreen: !!document.fullscreenElement });
});
},
observeFullscreenChange() {
Object.defineProperty(this, "isFullscreen", {
get: () => this._isFullscreen ?? false,
set: (value) => {
this._isFullscreen = value;
!value && this.fsWrapper && this.dispatchShortcutKey(Consts.P);
}
});
},
setupMouseMoveListener() {
const handle = ({ type, clientX, clientY }) => {
if (Tools.isThrottle(type, 300)) return;
const video = this.getVideoForCoordinate(clientX, clientY);
video && this.createEdgeClickElement(video);
};
document.addEventListener("mousemove", handle, { passive: true });
},
getVideoForCoordinate(clientX, clientY) {
return Tools.querys("video").find((video) => Tools.pointInElement(clientX, clientY, video));
},
createEdgeClickElement(video) {
const container = this.getEdgeClickContainer(video);
if (video.lArea?.parentNode === container) return;
if (container instanceof Element && this.lacksRelativePosition(container)) {
Tools.setStyle(container, "position", "relative");
}
if (video.lArea) return container.prepend(video.lArea, video.rArea);
const createEdge = (clas = "") => {
const element = Object.assign(document.createElement("div"), { video, className: `video-edge-click ${clas}` });
element.onclick = (e) => {
Tools.preventDefault(e);
const vid = e.target.video;
if (this.player !== vid) this.player = vid, this.setVideoInfo(vid);
Tools.microTask(() => this.dispatchShortcutKey(Consts.P, true));
};
return element;
};
[video.lArea, video.rArea] = [createEdge(), createEdge("right")];
container.prepend(video.lArea, video.rArea);
},
getEdgeClickContainer(video) {
if (this.fsWrapper) return video.closest(`[part="${Consts.webFull}"]`) ?? this.fsWrapper;
const parentNode = video.parentNode;
const sroot = video.getRootNode() instanceof ShadowRoot;
return sroot ? parentNode : this.findVideoParentContainer(parentNode, void 0, false);
},
lacksRelativePosition(element) {
return Tools.getParents(element, true, 2).every((el) => el && getComputedStyle(el).position === "static");
}
};
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_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 Keydown = {
dispatchShortcutKey: (key, isTrusted = false) => Tools.postMessage(window.top, { key: key.toUpperCase(), isTrusted }),
setupKeydownListener() {
_unsafeWindow.addEventListener("message", ({ data }) => this.handleMessage(data));
_unsafeWindow.addEventListener("keydown", (event) => this.handleKeydown(event), true);
_unsafeWindow.addEventListener("scroll", () => this.fsWrapper && Tools.scrollTop(this.fsWrapper.scrollY));
},
handleKeydown(event) {
const { key, isTrusted } = event;
const target = event.composedPath()[0];
if (this.noVideo() || Tools.isInputable(target) || !["p", "Enter"].includes(key)) return;
Tools.preventDefault(event);
this.dispatchShortcutKey(key, isTrusted);
},
handleMessage(data) {
if (!data?.source?.includes(Consts.MSG_SOURCE)) return;
if (data?.videoInfo) return this.setParentWinVideoInfo(data.videoInfo);
if ("isFullscreen" in data) this.isFullscreen = data.isFullscreen;
if (data?.topWin) window.topWin = this.topWin = data.topWin;
this.processEvent(data);
},
processEvent(data) {
if (!this.player) Tools.sendToIFrames(data);
if (data?.key) this.execHotKeyActions(data);
},
execHotKeyActions: ({ key, isTrusted }) => key === Consts.P ? App.toggleWebFullscreen(isTrusted) : App.toggleFullscreen()
};
const Events = {
videoEvents: ["loadedmetadata", "timeupdate", "playing"],
setupVideoListeners(video) {
const handleEvent = (event) => this[event.type](video ?? event.target);
this.videoEvents.forEach((type) => (video ?? document).addEventListener(type, handleEvent, true));
},
setupShadowVideoListeners() {
document.addEventListener("shadow-video", (e) => {
const { video } = e.detail;
if (!video || video.hasAttribute("received")) return;
this.setupVideoListeners(video), video.setAttribute("received", true);
Tools.microTask(() => this.createEdgeClickElement(video));
if (!this.player) this.setCurrentVideo(video);
});
},
loadedmetadata(video) {
this.initVideoProps(video);
if (!this.player) this.playing(video);
},
timeupdate(video) {
if (isNaN(video.duration)) return;
this.autoWebFullscreen(video);
},
playing(video) {
this.setCurrentVideo(video);
},
initVideoProps(video) {
delete video.__isWide;
Tools.resetLimit("autoWide");
if (!Tools.isAttached(this.player)) delete this.player;
},
setCurrentVideo(video) {
if (!video || this.player === video || video.offsetWidth < 240 || this.isBackgroundVideo(video)) return;
if (this.player && !this.player.paused && !isNaN(this.player.duration)) return;
this.player = video;
this.setVideoInfo(video);
},
setVideoInfo(video) {
const videoInfo = { ...Tools.getCenterPoint(video), isLive: video.duration === Infinity };
this.setParentWinVideoInfo(videoInfo);
},
setParentWinVideoInfo(videoInfo) {
window.videoInfo = this.videoInfo = videoInfo;
if (!Tools.isTopWin()) return Tools.postMessage(window.parent, { videoInfo: { ...videoInfo, iframeSrc: location.href } });
Tools.microTask(() => this.setupScriptMenuCommand());
this.sendTopWinInfo();
},
sendTopWinInfo() {
const { host, href: url } = location;
const { innerWidth: viewWidth, innerHeight: viewHeight } = window;
const topWin = { url, host, viewWidth, viewHeight };
window.topWin = this.topWin = topWin;
Tools.sendToIFrames({ topWin });
}
};
class BasicStorage {
constructor(name, defVal, parser = (v) => v) {
Object.assign(this, { name, defVal, parser });
this.storage = { getItem: _GM_getValue, setItem: _GM_setValue };
}
#getFinalKey(suffix) {
if ([null, void 0].includes(suffix)) throw new Error("键名后缀不能为空");
return suffix.startsWith(this.name) ? suffix : this.name + suffix;
}
set(value, key) {
this.storage.setItem(this.#getFinalKey(key), value);
}
get(key) {
const value = this.storage.getItem(this.#getFinalKey(key));
return this.parser(value ?? this.defVal);
}
}
const Storage = {
IS_AUTO: new BasicStorage("IS_AUTO_", false, Boolean),
DETACH_THRESHOLD: new BasicStorage("DETACH_THRESHOLD_", 20, Number),
CUSTOM_CONTAINER: new BasicStorage("CUSTOM_CONTAINER_", ""),
IGNORE_URLS: new BasicStorage("IGNORE_URLS", "")
};
const WebFull = {
toggleFullscreen() {
if (!Tools.isTopWin() || Tools.isThrottle("toggleFull")) return;
this.isFullscreen ? document.exitFullscreen() : this.getVideoHostContainer()?.requestFullscreen();
if (this.isFullscreen || !this.fsWrapper) this.dispatchShortcutKey(Consts.P);
},
toggleWebFullscreen(isTrusted) {
if (this.noVideo() || Tools.isThrottle("toggleWeb")) return;
if (this.isFullscreen && isTrusted) return document.fullscreenElement && document.exitFullscreen();
this.fsWrapper ? this.exitWebFullscreen() : this.enterWebFullscreen();
},
enterWebFullscreen() {
const container = this.fsWrapper = this.getVideoHostContainer();
if (!container || container.matches(":is(html, body)")) return this.ensureWebFullscreen();
container.scrollY = window.scrollY;
const parents = Tools.getParents(container, true);
container instanceof HTMLIFrameElement || parents.length < Storage.DETACH_THRESHOLD.get(location.host) ? parents.forEach((el) => {
Tools.emitEvent("addStyle", { shadowRoot: el.getRootNode() });
Tools.setPart(el, Consts.webFull);
}) : this.detachForFullscreen();
this.ensureWebFullscreen();
},
detachForFullscreen() {
if (this.fsParent) return;
this.fsParent = Tools.getParent(this.fsWrapper);
this.fsPlaceholder = document.createElement("div");
Tools.cloneAttrs(this.fsWrapper, this.fsPlaceholder, ["id", "class", "style"]);
Tools.cloneStyle(this.fsWrapper, this.fsPlaceholder, ["position", "width", "height"]);
this.fsParent.replaceChild(this.fsPlaceholder, this.fsWrapper);
document.body.insertAdjacentElement("beforeend", this.fsWrapper);
this.fsWrapper.querySelector("video")?.play();
Tools.setPart(this.fsWrapper, Consts.webFull);
},
exitWebFullscreen() {
if (!this.fsWrapper) return;
const { scrollY } = this.fsWrapper;
Tools.setStyle(document.documentElement, "scroll-behavior", "auto", "important");
if (this.fsParent?.contains(this.fsPlaceholder)) this.fsParent?.replaceChild(this.fsWrapper, this.fsPlaceholder);
Tools.querys(`[part*=${Consts.webFull}]`).forEach((el) => Tools.delPart(el, Consts.webFull));
requestAnimationFrame(() => (Tools.scrollTop(scrollY), Tools.setStyle(document.documentElement, "scroll-behavior")));
this.videoParents.clear();
this.fsPlaceholder = this.fsWrapper = this.fsParent = null;
},
getVideoHostContainer() {
if (this.player) return this.getVideoContainer();
const videoIFrame = this.getVideoIFrame();
if (videoIFrame) return videoIFrame;
const ifrs = Tools.getIFrames();
const { centerX, centerY } = this?.videoInfo ?? {};
return ifrs.length <= 1 ? ifrs[0] : ifrs.find((el) => Tools.isVisible(el) && Tools.pointInElement(centerX, centerY, el));
},
getVideoIFrame() {
if (!this?.videoInfo?.iframeSrc) return null;
const { pathname, search } = new URL(this.videoInfo.iframeSrc);
const decoded = decodeURI(search);
const partial = decoded.slice(0, decoded.length * 0.8);
return Tools.query(`iframe[src*="${pathname + partial}"]`) ?? Tools.query(`iframe[src*="${pathname}"]`);
},
getVideoContainer() {
const selector = Storage.CUSTOM_CONTAINER.get(this.topWin?.host)?.trim();
const container = selector ? this.player.closest(selector) ?? Tools.query(selector) : null;
return container ?? this.findVideoParentContainer(this.findControlBarContainer());
},
findControlBarContainer() {
const ignore = ":not(.Drag-Control, .vjs-controls-disabled, .vjs-control-text, .xgplayer-prompt)";
const selector = `[class*="contr" i]${ignore}, [id*="control"], [class*="ctrl"], [class*="progress"], [class*="volume"]`;
let parent = Tools.getParent(this.player);
while (parent && parent.offsetHeight <= this.player.offsetHeight) {
if (Tools.query(selector, parent)) return parent;
parent = Tools.getParent(parent);
}
return null;
},
videoParents: /* @__PURE__ */ new Set(),
findVideoParentContainer(container, maxLevel = 4, track = true) {
container = container ?? Tools.getParent(this.player);
if (!container.offsetHeight) container = Tools.getParent(container);
const { offsetWidth: cw, offsetHeight: ch } = container;
if (track) this.videoParents.clear();
for (let parent = container, level = 0; parent && level < maxLevel; parent = Tools.getParent(parent), level++) {
if (parent.offsetWidth === cw && parent.offsetHeight === ch) container = parent;
if (this.hasExplicitlySize(parent)) return container;
if (track) this.videoParents.add(parent);
}
return container;
},
hasExplicitlySize(element) {
const style = element.style;
const sizeRegex = /^\d+(\.\d+)?(px|em|rem)$/;
return ["width", "height"].some((prop) => {
const value = style?.getPropertyValue(prop);
return value && sizeRegex.test(value);
});
},
ensureWebFullscreen() {
const { viewWidth, viewHeight } = this.topWin;
const elements = [...this.videoParents].reverse();
for (const element of elements) {
const { offsetWidth: width, offsetHeight: height } = this.player;
if (width === viewWidth && height === viewHeight && element.offsetHeight === viewHeight) continue;
Tools.setPart(element, Consts.webFull);
}
}
};
const Automatic = {
async autoWebFullscreen(video) {
if (!this.topWin || !video.offsetWidth || this.player !== video) return;
if (video.__isWide || Tools.isThrottle("autoWide", Consts.ONE_SEC) || !this.isAuto()) return;
if (this.isIgnoreUrl() || await this.isWebFull(video) || Tools.isOverLimit("autoWide")) return video.__isWide = true;
this.dispatchShortcutKey(Consts.P);
},
async isWebFull(video) {
const isWebFull = video.offsetWidth >= this.topWin.viewWidth;
if (!isWebFull) return false;
await Tools.sleep(Consts.HALF_SEC);
return video.offsetWidth >= this.topWin.viewWidth;
}
};
const Ignore = {
initIgnoreUrls: () => App.urlFilter = App.getIgnoreUrls(),
setupIgnoreUrlsChangeListener() {
_GM_addValueChangeListener(Storage.IGNORE_URLS.name, (_, oldVal, newVal) => oldVal !== newVal && this.initIgnoreUrls());
},
isIgnoreUrl() {
if (!this.urlFilter) this.initIgnoreUrls();
return this.isBlocked(this.urlFilter);
},
getIgnoreUrls() {
const urlsStr = Storage.IGNORE_URLS.get(this.topWin.host);
return urlsStr.split(/[;\n]/).filter((url) => url.trim());
},
isBlocked(urls = []) {
const { href, pathname } = new URL(this.topWin.url);
return pathname === "/" || urls.some((u) => href.startsWith(u));
}
};
const Menu = {
isAuto: () => Storage.IS_AUTO.get(Tools.isTopWin() ? location.host : window.topWin?.host),
setupScriptMenuCommand() {
if (this.hasMenu || !Tools.isTopWin()) return;
const key = Storage.IS_AUTO.name + location.host;
_GM_addValueChangeListener(key, () => this.registMenuCommand());
this.registMenuCommand();
this.hasMenu = true;
},
registMenuCommand() {
const host = location.host;
const isAuto = `此站${this.isAuto() ? "禁" : "启"}用自动网页全屏`;
const configs = [
{ title: isAuto, cache: Storage.IS_AUTO, fn: (cache, val) => cache.set(!val, host) },
{ title: "此站脱离式全屏阈值", cache: Storage.DETACH_THRESHOLD },
{ title: "自动时忽略的网址", cache: Storage.IGNORE_URLS },
{ title: "自定义视频容器", cache: Storage.CUSTOM_CONTAINER }
];
configs.forEach(({ title, cache, fn }) => {
const id = `${cache.name}_MENU_ID`;
_GM_unregisterMenuCommand(this[id]);
this[id] = _GM_registerMenuCommand(title, () => {
const value = cache.get(host);
if (fn) return fn.call(this, cache, value);
const input = prompt(title, value);
if (input !== null) cache.set(input, host);
});
});
}
};
window.App = {};
const handlers = [Listen, Keydown, Events, WebFull, Automatic, Ignore, Menu];
handlers.forEach((handler) => {
const entries = Object.entries(handler);
for (const [key, value] of entries) {
App[key] = value instanceof Function ? value.bind(App) : value;
}
});
App.init();
})();