YouTube Live: Auto RealTime

1/13/2024, 7:11:55 PM

目前为 2024-01-13 提交的版本。查看 最新版本

// ==UserScript==
// @name        YouTube Live: Auto RealTime
// @namespace   UserScripts
// @match       https://www.youtube.com/*
// @grant       none
// @version     0.2.12
// @author      CY Fung
// @description 1/13/2024, 7:11:55 PM
// @run-at      document-start
// @license     MIT
//
// ==/UserScript==

(() => {

  let byPassPlaybackRate = false;
  let tmpPlaybackRate = null;

  if (typeof AbortSignal === 'undefined') return;

  // const nextBrowserTick = (self || 0).nextBrowserTick || 0;

  let __requestAnimationFrame__ = typeof webkitRequestAnimationFrame === 'function' ? window.webkitRequestAnimationFrame.bind(window) : window.requestAnimationFrame.bind(window);


  // if (!nextBrowserTick) return;

  let instance = null;

  /** @type {globalThis.PromiseConstructor} */
  const Promise = (async () => { })().constructor; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve.


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

  let fc = 0;
  // const pNextBrowserTick = () => new Promise(resolve => nextBrowserTick(resolve));




  const pd = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'playbackRate');

  const isPassiveArgSupport = (typeof IntersectionObserver === 'function');
  const bubblePassive = isPassiveArgSupport ? { capture: false, passive: true } : false;
  const capturePassive = isPassiveArgSupport ? { capture: true, passive: true } : true;

  const insp = o => o ? (o.polymerController || o.inst || o || 0) : (o || 0);

  let pageFetchedDataLocal = null;
  document.addEventListener('yt-page-data-fetched', (evt) => {
    pageFetchedDataLocal = evt.detail;

  }, bubblePassive);


  function getFormatDates() {

    if (!pageFetchedDataLocal) return null;

    const formatDates = {}
    try {
      formatDates.publishDate = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.publishDate
    } catch (e) { }
    // 2022-12-30

    try {
      formatDates.uploadDate = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.uploadDate
    } catch (e) { }
    // 2022-12-30

    try {
      formatDates.publishDate2 = pageFetchedDataLocal.pageData.response.contents.twoColumnWatchNextResults.results.results.contents[0].videoPrimaryInfoRenderer.dateText.simpleText
    } catch (e) { }
    // 2022/12/31

    if (typeof formatDates.publishDate2 === 'string' && formatDates.publishDate2 !== formatDates.publishDate) {
      formatDates.publishDate = formatDates.publishDate2
      formatDates.uploadDate = null
    }

    try {
      formatDates.broadcastBeginAt = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.liveBroadcastDetails.startTimestamp
    } catch (e) { }
    try {
      formatDates.broadcastEndAt = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.liveBroadcastDetails.endTimestamp
    } catch (e) { }
    try {
      formatDates.isLiveNow = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.liveBroadcastDetails.isLiveNow
    } catch (e) { }


    return formatDates;
  }

  const promiseVideoNextFn = async (video, v) => {

    if (typeof video.requestVideoFrameCallback === 'function' && v !== false) {

      return true === await Promise.race([
        new Promise(resolve => video.requestVideoFrameCallback(() => {
          resolve(true);
        })),
        new Promise(resolve => setTimeout(resolve, 1000))
      ]);

    } else {

      return true === await Promise.race([
        new Promise(resolve => video.addEventListener('timeupdate', () => {
          resolve(true);
        }, { once: true, passive: true, capture: false })),
        new Promise(resolve => setTimeout(resolve, 1000))
      ]);

    }



  }

  let promisePR1;

  let videoTarget = null;


  let pr01 = new PromiseExternal();
  let pr02 = new PromiseExternal();

  document.addEventListener('durationchange', function (evt) {

    const target = (evt || 0).target;
    if (!(target instanceof HTMLVideoElement)) return;

    if (target.classList.contains('video-stream') && target.classList.contains('html5-main-video')) {

      videoTarget = target;

      if (target.duration !== 3600 && target.duration > 120) {


        // console.log(1233)

        pr01.resolve();
        pr01 = new PromiseExternal();

      }

    }


  }, true);

  async function run(pageFetchedDataLocal) {


    if (fc > 1e9) fc = 9;
    let tc = ++fc;

    const timeout = new Promise(r => setTimeout(r, 1000));
    /*

    // console.log(10001)
    const watchPages = [...document.querySelectorAll('ytd-watch-flexy')].filter(e => !e.closest('[hidden]'));
    if (watchPages.length !== 1 || !watchPages[0]) return;
    const watchPage = watchPages[0];
    // console.log(10002)

    const timeout = new Promise(r => setTimeout(r, 1000));
    const liveBtn = await observablePromise(() => watchPage.querySelector('button.ytp-live-badge.ytp-button[disabled]'), timeout).obtain();

    if (tc !== fc) return;

    // console.log(10003)
    if (!liveBtn) return;

    // console.log(10004)
    if (liveBtn.closest('[hidden]')) return;

    // console.log(10005)
    if (!liveBtn.matches('.ytp-chrome-controls .ytp-live .ytp-live-badge')) return;

    // console.log(10006)
    await new Promise((resolve) => __requestAnimationFrame__(resolve));

    if (tc !== fc) return;

    // console.log(10007)
    if (!liveBtn.isConnected) return;

    // console.log(10008)
    const settingBtn = watchPage.querySelector('button.ytp-button.ytp-settings-button:not([disabled])');

    // console.log(10009)
    if (!settingBtn) return;
    if (settingBtn.closest('[hidden]')) return;
    */

    // console.log(100010)
    //
    // console.log(10001)
    const video = videoTarget;
    // const video = watchPage.querySelector('video.video-stream.html5-main-video');
    if (!video) return;

    if (video.paused !== false || video.isConnected !== true) return;

    // console.log(10011)




    if (!pageFetchedDataLocal) return;
    const dates = getFormatDates();

    if (!dates) return;


    // console.log(dates)


    // console.log(10002, video.paused || !video.isConnected || video.networkState !== 2 || video.readyState !== 4, video.currentTime > 0.1 && video.duration > 0.1, instance)


    const fn = () => {

      if (video.paused || !video.isConnected || video.networkState !== 2 || video.readyState !== 4) return false;
      return video.currentTime > 0.1 && video.duration > 0.1 && instance;

    }
    if (!fn()) {

      await observablePromise(fn, timeout).obtain();

    }


    // console.log(10003)
    if (tc !== fc) return;

    await new Promise(resolve => video.addEventListener('timeupdate', () => {
      resolve();
    }, { once: true, passive: true, capture: false }));



    // console.log(10004)

    if (tc !== fc) return;
    // await new Promise((resolve) => __requestAnimationFrame__(resolve));


    if (dates.broadcastBeginAt && !dates.broadcastEndAt && dates.isLiveNow === true) {
    } else {
      return;
    }

    // console.log(10005)

    let setTime = null;
    if (dates.broadcastBeginAt && !dates.broadcastEndAt && dates.isLiveNow === true) {

      let t1 = video.currentTime;
      let t2 = (new Date() - new Date(dates.broadcastBeginAt)) / 1000;
      let te = video.duration;
      // console.log(12342, t1,t2)
      // console.log(12343, t1>0.8 , t2>0.8 , te>0.8 , t2-t1> 0.6 , te > t2 + 3.0 , te > t1 + 3.0)
      if (t1 > 0.8 && t2 > 0.8 && te > 0.8 && te > t2 + 3.0 && te > t1 + 3.0) {
        // await new Promise((resolve) => requestAnimationFrame(resolve));

        // pd.set.call(video, 1.0);


        if (t2 - t1 > 0.6) {

          setTime = t2 - 0.49;

        }

        // console.log(4320, instance)
        // instance.setCurrentTime(t2, true);

        // setInterval(() => {

        //   console.log(new Date(), video.currentTime, (new Date() - new Date(dates.broadcastBeginAt)) / 1000)

        // }, 1000);




      }





    }


    let crate = pd.get.call(video)
    if (!(crate > 0.99 && crate < 1.01)) return;
    // pd.set.call(video, 1);

    if (typeof setTime === 'number') {

      // video.currentTime = setTime;
    }


    // console.log('yyee')

    for (let i = 0; i < 3; i++) {

      promisePR1 = null;
      // console.log(3170)

      if (video.paused || !video.isConnected || video.networkState !== 2 || video.readyState !== 4) return;
      if (false === await promiseVideoNextFn(video)) return;
      if (tc !== fc) return;

      // console.log(3171)

      byPassPlaybackRate = true;

      try {

        const pr = promisePR1 = new PromiseExternal();

        let rate = 1;
        if (i === 0) rate = 13.52 + Math.random() * 0.45;
        else if (i === 1) rate = 1.26 + Math.random() * 0.22;
        else rate = 1.06 + Math.random() * 0.01;

        // console.log(3172)

        rate = +rate.toFixed(5);
        console.log('rate', rate)
        instance.setPlaybackRate(rate, true);

        if (video.paused || !video.isConnected || video.networkState !== 2 || video.readyState !== 4) {

        } else {
          await promiseVideoNextFn(video);
        }

        // console.log(3173, video.paused , !video.isConnected , video.networkState !== 2 , video.readyState !== 4)

        await pr.then();

        // console.log(3174)

        await promiseVideoNextFn(video);
        await promiseVideoNextFn(video, false);

      } finally {
        byPassPlaybackRate = false;

      }


      // console.log(3175)



    }



    /*

    console.log(12312)
    console.log()

    if (tc !== fc) return;

    settingBtn.click();

    await pNextBrowserTick();

    let menuItemBtns = [...watchPage.querySelectorAll('.ytp-panel .ytp-menuitem[aria-haspopup]')].filter(e => {
      return e.querySelector('[d^="M10,8v8l6-4L10,"]')
    });

    if (menuItemBtns.length !== 1 || !menuItemBtns[0]) return;
    let menuItemBtn = menuItemBtns[0]

    menuItemBtn.click();

    await pNextBrowserTick();

    let radioBtns = [...watchPage.querySelectorAll('.ytp-popup.ytp-settings-menu .ytp-panel-menu .ytp-menuitem[role="menuitemradio"]')].filter(e => {
      return e.textContent.trim() === '2'
    });
    if (radioBtns.length !== 1 || !radioBtns[0]) return;
    let radioBtn = radioBtns[0];


    radioBtn.click();

    await pNextBrowserTick();
    pd.set.call(video, 8.98);
    if (radioBtn.isConnected) settingBtn.click();

*/


    // console.log(1232131)



  }

  document.addEventListener('yt-navigate-finish', () => {

    pr02.resolve();

    // console.log(1234)
    pr02 = new PromiseExternal();

  }, false);


  (async () => {

    let lastSrc = null;
    while (1) {
      await Promise.all([pr01, pr02]);

      if (!videoTarget || !videoTarget.isConnected) return;
      // console.log(12332)

      let src = videoTarget.src;


      if (lastSrc !== src) {
        lastSrc = src;

        run(pageFetchedDataLocal);
      }


      await new Promise(resolve => __requestAnimationFrame__(resolve));
    }
  })();


  const _yt_player_observable = observablePromise(() => {
    return (((window || 0)._yt_player || 0) || 0);
  });

  (async () => {



    const _yt_player = await _yt_player_observable.obtain();



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



    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 getGU = (_yt_player) => {

      const w = 'GU';

      let arr = [];

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

        const p = typeof v === 'function' ? v.prototype : 0;
        if (p
          && typeof p.setPlaybackRate === 'function' && p.setPlaybackRate.length === 2
          && typeof p.getPlaybackRate === 'function' && p.getPlaybackRate.length === 0

          // && typeof p.isAtLiveHead === 'function'

          && typeof p.getVideoUrl === 'function' && p.getVideoUrl.length === 4
          && typeof p.getCurrentTime === 'function' && p.getCurrentTime.length == 2
          && typeof p.getDuration === 'function' && p.getDuration.length == 2

          && !(typeof p.isPaused === 'function' && p.isPaused.length === 0
            && typeof p.getCurrentTime === 'function' && p.getCurrentTime.length === 0
            && typeof p.getDuration === 'function' && p.getDuration.length === 0

            // && typeof p.isAtLiveHead === 'function' && p.isAtLiveHead.length === 0
            && typeof p.getVideoPlaybackQuality === 'function' && p.getVideoPlaybackQuality.length === 0
            && typeof p.stopVideo === 'function' && p.stopVideo.length === 0)
          // && Object.keys(_yt_player[k].prototype).includes('addEventListener')
          // && !p.dispose && !p.isDisposed

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


        }

      }


      // console.log(1222, arr.map(k=> Object.keys(_yt_player[k].prototype).sort()))

      if (arr.length === 0) {

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

        console.log(`[${w}]`, arr);
        return arr[0];
      }




    }


    const getW0 = (_yt_player) => {

      const w = 'W0';

      let arr = [];

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

        const p = typeof v === 'function' ? v.prototype : 0;
        if (p
          && typeof p.isAtLiveHead === 'function'
          && typeof p.seekTo === 'function'
          && typeof p.loadVideoByPlayerVars === 'undefined'

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


        }

      }


      // console.log(1222, arr.map(k=> _yt_player[k].prototype['isAtLiveHead']))
      // console.log(1223, arr.map(k=> Object.keys( _yt_player[k].prototype)))

      if (arr.length === 0) {

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

        console.log(`[${w}]`, arr);
        return arr[0];
      }




    }

    const getg1 = (_yt_player) => {

      const w = 'g1';

      let arr = [];

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

        const p = typeof v === 'function' ? v.prototype : 0;
        if (p
          && typeof p.canPlayType === 'function' && p.canPlayType.length === 1
          && typeof p.cancelPlayback === 'function' && p.cancelPlayback.length === 2
          && typeof p.getInternalApi === 'function' && p.getInternalApi.length === 0
          && typeof p.getAppState === 'function' && p.getAppState.length === 0
          && typeof p.getPresentingPlayerType === 'function' && p.getPresentingPlayerType.length === 1
          && typeof p.getVideoData === 'function' && p.getVideoData.length === 0
          && typeof p.seekTo === 'function'
          && typeof p.seekBy === 'function'
          && typeof p.sendVideoStatsEngageEvent === 'function'

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


        }

      }


      // console.log(1222, arr.map(k=> _yt_player[k].prototype['isAtLiveHead']))
      // console.log(1223, arr.map(k=> Object.keys( _yt_player[k].prototype)))

      if (arr.length === 0) {

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

        console.log(`[${w}]`, arr);
        return arr[0];
      }




    }

    const key = getGU(_yt_player);

    // console.log(1233, key)
    const g = _yt_player;
    const k = key;
    const gk = g[k];
    const gkp = g[k].prototype;



    //     gkp.isAtLiveHead322 = gkp.isAtLiveHead;
    //     gkp.isAtLiveHead = function(){

    //       // console.log(5555)
    //       instance = this;
    //       return this.isAtLiveHead322();
    //     }


    gkp.getPlaybackRate322 = gkp.getPlaybackRate;
    gkp.getPlaybackRate = function () {

      // console.log(5556, this.getPlaybackRate322)
      instance = this;
      return this.getPlaybackRate322();
    }

    gkp.setPlaybackRate322 = gkp.setPlaybackRate;
    gkp.setPlaybackRate = function (a, b) {
      instance = this;
      if (byPassPlaybackRate) {
        // console.log(5888, 12333,a, b)
        // return;
      }
      // console.log(5388, arguments)
      return this.setPlaybackRate322(a, b);
    }

    let key2 = getW0(_yt_player);

    // console.log('XXA', key2)

    let key3 = getg1(_yt_player);
    // console.log('XXB', key3)

    /*
    _yt_player[key3].prototype.E8xx =  _yt_player[key3].prototype.E8;
     _yt_player[key3].prototype.E8 = function(a){

       let b1= g.sH(a, 1);
       let b2= !g.qH(a.state, 64)
       let b3= this.Hd().isLivePlayback;
       let b4= this.zb.isAtLiveHead();
       let b5= 1 < this.Va.getPlaybackRate();


       console.log('XXPP0', a)
       console.log('XXPP1' , b1,b2,b3,b4,b5)

       setTimeout(()=>{


       let b1= g.sH(a, 1);
       let b2= !g.qH(a.state, 64)
       let b3= this.Hd().isLivePlayback;
       let b4= this.zb.isAtLiveHead();
       let b5= 1 < this.Va.getPlaybackRate();


       // console.log('XXPP2' , b1,b2,b3,b4,b5)

       }, 1000)

       return this.E8xx(a);

     }

*/


  })();

  Storage.prototype.setItem322 = Storage.prototype.setItem;
  Storage.prototype.setItem = function (a, b) {



    if (a === 'yt-player-playback-rate') {
      tmpPlaybackRate = b;
      // if(!byPassPlaybackRate) debugger
      if (promisePR1 && b && typeof b === 'string' && b.indexOf('{"data":"1"') >= 0) {

        promisePR1.resolve();
        promisePR1 = null;
      }
      // console.log(5883, a, b,byPassPlaybackRate, 'XX_'+tmpPlaybackRate+'_', location.pathname)
      if (window.location.pathname === '/live_chat') return;
    }

    if (byPassPlaybackRate && a === 'yt-player-playback-rate') return;
    this.setItem322(a, b);

  }


  Storage.prototype.getItem322 = Storage.prototype.getItem;
  Storage.prototype.getItem = function (a) {



    if (a === 'yt-player-playback-rate') {

      if (window.location.pathname === '/live_chat') return null;
      // console.log(5884, a, 'XX_'+tmpPlaybackRate+'_', location.pathname)
      if (typeof tmpPlaybackRate === 'string') return tmpPlaybackRate;
    }

    if (a === 'yt-player-playback-rate' && typeof tmpPlaybackRate === 'string') return tmpPlaybackRate;
    return this.getItem322(a);

  }

  Object.defineProperty(Storage.prototype, 'yt-player-playback-rate', {
    get() {
      // console.log(5881, 'XX_'+tmpPlaybackRate+'_', location.pathname)
      return this.getItem('yt-player-playback-rate');
    },
    set(nv) {
      // console.log(5882, nv, 'XX_'+tmpPlaybackRate+'_', location.pathname);
      this.setItem('yt-player-playback-rate', nv);
      return true;
    },
    enumerable: true,
    configurable: true
  });



})();

QingJ © 2025

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