Réducteur de charge CPU YouTube – Édition Hybride

Réduit considérablement l'utilisation du CPU sur YouTube en régulant intelligemment les minuteurs, tout en protégeant les fonctions critiques du lecteur pour éviter les blocages.

// ==UserScript==
// @name              YouTube CPU Tamer – Hybrid Edition
// @name:ja           YouTube CPU負荷軽減スクリプト – ハイブリッド方式
// @name:en           YouTube CPU Tamer – Hybrid Edition
// @name:zh-CN        YouTube CPU减负脚本 – 混合策略
// @name:zh-TW        YouTube CPU負載減輕工具 – 混合策略
// @name:ko           YouTube CPU 부하 감소 스크립트 – 하이브리드 방식
// @name:fr           Réducteur de charge CPU YouTube – Édition Hybride
// @name:es           Reductor de carga de CPU para YouTube – Edición Híbrida
// @name:de           YouTube CPU-Last-Reduzierer – Hybrid-Edition
// @name:pt-BR        Redutor de uso da CPU no YouTube – Edição Híbrida
// @name:ru           Снижение нагрузки на CPU в YouTube – Гибридная версия
// @version           4.80
// @description       Dramatically reduces CPU usage on YouTube by intelligently throttling timers, while protecting critical player functions to prevent freezing.
// @description:ja    YouTubeのCPU負荷を劇的に削減します。動画プレイヤーの重要機能を保護し、無限ロードなどのフリーズ現象を防止する安定性重視の設計です。
// @description:en    Dramatically reduces CPU usage on YouTube by intelligently throttling timers, while protecting critical player functions to prevent freezing.
// @description:zh-CN 通过智能节流计时器,显著降低YouTube的CPU使用率,同时保护关键播放器功能以防冻结。
// @description:zh-TW 透過智慧節流計時器,顯著降低YouTube的CPU使用率,同時保護關鍵播放器功能以防凍結。
// @description:ko    타이머를 지능적으로 조절하여 YouTube의 CPU 사용량을 크게 줄이고, 중요한 플레이어 기능을 보호하여 멈춤 현상을 방지합니다.
// @description:fr    Réduit considérablement l'utilisation du CPU sur YouTube en régulant intelligemment les minuteurs, tout en protégeant les fonctions critiques du lecteur pour éviter les blocages.
// @description:es    Reduce drásticamente el uso de la CPU en YouTube al regular inteligentemente los temporizadores, protegiendo las funciones críticas del reproductor para evitar congelaciones.
// @description:de    Reduziert die CPU-Auslastung auf YouTube drastisch durch intelligentes Drosseln von Timern, während kritische Player-Funktionen geschützt werden, um ein Einfrieren zu verhindern.
// @description:pt-BR Reduz drasticamente o uso da CPU no YouTube ao limitar inteligentemente os temporizadores, protegendo as funções críticas do player para evitar congelamentos.
// @description:ru    Значительно снижает нагрузку на процессор на YouTube за счет интеллектуального регулирования таймеров, защищая при этом критически важные функции плеера для предотвращения зависаний.
// @namespace         https://github.com/koyasi777/youtube-cpu-tamer-hybrid
// @author            koyasi777
// @match             https://www.youtube.com/*
// @match             https://www.youtube.com/embed/*
// @match             https://www.youtube-nocookie.com/embed/*
// @match             https://music.youtube.com/*
// @run-at            document-start
// @grant             none
// @inject-into       page
// @license           MIT
// @icon              https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @homepageURL       https://github.com/koyasi777/youtube-cpu-tamer-hybrid
// @supportURL        https://github.com/koyasi777/youtube-cpu-tamer-hybrid/issues
// ==/UserScript==

