Universal Video Speed Adjuster

Adjusts video playback speed with keyboard shortcuts and shows a temporary UI.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Universal Video Speed Adjuster
// @namespace    http://tampermonkey.net/
// @version      1.5
// @description  Adjusts video playback speed with keyboard shortcuts and shows a temporary UI.
// @match        *://*/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Set to true to enable debug logs in the console.
    const DEBUG_MODE = true;
    let originalPlaybackRate = null;
    let isSpeedBoostActive = false;

    // Function to create and display the playback rate UI
    function displayPlaybackRate(rate) {
        // Find if a display element already exists
        let displayElement = document.getElementById("playback-rate-display");
        if (!displayElement) {
            // If not, create a new one
            displayElement = document.createElement("div");
            displayElement.id = "playback-rate-display";

            // Apply styles to the display element
            Object.assign(displayElement.style, {
                position: 'fixed',
                top: '50%',
                left: '50%',
                transform: 'translate(-50%, -50%)',
                backgroundColor: 'rgba(0, 0, 0, 0.7)',
                color: 'white',
                padding: '10px 15px',
                borderRadius: '8px',
                fontFamily: 'Inter, sans-serif',
                fontSize: '18px',
                fontWeight: 'bold',
                zIndex: '99999',
                opacity: '0',
                transition: 'opacity 0.5s ease',
                pointerEvents: 'none'
            });
            document.body.appendChild(displayElement);
        }

        // Set the new text content and make it visible
        displayElement.textContent = `Speed: ${rate.toFixed(2)}x`;
        displayElement.style.opacity = '1';

        // Clear any existing fade-out timer
        clearTimeout(window.playbackRateTimeout);

        // Set a new timer to fade the UI out after 2 seconds
        window.playbackRateTimeout = setTimeout(() => {
            displayElement.style.opacity = '0';
            setTimeout(() => {
                if (displayElement.parentElement) {
                    displayElement.parentElement.removeChild(displayElement);
                }
            }, 500); // Wait for the transition to finish
        }, 2000);
    }

    // Function to find the video element and adjust its speed
    function adjustVideoSpeed(adjustment, newRate = null) {
        let videoFound = false;

        // 1. Check the main document for a video element
        const mainVideo = document.getElementsByTagName("video")[0];
        if (mainVideo) {
            if (newRate !== null) {
                mainVideo.playbackRate = newRate;
            } else {
                mainVideo.playbackRate = Math.max(0.25, mainVideo.playbackRate + adjustment);
            }
            displayPlaybackRate(mainVideo.playbackRate);
            videoFound = true;
            if (DEBUG_MODE) {
                console.log("Video found in the main document. Speed adjusted.");
            }
        }

        // 2. Check all iframes on the page
        const iframes = document.getElementsByTagName("iframe");
        for (let i = 0; i < iframes.length; i++) {
            try {
                // Try to access the iframe's contentWindow and document
                const iframeDocument = iframes[i].contentWindow.document;
                const iframeVideo = iframeDocument.getElementsByTagName("video")[0];
                if (iframeVideo) {
                    if (newRate !== null) {
                        iframeVideo.playbackRate = newRate;
                    } else {
                        iframeVideo.playbackRate = Math.max(0.25, iframeVideo.playbackRate + adjustment);
                    }
                    displayPlaybackRate(iframeVideo.playbackRate);
                    videoFound = true;
                    if (DEBUG_MODE) {
                        console.log("Video found inside an iframe. Speed adjusted.");
                    }
                }
            } catch (e) {
                // This catch block handles the Same-Origin Policy error
                // when we can't access the iframe's content.
                if (DEBUG_MODE) {
                    console.log(`Blocked from accessing iframe due to Same-Origin Policy: ${e.message}`);
                }
            }
        }

        if (!videoFound) {
            if (DEBUG_MODE) {
                console.warn("No video element found to adjust.");
            }
        }
    }

    // Attach a keyboard event listener to the entire document
    document.addEventListener('keydown', function(event) {
        if (DEBUG_MODE) {
            console.log(`Keydown event detected: key=${event.key}, code=${event.code}, altKey=${event.altKey}, ctrlKey=${event.ctrlKey}, shiftKey=${event.shiftKey}`);
        }

        // Check for Option + . (increase speed)
        if (event.altKey && event.code === 'Period') {
            event.preventDefault(); // Prevents default browser actions
            adjustVideoSpeed(0.5);
        }
        // Check for Option + , (decrease speed)
        else if (event.altKey && event.code === 'Comma') {
            event.preventDefault(); // Prevents default browser actions
            adjustVideoSpeed(-0.5);
        }
        // Check for Option + / (speed boost)
        else if (event.altKey && event.code === 'Slash' && !isSpeedBoostActive) {
            event.preventDefault(); // Prevents default browser actions
            const video = document.getElementsByTagName("video")[0];
            if (video) {
                originalPlaybackRate = video.playbackRate;
                isSpeedBoostActive = true;
                adjustVideoSpeed(null, 10);
            }
        }
    });

    document.addEventListener('keyup', function(event) {
        if (DEBUG_MODE) {
            console.log(`Keyup event detected: key=${event.key}, code=${event.code}`);
        }

        // Revert speed when Option or / is released
        if (isSpeedBoostActive && (event.code === 'AltLeft' || event.code === 'AltRight' || event.code === 'Slash')) {
            if (originalPlaybackRate !== null) {
                adjustVideoSpeed(null, originalPlaybackRate);
                originalPlaybackRate = null;
                isSpeedBoostActive = false;
            }
        }
    });
})();