YouTube: Make AutoPlay Next More Than 3 seconds

To make AutoPlay Next Duration longer

当前为 2023-09-18 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        YouTube: Make AutoPlay Next More Than 3 seconds
// @namespace   UserScripts
// @match       https://www.youtube.com/*
// @version     0.1.1
// @author      CY Fung
// @license     MIT
// @description To make AutoPlay Next Duration longer
// @grant       none
// @run-at      document-start
// @unwrap
// @inject-into page
// ==/UserScript==

(() => {

  const second_to_play_next = 8;

  /*
   *



        , $mb = function(a, b) {
          b = void 0 === b ? -1 : b;
          a = a.j.Ha("ytp-autonav-endscreen-upnext-header");
          g.of(a);
          if (0 <= b) {
              b = String(b);
              var c = "$SECONDS \u79d2\u5f8c\u306b\u6b21\u306e\u52d5\u753b\u3092\u518d\u751f".match(RegExp("\\$SECONDS", "gi"))[0]
                , d = "$SECONDS \u79d2\u5f8c\u306b\u6b21\u306e\u52d5\u753b\u3092\u518d\u751f".indexOf(c);
              if (0 <= d) {
                  a.appendChild(g.mf("$SECONDS \u79d2\u5f8c\u306b\u6b21\u306e\u52d5\u753b\u3092\u518d\u751f".slice(0, d)));
                  var e = g.lf("span");
                  g.$t(e, "ytp-autonav-endscreen-upnext-header-countdown-number");
                  g.Bf(e, b);
                  a.appendChild(e);
                  a.appendChild(g.mf("$SECONDS \u79d2\u5f8c\u306b\u6b21\u306e\u52d5\u753b\u3092\u518d\u751f".slice(d + c.length)));
                  return
              }
          }
          g.Bf(a, "\u6b21\u306e\u52d5\u753b")
      }





      */





  /*
   *



          if (!a.Qk()) {
              a.J.Bf() ? $mb(a, Math.round(anb(a) / 1E3)) : $mb(a);
              b = !!a.suggestion && !!a.suggestion.Bs;
              var c = a.J.Bf() || !b;
              g.fu(a.container.element, "ytp-autonav-endscreen-upnext-alternative-header-only", !c && b);
              g.fu(a.container.element, "ytp-autonav-endscreen-upnext-no-alternative-header", c && !b);
              g.FG(a.B, a.J.Bf());
              g.fu(a.element, "ytp-enable-w2w-color-transitions", bnb(a))
          }


      */

  /*
   *
   *


        , anb = function(a) {
          if (a.J.isFullscreen()) {
              var b;
              a = null == (b = a.J.getVideoData()) ? void 0 : b.CB;
              return -1 === a || void 0 === a ? 8E3 : a
          }
          return 0 <= a.J.Ss() ? a.J.Ss() : g.WJ(a.J.W().experiments, "autoplay_time") || 1E4
      }

      */

  // a.J instanceof g.AU
  /*
   *
   *

  g.AU {Dp: false, xk: undefined, app: g.b1, state: WQa, playerType: undefined, …}

  Dp: false
  UK: pYa {Dp: false, xk: Array(6), Ub: {…}, Td: {…}, element: div#ytp-id-18.ytp-popup.ytp-settings-menu, …}
  app: g.b1 {Dp: false, xk: Array(21), logger: g.pY, di: false, bA: false, …}
  element: null
  j: false
  playerType: undefined
  state: WQa {Dp: false, xk: undefined, D: Set(89), G: {…}, S: {…}, …}
  xk: undefined
  */

  // a.J.Ss = ƒ (){return this.app.Ss()}
  // a.J.app.Ss = ƒ (){return this.getVideoData().aU}
  // a.J.app.getVideoData() = ƒ (){return this.Qb.getVideoData()} = object instanceof g.sT
  // a.J.app.Qb.getVideoData = ƒ (){return this.videoData}
  // g.sT.prototype

  /*
   *
  getAudioTrack
  getAvailableAudioTracks
  getHeartbeatResponse
  getPlayerResponse
  getPlaylistSequenceForTime
  getStoryboardFormat
  hasProgressBarBoundaries
  hasSupportedAudio51Tracks
  isAd
  isDaiEnabled
  isLoaded
  isOtf
  useInnertubeDrmService

  */



  const getsT = (_yt_player) => {
    const w = 'sT';

    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.hasSupportedAudio51Tracks === 'function' && p.hasSupportedAudio51Tracks.length === 0) q += 2;
        if (typeof p.getStoryboardFormat === 'function' && p.getStoryboardFormat.length === 0) q += 4;
        if (typeof p.getPlaylistSequenceForTime === 'function' && p.getPlaylistSequenceForTime.length === 1) q += 4;
        if (typeof p.isLoaded === 'function' && p.isLoaded.length === 0) q += 2;

        if (typeof p.isOtf === 'function' && p.isOtf.length === 0) q += 2;
        if (typeof p.getAvailableAudioTracks === 'function' && p.getAvailableAudioTracks.length === 0) q += 4;
        if (typeof p.getAudioTrack === 'function' && p.getAudioTrack.length === 0) q += 4;
        if (typeof p.getPlayerResponse === 'function' && p.getPlayerResponse.length === 0) q += 2;
        if (typeof p.getHeartbeatResponse === 'function' && p.getHeartbeatResponse.length === 0) q += 2;

        if (typeof p.isAd === 'function' && p.isAd.length === 0) q += 2;
        if (typeof p.isDaiEnabled === 'function' && p.isDaiEnabled.length === 1) q += 2;
        if (typeof p.useInnertubeDrmService === 'function' && p.useInnertubeDrmService.length === 0) q++;
        if (typeof p.hasProgressBarBoundaries === 'function' && p.hasProgressBarBoundaries.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 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 getAU = (_yt_player) => {
    const w = 'VG';

    let arr = [];

    for (const [k, v] of Object.entries(_yt_player)) {

      const p = typeof v === 'function' ? v.prototype : 0;
      if (p
        && typeof p.show === 'function' && p.show.length === 1
        && typeof p.hide === 'function' && p.hide.length === 0
        && typeof p.stop === 'function' && p.stop.length === 0) {

        arr = addProtoToArr(_yt_player, k, arr) || arr;

      }

    }


    if (arr.length === 0) {

      console.warn(`Key does not exist. [${w}]`);
    } else {

      console.log(`[${w}]`, arr);
      return arr[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 { requestAnimationFrame, setTimeout, cancelAnimationFrame, setInterval, clearInterval, animate, requestIdleCallback, getComputedStyle } = __CONTEXT__;



    let rafPromiseForTickers = null;

    const getRafPromiseForTickers = () => rafPromiseForTickers || (rafPromiseForTickers = new Promise(resolve => {
      requestAnimationFrame(hRes => {
        rafPromiseForTickers = null;
        resolve(hRes);
      });
    }));

    const getForegroundPromise = () => {
      if (document.visibilityState === 'visible') {
        return Promise.resolve();
      } else {
        return getRafPromiseForTickers();
      }
    };


    const promiseForTamerTimeout = new Promise(resolve => {
      promiseForCustomYtElementsReady.then(() => {
        customElements.whenDefined('ytd-app').then(() => {
          setTimeout(resolve, 1200);
        });
      });
      setTimeout(resolve, 3000);
    });


    class RAFHub {
      constructor() {
        /** @type {number} */
        this.startAt = 8170;
        /** @type {number} */
        this.counter = 0;
        /** @type {number} */
        this.rid = 0;
        /** @type {Map<number, FrameRequestCallback>} */
        this.funcs = new Map();
        const funcs = this.funcs;
        /** @type {FrameRequestCallback} */
        this.bCallback = this.mCallback.bind(this);
        this.pClear = () => funcs.clear();
      }
      /** @param {DOMHighResTimeStamp} highResTime */
      mCallback(highResTime) {
        this.rid = 0;
        Promise.resolve().then(this.pClear);
        this.funcs.forEach(func => Promise.resolve(highResTime).then(func).catch(console.warn));
      }
      /** @param {FrameRequestCallback} f */
      request(f) {
        if (this.counter > 1e9) this.counter = 9;
        let cid = this.startAt + (++this.counter);
        this.funcs.set(cid, f);
        if (this.rid === 0) this.rid = requestAnimationFrame(this.bCallback);
        return cid;
      }
      /** @param {number} cid */
      cancel(cid) {
        cid = +cid;
        if (cid > 0) {
          if (cid <= this.startAt) {
            return cancelAnimationFrame(cid);
          }
          if (this.rid > 0) {
            this.funcs.delete(cid);
            if (this.funcs.size === 0) {
              cancelAnimationFrame(this.rid);
              this.rid = 0;
            }
          }
        }
      }
    }



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

    (async () => {



      const dataSetPromise = new PromiseExternal();
      const instSetPromise = new PromiseExternal();



      const rafHub = new RAFHub();


      const _yt_player = await new Promise(resolve => {

        let cid = setInterval(() => {
          let t = (((window || 0)._yt_player || 0) || 0);
          if (t) {

            clearInterval(cid);
            resolve(t);
          }
        }, 1);

        promiseForTamerTimeout.then(() => {
          resolve(null)
        });

      });



      if (!_yt_player || typeof _yt_player !== 'object') return;


      function onSetsT(inst) {

        if (!inst) return;

        let r = setDataHolder();

        if (r) qrz ? assignDurationToData(pm.playerOverlayAutoplayRenderer, null) : dataSetPromise.resolve();

        instSetPromise.resolve();





      }


      const pm = new Proxy({}, {
        set(target, prop, value, receiver) {
          let old = target[prop];
          if (old !== value) {
            target[prop] = old = value;
            if (prop === 'instsT') onSetsT(value);
          }
        }
      });


      const assignDurationToData = (playerOverlayAutoplayRenderer) => {
        if (!playerOverlayAutoplayRenderer) return;
        const t4 = playerOverlayAutoplayRenderer.countDownSecsForFullscreen;
        const t5 = playerOverlayAutoplayRenderer.countDownSecs;
        let b = t4 === t5
        playerOverlayAutoplayRenderer.countDownSecsForFullscreen = second_to_play_next;
        if (b) playerOverlayAutoplayRenderer.countDownSecs = second_to_play_next;
        return b;
      }

      let qrz = false;
      Promise.all([dataSetPromise, instSetPromise]).then(() => {


        const inst = pm.instsT;
        const playerOverlayAutoplayRenderer = pm.playerOverlayAutoplayRenderer;
        if (!inst || !playerOverlayAutoplayRenderer) return;

        let entriesA = Object.entries(inst).filter(e => typeof e[1] === 'number');
        let m = new Map();
        for(const entry of entriesA){
          m.set(entry[0], entry[1]);
        }

        let b = assignDurationToData(playerOverlayAutoplayRenderer);

        setTimeout(() => {
          const entriesB = Object.entries(inst).filter(e => typeof e[1] === 'number' && m.get(e[0])!==e[1]);
          m = null;
          let filtered = entriesB.filter(e => Math.abs(e[1] - second_to_play_next * 1E3) < 1e-8)
          console.log(`sT(${b ? 'T' : 'F'}):`, filtered, filtered.length)
        }, 80);

        qrz = true;

      })

      const setDataHolder = () => {

        let playerOverlayAutoplayRenderer = null;
        let pageDataMgr = document.querySelector('ytd-page-manager#page-manager');
        let pageData = pageDataMgr ? (pageDataMgr.inst || pageDataMgr).data : null;
        if (pageData) {

          try {
            playerOverlayAutoplayRenderer = pageData.response.playerOverlays.playerOverlayRenderer.autoplay.playerOverlayAutoplayRenderer;
          } catch (e) { }
        }
        if (playerOverlayAutoplayRenderer && typeof playerOverlayAutoplayRenderer.countDownSecsForFullscreen === 'number' && playerOverlayAutoplayRenderer.countDownSecsForFullscreen < 15) {

          pm.playerOverlayAutoplayRenderer = playerOverlayAutoplayRenderer;
          return true;
        }

      }

      document.addEventListener('yt-navigate-finish', function () {
        // onSetsT(pm.instsT)


        let r = setDataHolder();
        if (r) qrz ? assignDurationToData(pm.playerOverlayAutoplayRenderer, null) : dataSetPromise.resolve();


      }, false);

      const g = _yt_player;
      const keysT = getsT(_yt_player);

      if (keysT) {
        let k = keysT;
        let gk = g[k];
        let gkp = g[k].prototype;


        /*
         *
        if (typeof p.hasSupportedAudio51Tracks === 'function' && p.hasSupportedAudio51Tracks.length === 0) q += 2;
        if (typeof p.getStoryboardFormat === 'function' && p.getStoryboardFormat.length === 0) q += 4;
        if (typeof p.getPlaylistSequenceForTime === 'function' && p.getPlaylistSequenceForTime.length === 1) q += 4;
        if (typeof p.isLoaded === 'function' && p.isLoaded.length === 0) q += 2;

        if (typeof p.isOtf === 'function' && p.isOtf.length === 0) q += 2;
        if (typeof p.getAvailableAudioTracks === 'function' && p.getAvailableAudioTracks.length === 0) q += 4;
        if (typeof p.getAudioTrack === 'function' && p.getAudioTrack.length === 0) q += 4;
        if (typeof p.getPlayerResponse === 'function' && p.getPlayerResponse.length === 0) q += 2;
        if (typeof p.getHeartbeatResponse === 'function' && p.getHeartbeatResponse.length === 0) q += 2;

        if (typeof p.isAd === 'function' && p.isAd.length === 0) q += 2;
        if (typeof p.isDaiEnabled === 'function' && p.isDaiEnabled.length === 1) q += 2;
        if (typeof p.useInnertubeDrmService === 'function' && p.useInnertubeDrmService.length === 0) q++;
        if (typeof p.hasProgressBarBoundaries === 'function' && p.hasProgressBarBoundaries.length === 0) q += 2;

  */

        //       for(const pk of ['hasSupportedAudio51Tracks','getStoryboardFormat','getPlaylistSequenceForTime','isLoaded',
        //                      'isOtf',//'getAvailableAudioTracks','getAudioTrack',
        //                      'getPlayerResponse','getHeartbeatResponse',
        //                     // 'isAd',
        //                        'isDaiEnabled','useInnertubeDrmService',
        //                        //'hasProgressBarBoundaries'
        //                     ]){



        //       if(gkp[pk]){
        //         gkp[pk+'75'] = gkp[pk];
        //         gkp[pk]=function(){
        //           console.log(pk)
        //           return this[pk+'75'].apply(this, arguments);
        //         }
        //       }

        //       }





        if (!gkp.isLoaded75 && typeof gkp.isLoaded === 'function') {


          gkp.isLoaded75 = gkp.isLoaded;
          gkp.isLoaded = function () {
            pm.instsT = this;

            return this.isLoaded75.apply(this, arguments);
          }


        }





      }

      console.log(keysT);

      const sT = [_yt_player]





    })();

  });

})();