// ==UserScript==
// @name YouTube: Quality Auto Max
// @namespace UserScripts
// @match https://www.youtube.com/*
// @version 0.2.4
// @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 > 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 > 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 onRegistryReady = (callback) => {
if (typeof customElements === 'undefined') {
if (!('__CE_registry' in document)) {
// https://github.com/webcomponents/polyfills/
Object.defineProperty(document, '__CE_registry', {
get() {
// return undefined
},
set(nv) {
if (typeof nv == 'object') {
delete this.__CE_registry;
this.__CE_registry = nv;
this.dispatchEvent(new CustomEvent(EVENT_KEY_ON_REGISTRY_READY));
}
return true;
},
enumerable: false,
configurable: true
})
}
let eventHandler = (evt) => {
document.removeEventListener(EVENT_KEY_ON_REGISTRY_READY, eventHandler, false);
const f = callback;
callback = null;
eventHandler = null;
f();
};
document.addEventListener(EVENT_KEY_ON_REGISTRY_READY, eventHandler, false);
} else {
callback();
}
};
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;
}
};
const promiseForCustomYtElementsReady = new Promise(onRegistryReady);
cleanContext(window).then(__CONTEXT__ => {
if (!__CONTEXT__) return null;
const { setTimeout } = __CONTEXT__;
const promiseForTamerTimeout = new Promise(resolve => {
promiseForCustomYtElementsReady.then(() => {
customElements.whenDefined('ytd-app').then(() => {
setTimeout(resolve, 1200);
});
});
setTimeout(resolve, 3000);
});
let resultantQualities = null;
let byPass = false;
(async () => {
let pm2 = new PromiseExternal();
let lastURL = null;
const fn = async (evt) => {
try {
const target = (evt || 0).target
if (!(target instanceof HTMLVideoElement)) return;
let _url = lastURL;
let url = target.src;
if (url === _url) return;
lastURL = url;
pm2.resolve();
pm2 = new PromiseExternal();
const ytdPlayerElm = await observablePromise(() => {
return target.closest('ytd-player#ytd-player')
}, pm2.then()).obtain();
if (!ytdPlayerElm) return;
let player_
for (let i = 10; --i;) {
player_ = await ((insp(ytdPlayerElm) || 0).player_ || 0);
if (player_) break;
await new Promise(r => setTimeout(r));
}
if (!player_) return;
for (let i = 10; --i;) {
if (player_.setPlaybackQualityRange) break;
await new Promise(r => setTimeout(r));
}
if (!player_.setPlaybackQualityRange) return;
if (resultantQualities) {
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;
player_.setPlaybackQualityRange(resultantQuality, resultantQuality)
byPass = false;
}
}
} catch (e) {
console.warn(e)
}
};
// document.addEventListener('loadstart', fn, true)
document.addEventListener('durationchange', fn, true);
})();
(async () => {
try {
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 _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;
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)
}
}
if (keyL0) {
let k = keyL0;
let gk = g[k];
let gkp = g[k].prototype;
let keyZf = null;
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;
// 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;
}
}
} catch (e) {
console.warn(e)
}
})();
});
})();