Reduce Browser's Energy Impact for playing YouTube Video
当前为
// ==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.30.2
// @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
const $busy = Symbol('$busy');
let mi = 0;
const sb = {};
const sFunc = (prop) => {
return (func, ms, ...args) => {
mi++; // start at 1
let handler = args.length > 0 ? func.bind(null, ...args) : func; // original func if no extra argument
handler[$busy] = handler[$busy] || 0;
sb[mi] = {
handler,
[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 pf = (
handler => new Promise(resolve => {
// try catch is not required - no further execution on the handler
// For function handler with high energy impact, discard 1st, 2nd, ... (n-1)th calling: (a,b,c,a,b,d,e,f) => (c,a,b,d,e,f)
// For function handler with low energy impact, discard or not discard depends on system performance
if (handler[$busy] == 1) handler();
handler[$busy]--;
resolve();
})
);
let jf, tf, toResetFuncHandlers = false;
let bgExecutionAt = 0; // set at 0 to trigger tf in background startup when requestAnimationFrame is not responsive
tf = () => {
if (toResetFuncHandlers) {
toResetFuncHandlers = false;
for (let jb in sb) sb[jb].handler[$busy] = 0;
}
new Promise(resolveApp1 => {
// microTask #1
let now = Date.now();
bgExecutionAt = now + 160; // if requestAnimationFrame is not responsive (e.g. background running)
let promisesF = [];
for (let jb in sb) {
const o = sb[jb];
let {
handler,
// timeout,
interval,
nextAt
} = o;
if (now < nextAt) continue;
handler[$busy]++;
promisesF.push(handler);
if (interval > 0) { // prevent undefined, zero, negative values
o.nextAt += +interval; // convertion from string to number if necessary; decimal is acceptable
} else {
rm(jb); // remove timeout
}
}
resolveApp1(promisesF);
}).then(promisesF => {
// microTask #2
bgExecutionAt = Date.now() + 160; // if requestAnimationFrame is not responsive (e.g. background running)
let hidden = document.hidden; // background running would not call requestAnimationFrame
if (promisesF.length == 0) { // no handler functions
// requestAnimationFrame when the page is active
// execution interval is no less than AnimationFrame
return hidden || jf();
}
if (!hidden) {
let ret2 = new Promise(resolve => $$setTimeout(resolve, 16));
let promises = promisesF.map(pf); //microTasks
let ret1 = Promise.all(promises);
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);
promises.length = 0;
} else {
promisesF.forEach(pf);
}
promisesF.length = 0;
})
};
(jf = $$requestAnimationFrame.bind(window, tf))();
$$setInterval(() => {
// no response of requestAnimationFrame; e.g. running in background
if (Date.now() > bgExecutionAt) {
toResetFuncHandlers = true; // prevent non-responsive function handler in background running
tf();
}
}, 250);
// i.e. 4 times per second for background execution - to keep YouTube application functional
window.addEventListener("yt-navigate-finish", () => {
toResetFuncHandlers = true; // ensure all function handlers can be executed after YouTube navigation.
}, true); // capturing event - to let it runs before all everything else.
// Your code here...
})();