Greasy Fork

Greasy Fork is available in English.

增强视频速度控制器

为HTML5、Video.js、JW Player、Plyr、HLS.js、YouTube等提供通用视频速度控制,具有智能检测功能

当前为 2025-09-12 提交的版本,查看 最新版本

// ==UserScript==
// @name         Enhanced Video Speed Controller
// @name:zh-CN   增强视频速度控制器
// @name:zh-TW   增強影片速度控制器
// @name:ja      拡張ビデオスピードコントローラー
// @name:ko      향상된 비디오 속도 컨트롤러
// @name:es      Controlador de Velocidad de Video Mejorado
// @name:fr      Contrôleur de Vitesse Vidéo Amélioré
// @name:de      Erweiterte Video-Geschwindigkeitskontrolle
// @name:pt      Controlador de Velocidade de Vídeo Aprimorado
// @name:ru      Расширенный Контроллер Скорости Видео
// @name:it      Controllore di Velocità Video Avanzato
// @name:nl      Verbeterde Video Snelheidscontroller
// @name:ar      تحكم محسن في سرعة الفيديو
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  Universal video speed control for HTML5, Video.js, JW Player, Plyr, HLS.js, YouTube, and more with intelligent detection
// @description:zh-CN  为HTML5、Video.js、JW Player、Plyr、HLS.js、YouTube等提供通用视频速度控制,具有智能检测功能
// @description:zh-TW  為HTML5、Video.js、JW Player、Plyr、HLS.js、YouTube等提供通用影片速度控制,具有智慧檢測功能
// @description:ja     HTML5、Video.js、JW Player、Plyr、HLS.js、YouTubeなどに対応したインテリジェント検出機能付きユニバーサル動画速度制御
// @description:ko     HTML5, Video.js, JW Player, Plyr, HLS.js, YouTube 등을 위한 지능형 감지 기능이 있는 범용 비디오 속도 제어
// @description:es     Control universal de velocidad de video para HTML5, Video.js, JW Player, Plyr, HLS.js, YouTube y más con detección inteligente
// @description:fr     Contrôle universel de vitesse vidéo pour HTML5, Video.js, JW Player, Plyr, HLS.js, YouTube et plus avec détection intelligente
// @description:de     Universelle Videogeschwindigkeitskontrolle für HTML5, Video.js, JW Player, Plyr, HLS.js, YouTube und mehr mit intelligenter Erkennung
// @description:pt     Controle universal de velocidade de vídeo para HTML5, Video.js, JW Player, Plyr, HLS.js, YouTube e mais com detecção inteligente
// @description:ru     Универсальное управление скоростью видео для HTML5, Video.js, JW Player, Plyr, HLS.js, YouTube и других с интеллектуальным обнаружением
// @description:it     Controllo universale della velocità video per HTML5, Video.js, JW Player, Plyr, HLS.js, YouTube e altri con rilevamento intelligente
// @description:nl     Universele videosnelheidscontrole voor HTML5, Video.js, JW Player, Plyr, HLS.js, YouTube en meer met intelligente detectie
// @description:ar     تحكم عالمي في سرعة الفيديو لـ HTML5، Video.js، JW Player، Plyr، HLS.js، YouTube والمزيد مع الكشف الذكي
// @author       aspen138 (using Claude code 1.0.112)
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// @run-at       document-idle
// @license      MIT
// ==/UserScript==




