Greasy Fork

Greasy Fork is available in English.

视频控制增强

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

当前为 2025-01-02 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         视频控制增强
// @namespace    http://tampermonkey.net/
// @version      2.2
// @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;

    // 在全局作用域注册菜单(只注册一次)
    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) {  // 只在加速时监控
                        
                        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;
    }

    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;
    }

    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);

        function checkPress() {
            handlePressDetection();
            requestAnimationFrame(checkPress);
        }
        checkPress();

        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();
})();