YouTube CPU Tamer by AnimationFrame

減少YouTube影片所致的能源消耗

目前為 2021-09-04 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name YouTube CPU Tamer by AnimationFrame
  3. // @name:en YouTube CPU Tamer by AnimationFrame
  4. // @name:jp YouTube CPU Tamer by AnimationFrame
  5. // @name:zh-tw YouTube CPU Tamer by AnimationFrame
  6. // @name:zh-cn YouTube CPU Tamer by AnimationFrame
  7. // @namespace http://tampermonkey.net/
  8. // @version 2021.09.04
  9. // @license MIT License
  10. // @description Reduce Browser's Energy Impact for playing YouTube Video
  11. // @description:en Reduce Browser's Energy Impact for playing YouTube Video
  12. // @description:jp YouTubeビデオのエネルギーインパクトを減らす
  13. // @description:zh-tw 減少YouTube影片所致的能源消耗
  14. // @description:zh-cn 减少YouTube影片所致的能源消耗
  15. // @author CY Fung
  16. // @include https://www.youtube.com/*
  17. // @include https://www.youtube.com/embed/*
  18. // @include https://www.youtube-nocookie.com/embed/*
  19. // @include https://www.youtube.com/live_chat*
  20. // @include https://www.youtube.com/live_chat_replay*
  21. // @icon https://www.google.com/s2/favicons?domain=youtube.com
  22. // @run-at document-start
  23. // @grant none
  24. // ==/UserScript==
  25. (function $$() {
  26. 'use strict';
  27.  
  28. const window = new Function('return window;')(); // real window object
  29.  
  30. const hkey_script = 'nzsxclvflluv';
  31. if (window[hkey_script]) return; // avoid duplicated scripting
  32. window[hkey_script] = true;
  33.  
  34. //if (!document.documentElement) return window.requestAnimationFrame($$); // no document access
  35.  
  36. // copies of native functions
  37. const $$requestAnimationFrame = window.requestAnimationFrame.bind(window); // core looping
  38. const $$setTimeout = window.setTimeout.bind(window); // for race
  39. const $$setInterval = window.setInterval.bind(window); // for background execution
  40.  
  41. const $busy = Symbol('$busy');
  42.  
  43. let mi = 0;
  44. const sb = {};
  45. const sFunc = (prop) => {
  46. return (func, ms, ...args) => {
  47. mi++; // start at 1
  48. let handler = args.length > 0 ? func.bind(null, ...args) : func; // original func if no extra argument
  49. handler[$busy] = handler[$busy] || 0;
  50. sb[mi] = {
  51. handler,
  52. [prop]: ms, // timeout / interval; value can be undefined
  53. nextAt: Date.now() + (ms > 0 ? ms : 0) // overload for setTimeout(func);
  54. };
  55. return mi;
  56. };
  57. };
  58. const rm = (jd) => {
  59. let o = sb[jd];
  60. if (typeof o != 'object') return;
  61. for (let k in o) o[k] = null;
  62. o = null;
  63. sb[jd] = null;
  64. delete sb[jd];
  65. };
  66. window.setTimeout = sFunc('timeout');
  67. window.setInterval = sFunc('interval');
  68. window.clearInterval = window.clearTimeout = rm;
  69.  
  70.  
  71. const pf = (
  72. handler => new Promise(resolve => {
  73. // try catch is not required - no further execution on the handler
  74. // 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)
  75. // For function handler with low energy impact, discard or not discard depends on system performance
  76. if (handler[$busy] == 1) handler();
  77. handler[$busy]--;
  78. handler = null; // remove the reference of `handler`
  79. resolve();
  80. resolve = null; // remove the reference of `resolve`
  81. })
  82. );
  83.  
  84. let jf, tf, toResetFuncHandlers = false;
  85. let bgExecutionAt = 0; // set at 0 to trigger tf in background startup when requestAnimationFrame is not responsive
  86. tf = () => {
  87. if (toResetFuncHandlers) {
  88. toResetFuncHandlers = false;
  89. for (let jb in sb) sb[jb].handler[$busy] = 0; // including the functions with error
  90. }
  91. new Promise(resolveApp1 => {
  92. // microTask #1
  93. let now = Date.now();
  94. bgExecutionAt = now + 160; // if requestAnimationFrame is not responsive (e.g. background running)
  95. let promisesF = [];
  96. for (let jb in sb) {
  97. const o = sb[jb];
  98. let {
  99. handler,
  100. // timeout,
  101. interval,
  102. nextAt
  103. } = o;
  104. if (now < nextAt) continue;
  105. handler[$busy]++;
  106. promisesF.push(handler);
  107. if (interval > 0) { // prevent undefined, zero, negative values
  108. o.nextAt += +interval; // convertion from string to number if necessary; decimal is acceptable
  109. } else {
  110. rm(jb); // remove timeout
  111. }
  112. }
  113. resolveApp1(promisesF);
  114. }).then(promisesF => {
  115. // microTask #2
  116. bgExecutionAt = Date.now() + 160; // if requestAnimationFrame is not responsive (e.g. background running)
  117. let hidden = document.hidden; // background running would not call requestAnimationFrame
  118. if (promisesF.length == 0) { // no handler functions
  119. // requestAnimationFrame when the page is active
  120. // execution interval is no less than AnimationFrame
  121. return hidden || jf();
  122. }
  123. if (!hidden) {
  124. let ret2 = new Promise(resolve => $$setTimeout(resolve, 16));
  125. let promises = promisesF.map(pf); //microTasks
  126. let ret1 = Promise.all(promises);
  127. let race = Promise.race([ret1, ret2]);
  128. // ensure jf must be called after 16ms to maintain visual changes in high fps.
  129. // >16ms examples: repaint/reflow, change of style/content
  130. race.then(jf);
  131. promises.length = 0;
  132. } else {
  133. promisesF.forEach(pf);
  134. }
  135. promisesF.length = 0;
  136. })
  137. };
  138. (jf = $$requestAnimationFrame.bind(window, tf))();
  139.  
  140. $$setInterval(() => {
  141. // no response of requestAnimationFrame; e.g. running in background
  142. // toResetFuncHandlers = true;
  143. if (Date.now() > bgExecutionAt) {
  144. toResetFuncHandlers = true;
  145. tf();
  146. }
  147. }, 250);
  148. // i.e. 4 times per second for background execution - to keep YouTube application functional
  149. // if there is Timer Throttling for background running, the execution become the same as native setTimeout & setInterval.
  150.  
  151. window.addEventListener("yt-navigate-finish", () => {
  152. toResetFuncHandlers = true; // ensure all function handlers can be executed after YouTube navigation.
  153. }, true); // capturing event - to let it runs before all everything else.
  154.  
  155. // Your code here...
  156. })();