YouTube CPU Tamer – Hybrid Edition (Improved)

Reduce CPU load on YouTube using hybrid DOMMutation + AnimationFrame strategy with dynamic switching and delay correction

  1. // ==UserScript==
  2. // @name YouTube CPU Tamer – Hybrid Edition (Improved)
  3. // @name:ja YouTube CPU負荷軽減スクリプト – ハイブリッド方式(改良版)
  4. // @name:en YouTube CPU Tamer – Hybrid Edition (Improved)
  5. // @name:zh-CN YouTube CPU减负脚本 – 混合策略(改进版)
  6. // @name:zh-TW YouTube CPU負載減輕工具 – 混合策略(改良版)
  7. // @name:ko YouTube CPU 부하 감소 스크립트 – 하이브리드 방식(개선판)
  8. // @name:fr Réducteur de charge CPU YouTube – Édition Hybride (Améliorée)
  9. // @name:es Reductor de carga de CPU para YouTube – Edición Híbrida (Mejorada)
  10. // @name:de YouTube CPU-Last-Reduzierer – Hybrid-Edition (Verbessert)
  11. // @name:pt-BR Redutor de uso da CPU no YouTube – Edição Híbrida (Aprimorada)
  12. // @name:ru Снижение нагрузки на CPU в YouTube – Гибридная версия (Улучшенная)
  13. // @version 4.00
  14. // @description Reduce CPU load on YouTube using hybrid DOMMutation + AnimationFrame strategy with dynamic switching and delay correction
  15. // @description:ja DOM変化とrequestAnimationFrameを組み合わせたハイブリッド戦略でYouTubeのCPU負荷を大幅軽減!遅延補正&動的切替も搭載。
  16. // @description:en Reduce CPU load on YouTube using hybrid DOMMutation + AnimationFrame strategy with dynamic switching and delay correction
  17. // @description:zh-CN 使用混合DOMMutation和requestAnimationFrame策略动态切换并校正延迟,降低YouTube的CPU负载
  18. // @description:zh-TW 採用混合DOMMutation與requestAnimationFrame策略,動態切換並修正延遲,降低YouTube的CPU負載
  19. // @description:ko DOM 변화 감지 + 애니메이션 프레임 전략으로 YouTube CPU 부하 감소, 지연 보정 및 동적 전환 포함
  20. // @description:fr Réduisez la charge CPU de YouTube avec une stratégie hybride DOMMutation + AnimationFrame, avec commutation dynamique et correction du délai
  21. // @description:es Reduce la carga de CPU en YouTube mediante una estrategia híbrida de DOMMutation y AnimationFrame, con conmutación dinámica y corrección de retrasos
  22. // @description:de Reduzieren Sie die CPU-Last von YouTube mit einer hybriden DOMMutation + AnimationFrame-Strategie mit dynamischem Wechsel und Verzögerungskorrektur
  23. // @description:pt-BR Reduza o uso da CPU no YouTube com uma estratégia híbrida DOMMutation + AnimationFrame com troca dinâmica e correção de atraso
  24. // @description:ru Снижение нагрузки на CPU в YouTube с помощью гибридной стратегии DOMMutation + requestAnimationFrame с динамическим переключением и коррекцией задержки
  25. // @namespace https://github.com/koyasi777/youtube-cpu-tamer-hybrid
  26. // @author koyasi777
  27. // @match https://www.youtube.com/*
  28. // @match https://www.youtube.com/embed/*
  29. // @match https://www.youtube-nocookie.com/embed/*
  30. // @match https://music.youtube.com/*
  31. // @run-at document-start
  32. // @grant none
  33. // @inject-into page
  34. // @license MIT
  35. // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
  36. // @homepageURL https://github.com/koyasi777/youtube-cpu-tamer-hybrid
  37. // @supportURL https://github.com/koyasi777/youtube-cpu-tamer-hybrid/issues
  38. // ==/UserScript==
  39.  
  40. (() => {
  41. 'use strict';
  42.  
  43. const key = '__yt_cpu_tamer_hybrid_running__';
  44. if (window[key]) return;
  45. window[key] = true;
  46.  
  47. const waitForDocumentReady = async () => {
  48. while (!document.documentElement || !document.head) {
  49. await new Promise(r => requestAnimationFrame(r));
  50. }
  51. };
  52.  
  53. const PromiseExt = (() => {
  54. let _resolve, _reject;
  55. const h = (res, rej) => { _resolve = res; _reject = rej; };
  56. return class extends Promise {
  57. constructor(cb = h) {
  58. super(cb);
  59. if (cb === h) {
  60. this.resolve = _resolve;
  61. this.reject = _reject;
  62. }
  63. }
  64. };
  65. })();
  66.  
  67. const setup = async () => {
  68. await waitForDocumentReady();
  69.  
  70. const frameId = 'yt-cpu-tamer-frame';
  71. let iframe = document.getElementById(frameId);
  72. if (!iframe) {
  73. iframe = document.createElement('iframe');
  74. iframe.style.display = 'none';
  75. iframe.id = frameId;
  76. iframe.sandbox = 'allow-same-origin';
  77. document.documentElement.appendChild(iframe);
  78. }
  79. while (!iframe.contentWindow) await new Promise(r => requestAnimationFrame(r));
  80.  
  81. const {
  82. requestAnimationFrame: iframeRAF,
  83. setTimeout: iframeSetTimeout,
  84. setInterval: iframeSetInterval,
  85. clearTimeout: iframeClearTimeout,
  86. clearInterval: iframeClearInterval
  87. } = iframe.contentWindow;
  88.  
  89. const dummyDiv = document.createElement('div');
  90. dummyDiv.style.display = 'none';
  91. document.documentElement.appendChild(dummyDiv);
  92.  
  93. let currentTrigger = () => new Promise(r => iframeRAF(r));
  94.  
  95. const createHybridTriggerBase = () => {
  96. if (document.visibilityState === 'visible') {
  97. return (callback) => {
  98. const p = new PromiseExt();
  99. requestAnimationFrame(() => p.resolve());
  100. return p.then(callback);
  101. };
  102. } else {
  103. return (callback) => {
  104. const attr = 'data-yt-cpu-tamer';
  105. dummyDiv.setAttribute(attr, Math.random().toString(36));
  106. const p = new PromiseExt();
  107. const obs = new MutationObserver(() => {
  108. obs.disconnect();
  109. p.resolve();
  110. });
  111. obs.observe(dummyDiv, { attributes: true });
  112. return p.then(callback);
  113. };
  114. }
  115. };
  116.  
  117. currentTrigger = createHybridTriggerBase();
  118. document.addEventListener('visibilitychange', () => {
  119. currentTrigger = createHybridTriggerBase();
  120. });
  121.  
  122. const activeTimeouts = new Set();
  123. const activeIntervals = new Set();
  124.  
  125. const overrideTimer = (timerFn, clearFn, activeSet) => {
  126. return (fn, delay = 0, ...args) => {
  127. if (typeof fn !== 'function') return timerFn(fn, delay, ...args);
  128. let isActive = true;
  129. const handler = () => {
  130. currentTrigger(() => {
  131. if (isActive) fn(...args);
  132. });
  133. };
  134. const nativeId = timerFn(handler, delay);
  135. activeSet.add(nativeId);
  136. return nativeId;
  137. };
  138. };
  139.  
  140. const overrideClear = (clearFn, activeSet) => {
  141. return (id) => {
  142. if (activeSet.has(id)) activeSet.delete(id);
  143. clearFn(id);
  144. };
  145. };
  146.  
  147. // ✅ 初期化ログ(確実に出る)
  148. console.log('[YouTube CPU Tamer – Hybrid Edition] Initializing');
  149.  
  150. // ✅ タイマー/clear系のパッチ適用
  151. const runMainPatch = () => {
  152. window.setTimeout = overrideTimer(iframeSetTimeout, iframeClearTimeout, activeTimeouts);
  153. window.setInterval = overrideTimer(iframeSetInterval, iframeClearInterval, activeIntervals);
  154. window.clearTimeout = overrideClear(iframeClearTimeout, activeTimeouts);
  155. window.clearInterval = overrideClear(iframeClearInterval, activeIntervals);
  156.  
  157. const patchToString = (target, source) => {
  158. try {
  159. target.toString = source.toString.bind(source);
  160. } catch {}
  161. };
  162.  
  163. patchToString(window.setTimeout, iframeSetTimeout);
  164. patchToString(window.setInterval, iframeSetInterval);
  165. patchToString(window.clearTimeout, iframeClearTimeout);
  166. patchToString(window.clearInterval, iframeClearInterval);
  167.  
  168. console.log('[YouTube CPU Tamer – Hybrid Edition (Patched)] Active');
  169. };
  170.  
  171. // ✅ DOMContentLoaded or 即時実行
  172. if (document.readyState === 'loading') {
  173. window.addEventListener('DOMContentLoaded', runMainPatch);
  174. } else {
  175. runMainPatch();
  176. }
  177.  
  178. // ✅ SPA遷移監視ログ(DOM完全構築を待つ版)
  179. const waitForTargetNode = async () => {
  180. while (!document.querySelector('ytd-app') && !document.body) {
  181. await new Promise(r => requestAnimationFrame(r));
  182. }
  183. return document.querySelector('ytd-app') || document.body;
  184. };
  185.  
  186. const targetNode = await waitForTargetNode();
  187.  
  188. // ✅ YouTube独自のナビゲーションイベントにも対応
  189. window.addEventListener('yt-navigate-finish', () => {
  190. console.log('[YouTube CPU Tamer] yt-navigate-finish – reapplying patch');
  191. runMainPatch();
  192. });
  193.  
  194. };
  195.  
  196. setup();
  197. })();