// ==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 // @downloadURL none // ==/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 = `