Greasy Fork

来自缓存

Greasy Fork is available in English.

通用网页视频倍速控制器

在页面左上角添加一个浮窗,通过填写的数值来控制网页上所有视频/音频的播放速度,并拦截 JS 时间引擎以加速 Unity/Canvas 游戏。新增F7暂停/恢复、F5重置、F6加速功能,UI已添加按键提示。

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         通用网页视频倍速控制器
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  在页面左上角添加一个浮窗,通过填写的数值来控制网页上所有视频/音频的播放速度,并拦截 JS 时间引擎以加速 Unity/Canvas 游戏。新增F7暂停/恢复、F5重置、F6加速功能,UI已添加按键提示。
// @author       Gemini
// @match        *://*/*
// @grant        none
// @run-at       document-start
// @license      CC
// ==/UserScript==

(function() {
    'use strict';

    // --- 全局配置 ---
    let currentSpeed = 1.0;
    let isPaused = false; // 新增:暂停状态

    // --- 时间劫持引擎 (针对 Unity/Canvas/JS 游戏) ---
    // 保存原生方法引用
    const nativeDateNow = Date.now;
    const nativePerformanceNow = performance.now.bind(performance);
    const nativeRequestAnimationFrame = window.requestAnimationFrame;

    // 时间累加器
    let lastRealTime = nativePerformanceNow();
    let virtualTime = lastRealTime;

    // 核心更新逻辑:计算虚拟时间
    function updateVirtualTime() {
        // 暂停状态下不更新虚拟时间
        if (isPaused) {
            return;
        }
        
        const realNow = nativePerformanceNow();
        const dt = realNow - lastRealTime;
        // 如果速度不为1,则应用倍率;否则保持同步防止漂移
        if (currentSpeed !== 1.0) {
            virtualTime += dt * currentSpeed;
        } else {
            virtualTime += dt;
        }
        lastRealTime = realNow;
    }

    // 劫持 performance.now
    // Unity 及其它游戏引擎主要依赖此 API 计算 deltaTime
    Object.defineProperty(performance, 'now', {
        value: function() {
            updateVirtualTime();
            return virtualTime;
        },
        configurable: true,
        writable: true
    });

    // 劫持 Date.now
    // 部分老旧逻辑或服务器同步可能用到
    Date.now = function() {
        updateVirtualTime();
        // 基准时间 + 虚拟流逝时间
        return Math.floor(nativeDateNow() + (virtualTime - nativePerformanceNow()));
    };

    // 劫持 requestAnimationFrame
    // 确保回调函数接收到的 timestamp 参数也是加速后的
    window.requestAnimationFrame = function(callback) {
        return nativeRequestAnimationFrame(function(timestamp) {
            updateVirtualTime();
            // 将虚拟时间传递给回调
            callback(virtualTime);
        });
    };

    // --- 新增功能:时间控制 ---
    function togglePause() {
        isPaused = !isPaused;
        if (isPaused) {
            // 暂停时,保持虚拟时间不变
            lastRealTime = nativePerformanceNow();
            statusMsg.innerText = `已暂停 (JS+媒体)`;
            statusMsg.style.color = '#FF9800';
        } else {
            // 恢复时,重置基准时间
            lastRealTime = nativePerformanceNow();
            virtualTime = nativePerformanceNow();
            statusMsg.innerText = `当前: ${currentSpeed}x (JS+媒体)`;
            statusMsg.style.color = '#81C784';
        }
    }

    function resetTime() {
        isPaused = false;
        lastRealTime = nativePerformanceNow();
        virtualTime = nativePerformanceNow();
        currentSpeed = 1.0;
        speedInput.value = 1.0;
        applyMediaSpeed(1.0);
        statusMsg.innerText = '已重置 (1.0x)';
        statusMsg.style.color = '#aaa';
    }

    function increaseTime() {
        if (isPaused) {
            return;
        }
        currentSpeed *= 5;
        if (currentSpeed > 100) currentSpeed = 100; // 防止过大
        speedInput.value = currentSpeed;
        applyMediaSpeed(currentSpeed);
        statusMsg.innerText = `当前: ${currentSpeed}x (JS+媒体)`;
        statusMsg.style.color = '#81C784';
    }

    // --- UI 界面构建 (等待 DOM 就绪) ---
    function initUI() {
        // 创建 UI 界面
        const panel = document.createElement('div');
        panel.style.cssText = `
            position: fixed;
            top: 20px;
            left: 20px;
            z-index: 2147483647; /* 确保最高层级 */
            background-color: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 10px;
            border-radius: 8px;
            font-family: Arial, sans-serif;
            font-size: 14px;
            box-shadow: 0 4px 6px rgba(0,0,0,0.1);
            display: flex;
            flex-direction: column;
            gap: 5px;
            transition: opacity 0.3s;
            opacity: 0.3;
            pointer-events: auto;
        `;

        panel.onmouseenter = () => { panel.style.opacity = '1'; };
        panel.onmouseleave = () => { panel.style.opacity = '0.3'; };

        const title = document.createElement('div');
        title.innerText = '⚡ 全局加速';
        title.style.fontWeight = 'bold';
        title.style.marginBottom = '5px';

        const inputContainer = document.createElement('div');
        inputContainer.style.display = 'flex';
        inputContainer.style.alignItems = 'center';
        inputContainer.style.gap = '5px';

        const speedInput = document.createElement('input');
        speedInput.type = 'number';
        speedInput.value = currentSpeed;
        speedInput.step = 0.1;
        speedInput.style.width = '60px';
        speedInput.style.padding = '4px';
        speedInput.style.borderRadius = '4px';
        speedInput.style.border = 'none';
        speedInput.style.color = '#333';

        const setBtn = document.createElement('button');
        setBtn.innerText = '应用';
        setBtn.style.padding = '4px 8px';
        setBtn.style.cursor = 'pointer';
        setBtn.style.backgroundColor = '#4CAF50';
        setBtn.style.color = 'white';
        setBtn.style.border = 'none';
        setBtn.style.borderRadius = '4px';

        const resetBtn = document.createElement('button');
        resetBtn.innerText = '重置';
        resetBtn.style.padding = '4px 8px';
        resetBtn.style.cursor = 'pointer';
        resetBtn.style.backgroundColor = '#f44336';
        resetBtn.style.color = 'white';
        resetBtn.style.border = 'none';
        resetBtn.style.borderRadius = '4px';
        resetBtn.style.marginTop = '5px';

        const statusMsg = document.createElement('div');
        statusMsg.style.fontSize = '12px';
        statusMsg.style.color = '#aaa';
        statusMsg.style.marginTop = '5px';
        statusMsg.innerText = '当前: 1.0x';

        // 新增按键提示
        const keyHint = document.createElement('div');
        keyHint.style.fontSize = '10px';
        keyHint.style.color = '#aaa';
        keyHint.style.marginTop = '2px';
        keyHint.innerText = 'F7: 暂停/恢复 | F5: 重置 | F6: 5倍加速';

        inputContainer.appendChild(speedInput);
        inputContainer.appendChild(setBtn);
        panel.appendChild(title);
        panel.appendChild(inputContainer);
        panel.appendChild(resetBtn);
        panel.appendChild(statusMsg);
        panel.appendChild(keyHint); // 添加按键提示
        document.body.appendChild(panel);

        // --- 逻辑绑定 ---

        function applyMediaSpeed(speed) {
            // 针对传统 <video> 和 <audio>
            const mediaElements = document.querySelectorAll('video, audio');
            let count = 0;
            mediaElements.forEach(media => {
                media.playbackRate = speed;
                count++;
            });
            return count;
        }

        function updateSpeed() {
            const val = parseFloat(speedInput.value);
            if (!isNaN(val) && val > 0) {
                // 更新全局倍速变量 (影响 JS/Unity)
                // 重置基准时间,防止跳变过大
                updateVirtualTime();
                currentSpeed = val;

                // 应用到媒体标签
                const count = applyMediaSpeed(currentSpeed);

                statusMsg.innerText = `当前: ${currentSpeed}x (JS+媒体)`;
                statusMsg.style.color = '#81C784';
            } else {
                alert("请输入有效的数字!");
            }
        }

        setBtn.onclick = updateSpeed;
        speedInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') updateSpeed();
        });

        resetBtn.onclick = () => {
            updateVirtualTime();
            currentSpeed = 1.0;
            speedInput.value = 1.0;
            applyMediaSpeed(1.0);
            statusMsg.innerText = '当前: 1.0x';
            statusMsg.style.color = '#aaa';
        };

        // --- 新增功能:键盘控制 ---
        document.addEventListener('keydown', function(event) {
            if (event.key === 'F7') {
                event.preventDefault();
                togglePause();
            } else if (event.key === 'F5') {
                event.preventDefault();
                resetTime();
            } else if (event.key === 'F6') {
                event.preventDefault();
                increaseTime();
            }
        });

        // 自动维护循环 (媒体标签)
        setInterval(() => {
            if (currentSpeed !== 1.0) {
                applyMediaSpeed(currentSpeed);
            }
        }, 1000);
    }

    // --- 初始化 ---
    // 尽可能早地劫持时间,但 UI 需要等待 body 加载
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initUI);
    } else {
        initUI();
    }

})();