(() => {
  "use strict";

  // --- 設定値 ---
  const MIN_DELAY_THRESHOLD = 250;
  const PLAYER_READY_SELECTOR = "#movie_player .ytp-chrome-bottom"; // プレイヤー準備完了を判断するためのDOMセレクタ
  const REPATCH_TIMEOUT = 10000; // プレイヤーを検出できなかった場合に再パッチするまでの最大待機時間(ms)

  // --- スクリプトの初期化と多重実行防止 ---
  const FLAG = "__yt_cpu_tamer_hybrid_running__";
  if (window[FLAG]) return;
  window[FLAG] = true;

  // --- 非同期ヘルパー関数 ---
  const nextAnimationFrame = () => new Promise(resolve => requestAnimationFrame(resolve));
  const waitForDocReady = async () => {
    while (!document.documentElement || !document.head) {
      await nextAnimationFrame();
    }
  };

  const PromiseExt = (() => {
    let _res, _rej;
    const shim = (r, j) => { _res = r; _rej = j; };
    return class extends Promise {
      constructor(cb = shim) { super(cb); if (cb === shim) { this.resolve = _res; this.reject = _rej; } }
    };
  })();

  const setup = async () => {
    await waitForDocReady();

    // --- 1. ネイティブタイマーAPIの安全な取得 ---
    const FRAME_ID = "yt-cpu-tamer-timer-frame";
    let frame = document.getElementById(FRAME_ID);
    if (frame && (!frame.contentWindow || !frame.contentWindow.setTimeout)) {
      frame.remove();
      frame = null;
    }
    if (!frame) {
      frame = document.createElement("iframe");
      frame.id = FRAME_ID;
      frame.style.display = "none";
      frame.sandbox = "allow-same-origin allow-scripts";
      frame.srcdoc = "<!doctype html><title>yt-cpu-tamer-timer-provider</title>";
      document.documentElement.appendChild(frame);
    }
    while (!frame.contentWindow || !frame.contentWindow.setTimeout) {
      await nextAnimationFrame();
    }

    const nativeTimers = {
      setTimeout: frame.contentWindow.setTimeout.bind(frame.contentWindow),
      setInterval: frame.contentWindow.setInterval.bind(frame.contentWindow),
      clearTimeout: frame.contentWindow.clearTimeout.bind(frame.contentWindow),
      clearInterval: frame.contentWindow.clearInterval.bind(frame.contentWindow),
    };

    // --- 2. バックグラウンド用トリガーの準備 ---
    const DUMMY_ID = "yt-cpu-tamer-trigger-node";
    let dummy = document.getElementById(DUMMY_ID);
    if (!dummy) {
      dummy = document.createElement("div");
      dummy.id = DUMMY_ID;
      dummy.style.display = "none";
      document.documentElement.appendChild(dummy);
    }

    // --- 3. ハイブリッド戦略の構築 ---
    let timersAreThrottled = document.visibilityState === "visible";

    const makeHybridTrigger = () => {
      if (document.visibilityState === "visible") {
        return (callback) => {
          const p = new PromiseExt();
          requestAnimationFrame(p.resolve);
          return p.then(callback);
        };
      } else {
        return (callback) => {
          const p = new PromiseExt();
          const MO = new MutationObserver(() => {
            MO.disconnect();
            p.resolve();
          });
          MO.observe(dummy, { attributes: true });
          dummy.setAttribute("data-yt-cpu-tamer-trigger", Math.random().toString(36));
          return p.then(callback);
        };
      }
    };

    let currentTrigger = makeHybridTrigger();

    const VC_LISTENER_FLAG = "__yt_cpu_tamer_visibility_listener__";
    if (!window[VC_LISTENER_FLAG]) {
      document.addEventListener("visibilitychange", () => {
        timersAreThrottled = document.visibilityState === "visible";
        currentTrigger = makeHybridTrigger();
        console.log(`[YouTube CPU Tamer] Visibility changed. Throttling is now ${timersAreThrottled ? 'ON' : 'OFF'}`);
      });
      window[VC_LISTENER_FLAG] = true;
    }

    // --- 4. タイマー関数のパッチ(モンキーパッチ) ---
    const activeTimeouts = new Set();

    const makeTimeoutPatcher = (nativeTimeout, pool) => {
      return function patchedSetTimeout(callback, delay = 0, ...args) {
        if (typeof callback !== "function" || !timersAreThrottled || delay < MIN_DELAY_THRESHOLD) {
          return nativeTimeout(callback, delay, ...args);
        }
        const id = nativeTimeout(() => {
          currentTrigger(() => callback.apply(window, args));
        }, delay);
        pool.add(id);
        return id;
      };
    };

    const makeClear = (nativeClear, pool) => (id) => {
      if (pool.has(id)) pool.delete(id);
      nativeClear(id);
    };

    const mirrorToString = (patched, native) => {
      try {
        patched.toString = native.toString.bind(native);
      } catch (e) { /* ignore */ }
    };

    const installPatches = () => {
      window.setTimeout = makeTimeoutPatcher(nativeTimers.setTimeout, activeTimeouts);
      window.clearTimeout = makeClear(nativeTimers.clearTimeout, activeTimeouts);
      window.setInterval = nativeTimers.setInterval;
      window.clearInterval = nativeTimers.clearInterval;
      mirrorToString(window.setTimeout, nativeTimers.setTimeout);
      mirrorToString(window.setInterval, nativeTimers.setInterval);
      mirrorToString(window.clearTimeout, nativeTimers.clearTimeout);
      mirrorToString(window.clearInterval, nativeTimers.clearInterval);
      console.log("[YouTube CPU Tamer] Patches installed.");
    };

    const uninstallPatches = () => {
        window.setTimeout = nativeTimers.setTimeout;
        window.clearTimeout = nativeTimers.clearTimeout;
        window.setInterval = nativeTimers.setInterval;
        window.clearInterval = nativeTimers.clearInterval;
        console.log("[YouTube CPU Tamer] Patches uninstalled. Timers are native.");
    };

    // --- 5. 初期パッチ適用 ---
    installPatches();

    // --- 6. YouTubeのSPA遷移への対応(v4.80: DOM監視によるインテリジェント方式) ---
    let navigationHandler = null;
    window.addEventListener("yt-navigate-finish", () => {
      if (navigationHandler) {
          navigationHandler.abort(); // 既に進行中のハンドラがあれば中止
      }

      // 新しいナビゲーションに対応するハンドラを生成
      navigationHandler = (function() {
          let aborted = false;
          let observer = null;
          let timeoutId = null;

          const cleanup = () => {
              if (observer) observer.disconnect();
              if (timeoutId) nativeTimers.clearTimeout(timeoutId);
              observer = null;
              timeoutId = null;
              navigationHandler = null;
          };

          const handleRepatch = (reason) => {
              if (aborted) return;
              console.log(`[YouTube CPU Tamer] ${reason}. Re-installing patches...`);
              installPatches();
              timersAreThrottled = document.visibilityState === "visible";
              console.log(`[YouTube CPU Tamer] Re-patching complete. Throttling is now ${timersAreThrottled ? 'ON' : 'OFF'}`);
              cleanup();
          };

          // --- メインロジック ---
          console.log("[YouTube CPU Tamer] 'yt-navigate-finish' detected. Uninstalling patches...");
          uninstallPatches();

          // プレイヤー要素を監視
          observer = new MutationObserver(() => {
              if (document.querySelector(PLAYER_READY_SELECTOR)) {
                  handleRepatch("Player element detected");
              }
          });

          if(document.body){
            observer.observe(document.body, { childList: true, subtree: true });
          }

          // タイムアウトを設定(安全装置)
          timeoutId = nativeTimers.setTimeout(() => {
              handleRepatch("Repatch timeout reached");
          }, REPATCH_TIMEOUT);

          // 既にプレイヤーが存在する場合に即時実行
          if (document.querySelector(PLAYER_READY_SELECTOR)) {
              nativeTimers.setTimeout(() => handleRepatch("Player element already exists"), 0);
          }

          return {
              abort: () => {
                  if (aborted) return;
                  console.log("[YouTube CPU Tamer] Aborting previous navigation handler.");
                  aborted = true;
                  cleanup();
              }
          };
      })();
    });
  };

  setup().catch(err => console.error("[YouTube CPU Tamer] A critical error occurred during setup:", err));

})();