YouTube: Quality Auto Max

To make Quality Auto Max

2023-12-29 या दिनांकाला. सर्वात नवीन आवृत्ती पाहा.

// ==UserScript==
// @name        YouTube: Quality Auto Max
// @namespace   UserScripts
// @match       https://www.youtube.com/*
// @version     0.1.0
// @author      CY Fung
// @license     MIT
// @description To make Quality Auto Max
// @grant       none
// @run-at      document-start
// @unwrap
// @inject-into page
// ==/UserScript==

(() => {




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



    (async () => {


      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 keyL0 = getL0(_yt_player);


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


          if (!videoTypes[0] || !videoTypes[0].quality || !videoTypes[0].height) 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] = videoTypes[0].height;
                  // videoData[pey][ley]='1080p Premium'
                  // videoData[pey][ley] = '1080p';
                  // videoData[pey]['reason']='m'
                } else if (typeof val === 'boolean' && val === false) {
                  videoData[pey][ley] = true;
                }
              }

            }
          }

          inc();
          // console.log(22, this)

          // const keyyU=getyU(_yt_player);
          // _yt_player[keyyU].prototype.


          //           (async()=>{
          //             await customElements.whenDefined('ytd-watch-flexy');
          //             await customElements.whenDefined('ytd-player');

          //             await new Promise(r=>setTimeout(r, 800));
          //             console.log(12321, document.querySelector('ytd-watch-flexy'))
          //             console.log(12322, document.querySelector('ytd-watch-flexy').polymerController.player )

          //             document.querySelector('ytd-watch-flexy').polymerController.player.setPlaybackQuality(highestQuality)
          //             console.log(this.getPreferredQuality())
          //           })();


          // 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;
        }


      }







    })();

  });

})();