YouTube: Quality Auto Max

To make Quality Auto Max

目前為 2024-01-13 提交的版本,檢視 最新版本

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



    })();

  });

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址