YouTube: Quality Auto Max

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)
            }



        })();

    });

})();