Universal Video Controls

Universal video controls: B=+10s, V=-5s, X=+0.1 speed, Z=-0.1 speed, R=reset speed, G=2x, H=3x, J=4x

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Universal Video Controls
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Universal video controls: B=+10s, V=-5s, X=+0.1 speed, Z=-0.1 speed, R=reset speed, G=2x, H=3x, J=4x
// @author       Stroage 05
// @licence MIT
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    let originalSpeed = 1.0;
    let currentVideo = null;

    // Function to get the currently active video
    function getActiveVideo() {
        const videos = document.querySelectorAll('video');

        // Try to find a playing video first
        for (let video of videos) {
            if (!video.paused && !video.ended) {
                return video;
            }
        }

        // If no playing video, return the largest visible video
        let largestVideo = null;
        let largestArea = 0;

        for (let video of videos) {
            const rect = video.getBoundingClientRect();
            const area = rect.width * rect.height;

            // Check if video is visible
            if (area > largestArea && rect.width > 0 && rect.height > 0) {
                largestVideo = video;
                largestArea = area;
            }
        }

        return largestVideo || videos[0] || null;
    }

    // Function to show speed notification
    function showSpeedNotification(speed) {
        // Remove existing notification
        const existing = document.getElementById('video-speed-notification');
        if (existing) {
            existing.remove();
        }

        // Create notification element
        const notification = document.createElement('div');
        notification.id = 'video-speed-notification';
        notification.textContent = `${speed.toFixed(1)}x`;

        notification.style.position = 'fixed';
        notification.style.top = '25%';
        notification.style.left = '25%';
        notification.style.transform = 'translate(-50%, -50%)';
        notification.style.background = 'rgba(0, 0, 0, 0.55)';
        notification.style.color = 'white';
        notification.style.padding = '15px 25px';
        notification.style.borderRadius = '8px';
        notification.style.fontFamily = 'Arial, sans-serif';
        notification.style.fontSize = '18px';
        notification.style.fontWeight = 'bold';
        notification.style.zIndex = '999999';
        notification.style.pointerEvents = 'none';
        notification.style.userSelect = 'none';
        notification.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.3)';

        document.body.appendChild(notification);

        // Force reflow to ensure styles are applied
        notification.offsetHeight;

        // Auto-remove after 0.7 seconds
        setTimeout(() => {
            if (notification && notification.parentNode) {
                notification.style.opacity = '0';
                notification.style.transition = 'opacity 0.3s ease';
                setTimeout(() => {
                    if (notification && notification.parentNode) {
                        notification.parentNode.removeChild(notification);
                    }
                }, 300);
            }
        }, 700);
    }

    // Function to show time notification
    function showTimeNotification(action) {
        const video = getActiveVideo();
        if (!video) return;

        // Remove existing notification
        const existing = document.getElementById('video-time-notification');
        if (existing) {
            existing.remove();
        }

        // Create notification element
        const notification = document.createElement('div');
        notification.id = 'video-time-notification';

        const currentTime = Math.floor(video.currentTime);
        const minutes = Math.floor(currentTime / 60);
        const seconds = currentTime % 60;
        const timeStr = `${minutes}:${seconds.toString().padStart(2, '0')}`;

        notification.textContent = `${action} | ${timeStr}`;
        notification.style.cssText = `
            position: fixed;
            top: 60px;
            right: 20px;
            background: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 10px 15px;
            border-radius: 5px;
            font-family: Arial, sans-serif;
            font-size: 14px;
            z-index: 10000;
            transition: opacity 0.3s ease;
        `;

        document.body.appendChild(notification);

        // Auto-remove after 1.5 seconds
        setTimeout(() => {
            if (notification.parentNode) {
                notification.style.opacity = '0';
                setTimeout(() => {
                    if (notification.parentNode) {
                        notification.remove();
                    }
                }, 300);
            }
        }, 1500);
    }

    // Store original speed when video loads
    function initializeVideo(video) {
        if (video && video !== currentVideo) {
            currentVideo = video;
            originalSpeed = video.playbackRate || 1.0;
        }
    }

    // Keyboard event handler
    function handleKeyPress(e) {
        // Don't trigger if user is typing in an input field
        if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable) {
            return;
        }

        const video = getActiveVideo();
        if (!video) return;

        // Initialize video if it's new
        initializeVideo(video);

        const key = e.key.toLowerCase();

        switch (key) {
            case 'b':
                // Forward 10 seconds
                video.currentTime = Math.min(video.currentTime + 10, video.duration || video.currentTime + 10);
                showTimeNotification('Forward +10s');
                e.preventDefault();
                break;

            case 'v':
                // Reverse 5 seconds
                video.currentTime = Math.max(video.currentTime - 5, 0);
                showTimeNotification('Rewind -5s');
                e.preventDefault();
                break;

            case 'x':
                // Increase speed by 0.1
                video.playbackRate = Math.min(video.playbackRate + 0.1, 16.0);
                showSpeedNotification(video.playbackRate);
                e.preventDefault();
                break;

            case 'z':
                // Decrease speed by 0.1
                video.playbackRate = Math.max(video.playbackRate - 0.1, 0.1);
                showSpeedNotification(video.playbackRate);
                e.preventDefault();
                break;

            case 'r':
                // Reset to original speed (only if no modifier keys are pressed)
                if (!e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey) {
                    video.playbackRate = originalSpeed;
                    showSpeedNotification(video.playbackRate);
                    e.preventDefault();
                }
                break;

            case 'g':
                // Set speed to 2x
                video.playbackRate = 2.0;
                showSpeedNotification(video.playbackRate);
                e.preventDefault();
                break;

            case 'h':
                // Set speed to 3x
                video.playbackRate = 3.0;
                showSpeedNotification(video.playbackRate);
                e.preventDefault();
                break;

            case 'j':
                // Set speed to 4x
                video.playbackRate = 4.0;
                showSpeedNotification(video.playbackRate);
                e.preventDefault();
                break;
        }
    }

    // Add event listener
    document.addEventListener('keydown', handleKeyPress, true);

    // Monitor for new videos (for dynamic content)
    const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            mutation.addedNodes.forEach((node) => {
                if (node.nodeType === 1) { // Element node
                    if (node.tagName === 'VIDEO') {
                        initializeVideo(node);
                    } else if (node.querySelector) {
                        const videos = node.querySelectorAll('video');
                        videos.forEach(initializeVideo);
                    }
                }
            });
        });
    });

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

    // Initialize existing videos
    document.addEventListener('DOMContentLoaded', () => {
        const videos = document.querySelectorAll('video');
        videos.forEach(initializeVideo);
    });

    // If DOM is already loaded
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => {
            const videos = document.querySelectorAll('video');
            videos.forEach(initializeVideo);
        });
    } else {
        const videos = document.querySelectorAll('video');
        videos.forEach(initializeVideo);
    }

    console.log('Universal Video Controls loaded! Controls: B=+10s, V=-5s, X=+0.1 speed, Z=-0.1 speed, R=reset speed, G=2x, H=3x, J=4x');
})();