YouTube: Make AutoPlay Next More Than 3 seconds

To make AutoPlay Next Duration longer

Від 18.09.2023. Дивіться остання версія.

// ==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]





    })();

  });

})();