Greasy Fork

Greasy Fork is available in English.

百度网盘视频倍速控制器

在百度网盘视频右侧添加倍速控制按钮(等待两个video元素出现)

// ==UserScript==
// @name         百度网盘视频倍速控制器
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  在百度网盘视频右侧添加倍速控制按钮(等待两个video元素出现)
// @author       Jociber
// @match        https://pan.baidu.com/*
// @match        http://pan.baidu.com/*
// @grant        none
// @noframes     不让脚本在frame中运行
// ==/UserScript==

(function() {
    'use strict';
    
    let observer = null;
    let controlCreated = false;
    let currentVideo = null;
    let currentSpeed = 1.0;
    
    // 检查是否满足条件并创建控制器
    function checkAndCreateControl() {
        // 如果已经创建过控制器,则不再创建
        if (controlCreated) return;
        
        // 查找所有video元素
        const videos = document.querySelectorAll('video');
        
        // 等待至少有两个video元素
        if (videos.length >= 2) {
            // 选择第1个video元素(通常是主要的播放器)
            const video = videos[0];
            currentVideo = video;
            createSpeedControl(video);
            controlCreated = true;
            
            // 停止观察
            if (observer) {
                observer.disconnect();
                observer = null
            }
        }
    }
    
    // 初始化检查
    function init() {
        // 立即检查一次
        checkAndCreateControl();
        
        // 如果还没有满足条件,设置观察器
        if (!controlCreated) {
            observer = new MutationObserver(function(mutations) {
                checkAndCreateControl();
            });
            
            // 观察整个文档的变化
            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
            
            // 设置超时,防止无限等待
            setTimeout(() => {
                if (!controlCreated && observer) {
                    observer.disconnect();
                    console.log('倍速控制器:等待超时,未找到足够的video元素');
                }
            }, 15000); // 15秒超时
        }
    }
    
    
    function createSpeedControl(video) {
        // 查找视频容器
        const videoContainer = video.closest('.video-player-container') || video.parentElement;
        if (!videoContainer) {
            console.log('倍速控制器:未找到视频容器');
            return;
        }
        
        // 检查是否已经存在控制器
        if (videoContainer.querySelector('.speed-control-container')) {
            return;
        }
        
        // 尝试从本地存储获取之前设置的倍速
        const savedSpeed = localStorage.getItem('baidu_pan_speed');
        if (savedSpeed) {
            currentSpeed = parseFloat(savedSpeed);
            video.playbackRate = currentSpeed;
        }
        
        // 创建主按钮容器
        const speedControl = document.createElement('div');
        speedControl.className = 'speed-control-container';
        speedControl.style.cssText = `
            position: absolute;
            right: 20px;
            top: -30px;
            z-index: 1000;
        `;
        
        // 创建主按钮
        const mainButton = document.createElement('button');
        mainButton.innerHTML = `${currentSpeed}x`;
        mainButton.style.cssText = `
            background: rgba(0, 0, 0, 0.7);
            color: white;
            border: none;
            padding: 8px 16px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
            transition: background 0.3s;
        `;
        
        mainButton.addEventListener('mouseenter', function() {
            this.style.background = 'rgba(0, 0, 0, 0.9)';
        });
        
        mainButton.addEventListener('mouseleave', function() {
            this.style.background = 'rgba(0, 0, 0, 0.7)';
        });
        
        // 创建下拉菜单
        const dropdown = document.createElement('div');
        dropdown.style.cssText = `
            position: absolute;
            top: 100%;
            right: 0;
            background: rgba(0, 0, 0, 0.9);
            border-radius: 4px;
            padding: 8px 0;
            min-width: 100px;
            display: none;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
        `;
        
        // 倍速选项
        const speeds = [0.5, 1.0, 1.5, 2.0];
        
        speeds.forEach(speed => {
            const speedButton = document.createElement('button');
            speedButton.innerHTML = `${speed}x`;
            speedButton.style.cssText = `
                display: block;
                width: 100%;
                background: none;
                border: none;
                color: white;
                padding: 8px 16px;
                cursor: pointer;
                text-align: left;
                font-size: 14px;
                transition: background 0.2s;
            `;
            
            // 高亮当前选中的倍速
            if (speed === currentSpeed) {
                speedButton.style.background = 'rgba(255, 255, 255, 0.2)';
                speedButton.style.fontWeight = 'bold';
            }
            
            speedButton.addEventListener('mouseenter', function() {
                this.style.background = 'rgba(255, 255, 255, 0.1)';
            });
            
            speedButton.addEventListener('mouseleave', function() {
                if (speed === currentSpeed) {
                    this.style.background = 'rgba(255, 255, 255, 0.2)';
                } else {
                    this.style.background = 'none';
                }
            });
            
            speedButton.addEventListener('click', function() {
                setPlaybackSpeed(speed, video, mainButton);
                dropdown.style.display = 'none';
            });
            
            dropdown.appendChild(speedButton);
        });
        
        // 添加自定义倍速输入
        const customContainer = document.createElement('div');
        customContainer.style.cssText = `
            padding: 8px 16px;
            border-top: 1px solid rgba(255, 255, 255, 0.2);
            margin-top: 8px;
        `;
        
        const customInput = document.createElement('input');
        customInput.type = 'number';
        customInput.placeholder = '自定义倍速';
        customInput.min = '0.1';
        customInput.max = '10';
        customInput.step = '0.1';
        customInput.style.cssText = `
            width: 100%;
            padding: 4px;
            background: rgba(255, 255, 255, 0.1);
            border: 1px solid rgba(255, 255, 255, 0.3);
            border-radius: 2px;
            color: white;
            font-size: 12px;
            box-sizing: border-box;
        `;
        
        const customButton = document.createElement('button');
        customButton.innerHTML = '应用';
        customButton.style.cssText = `
            width: 100%;
            margin-top: 4px;
            padding: 4px;
            background: #4CAF50;
            border: none;
            border-radius: 2px;
            color: white;
            cursor: pointer;
            font-size: 12px;
        `;
        
        customButton.addEventListener('click', function() {
            const customSpeed = parseFloat(customInput.value);
            if (customSpeed && customSpeed > 0 && customSpeed <= 10) {
                setPlaybackSpeed(customSpeed, video, mainButton);
                dropdown.style.display = 'none';
                customInput.value = '';
            }
        });
        
        customContainer.appendChild(customInput);
        customContainer.appendChild(customButton);
        dropdown.appendChild(customContainer);
        
        // 鼠标事件处理
        mainButton.addEventListener('click', function() {
            dropdown.style.display = dropdown.style.display === 'block' ? 'none' : 'block';
        });
        
        // 点击页面其他区域关闭下拉菜单
        document.addEventListener('click', function(e) {
            if (!speedControl.contains(e.target)) {
                dropdown.style.display = 'none';
            }
        });
        
        // 组装元素
        speedControl.appendChild(mainButton);
        speedControl.appendChild(dropdown);
        
        // 确保视频容器有相对定位
        if (getComputedStyle(videoContainer).position === 'static') {
            videoContainer.style.position = 'relative';
        }
        
        videoContainer.appendChild(speedControl);
        
        // 监听视频事件
        setupVideoListeners(video, mainButton);
        
        console.log('倍速控制器:创建成功');
    }
    
    // 设置播放速率并更新显示
    function setPlaybackSpeed(speed, video, button) {
        currentSpeed = speed;
        video.playbackRate = speed;
        button.innerHTML = `${speed}x`;
        
        // 保存到本地存储
        localStorage.setItem('baidu_pan_speed', speed.toString());
        
        // 更新下拉菜单中的高亮状态
        // updateSpeedButtonsHighlight(speed, button.parentElement);
    }
    
    // 更新下拉菜单中的高亮状态
    function updateSpeedButtonsHighlight(speed, container) {
        const buttons = container.querySelectorAll('button');
        buttons.forEach(btn => {
            const btnSpeed = parseFloat(btn.innerHTML.replace('x', ''));
            if (!isNaN(btnSpeed)) {
                if (btnSpeed === speed) {
                    btn.style.background = 'rgba(255, 255, 255, 0.2)';
                    btn.style.fontWeight = 'bold';
                } else {
                    btn.style.background = 'none';
                    btn.style.fontWeight = 'normal';
                }
            }
        });
    }
    
    // 设置视频事件监听器
    function setupVideoListeners(video, button) {
        // 监听播放事件,确保倍速设置不被重置
        // video.addEventListener('play', function() {
        //     video.playbackRate = currentSpeed;
        // });

        // 监听速率变化事件(防止被其他脚本修改)
        video.addEventListener('ratechange', function() {
            if (video.playbackRate !== currentSpeed) {
                // 如果速率被外部修改,更新我们的状态
                video.playbackRate = currentSpeed;
                // button.innerHTML = `${currentSpeed.toFixed(1)}x`;
            }
        });
        
        // 监听页面可见性变化(标签页切换)
        document.addEventListener('visibilitychange', function() {
            if (!document.hidden && video.playbackRate !== currentSpeed) {
                // 页面重新可见时,重新应用倍速
                setTimeout(() => {
                    video.playbackRate = currentSpeed;
                }, 100);
            }
        });
    }

    // 等待窗口完全加载完成
    // if (document.readyState === 'loading') {
    //     window.addEventListener('load', init);
    // } else {
    //     // 如果文档已经加载完成,直接执行
    //     if (document.readyState === 'complete') {
    //         init();
    //     } else {
    //         window.addEventListener('load', init);
    //     }
    // }
    init();

})();