您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
To make Quality Auto Max
// ==UserScript== // @name YouTube: Quality Auto Max // @namespace UserScripts // @match https://www.youtube.com/* // @version 0.3.2 // @author CY Fung // @license MIT // @description To make Quality Auto Max // @grant none // @run-at document-start // @unwrap // @inject-into page // // ==/UserScript== (() => { const Promise = (async () => { })().constructor; const PromiseExternal = ((resolve_, reject_) => { const h = (resolve, reject) => { resolve_ = resolve; reject_ = reject }; return class PromiseExternal extends Promise { constructor(cb = h) { super(cb); if (cb === h) { /** @type {(value: any) => void} */ this.resolve = resolve_; /** @type {(reason?: any) => void} */ this.reject = reject_; } } }; })(); const insp = o => o ? (o.polymerController || o.inst || o || 0) : (o || 0); const getResValue = (m) => { return m.width < m.height ? m.width : m.height } 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 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 getuU = (_yt_player) => { const w = 'uU'; let arr = []; let brr = new Map(); for (const [k, v] of Object.entries(_yt_player)) { const p = typeof v === 'function' ? v.prototype : 0; if (p) { let q = 0; if (typeof p.setPlaybackQualityRange === 'function' && p.setPlaybackQualityRange.length === 3) q += 200; if (typeof p.updateVideoData === 'function' && p.updateVideoData.length === 2) q += 80; if (p.getVideoAspectRatio) q += 20; if (p.getStreamTimeOffset) q += 20; // if (typeof p.updatePlaylist ==='function' && p.updatePlaylist.length===1)q += 80; if (q < 200) continue; // p.setPlaybackQualityRange must be available if (q > 0) arr = addProtoToArr(_yt_player, k, arr) || arr; if (q > 0) brr.set(k, q); } } if (arr.length === 0) { console.warn(`Key does not exist. [${w}]`); } else { arr = arr.map(key => [key, (brr.get(key) || 0)]); if (arr.length > 1) arr.sort((a, b) => b[1] - a[1]); console.log(`[${w}]`, arr); return arr[0][0]; } } const getL0 = (_yt_player) => { const w = 'L0'; let arr = []; for (const [k, v] of Object.entries(_yt_player)) { const p = typeof v === 'function' ? v.prototype : 0; if (p) { let q = 0; if (typeof p.getPreferredQuality === 'function' && p.getPreferredQuality.length === 0) q += 200; if (typeof p.getVideoData === 'function' && p.getVideoData.length === 0) q += 80; if (typeof p.isPlaying === 'function' && p.isPlaying.length === 0) q += 2; if (typeof p.getPlayerState === 'function' && p.getPlayerState.length === 0) q += 2; if (typeof p.getPlayerType === 'function' && p.getPlayerType.length === 0) q += 2; if (q < 280) continue; // p.getPreferredQuality and p.getVideoData must be available if (q > 0) arr.push([k, q]) } } if (arr.length === 0) { console.warn(`Key does not exist. [${w}]`); } else { if (arr.length > 1) arr.sort((a, b) => b[1] - a[1]); console.log(`[${w}]`, arr); return arr[0][0]; } } const getZf = (vL0) => { const w = 'vL0'; let arr = []; for (const [k, v] of Object.entries(vL0)) { // console.log(k,v) const p = v; if (p) { let q = 0; if (typeof p.videoData === 'object' && p.videoData) { if (Object.keys(p).length === 2) q += 200; } if (q > 0) arr.push([k, q]) } } if (arr.length === 0) { // console.warn(`Key does not exist. [${w}]`); } else { if (arr.length > 1) arr.sort((a, b) => b[1] - a[1]); console.log(`[${w}]`, arr); return arr[0][0]; } } 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' && typeof kagi === 'undefined' ? (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; } }; const isUrlInEmbed = location.href.includes('.youtube.com/embed/'); const isAbortSignalSupported = typeof AbortSignal !== "undefined"; cleanContext(window).then(__CONTEXT__ => { if (!__CONTEXT__) return null; const { setTimeout } = __CONTEXT__; 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); }); let resultantQualities = null; let byPass = false; // @@ durationchange @@ let pm2 = new PromiseExternal(); let lastURL = null; let activatedOnce = false; const fn = async (evt) => { try { const target = (evt || 0).target if (!(target instanceof HTMLMediaElement)) return; pm2.resolve(); const pm1 = pm2 = new PromiseExternal(); const mainMedia = await observablePromise(() => { return isUrlInEmbed ? document.querySelector('#movie_player .html5-main-video') : document.querySelector('ytd-player#ytd-player #movie_player .html5-main-video') }, pm2.then()).obtain(); if (!mainMedia) return; if (pm1 !== pm2) return; const ytdPlayerElm = isUrlInEmbed ? mainMedia.closest('#movie_player') : mainMedia.closest('ytd-player#ytd-player'); if (!ytdPlayerElm) return; let player_ for (let i = 10; --i;) { player_ = isUrlInEmbed ? ytdPlayerElm : await ((insp(ytdPlayerElm) || 0).player_ || 0); if (player_) break; if (pm1 !== pm2) return; await new Promise(r => setTimeout(r, 18)); } if (!player_) return; for (let i = 10; --i;) { if (player_.setPlaybackQualityRange) break; if (pm1 !== pm2) return; await new Promise(r => setTimeout(r, 18)); } if (pm1 !== pm2) return; if (typeof player_.setPlaybackQualityRange !== 'function') return; let _url = lastURL; let url = mainMedia.src; if (url === _url) return; lastURL = url; if (resultantQualities) { !activatedOnce && console.log('YouTube Quality Auto Max is activated.'); activatedOnce = true; let resultantQuality; let qualityThreshold = +localStorage.qualityThreshold || 0; if (!(qualityThreshold > 60)) qualityThreshold = 0; for (const entry of resultantQualities) { const entryRes = getResValue(entry); if (entryRes > 60 && entry.quality && typeof entry.quality === 'string') { if (qualityThreshold === 0 || (qualityThreshold > 60 && entryRes <= qualityThreshold)) { resultantQuality = entry.quality; break; } } } if (resultantQuality) { byPass = true; const setItemN = function (a, b) { // console.log('setItem', ...arguments) // yt-player-quality // yt-player-performance-cap // yt-player-performance-cap-active-set }; const pd = Object.getOwnPropertyDescriptor(localStorage.constructor.prototype, 'setItem'); if (pd && pd.configurable) { delete localStorage.constructor.prototype.setItem; Object.defineProperty(localStorage.constructor.prototype, 'setItem', { get() { return setItemN }, set(nv) { return true; }, enumerable: false, configurable: true, }); } player_.setPlaybackQualityRange(resultantQuality, resultantQuality); if (pd && pd.configurable && setItemN === localStorage.setItem) { delete localStorage.constructor.prototype.setItem; Object.defineProperty(localStorage.constructor.prototype, 'setItem', pd); } byPass = false; console.log('YouTube Quality Auto Max sets Quality to ', resultantQuality); } } } catch (e) { console.warn(e) } }; // document.addEventListener('loadstart', fn, true) document.addEventListener('durationchange', fn, true); // @@ durationchange @@ (async () => { try { const _yt_player = await observablePromise(() => { return (((window || 0)._yt_player || 0) || 0); }, promiseForTamerTimeout).obtain(); if (!_yt_player || typeof _yt_player !== 'object') return; const vmHash = new WeakSet(); const g = _yt_player; const keyuU = getuU(_yt_player); const keyL0 = getL0(_yt_player); if (keyuU) { let k = keyuU; let gk = g[k]; let gkp = g[k].prototype; if(typeof gkp.setPlaybackQualityRange132 !== "function" && typeof gkp.setPlaybackQualityRange === "function"){ gkp.setPlaybackQualityRange132 = gkp.setPlaybackQualityRange; gkp.setPlaybackQualityRange = function (...args) { if (!byPass && resultantQualities && document.visibilityState === 'visible') { if (args[0] === args[1] && typeof args[0] === 'string' && args[0]) { const selectionEntry = resultantQualities.filter(e => e.quality === args[0])[0] || 0 const selectionHeight = selectionEntry ? getResValue(selectionEntry) : 0; if (selectionHeight > 60) { localStorage.qualityThreshold = selectionHeight; } } else if (!args[0] && !args[1]) { delete localStorage.qualityThreshold; } } return this.setPlaybackQualityRange132(...args) }; console.log('YouTube Quality Auto Max - function modified [setPlaybackQualityRange]') } } if (keyL0) { let k = keyL0; let gk = g[k]; let gkp = g[k].prototype; let keyZf = null; if (typeof gkp.getVideoData31 !== "function" && typeof gkp.getVideoData === "function" && typeof gkp.setupOnNewVideoData61 !== "function") { gkp.getVideoData31 = gkp.getVideoData; gkp.setupOnNewVideoData61 = function () { keyZf = getZf(this); if (!keyZf) return; const tZf = this[keyZf]; if (!tZf) return; let keyJ = Object.keys(tZf).filter(e => e !== 'videoData')[0] const tZfJ = tZf[keyJ]; const videoData = tZf.videoData; if (!tZfJ || !videoData || !tZfJ.videoInfos) return; let videoTypes = tZfJ.videoInfos.map(info => info.video); // console.log(videoTypes) if (!videoTypes[0] || !videoTypes[0].quality || !getResValue(videoTypes[0])) return; let highestQuality = videoTypes[0].quality // console.log('highestQuality', highestQuality) let keyLists = new Set(); let keyLists2 = new Set(); const o = { [keyZf]: { videoData: new Proxy(videoData, { get(obj, key) { keyLists.add(key); const v = obj[key]; if (typeof v === 'object') return new Proxy(v, { get(obj, key) { keyLists2.add(key); return obj[key] } }) return v } }) } } this.getPreferredQuality.call(o) // console.log(keyLists.size, keyLists2.size) if (keyLists.size !== 2) return; if (keyLists2.size < 3) return; /* * 1080p Premium g.k.Nj = function(a) { h_a(this); this.options[a].element.setAttribute("aria-checked", "true"); this.Yd(this.Dk(a)); this.C = a } */ /* TP = function(a) { return SP[a.j || a.B] || "auto" } */ const [keyAy, keyxU] = [...keyLists]; const keyLs = [...keyLists2] const keyPs = [keyAy, keyxU] let cz = 0; function inc() { for (const pey of keyPs) { for (const ley of keyLs) { const val = videoData[pey][ley] if (typeof val === 'number' && val >= 0 && ~~val === val) { if (!cz) cz = ley; if (cz === ley) { // videoData[pey][ley] = 5120; // videoData[pey][ley] = videoTypes[0].height; continue } videoData[pey][ley] = getResValue(videoTypes[0]); // videoData[pey][ley]='1080p Premium' // videoData[pey][ley] = '1080p'; videoData[pey]['reason'] = 'm' } else if (typeof val === 'boolean' && val === false) { videoData[pey][ley] = true; } } } } // console.log(22, this) // const keyyU=getyU(_yt_player); // _yt_player[keyyU].prototype. resultantQualities = videoTypes; console.log('YouTube Quality Auto Max - resultantQualities is detected.'); // inc(); // console.log(this.getPreferredQuality()) // inc(); // console.log(this.getPreferredQuality()) // console.log(videoData, keyxU) // console.log(this) // console.log(1237, keyZf, keyJ, this[keyZf], videoTypes, videoData[keyAy], videoData[keyxU], keyLists2) } gkp.getVideoData = function () { const vd = this.getVideoData31();; if (!vd || typeof vd !== 'object') return vd; if (!vmHash.has(vd)) { vmHash.add(vd); this.setupOnNewVideoData61(); if (!keyZf) vmHash.delete(vd) } return vd; } console.log('YouTube Quality Auto Max - function modified [getVideoData]') } } } catch (e) { console.warn(e) } })(); }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址