您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
To force Low Resource
// ==UserScript== // @name YouTube: Plain Video Player // @namespace UserScripts // @match https://www.youtube.com/watch?* // @exclude /^https?://\S+\.(txt|png|jpg|jpeg|gif|xml|svg|manifest|log|ini)[^\/]*$/ // @grant none // @version 0.2.0 // @author CY Fung // @license MIT // @description To force Low Resource // @run-at document-start // @inject-into page // @unwrap // @license MIT // @compatible chrome // @compatible firefox // @compatible opera // @compatible edge // @compatible safari // @allFrames true // ==/UserScript== (() => { let enable = true; // for debug const observablePromise = (proc, timeoutPromise) => { let promise = null; return { obtain() { if (!promise) { promise = new Promise(resolve => { let mo = null; const f = () => { let t = proc(); if (t) { mo.disconnect(); mo.takeRecords(); mo = null; resolve(t); } } mo = new MutationObserver(f); mo.observe(document, { subtree: true, childList: true }) f(); timeoutPromise && timeoutPromise.then(() => { resolve(null) }); }); } return promise } } } const pp = { create() { }, setProperties() { } } let mm = new Proxy({}, { get(target, prop) { // throw SyntaxError(); // return pp; return true }, set(target, prop, val) { return true; }, configurable: true, enumerable: false }); const cssText = () => ` .container, #container, #masthead { visibility: collapse; display: none !important; } .container *, #container *, #masthead *{ margin:0 !important; padding:0 !important; border:0 !important; } :fullscreen #movie_player{ position:fixed; top:0; left:0; right:0; bottom:0; } #player.skeleton.flexy #player-wrap[id] { width:initial; } .ytp-next-button, .ytp-miniplayer-button /* , .ytp-size-button */ { display: none !important; } .ytp-pip-button{ display: inline-block !important; } `; addCSS = 0; enable && Object.defineProperty(Object.prototype, '__mixinSet', { get() { if (!addCSS) { addCSS = 1; // document.body.appendChild(document.createElement('ytd-watch-flexy')) document.head.appendChild(document.createElement('style')).textContent = cssText(); } let rr = window.onerror; window.onerror = function () { return true; } Promise.resolve().then(() => { window.onerror = rr }) throw new Error(''); return mm; }, set(nv) { return true; }, configurable: true, enumerable: false }) const addProtoToArr = (parent, key, arr) => { let isChildProto = false; for (const sr of arr) { if (parent[key].prototype instanceof parent[sr]) { isChildProto = true; break; } } if (isChildProto) return; arr = arr.filter(sr => { if (parent[sr].prototype instanceof parent[key]) { return false; } return true; }); arr.push(key); return arr; } const getKeys = (_yt_player) => { const w = 'Keys'; let arr = []; for (const [k, v] of Object.entries(_yt_player)) { const p = typeof v === 'function' ? v.prototype : 0; if (p && typeof p.isFullscreen === 'function' && p.isFullscreen.length === 0 && typeof p.getVisibilityState === 'function' // && typeof p.getAppState === 'function' ) { arr = addProtoToArr(_yt_player, k, arr) || arr; } } if (arr.length === 0) { console.warn(`Key does not exist. [${w}]`); } else { return arr; } } const cleanContext = async (win) => { const waitFn = requestAnimationFrame; // shall have been binded to window try { let mx = 16; // MAX TRIAL const frameId = 'vanillajs-iframe-v1'; /** @type {HTMLIFrameElement | null} */ let frame = document.getElementById(frameId); let removeIframeFn = null; if (!frame) { frame = document.createElement('iframe'); frame.id = frameId; const blobURL = typeof webkitCancelAnimationFrame === 'function' ? (frame.src = URL.createObjectURL(new Blob([], { type: 'text/html' }))) : null; // avoid Brave Crash frame.sandbox = 'allow-same-origin'; // script cannot be run inside iframe but API can be obtained from iframe let n = document.createElement('noscript'); // wrap into NOSCRPIT to avoid reflow (layouting) n.appendChild(frame); while (!document.documentElement && mx-- > 0) await new Promise(waitFn); // requestAnimationFrame here could get modified by YouTube engine const root = document.documentElement; root.appendChild(n); // throw error if root is null due to exceeding MAX TRIAL if (blobURL) Promise.resolve().then(() => URL.revokeObjectURL(blobURL)); removeIframeFn = (setTimeout) => { const removeIframeOnDocumentReady = (e) => { e && win.removeEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false); win = null; const m = n; n = null; setTimeout(() => m.remove(), 200); } if (document.readyState !== 'loading') { removeIframeOnDocumentReady(); } else { win.addEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false); } } } while (!frame.contentWindow && mx-- > 0) await new Promise(waitFn); const fc = frame.contentWindow; if (!fc) throw "window is not found."; // throw error if root is null due to exceeding MAX TRIAL const { requestAnimationFrame, setTimeout, cancelAnimationFrame, setInterval, clearInterval, requestIdleCallback, getComputedStyle } = fc; const res = { requestAnimationFrame, setTimeout, cancelAnimationFrame, setInterval, clearInterval, requestIdleCallback, getComputedStyle }; for (let k in res) res[k] = res[k].bind(win); // necessary if (removeIframeFn) Promise.resolve(res.setTimeout).then(removeIframeFn); res.animate = fc.HTMLElement.prototype.animate; return res; } catch (e) { console.warn(e); return null; } }; cleanContext(window).then(__CONTEXT__ => { if (!__CONTEXT__) return null; const { setTimeout } = __CONTEXT__; const isUrlInEmbed = location.href.includes('.youtube.com/embed/'); const isAbortSignalSupported = typeof AbortSignal !== "undefined"; const promiseForTamerTimeout = new Promise(resolve => { !isUrlInEmbed && isAbortSignalSupported && document.addEventListener('yt-action', function () { setTimeout(resolve, 480); }, { capture: true, passive: true, once: true }); !isUrlInEmbed && isAbortSignalSupported && typeof customElements === "object" && customElements.whenDefined('ytd-app').then(() => { setTimeout(resolve, 1200); }); setTimeout(resolve, 3000); }); (async () => { const _yt_player = await observablePromise(() => { return (((window || 0)._yt_player || 0) || 0); }, promiseForTamerTimeout).obtain(); const keys = getKeys(_yt_player); const selectionForJ0 = keys.map(k => { const p = _yt_player[k].prototype; let v = 0; if (!p.getAppState) v -= 900; if (!p.getInternalApi) v -= 400; if (!p.getApiInterface) v += 100; if (!p.getPlayerResponse) v += 100; return [k, v]; }); const selectionForQT = keys.map(k => { const p = _yt_player[k].prototype; let v = 0; if (!p.getAppState) v -= 900; if (!p.getInternalApi) v -= 400; if (p.getApiInterface) v += 100; if (p.getPlayerResponse) v += 100; return [k, v]; }); const keyJ0 = selectionForJ0.sort((a, b) => b[1] - a[1])[0][0]; const keyQT = selectionForQT.sort((a, b) => b[1] - a[1])[0][0]; if (keyJ0 && keyQT && keyJ0 !== keyQT) { let _visibility = null; document.addEventListener('fullscreenchange', () => { if (enable) { if (_visibility) _visibility.fullscreen = !!document.fullscreenElement ? 2 : 0; } }); document.addEventListener('enterpictureinpicture', () => { if (enable) { if (_visibility) _visibility.pictureInPicture = true; } }); document.addEventListener('leavepictureinpicture', () => { if (enable) { if (_visibility) _visibility.pictureInPicture = false; } }); // let pp = 0; const gkp = _yt_player[keyJ0].prototype; gkp.isFullscreen68 = gkp.isFullscreen; gkp.isFullscreen = function () { _visibility = this.visibility; // if (this.visibility && !pp) { // pp = 1; // console.log(3992, this.visibility) // } return this.isFullscreen68(); } } const ytpSizeBtn = await observablePromise(() => { return document.querySelector('.ytp-size-button'); }, promiseForTamerTimeout).obtain(); ytpSizeBtn.addEventListener('click', (evt) => { const player = document.querySelector('#player') player.classList.toggle('theater') player.classList.toggle('flexy') window.dispatchEvent(new Event('resize')); // document.body.dispatchEvent(new Event('resize')); evt.preventDefault(); evt.stopImmediatePropagation(); evt.stopPropagation(); }, true) })(); }); })();