您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
减少YouTube影片所致的能源消耗
当前为
- // ==UserScript==
- // @name YouTube CPU Tamer by AnimationFrame
- // @name:en YouTube CPU Tamer by AnimationFrame
- // @name:ja 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 2022.11.26
- // @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:ja YouTubeビデオのエネルギーインパクトを減らす
- // @description:zh-TW 減少YouTube影片所致的能源消耗
- // @description:zh-CN 减少YouTube影片所致的能源消耗
- // @author CY Fung
- // @match https://www.youtube.com/*
- // @match https://www.youtube.com/embed/*
- // @match https://www.youtube-nocookie.com/embed/*
- // @match https://www.youtube.com/live_chat*
- // @match https://www.youtube.com/live_chat_replay*
- // @match https://music.youtube.com/*
- // @icon https://www.google.com/s2/favicons?domain=youtube.com
- // @run-at document-start
- // @grant none
- // ==/UserScript==
- /* jshint esversion:8 */
- (function () {
- 'use strict';
- const $busy = Symbol('$busy');
- // Number.MAX_SAFE_INTEGER = 9007199254740991
- const INT_INITIAL_VALUE = 8192; // 1 ~ {INT_INITIAL_VALUE} are reserved for native setTimeout/setInterval
- const SAFE_INT_LIMIT = 2251799813685248; // in case cid would be used for multiplying
- const SAFE_INT_REDUCED = 67108864; // avoid persistent interval handlers with cids between {INT_INITIAL_VALUE + 1} and {SAFE_INT_REDUCED - 1}
- let toResetFuncHandlers = false;
- const [$$requestAnimationFrame, $$setTimeout, $$setInterval, $$clearTimeout, $$clearInterval, sb, rm] = (()=>{
- let [window] = new Function('return [window];')(); // real window object
- const hkey_script = 'nzsxclvflluv';
- if (window[hkey_script]) throw new Error('Duplicated Userscript Calling'); // avoid duplicated scripting
- window[hkey_script] = true;
- // copies of native functions
- /** @type {requestAnimationFrame} */
- const $$requestAnimationFrame = window.requestAnimationFrame.bind(window); // core looping
- /** @type {setTimeout} */
- const $$setTimeout = window.setTimeout.bind(window); // for race
- /** @type {setInterval} */
- const $$setInterval = window.setInterval.bind(window); // for background execution
- /** @type {clearTimeout} */
- const $$clearTimeout = window.clearTimeout.bind(window); // for native clearTimeout
- /** @type {clearInterval} */
- const $$clearInterval = window.clearInterval.bind(window); // for native clearInterval
- let mi = INT_INITIAL_VALUE; // skip first {INT_INITIAL_VALUE} cids to avoid browser not yet initialized
- /** @type { Map<number, object> } */
- const sb = new Map();
- let sFunc = (prop) => {
- return (func, ms, ...args) => {
- mi++; // start at {INT_INITIAL_VALUE + 1}
- if (mi > SAFE_INT_LIMIT) mi = SAFE_INT_REDUCED; // just in case
- let handler = args.length > 0 ? func.bind(null, ...args) : func; // original func if no extra argument
- handler[$busy] || (handler[$busy] = 0);
- sb.set(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 = function (jd) {
- if (!jd) return; // native setInterval & setTimeout start from 1
- let o = sb.get(jd);
- if (typeof o !== 'object') { // to clear the same cid is unlikely to happen || requiring nativeFn is unlikely to happen
- if (jd <= INT_INITIAL_VALUE) this.nativeFn(jd); // only for clearTimeout & clearInterval
- }else{
- for (let k in o) o[k] = null;
- o = null;
- sb.delete(jd);
- }
- };
- window.setTimeout = sFunc('timeout');
- window.setInterval = sFunc('interval');
- window.clearTimeout = rm.bind({
- nativeFn: $$clearTimeout
- });
- window.clearInterval = rm.bind({
- nativeFn: $$clearInterval
- });
- // window.clearInterval = window.clearTimeout = rm;
- 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.
- window = null;
- sFunc = null;
- return [$$requestAnimationFrame, $$setTimeout, $$setInterval, $$clearTimeout, $$clearInterval, sb, rm];
- })();
- const delay16ms = (resolve => $$setTimeout(resolve, 16));
- 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]--;
- handler = null; // remove the reference of `handler`
- resolve();
- resolve = null; // remove the reference of `resolve`
- })
- );
- let bgExecutionAt = 0; // set at 0 to trigger tf in background startup when requestAnimationFrame is not responsive
- let dexActivePage = true; // true for default; false when checking triggered by setInterval
- /** @type {Function|null} */
- let interupter = null;
- const infiniteLooper = (resolve) => $$requestAnimationFrame(interupter = resolve); // rAF will not execute if document is hidden
- const mbx = async () => {
- // microTask #1
- let now = Date.now();
- // bgExecutionAt = now + 160; // if requestAnimationFrame is not responsive (e.g. background running)
- let promisesF = [];
- const lsb = sb;
- for (let jb of lsb.keys()) {
- const o = lsb.get(jb);
- let {
- handler,
- // timeout,
- interval,
- nextAt
- } = o;
- if (now < nextAt) continue;
- handler[$busy]++;
- promisesF.push(handler);
- if (interval > 0) { // prevent undefined, zero, negative values
- const _interval = +interval; // convertion from string to number if necessary; decimal is acceptable
- if (o.nextAt + _interval > now) o.nextAt += _interval;
- else if (o.nextAt + 2 * _interval > now) o.nextAt += 2 * _interval;
- else if (o.nextAt + 3 * _interval > now) o.nextAt += 3 * _interval;
- else if (o.nextAt + 4 * _interval > now) o.nextAt += 4 * _interval;
- else if (o.nextAt + 5 * _interval > now) o.nextAt += 5 * _interval;
- else o.nextAt = now + _interval;
- } else {
- // jb in sb must > INT_INITIAL_VALUE
- rm(jb); // remove timeout
- }
- }
- await Promise.resolve(0); // split microTasks inside async()
- // microTask #2
- // bgExecutionAt = Date.now() + 160; // if requestAnimationFrame is not responsive (e.g. background running)
- if (promisesF.length === 0) { // no handler functions
- // requestAnimationFrame when the page is active
- // execution interval is no less than AnimationFrame
- promisesF = null;
- } else if (dexActivePage) {
- let ret2 = new Promise(delay16ms);
- let ret3 = new Promise(resolveK => {
- // error would not affect calling the next tick
- Promise.all(promisesF.map(pf)).then(resolveK); //microTasks
- promisesF.length = 0;
- promisesF = null;
- })
- let race = Promise.race([ret2, ret3]);
- // ensure checking function must be called after 16ms to maintain visual changes in high fps.
- // >16ms examples: repaint/reflow, change of style/content
- await race;
- } else {
- new Promise(resolveK => {
- // error would not affect calling the next tick
- promisesF.forEach(pf); //microTasks
- promisesF.length = 0;
- promisesF = null;
- })
- }
- };
- (async () => {
- while (true) {
- bgExecutionAt = Date.now() + 160;
- await new Promise(infiniteLooper);
- if (interupter === null) {
- // triggered by setInterval
- dexActivePage = false;
- } else {
- // triggered by rAF
- interupter = null;
- if (dexActivePage === false) toResetFuncHandlers = true;
- dexActivePage = true;
- }
- if (toResetFuncHandlers) {
- // true if page change from hidden to visible OR yt-finish
- toResetFuncHandlers = false;
- for (let eb of sb.values()) eb.handler[$busy] = 0; // including the functions with error
- }
- await mbx();
- }
- })();
- $$setInterval(() => {
- // no response of requestAnimationFrame; e.g. running in background
- let interupter_t = interupter, now;
- if (interupter_t && (now = Date.now()) > bgExecutionAt) {
- // interupter not triggered by rAF
- bgExecutionAt = now + 230;
- interupter = null;
- interupter_t();
- }
- }, 250);
- // i.e. 4 times per second for background execution - to keep YouTube application functional
- // if there is Timer Throttling for background running, the execution become the same as native setTimeout & setInterval.
- })();