YouTube CPU Tamer

It just reduces CPU usage on YouTube.

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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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