YouTube CPU Tamer

It just reduces CPU usage on YouTube.

As of 2020-12-07. See the latest version.

// ==UserScript==
// @name        YouTube CPU Tamer
// @name:ja     YouTube CPU Tamer
// @name:zh-CN  YouTube CPU Tamer
// @namespace   knoa.jp
// @description It just reduces CPU usage on YouTube.
// @description:ja YouTubeでのCPU使用率を削減します。
// @description:zh-CN 减少YouTube页面上的CPU利用率。
// @include     https://www.youtube.com/*
// @include     https://www.youtube.com/embed/*
// @include     https://www.youtube-nocookie.com/embed/*
// @version     1
// @grant       none
// @run-at      document-start
// ==/UserScript==

/*
[update]

[memo]
interval
  interval 自体のインスタンスを減らすためにすべて1つの関数にまとめて実行する。
  前面タブなら1秒ごと、背面タブなら60秒ごとに頻度を落とす。
timeout
  前面タブではユーザーインタラクションに関わる場合があるのでそのまま実行する。
  背面タブなら最初の10秒はUI生成のためにそのまま実行するが、以降は完全に無視する。
*/
(function(){
  const SCRIPTID = 'YouTubeCpuTamer';
  console.log(SCRIPTID, location.href);
  const BUNDLEDINTERVAL    =    1000;/* the bundled interval */
  const BACKGROUNDINTERVAL = 60*1000;/* take even longer interval on hidden tab */
  const INITIALIZINGTIME   = 10*1000;/* it should allow background timeouts for initializing UI of YouTube on load */
  /*
    [interval]
  */
  /* integrate each of intervals */
  const bundle = {};/* {0: {f, interval, lastExecution}} */
  let index = 0;/* use it instead of interval id */
  let lastExecution = 0;
  /* bundle intervals */
  const originalSetInterval = window.setInterval.bind(window);
  window.setInterval = function(f, interval, ...args){
    //console.log(SCRIPTID, 'original interval:', interval);
    bundle[index] = {
      f: f.bind(null, ...args),
      interval: interval,
      lastExecution: 0,
    };
    return index++;
  };
  window.clearInterval = function(id){
    //console.log(SCRIPTID, 'clearInterval:', id);
    delete bundle[id];
  };
  /* execute bundled intervals */
  /* a bunch of intervals does cost so much even if the processes do nothing */
  originalSetInterval(function(){
    const now = Date.now();
    if(document.hidden && now < lastExecution + BACKGROUNDINTERVAL) return;
    Object.keys(bundle).forEach(id => {
      const item = bundle[id];
      if(item === undefined) return;/* it could be occur on tiny deletion chance */
      if(now < item.lastExecution + item.interval) return;/* not yet */
      item.f();
      item.lastExecution = now;
    });
    lastExecution = now;
  }, BUNDLEDINTERVAL);
  /*
    [timeout]
  */
  /* kill the background timeouts after initializing */
  const originalSetTimeout = window.setTimeout.bind(window);
  const started = Date.now()
  let initialized = false;
  window.setTimeout = function(f, timeout, ...args){
    //console.log(SCRIPTID, 'timeout:', timeout);
    if(document.hidden){
      /* do nothing after initializing */
      /* should return true for tricking YouTube as succeeded */
      if(initialized) return true;
      if(started + INITIALIZINGTIME < Date.now()) return initialized = true;
    }
    return originalSetTimeout(f, timeout, ...args);
  };
})();