YouTube CPU Tamer by AnimationFrame

Reduce Browser's Energy Impact for playing YouTube Video

As of 29.08.2021. See ბოლო ვერსია.

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 by AnimationFrame
// @name:en      YouTube CPU Tamer by AnimationFrame
// @name:jp      YouTube CPU Tamer by AnimationFrame
// @name:zh-tw   YouTube CPU Tamer by AnimationFrame
// @name:zh-cn   YouTube CPU Tamer by AnimationFrame
// @namespace    http://tampermonkey.net/
// @version     2021.08.29.4
// @license     MIT License
// @description     Reduce Browser's Energy Impact for playing YouTube Video
// @description:en  Reduce Browser's Energy Impact for playing YouTube Video
// @description:jp  YouTubeビデオのエネルギーインパクトを減らす
// @description:zh-tw  減少YouTube影片所致的能源消耗
// @description:zh-cn  减少YouTube影片所致的能源消耗
// @author       CY Fung
// @include     https://www.youtube.com/*
// @include     https://www.youtube.com/embed/*
// @include     https://www.youtube-nocookie.com/embed/*
// @include     https://www.youtube.com/live_chat*
// @include     https://www.youtube.com/live_chat_replay*
// @icon         https://www.google.com/s2/favicons?domain=youtube.com
// @run-at      document-start
// @grant       none
// ==/UserScript==
(function $$() {
    'use strict';

    const window = new Function('return window;')(); // real window object

    const hkey_script = 'nzsxclvflluv';
    if (window[hkey_script]) return; // avoid duplicated scripting
    window[hkey_script] = true;

    //if (!document.documentElement) return window.requestAnimationFrame($$); // no document access

    // copies of native functions
    const $$requestAnimationFrame = window.requestAnimationFrame.bind(window); // core looping
    const $$setTimeout = window.setTimeout.bind(window); // for race
    const $$setInterval = window.setInterval.bind(window); // for background execution

    let mi = 0;
    const sb = {};
    const sFunc = (prop) => {
        return (func, ms, ...args) => {
            mi++; // start at 1
            sb[mi] = {
                handler: args.length > 0 ? func.bind(null, ...args) : func, // original func if no extra argument
                [prop]: ms, // timeout / interval; value can be undefined
                nextAt: Date.now() + (ms > 0 ? ms : 0) // overload for setTimeout(func);
            };
            return mi;
        };
    };
    const rm = (jd) => {
        let o = sb[jd];
        if (typeof o != 'object') return;
        for (let k in o) o[k] = null;
        o = null;
        sb[jd] = null;
        delete sb[jd];
    };
    window.setTimeout = sFunc('timeout');
    window.setInterval = sFunc('interval');
    window.clearInterval = window.clearTimeout = rm;

    const $busy = Symbol('$busy');

    const pf = (handler => {
        if ($busy in handler) return;
        handler[$busy] = true; // max. 1 time of calling per each tf
        return new Promise(resolve => {
            // == microTask [Promise] ==
            // microTask is called after all handlers set with $busy, though it is not guaranteed.
            handler(); // try catch is not required - no further execution on the handler
            delete handler[$busy];
            resolve();
            // == microTask [Promise] ==
        });
    });

    let jf, tf;
    let bgExecutionAt = 0; // set at 0 to trigger tf in background startup when requestAnimationFrame is not responsive
    tf = () => {
        let now = Date.now();
        // ======= MarcoTask [requestAnimationFrame / setInterval] =======
        let promises = [];
        for (let mi in sb) {
            const o = sb[mi];
            let {
                handler,
                // timeout,
                interval,
                nextAt
            } = o;
            if (now < nextAt) continue;
            promises.push(pf(handler));
            if (interval > 0) { // prevent undefined, zero, negative values
                o.nextAt += +interval; // convertion from string to number if necessary; decimal is acceptable
            } else {
                rm(mi);
            }
        }
        // ======= MarcoTask [requestAnimationFrame / setInterval] =======
        if (!document.hidden) {
            // calling requestAnimationFrame in visible tab(s) only
            if (promises.length > 0) {
                let ret1 = Promise.all(promises);
                let ret2 = new Promise(resolve => $$setTimeout(resolve, 16));
                let race = Promise.race([ret1, ret2]); 
                // ensure jf must be called after 16ms to maintain visual changes in high fps. 
                // >16ms examples: repaint/reflow, change of style/content
                race.then(jf);
            } else {
                jf(); // execution interval is no less than AnimationFrame
            }
        }
        bgExecutionAt = now + 160; // if requestAnimationFrame is not responsive (e.g. background running)
    };
    (jf = $$requestAnimationFrame.bind(window, tf))();

    $$setInterval(() => {
        // no response of requestAnimationFrame; e.g. running in background
        if (Date.now() > bgExecutionAt) tf();
    }, 250);
    // i.e. 4 times per second for background execution - to keep YouTube application functional

    // Your code here...
})();