// ==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)
}
})();
});
})();