Greasy Fork is available in English.

视频控制增强

视频播放增强:1. 长按左键临时加速 2. B站自动开启AI字幕 3. 支持更多视频播放器

// ==UserScript==
// @name         视频控制增强
// @namespace    http://tampermonkey.net/
// @version      2.3
// @description  视频播放增强:1. 长按左键临时加速 2. B站自动开启AI字幕 3. 支持更多视频播放器
// @author       Alonewinds
// @match        *://*/*
// @exclude      *://*/iframe/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 在脚本最开始添加一个检查
    // 对于 B 站,只在主页面和播放器 iframe 中执行
    // 对于其他网站,允许在所有环境中执行
    if (window.location.hostname.includes('bilibili.com') &&
        window.self !== window.top &&
        window.location.hostname !== 'player.bilibili.com') {
        return;
    }

    // 默认配置
    const config = {
        speedRate: GM_getValue('speedRate', 2.0),
        minPressTime: 200,
        subtitleEnabled: GM_getValue('subtitleEnabled', false),
        // 保持通用选择器,添加B站和YouTube选择器
        selectors: {
            'www.bilibili.com': '.bpx-player-video-area',
            'www.youtube.com': '.html5-video-player',  // 添加 YouTube 选择器
            'default': '.video-controls, .progress-bar, [role="slider"]'
        }
    };

    // 状态变量
    let pressStartTime = 0;
    let originalSpeed = 1.0;
    let isPressed = false;
    let activeVideo = null;
    let isLongPress = false;
    let preventNextClick = false;

    // B站字幕相关变量
    let subtitleEnabled = false;
    let subtitleAttempts = 0;
    let subtitleRetryTimer = null;
    let manuallyDisabled = false;
    let currentVideoId = '';
    let clickHandler = null;

    // 添加动画帧ID跟踪
    let animationFrameId = null;

    // 添加开始检测函数
    function startPressDetection() {
        if (!animationFrameId) {
            function checkPress() {
                handlePressDetection();
                animationFrameId = requestAnimationFrame(checkPress);
            }
            checkPress();
        }
    }

    // 添加停止检测函数
    function stopPressDetection() {
        if (animationFrameId) {
            cancelAnimationFrame(animationFrameId);
            animationFrameId = null;
        }
    }

    // 在全局作用域注册菜单(只注册一次)
    GM_registerMenuCommand('设置倍速值', () => {
        // 检查是否在主页面或B站播放器
        if (window.self !== window.top && window.location.hostname !== 'player.bilibili.com') return;

        const newSpeed = prompt('请输入新的倍速值(建议范围:1.1-4):', config.speedRate);
        if (newSpeed && !isNaN(newSpeed)) {
            config.speedRate = parseFloat(newSpeed);
            GM_setValue('speedRate', config.speedRate);

            const indicator = document.querySelector('.speed-indicator');
            if (indicator) {
                indicator.innerHTML = `当前加速 ${config.speedRate}x <span class="speed-arrow">▶▶</span>`;
            }
        }
    });

    // 仅在B站页面显示字幕菜单(只注册一次)
    if (window.location.hostname === 'www.bilibili.com') {
        GM_registerMenuCommand('B站字幕: ' + (config.subtitleEnabled ? '已开启' : '已关闭'), () => {
            // 检查是否在主页面或B站播放器
            if (window.self !== window.top && window.location.hostname !== 'player.bilibili.com') return;

            config.subtitleEnabled = !config.subtitleEnabled;
            GM_setValue('subtitleEnabled', config.subtitleEnabled);
            alert('B站字幕' + (config.subtitleEnabled ? '开启,刷新页面生效' : '关闭,刷新页面生效'));
        });
    }

    // ================ 倍速控制功能 ================
    function findVideoElement(element) {
        if (!element) return null;

        if (element instanceof HTMLVideoElement) {
            return element;
        }

        const domain = window.location.hostname;

        // B站和YouTube使用区域限制
        if (domain === 'www.bilibili.com') {
            const playerArea = document.querySelector('.bpx-player-video-area');
            if (!playerArea?.contains(element)) return null;
        } else if (domain === 'www.youtube.com') {
            const ytPlayer = element.closest('.html5-video-player');
            if (!ytPlayer?.contains(element)) return null;
            const video = ytPlayer.querySelector('video');
            if (video) return video;
        }

        const controlSelector = config.selectors.default;
        if (element.closest(controlSelector)) {
            return null;
        }

        const container = element.closest('*:has(video)');
        const video = container?.querySelector('video');
        return video && window.getComputedStyle(video).display !== 'none' ? video : null;
    }

    function setYouTubeSpeed(video, speed) {
        if (window.location.hostname === 'www.youtube.com') {
            const player = video.closest('.html5-video-player');
            if (player) {
                try {
                    // 清理之前的监控器
                    if (player._speedInterval) {
                        clearInterval(player._speedInterval);
                        player._speedInterval = null;
                    }

                    // 设置速度
                    video.playbackRate = speed;

                    if (speed !== 1.0) {  // 只在加速时监控
                        // 增加检查间隔到 100ms
                        player._speedInterval = setInterval(() => {
                            if (video.playbackRate !== speed) {
                                video.playbackRate = speed;
                            }
                        }, 100);  

                        // 添加超时清理
                        setTimeout(() => {
                            if (player._speedInterval) {
                                clearInterval(player._speedInterval);
                                player._speedInterval = null;
                            }
                        }, 5000);  // 5秒后自动清理
                    }

                    video.dispatchEvent(new Event('ratechange'));
                } catch (e) {
                    console.error('设置 YouTube 播放速度失败:', e);
                }
            }
        } else {
            video.playbackRate = speed;
        }
    }

    // ================ B站字幕功能 ================
    function getVideoId() {
        const match = location.pathname.match(/\/video\/(.*?)\//);
        return match ? match[1] : '';
    }

    function enableSubtitle() {
        if (!config.subtitleEnabled) return;
        if (window.location.hostname !== 'www.bilibili.com') return;
        if (subtitleAttempts >= 5 || manuallyDisabled) return;

        subtitleAttempts++;

        const button = document.querySelector("div.bpx-player-ctrl-btn.bpx-player-ctrl-subtitle > div.bpx-player-ctrl-btn-icon > span");
        if (!button) {
            subtitleRetryTimer = setTimeout(enableSubtitle, 1000);
            return;
        }

        const parent = button.closest('.bpx-player-ctrl-subtitle');
        if (!parent) return;

        if (clickHandler) {
            parent.removeEventListener('click', clickHandler);
        }

        clickHandler = (event) => {
            requestAnimationFrame(() => {
                if (!parent.classList.contains('bpx-player-ctrl-subtitle-on')) {
                    manuallyDisabled = true;
                    console.log('用户已手动关闭字幕,本视频将不再自动开启');
                }
            });
        };

        parent.addEventListener('click', clickHandler);

        if (!parent.classList.contains('bpx-player-ctrl-subtitle-on') && !manuallyDisabled) {
            button.click();
            subtitleEnabled = true;
        }
    }

    // ================ 事件处理 ================
    function handleMouseDown(e) {
        if (e.button !== 0) return;

        const domain = window.location.hostname;
        let video = null;
        let playerArea = null;

        // B站和YouTube使用严格区域限制
        if (domain === 'www.bilibili.com' || domain === 'www.youtube.com') {
            const selector = config.selectors[domain];
            playerArea = document.querySelector(selector);
            if (!playerArea?.contains(e.target)) return;
            video = findVideoElement(e.target);
        } else {
           
            video = findVideoElement(e.target);
            if (video) {
                playerArea = video.closest('*:has(video)') || video.parentElement;
            }
        }

        if (!video) return;

        if (video.paused) {
            hideSpeedIndicator();
            return;
        }

        pressStartTime = Date.now();
        activeVideo = video;
        originalSpeed = video.playbackRate;
        isPressed = true;
        isLongPress = false;
        preventNextClick = false;

        // 开始检测
        startPressDetection();
    }

    function handleMouseUp(e) {
        if (!isPressed || !activeVideo) return;

        const pressDuration = Date.now() - pressStartTime;
        if (pressDuration >= config.minPressTime) {
            preventNextClick = true;
            setYouTubeSpeed(activeVideo, originalSpeed);
            hideSpeedIndicator();
        }

        isPressed = false;
        isLongPress = false;
        activeVideo = null;

        // 停止检测
        stopPressDetection();
    }

    function handlePressDetection() {
        if (!isPressed || !activeVideo) return;

        const pressDuration = Date.now() - pressStartTime;
        if (pressDuration >= config.minPressTime) {
            // 获取最新的速度值
            const currentSpeedRate = GM_getValue('speedRate', config.speedRate);

            if (activeVideo.playbackRate !== currentSpeedRate) {
                setYouTubeSpeed(activeVideo, currentSpeedRate);
            }

            
            if (!isLongPress) {
                isLongPress = true;
                const playerArea = activeVideo.closest('*:has(video)') || activeVideo.parentElement;
                let indicator = document.querySelector('.speed-indicator');
                if (!indicator) {
                    indicator = document.createElement('div');
                    indicator.className = 'speed-indicator';
                    playerArea.appendChild(indicator);
                }
                indicator.innerHTML = `当前加速 ${currentSpeedRate}x <span class="speed-arrow">▶▶</span>`;
                indicator.style.display = 'block';
            }
        }
    }

    function handleClick(e) {
        if (preventNextClick) {
            e.stopPropagation();
            preventNextClick = false;
            return;
        }
    }

    function onVideoLoad() {
        if (window.location.hostname !== 'www.bilibili.com') return;

        const video = document.querySelector('video');
        if (!video) {
            setTimeout(onVideoLoad, 500);
            return;
        }

        function reset() {
            const newVideoId = getVideoId();
            if (newVideoId !== currentVideoId) {
                manuallyDisabled = false;
                currentVideoId = newVideoId;
                if (clickHandler) {
                    const oldButton = document.querySelector('.bpx-player-ctrl-subtitle');
                    if (oldButton) {
                        oldButton.removeEventListener('click', clickHandler);
                    }
                    clickHandler = null;
                }
            }

            subtitleEnabled = false;
            subtitleAttempts = 0;

            if (subtitleRetryTimer) {
                clearTimeout(subtitleRetryTimer);
                subtitleRetryTimer = null;
            }

            if (!manuallyDisabled) {
                setTimeout(enableSubtitle, 500);
            }
        }

        video.addEventListener('loadeddata', reset);
        video.addEventListener('play', () => {
            if (!subtitleEnabled && !manuallyDisabled) {
                reset();
            }
        });

        reset();
    }

    // ================ 初始化 ================
    function initializeEvents() {
        addSpeedIndicatorStyle();

        document.addEventListener('mousedown', handleMouseDown, true);
        document.addEventListener('mouseup', handleMouseUp, true);
        document.addEventListener('click', handleClick, true);
        document.addEventListener('mouseleave', handleMouseUp, true);

        
        document.addEventListener('fullscreenchange', hideSpeedIndicator);
        document.addEventListener('webkitfullscreenchange', hideSpeedIndicator);
        document.addEventListener('mozfullscreenchange', hideSpeedIndicator);
        document.addEventListener('MSFullscreenChange', hideSpeedIndicator);

        document.addEventListener('pause', (e) => {
            if (e.target instanceof HTMLVideoElement) {
                hideSpeedIndicator();
            }
        }, true);

        if (window.location.hostname === 'www.bilibili.com') {
            if (document.readyState === 'loading') {
                document.addEventListener('DOMContentLoaded', onVideoLoad);
            } else {
                onVideoLoad();
            }
        }
    }

    function addSpeedIndicatorStyle() {
        const style = document.createElement('style');
        style.textContent = `
            .speed-indicator {
                position: absolute;
                top: 15%;
                left: 50%;
                transform: translateX(-50%);
                background: rgba(0, 0, 0, 0.7);
                color: white;
                padding: 5px 10px;
                border-radius: 4px;
                z-index: 999999;
                display: none;
                font-size: 14px;
            }
            .speed-arrow {
                color: #00a1d6;
                margin-left: 2px;
            }`;
        document.head.appendChild(style);
    }

    function hideSpeedIndicator() {
        const indicator = document.querySelector('.speed-indicator');
        if (indicator) {
            indicator.style.display = 'none';
        }
    }

    initializeEvents();
})();