(function() {
    'use strict';

    // Configuration
    const CONFIG = {
        version: '2.0.0',
        defaultSpeed: 1.0,
        speedPresets: [0.5, 0.75, 1, 1.25, 1.5, 2],
        minSpeed: 0.25,
        maxSpeed: 4.0,
        speedStep: 0.25,
        detectionDelay: 1000,
        retryDelay: 500,
        persistKey: 'videoSpeedController',
        uiPosition: { right: '20px', top: '20px' }
    };

    // Enhanced Video Speed Manager Class
    class EnhancedVideoSpeedManager {
        constructor() {
            this.detectedPlayers = [];
            this.currentSpeed = CONFIG.defaultSpeed;
            this.isInitialized = false;
            this.contentObserver = null;
            this.detectionTimer = null;
            this.ui = null;
            this.isUIHidden = true; // Start hidden by default

            // Player type definitions and detection patterns
            this.playerTypes = {
                HTML5: { selector: 'video', priority: 1, name: 'HTML5 Video' },
                VIDEOJS: { selector: '.video-js', priority: 3, name: 'Video.js' },
                JWPLAYER: { selector: '.jw-video, .jwplayer', priority: 3, name: 'JW Player' },
                PLYR: { selector: '.plyr', priority: 2, name: 'Plyr' },
                HLSJS: { selector: 'video[src*=".m3u8"], video[data-hls]', priority: 2, name: 'HLS.js' },
                YOUTUBE: { selector: '#movie_player, .html5-video-player', priority: 4, name: 'YouTube' },
                VIMEO: { selector: '.vp-player, [data-vimeo-player]', priority: 4, name: 'Vimeo' },
                TWITCH: { selector: '.video-player, [data-a-target="video-player"]', priority: 4, name: 'Twitch' }
            };

            this.init();
        }

        async init() {
            console.log('Enhanced Video Speed Controller: Initializing...');

            // Load saved settings
            await this.loadSettings();

            // Wait for DOM to be ready
            if (document.readyState === 'loading') {
                document.addEventListener('DOMContentLoaded', () => this.setup());
            } else {
                this.setup();
            }
        }

        setup() {
            // Create UI
            this.createUI();

            // Initial player detection
            this.detectAllPlayers();

            // Set up observers and event listeners
            this.setupContentObserver();
            this.setupKeyboardShortcuts();
            this.setupMenuCommands();

            // Apply saved speed
            if (this.currentSpeed !== CONFIG.defaultSpeed) {
                setTimeout(() => this.setAllPlayersSpeed(this.currentSpeed), CONFIG.detectionDelay);
            }

            this.isInitialized = true;
            console.log('Enhanced Video Speed Controller: Initialization complete');
        }

        // Player Detection System
        detectAllPlayers() {
            console.log('Enhanced Video Speed Controller: Scanning for video players...');

            this.detectedPlayers = [];

            Object.keys(this.playerTypes).forEach(playerType => {
                const players = this.detectPlayerType(playerType);
                this.detectedPlayers.push(...players);
            });

            // Sort by priority (higher priority first)
            this.detectedPlayers.sort((a, b) => b.priority - a.priority);

            const playerSummary = this.getPlayerSummary();
            console.log('Enhanced Video Speed Controller: Detected players:', playerSummary);

            // Update UI
            this.updateUI();

            return this.detectedPlayers;
        }

        detectPlayerType(playerType) {
            const config = this.playerTypes[playerType];
            const elements = document.querySelectorAll(config.selector);
            const players = [];

            elements.forEach((element, index) => {
                const player = this.createPlayerInstance(playerType, element, index);
                if (player) {
                    players.push(player);
                }
            });

            if (players.length > 0) {
                console.log(`Enhanced Video Speed Controller: Found ${players.length} ${playerType} player(s)`);
            }

            return players;
        }

        createPlayerInstance(type, element, index) {
            const basePlayer = {
                type,
                element,
                index,
                id: `${type}_${index}`,
                priority: this.playerTypes[type].priority,
                name: this.playerTypes[type].name,
                currentSpeed: CONFIG.defaultSpeed
            };

            switch (type) {
                case 'HTML5': return this.createHTML5Player(basePlayer);
                case 'VIDEOJS': return this.createVideoJSPlayer(basePlayer);
                case 'JWPLAYER': return this.createJWPlayer(basePlayer);
                case 'PLYR': return this.createPlyrPlayer(basePlayer);
                case 'HLSJS': return this.createHLSJSPlayer(basePlayer);
                case 'YOUTUBE': return this.createYouTubePlayer(basePlayer);
                case 'VIMEO': return this.createVimeoPlayer(basePlayer);
                case 'TWITCH': return this.createTwitchPlayer(basePlayer);
                default: return null;
            }
        }

        // Player Type Implementations
        createHTML5Player(basePlayer) {
            const video = basePlayer.element;
            if (!(video instanceof HTMLVideoElement)) return null;

            return {
                ...basePlayer,
                setSpeed: (speed) => {
                    try {
                        video.playbackRate = speed;
                        basePlayer.currentSpeed = speed;
                        return true;
                    } catch (error) {
                        console.error(`Enhanced Video Speed Controller: HTML5 speed error:`, error);
                        return false;
                    }
                },
                getSpeed: () => video.playbackRate || CONFIG.defaultSpeed,
                isReady: () => video.readyState >= 1
            };
        }

        createVideoJSPlayer(basePlayer) {
            const container = basePlayer.element;
            let vjsPlayer = null;

            // Try multiple methods to get Video.js instance
            if (typeof videojs !== 'undefined') {
                try {
                    vjsPlayer = videojs(container);
                } catch (e) {
                    // Fallback methods
                    vjsPlayer = container.player || window.videojs?.getPlayer?.(container);
                }
            }

            // Fallback to HTML5 if Video.js API not available
            const videoElement = container.querySelector('video');
            if (!vjsPlayer && videoElement) {
                return this.createHTML5Player({ ...basePlayer, element: videoElement, type: 'HTML5_VIDEOJS_FALLBACK' });
            }

            if (!vjsPlayer) return null;

            return {
                ...basePlayer,
                vjsInstance: vjsPlayer,
                setSpeed: (speed) => {
                    try {
                        if (vjsPlayer.playbackRate) {
                            vjsPlayer.playbackRate(speed);
                        } else if (vjsPlayer.tech_?.el_) {
                            vjsPlayer.tech_.el_.playbackRate = speed;
                        }
                        basePlayer.currentSpeed = speed;
                        return true;
                    } catch (error) {
                        console.error(`Enhanced Video Speed Controller: Video.js speed error:`, error);
                        return false;
                    }
                },
                getSpeed: () => {
                    try {
                        return vjsPlayer.playbackRate ? vjsPlayer.playbackRate() : CONFIG.defaultSpeed;
                    } catch (e) {
                        return CONFIG.defaultSpeed;
                    }
                },
                isReady: () => {
                    try {
                        return vjsPlayer.readyState() >= 1;
                    } catch (e) {
                        return true;
                    }
                }
            };
        }

        createJWPlayer(basePlayer) {
            const container = basePlayer.element;
            let jwPlayer = null;

            if (typeof jwplayer !== 'undefined') {
                try {
                    jwPlayer = jwplayer(container);
                } catch (e) {
                    // Try alternative detection
                    jwPlayer = container.jwplayer || window.jwplayer?.getPlayer?.(container);
                }
            }

            // Fallback to HTML5
            const videoElement = container.querySelector('video');
            if (!jwPlayer && videoElement) {
                return this.createHTML5Player({ ...basePlayer, element: videoElement, type: 'HTML5_JW_FALLBACK' });
            }

            if (!jwPlayer) return null;

            return {
                ...basePlayer,
                jwInstance: jwPlayer,
                setSpeed: (speed) => {
                    try {
                        if (jwPlayer.setPlaybackRate) {
                            jwPlayer.setPlaybackRate(speed);
                        } else if (jwPlayer.getContainer) {
                            const video = jwPlayer.getContainer().querySelector('video');
                            if (video) video.playbackRate = speed;
                        }
                        basePlayer.currentSpeed = speed;
                        return true;
                    } catch (error) {
                        console.error(`Enhanced Video Speed Controller: JW Player speed error:`, error);
                        return false;
                    }
                },
                getSpeed: () => {
                    try {
                        return jwPlayer.getPlaybackRate ? jwPlayer.getPlaybackRate() : CONFIG.defaultSpeed;
                    } catch (e) {
                        return CONFIG.defaultSpeed;
                    }
                },
                isReady: () => {
                    try {
                        return jwPlayer.getState && jwPlayer.getState() !== 'idle';
                    } catch (e) {
                        return true;
                    }
                }
            };
        }

        createPlyrPlayer(basePlayer) {
            const container = basePlayer.element;
            const plyrPlayer = container.plyr;

            // Fallback to HTML5
            const videoElement = container.querySelector('video');
            if (!plyrPlayer && videoElement) {
                return this.createHTML5Player({ ...basePlayer, element: videoElement, type: 'HTML5_PLYR_FALLBACK' });
            }

            if (!plyrPlayer) return null;

            return {
                ...basePlayer,
                plyrInstance: plyrPlayer,
                setSpeed: (speed) => {
                    try {
                        if (plyrPlayer.speed) {
                            plyrPlayer.speed = speed;
                        } else if (plyrPlayer.media) {
                            plyrPlayer.media.playbackRate = speed;
                        }
                        basePlayer.currentSpeed = speed;
                        return true;
                    } catch (error) {
                        console.error(`Enhanced Video Speed Controller: Plyr speed error:`, error);
                        return false;
                    }
                },
                getSpeed: () => {
                    try {
                        return plyrPlayer.speed || plyrPlayer.media?.playbackRate || CONFIG.defaultSpeed;
                    } catch (e) {
                        return CONFIG.defaultSpeed;
                    }
                },
                isReady: () => plyrPlayer.ready
            };
        }

        createHLSJSPlayer(basePlayer) {
            // HLS.js uses standard HTML5 video elements
            return this.createHTML5Player({ ...basePlayer, type: 'HLSJS_HTML5' });
        }

        createYouTubePlayer(basePlayer) {
            const container = basePlayer.element;

            return {
                ...basePlayer,
                setSpeed: (speed) => {
                    const video = container.querySelector('video');
                    if (video) {
                        try {
                            video.playbackRate = speed;
                            basePlayer.currentSpeed = speed;
                            return true;
                        } catch (error) {
                            console.error(`Enhanced Video Speed Controller: YouTube speed error:`, error);
                            return false;
                        }
                    }
                    return false;
                },
                getSpeed: () => {
                    const video = container.querySelector('video');
                    return video ? video.playbackRate || CONFIG.defaultSpeed : CONFIG.defaultSpeed;
                },
                isReady: () => true
            };
        }

        createVimeoPlayer(basePlayer) {
            const container = basePlayer.element;

            return {
                ...basePlayer,
                setSpeed: (speed) => {
                    const video = container.querySelector('video');
                    if (video) {
                        try {
                            video.playbackRate = speed;
                            basePlayer.currentSpeed = speed;
                            return true;
                        } catch (error) {
                            console.error(`Enhanced Video Speed Controller: Vimeo speed error:`, error);
                            return false;
                        }
                    }
                    return false;
                },
                getSpeed: () => {
                    const video = container.querySelector('video');
                    return video ? video.playbackRate || CONFIG.defaultSpeed : CONFIG.defaultSpeed;
                },
                isReady: () => true
            };
        }

        createTwitchPlayer(basePlayer) {
            const container = basePlayer.element;

            return {
                ...basePlayer,
                setSpeed: (speed) => {
                    const video = container.querySelector('video');
                    if (video) {
                        try {
                            video.playbackRate = speed;
                            basePlayer.currentSpeed = speed;
                            return true;
                        } catch (error) {
                            console.error(`Enhanced Video Speed Controller: Twitch speed error:`, error);
                            return false;
                        }
                    }
                    return false;
                },
                getSpeed: () => {
                    const video = container.querySelector('video');
                    return video ? video.playbackRate || CONFIG.defaultSpeed : CONFIG.defaultSpeed;
                },
                isReady: () => true
            };
        }

        // Content Observer for Dynamic Detection
        setupContentObserver() {
            this.contentObserver = new MutationObserver((mutations) => {
                let shouldDetect = false;

                mutations.forEach((mutation) => {
                    mutation.addedNodes.forEach((node) => {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            const hasVideo = node.tagName === 'VIDEO' ||
                                           node.querySelector && Object.values(this.playerTypes).some(type =>
                                               node.querySelector(type.selector)
                                           );

                            if (hasVideo) {
                                shouldDetect = true;
                            }
                        }
                    });
                });

                if (shouldDetect) {
                    clearTimeout(this.detectionTimer);
                    this.detectionTimer = setTimeout(() => {
                        console.log('Enhanced Video Speed Controller: New content detected, re-scanning...');
                        this.detectAllPlayers();
                    }, CONFIG.retryDelay);
                }
            });

            this.contentObserver.observe(document.body || document.documentElement, {
                childList: true,
                subtree: true,
                attributes: true,
                attributeFilter: ['class', 'id']
            });
        }

        // Speed Control Methods
        setAllPlayersSpeed(speed) {
            if (isNaN(speed) || speed <= 0 || speed > 10) {
                console.error('Enhanced Video Speed Controller: Invalid speed value:', speed);
                return false;
            }

            this.currentSpeed = speed;
            let successCount = 0;

            this.detectedPlayers.forEach(player => {
                try {
                    if (player.setSpeed && player.setSpeed(speed)) {
                        successCount++;
                    }
                } catch (error) {
                    console.error(`Enhanced Video Speed Controller: Failed to set speed for ${player.type}:`, error);
                }
            });

            console.log(`Enhanced Video Speed Controller: Set speed to ${speed}x for ${successCount}/${this.detectedPlayers.length} players`);

            // Update UI
            this.updateUI();

            // Save settings
            this.saveSettings();

            // Re-detect and apply to new players
            setTimeout(() => {
                this.detectAllPlayers();
                this.detectedPlayers.forEach(player => {
                    if (player.currentSpeed !== speed && player.setSpeed) {
                        player.setSpeed(speed);
                    }
                });
            }, CONFIG.retryDelay);

            return successCount > 0;
        }

        adjustSpeed(delta) {
            const newSpeed = Math.max(CONFIG.minSpeed, Math.min(CONFIG.maxSpeed, this.currentSpeed + delta));
            this.setAllPlayersSpeed(parseFloat(newSpeed.toFixed(2)));
        }

        resetSpeed() {
            this.setAllPlayersSpeed(CONFIG.defaultSpeed);
        }

        // UI Creation and Management
        createUI() {
            if (this.ui) return;

            // Create main container
            this.ui = document.createElement('div');
            this.ui.id = 'enhanced-video-speed-controller';
            this.ui.innerHTML = `
                <div class="evsc-header">
                    <span class="evsc-title">Video Speed</span>
                    <div class="evsc-header-buttons">
                        <button class="evsc-hide" title="Hide (Press H to show/hide)">⚊</button>
                        <button class="evsc-toggle" title="Collapse/Expand">−</button>
                    </div>
                </div>
                <div class="evsc-content">
                    <div class="evsc-current-speed">
                        <span>Current: </span>
                        <span class="evsc-speed-value">${this.currentSpeed}x</span>
                    </div>
                    <div class="evsc-player-info">
                        <span class="evsc-player-count">0 players</span>
                    </div>
                    <div class="evsc-presets">
                        ${CONFIG.speedPresets.map(speed =>
                            `<button class="evsc-preset-btn" data-speed="${speed}" ${speed === CONFIG.defaultSpeed ? 'data-active="true"' : ''}>${speed}x</button>`
                        ).join('')}
                    </div>
                    <div class="evsc-slider-container">
                        <input type="range" class="evsc-slider" min="${CONFIG.minSpeed}" max="${CONFIG.maxSpeed}" step="0.05" value="${this.currentSpeed}">
                        <div class="evsc-slider-labels">
                            <span>${CONFIG.minSpeed}x</span>
                            <span>${CONFIG.maxSpeed}x</span>
                        </div>
                    </div>
                    <div class="evsc-custom-speed">
                        <input type="number" class="evsc-custom-input" placeholder="Custom speed" min="0.1" max="10" step="0.1">
                        <button class="evsc-apply-btn">Apply</button>
                    </div>
                    <div class="evsc-controls">
                        <button class="evsc-control-btn" data-action="decrease">− Slower</button>
                        <button class="evsc-control-btn" data-action="reset">Reset</button>
                        <button class="evsc-control-btn" data-action="increase">+ Faster</button>
                    </div>
                    <div class="evsc-player-details">
                        <details class="evsc-details">
                            <summary>Player Details</summary>
                            <div class="evsc-player-list"></div>
                        </details>
                    </div>
                </div>
            `;

            document.body.appendChild(this.ui);

            // Apply initial hidden state
            if (this.isUIHidden) {
                this.ui.classList.add('evsc-hidden');
            }

            this.addStyles();
            this.setupUIEventListeners();
        }

        addStyles() {
            GM_addStyle(`
                #enhanced-video-speed-controller {
                    position: fixed;
                    top: ${CONFIG.uiPosition.top};
                    right: ${CONFIG.uiPosition.right};
                    width: 280px;
                    background: rgba(0, 0, 0, 0.9);
                    color: white;
                    border-radius: 8px;
                    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
                    font-size: 13px;
                    z-index: 999999;
                    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
                    backdrop-filter: blur(10px);
                    transition: all 0.3s ease;
                }

                #enhanced-video-speed-controller.evsc-collapsed .evsc-content {
                    display: none;
                }

                #enhanced-video-speed-controller.evsc-hidden {
                    display: none;
                }

                .evsc-header {
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    padding: 12px 16px;
                    background: rgba(0, 122, 204, 0.8);
                    border-radius: 8px 8px 0 0;
                    cursor: move;
                }

                .evsc-title {
                    font-weight: 600;
                    font-size: 14px;
                }

                .evsc-header-buttons {
                    display: flex;
                    gap: 4px;
                }

                .evsc-hide,
                .evsc-toggle {
                    background: none;
                    border: none;
                    color: white;
                    cursor: pointer;
                    font-size: 18px;
                    padding: 0;
                    width: 24px;
                    height: 24px;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    border-radius: 4px;
                }

                .evsc-toggle:hover {
                    background: rgba(255, 255, 255, 0.2);
                }

                .evsc-content {
                    padding: 16px;
                    display: flex;
                    flex-direction: column;
                    gap: 12px;
                }

                .evsc-current-speed {
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    padding: 8px 12px;
                    background: rgba(255, 255, 255, 0.1);
                    border-radius: 6px;
                }

                .evsc-speed-value {
                    font-weight: 600;
                    color: #4da6ff;
                }

                .evsc-player-info {
                    text-align: center;
                    font-size: 11px;
                    color: rgba(255, 255, 255, 0.7);
                    padding: 4px;
                }

                .evsc-presets {
                    display: grid;
                    grid-template-columns: repeat(3, 1fr);
                    gap: 6px;
                }

                .evsc-preset-btn {
                    background: rgba(255, 255, 255, 0.1);
                    border: 1px solid rgba(255, 255, 255, 0.2);
                    color: white;
                    padding: 8px;
                    border-radius: 4px;
                    cursor: pointer;
                    font-size: 12px;
                    transition: all 0.2s ease;
                }

                .evsc-preset-btn:hover {
                    background: rgba(255, 255, 255, 0.2);
                    border-color: #4da6ff;
                }

                .evsc-preset-btn[data-active="true"] {
                    background: #4da6ff;
                    border-color: #4da6ff;
                    color: white;
                }

                .evsc-slider-container {
                    display: flex;
                    flex-direction: column;
                    gap: 4px;
                }

                .evsc-slider {
                    width: 100%;
                    height: 4px;
                    border-radius: 2px;
                    background: rgba(255, 255, 255, 0.2);
                    outline: none;
                    cursor: pointer;
                    -webkit-appearance: none;
                    appearance: none;
                }

                .evsc-slider::-webkit-slider-thumb {
                    -webkit-appearance: none;
                    appearance: none;
                    width: 16px;
                    height: 16px;
                    border-radius: 50%;
                    background: #4da6ff;
                    cursor: pointer;
                    border: 2px solid white;
                    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
                }

                .evsc-slider::-moz-range-thumb {
                    width: 16px;
                    height: 16px;
                    border-radius: 50%;
                    background: #4da6ff;
                    cursor: pointer;
                    border: 2px solid white;
                    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
                }

                .evsc-slider-labels {
                    display: flex;
                    justify-content: space-between;
                    font-size: 10px;
                    color: rgba(255, 255, 255, 0.6);
                }

                .evsc-custom-speed {
                    display: flex;
                    gap: 8px;
                }

                .evsc-custom-input {
                    flex: 1;
                    background: rgba(255, 255, 255, 0.1);
                    border: 1px solid rgba(255, 255, 255, 0.2);
                    color: white;
                    padding: 6px 8px;
                    border-radius: 4px;
                    font-size: 12px;
                }

                .evsc-custom-input::placeholder {
                    color: rgba(255, 255, 255, 0.5);
                }

                .evsc-custom-input:focus {
                    outline: none;
                    border-color: #4da6ff;
                }

                .evsc-apply-btn {
                    background: #4da6ff;
                    border: none;
                    color: white;
                    padding: 6px 12px;
                    border-radius: 4px;
                    cursor: pointer;
                    font-size: 12px;
                    transition: background-color 0.2s ease;
                }

                .evsc-apply-btn:hover {
                    background: #3d8bd1;
                }

                .evsc-controls {
                    display: grid;
                    grid-template-columns: 1fr 1fr 1fr;
                    gap: 6px;
                }

                .evsc-control-btn {
                    background: rgba(255, 255, 255, 0.1);
                    border: 1px solid rgba(255, 255, 255, 0.2);
                    color: white;
                    padding: 8px 6px;
                    border-radius: 4px;
                    cursor: pointer;
                    font-size: 11px;
                    transition: all 0.2s ease;
                }

                .evsc-control-btn:hover {
                    background: rgba(255, 255, 255, 0.2);
                    border-color: #4da6ff;
                }

                .evsc-control-btn[data-action="reset"] {
                    border-color: #28a745;
                    color: #28a745;
                }

                .evsc-control-btn[data-action="reset"]:hover {
                    background: #28a745;
                    color: white;
                }

                .evsc-details {
                    border-top: 1px solid rgba(255, 255, 255, 0.1);
                    padding-top: 8px;
                }

                .evsc-details summary {
                    cursor: pointer;
                    font-size: 11px;
                    color: rgba(255, 255, 255, 0.7);
                    padding: 4px 0;
                }

                .evsc-details[open] summary {
                    margin-bottom: 8px;
                }

                .evsc-player-list {
                    display: flex;
                    flex-direction: column;
                    gap: 4px;
                    max-height: 120px;
                    overflow-y: auto;
                }

                .evsc-player-item {
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    padding: 4px 8px;
                    background: rgba(255, 255, 255, 0.05);
                    border-radius: 4px;
                    font-size: 10px;
                }

                .evsc-player-name {
                    color: rgba(255, 255, 255, 0.8);
                }

                .evsc-player-speed {
                    color: #4da6ff;
                    font-weight: 600;
                }

                .evsc-no-players {
                    text-align: center;
                    color: rgba(255, 255, 255, 0.5);
                    font-style: italic;
                    padding: 8px;
                    font-size: 10px;
                }

                /* Draggable functionality */
                .evsc-dragging {
                    cursor: move;
                    user-select: none;
                }

                /* Scrollbar for player list */
                .evsc-player-list::-webkit-scrollbar {
                    width: 4px;
                }

                .evsc-player-list::-webkit-scrollbar-track {
                    background: rgba(255, 255, 255, 0.1);
                    border-radius: 2px;
                }

                .evsc-player-list::-webkit-scrollbar-thumb {
                    background: rgba(255, 255, 255, 0.3);
                    border-radius: 2px;
                }

                .evsc-player-list::-webkit-scrollbar-thumb:hover {
                    background: rgba(255, 255, 255, 0.5);
                }
            `);
        }

        setupUIEventListeners() {
            if (!this.ui) return;

            // Hide UI
            const hideBtn = this.ui.querySelector('.evsc-hide');
            hideBtn.addEventListener('click', () => {
                this.toggleUI();
            });

            // Toggle UI (collapse/expand)
            const toggleBtn = this.ui.querySelector('.evsc-toggle');
            toggleBtn.addEventListener('click', () => {
                this.ui.classList.toggle('evsc-collapsed');
                toggleBtn.textContent = this.ui.classList.contains('evsc-collapsed') ? '+' : '−';
            });

            // Preset buttons
            this.ui.querySelectorAll('.evsc-preset-btn').forEach(btn => {
                btn.addEventListener('click', (e) => {
                    const speed = parseFloat(e.target.dataset.speed);
                    this.setAllPlayersSpeed(speed);
                });
            });

            // Slider
            const slider = this.ui.querySelector('.evsc-slider');
            slider.addEventListener('input', (e) => {
                const speed = parseFloat(e.target.value);
                this.setAllPlayersSpeed(speed);
            });

            // Custom speed
            const customInput = this.ui.querySelector('.evsc-custom-input');
            const applyBtn = this.ui.querySelector('.evsc-apply-btn');

            const applyCustomSpeed = () => {
                const speed = parseFloat(customInput.value);
                if (!isNaN(speed) && speed > 0 && speed <= 10) {
                    this.setAllPlayersSpeed(speed);
                    customInput.value = '';
                }
            };

            applyBtn.addEventListener('click', applyCustomSpeed);
            customInput.addEventListener('keypress', (e) => {
                if (e.key === 'Enter') applyCustomSpeed();
            });

            // Control buttons
            this.ui.querySelectorAll('.evsc-control-btn').forEach(btn => {
                btn.addEventListener('click', (e) => {
                    const action = e.target.dataset.action;
                    switch (action) {
                        case 'decrease':
                            this.adjustSpeed(-CONFIG.speedStep);
                            break;
                        case 'increase':
                            this.adjustSpeed(CONFIG.speedStep);
                            break;
                        case 'reset':
                            this.resetSpeed();
                            break;
                    }
                });
            });

            // Make UI draggable
            this.makeDraggable();
        }

        makeDraggable() {
            const header = this.ui.querySelector('.evsc-header');
            let isDragging = false;
            let currentX, currentY, initialX, initialY, xOffset = 0, yOffset = 0;

            header.addEventListener('mousedown', (e) => {
                if (e.target.classList.contains('evsc-toggle') || e.target.classList.contains('evsc-hide')) return;

                initialX = e.clientX - xOffset;
                initialY = e.clientY - yOffset;

                if (e.target === header || header.contains(e.target)) {
                    isDragging = true;
                    this.ui.classList.add('evsc-dragging');
                }
            });

            document.addEventListener('mousemove', (e) => {
                if (isDragging) {
                    e.preventDefault();
                    currentX = e.clientX - initialX;
                    currentY = e.clientY - initialY;
                    xOffset = currentX;
                    yOffset = currentY;
                    this.ui.style.transform = `translate(${currentX}px, ${currentY}px)`;
                }
            });

            document.addEventListener('mouseup', () => {
                if (isDragging) {
                    isDragging = false;
                    this.ui.classList.remove('evsc-dragging');
                }
            });
        }

        updateUI() {
            if (!this.ui) return;

            // Update current speed display
            const speedValue = this.ui.querySelector('.evsc-speed-value');
            speedValue.textContent = `${this.currentSpeed}x`;

            // Update slider
            const slider = this.ui.querySelector('.evsc-slider');
            slider.value = this.currentSpeed;

            // Update preset button states
            this.ui.querySelectorAll('.evsc-preset-btn').forEach(btn => {
                const isActive = parseFloat(btn.dataset.speed) === this.currentSpeed;
                btn.setAttribute('data-active', isActive);
            });

            // Update player count
            const playerCount = this.ui.querySelector('.evsc-player-count');
            const summary = this.getPlayerSummary();
            const summaryText = Object.keys(summary).length > 0
                ? Object.entries(summary).map(([type, count]) => `${type}: ${count}`).join(', ')
                : 'No players detected';
            playerCount.textContent = `${this.detectedPlayers.length} players (${summaryText})`;

            // Update player details
            this.updatePlayerDetails();
        }

        updatePlayerDetails() {
            const playerList = this.ui.querySelector('.evsc-player-list');

            if (this.detectedPlayers.length === 0) {
                playerList.innerHTML = '<div class="evsc-no-players">No video players found</div>';
                return;
            }

            const playerItems = this.detectedPlayers.map(player => {
                const speedText = player.currentSpeed ? `${player.currentSpeed}x` : '1x';
                return `
                    <div class="evsc-player-item">
                        <span class="evsc-player-name">${player.name} #${player.index + 1}</span>
                        <span class="evsc-player-speed">${speedText}</span>
                    </div>
                `;
            }).join('');

            playerList.innerHTML = playerItems;
        }

        // UI Toggle Methods
        toggleUI() {
            this.isUIHidden = !this.isUIHidden;
            if (this.isUIHidden) {
                this.ui.classList.add('evsc-hidden');
            } else {
                this.ui.classList.remove('evsc-hidden');
            }
        }

        showUI() {
            this.isUIHidden = false;
            this.ui.classList.remove('evsc-hidden');
        }

        hideUI() {
            this.isUIHidden = true;
            this.ui.classList.add('evsc-hidden');
        }

        // Keyboard Shortcuts
        setupKeyboardShortcuts() {
            document.addEventListener('keydown', (e) => {
                // Only handle shortcuts when not typing in input fields
                if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.contentEditable === 'true') return;

                // Handle single key shortcuts
                switch (e.key.toLowerCase()) {
                    case 'h':
                        if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
                            e.preventDefault();
                            this.toggleUI();
                        }
                        break;
                    case 'd':
                        if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
                            e.preventDefault();
                            this.adjustSpeed(CONFIG.speedStep);
                        }
                        break;
                    case 's':
                        if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
                            e.preventDefault();
                            this.adjustSpeed(-CONFIG.speedStep);
                        }
                        break;
                }

                // Handle Ctrl/Cmd shortcuts
                if (e.ctrlKey || e.metaKey) {
                    switch (e.key) {
                        case ',':
                        case '<':
                            e.preventDefault();
                            this.adjustSpeed(-CONFIG.speedStep);
                            break;
                        case '.':
                        case '>':
                            e.preventDefault();
                            this.adjustSpeed(CONFIG.speedStep);
                            break;
                        case '0':
                            e.preventDefault();
                            this.resetSpeed();
                            break;
                    }
                }
            });
        }

        // Menu Commands
        setupMenuCommands() {
            GM_registerMenuCommand('Toggle Video Speed Controller UI', () => {
                this.toggleUI();
            });

            GM_registerMenuCommand('Show/Hide Speed Controller', () => {
                this.toggleUI();
            });

            GM_registerMenuCommand('Reset to Normal Speed', () => {
                this.resetSpeed();
            });

            GM_registerMenuCommand('Refresh Player Detection', () => {
                this.detectAllPlayers();
            });
        }

        // Settings Persistence
        async loadSettings() {
            try {
                const saved = GM_getValue(CONFIG.persistKey, null);
                if (saved) {
                    const settings = JSON.parse(saved);
                    this.currentSpeed = settings.currentSpeed || CONFIG.defaultSpeed;
                }
            } catch (error) {
                console.error('Enhanced Video Speed Controller: Failed to load settings:', error);
            }
        }

        saveSettings() {
            try {
                const settings = {
                    currentSpeed: this.currentSpeed,
                    timestamp: Date.now()
                };
                GM_setValue(CONFIG.persistKey, JSON.stringify(settings));
            } catch (error) {
                console.error('Enhanced Video Speed Controller: Failed to save settings:', error);
            }
        }

        // Utility Methods
        getPlayerSummary() {
            return this.detectedPlayers.reduce((acc, player) => {
                const displayName = player.name || player.type;
                acc[displayName] = (acc[displayName] || 0) + 1;
                return acc;
            }, {});
        }
    }

    // Initialize the Enhanced Video Speed Manager
    let videoSpeedManager;

    function initializeManager() {
        if (!videoSpeedManager && document.body) {
            videoSpeedManager = new EnhancedVideoSpeedManager();
            console.log('Enhanced Video Speed Controller: UserScript loaded successfully');
        }
    }

    // Wait for DOM to be ready and initialize
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initializeManager);
    } else {
        setTimeout(initializeManager, 100);
    }

    // Handle SPA navigation
    let lastUrl = location.href;
    new MutationObserver(() => {
        const url = location.href;
        if (url !== lastUrl) {
            lastUrl = url;
            console.log('Enhanced Video Speed Controller: SPA navigation detected');

            if (videoSpeedManager) {
                setTimeout(() => {
                    videoSpeedManager.detectAllPlayers();
                }, CONFIG.detectionDelay);
            }
        }
    }).observe(document, { subtree: true, childList: true });

})();