YouTube Live: Auto RealTime

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

13.01.2024 itibariyledir. En son verisyonu görün.

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

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



})();