YouTube CPU Tamer by AnimationFrame

Reduce Browser's Energy Impact for playing YouTube Video

当前为 2021-08-29 提交的版本,查看 最新版本

  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.08.29.4
  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. let mi = 0;
  42. const sb = {};
  43. const sFunc = (prop) => {
  44. return (func, ms, ...args) => {
  45. mi++; // start at 1
  46. sb[mi] = {
  47. handler: args.length > 0 ? func.bind(null, ...args) : func, // original func if no extra argument
  48. [prop]: ms, // timeout / interval; value can be undefined
  49. nextAt: Date.now() + (ms > 0 ? ms : 0) // overload for setTimeout(func);
  50. };
  51. return mi;
  52. };
  53. };
  54. const rm = (jd) => {
  55. let o = sb[jd];
  56. if (typeof o != 'object') return;
  57. for (let k in o) o[k] = null;
  58. o = null;
  59. sb[jd] = null;
  60. delete sb[jd];
  61. };
  62. window.setTimeout = sFunc('timeout');
  63. window.setInterval = sFunc('interval');
  64. window.clearInterval = window.clearTimeout = rm;
  65.  
  66. const $busy = Symbol('$busy');
  67.  
  68. const pf = (handler => {
  69. if ($busy in handler) return;
  70. handler[$busy] = true; // max. 1 time of calling per each tf
  71. return new Promise(resolve => {
  72. // == microTask [Promise] ==
  73. // microTask is called after all handlers set with $busy, though it is not guaranteed.
  74. handler(); // try catch is not required - no further execution on the handler
  75. delete handler[$busy];
  76. resolve();
  77. // == microTask [Promise] ==
  78. });
  79. });
  80.  
  81. let jf, tf;
  82. let bgExecutionAt = 0; // set at 0 to trigger tf in background startup when requestAnimationFrame is not responsive
  83. tf = () => {
  84. let now = Date.now();
  85. // ======= MarcoTask [requestAnimationFrame / setInterval] =======
  86. let promises = [];
  87. for (let mi in sb) {
  88. const o = sb[mi];
  89. let {
  90. handler,
  91. // timeout,
  92. interval,
  93. nextAt
  94. } = o;
  95. if (now < nextAt) continue;
  96. promises.push(pf(handler));
  97. if (interval > 0) { // prevent undefined, zero, negative values
  98. o.nextAt += +interval; // convertion from string to number if necessary; decimal is acceptable
  99. } else {
  100. rm(mi);
  101. }
  102. }
  103. // ======= MarcoTask [requestAnimationFrame / setInterval] =======
  104. if (!document.hidden) {
  105. // calling requestAnimationFrame in visible tab(s) only
  106. if (promises.length > 0) {
  107. let ret1 = Promise.all(promises);
  108. let ret2 = new Promise(resolve => $$setTimeout(resolve, 16));
  109. let race = Promise.race([ret1, ret2]);
  110. // ensure jf must be called after 16ms to maintain visual changes in high fps.
  111. // >16ms examples: repaint/reflow, change of style/content
  112. race.then(jf);
  113. } else {
  114. jf(); // execution interval is no less than AnimationFrame
  115. }
  116. }
  117. bgExecutionAt = now + 160; // if requestAnimationFrame is not responsive (e.g. background running)
  118. };
  119. (jf = $$requestAnimationFrame.bind(window, tf))();
  120.  
  121. $$setInterval(() => {
  122. // no response of requestAnimationFrame; e.g. running in background
  123. if (Date.now() > bgExecutionAt) tf();
  124. }, 250);
  125. // i.e. 4 times per second for background execution - to keep YouTube application functional
  126.  
  127. // Your code here...
  128. })();