Greasy Fork 支持简体中文。

Speed Adjustment

Video Speed Adjustment

// ==UserScript==
// @name         Speed Adjustment
// @namespace    http://tampermonkey.net/
// @version      2025-02-27 V1.0.1
// @description  Video Speed Adjustment
// @author       SMK
// @include      *
// @license     MIT
// @icon         https://bkimg.cdn.bcebos.com/pic/d043ad4bd11373f0b4cbf3e2ab0f4bfbfaed04d2?x-bce-process=image/format,f_auto/quality,Q_70/resize,m_lfit,limit_1,w_536
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // 等待页面加载完成
    window.addEventListener('load', function() {
        const targetSelector = '#ad-pic,.ad-pic,#ad-overlay';

        // 隐藏广告元素
        function hideElement() {
            const element = document.querySelector(targetSelector);
            if (element) {
                element.style.display = 'none';
            }
        }

        // 监听页面变化,确保广告及时移除
        const observer = new MutationObserver(hideElement);
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        // 初始执行一次
        hideElement();

        // 保存位置到 localStorage
        function savePosition(position) {
            localStorage.setItem('speedControlPosition', JSON.stringify(position));
        }

        // 获取保存的位置,默认为右上角
        function getSavedPosition() {
            const savedPosition = localStorage.getItem('speedControlPosition');
            return savedPosition ? JSON.parse(savedPosition) : { top: '30px', right: '5px' };
        }

        // 选择页面上的所有视频元素
        function addSpeedControlsToVideos() {
            var videos = document.querySelectorAll('video');
            videos.forEach(function(video) {
                if (!video.hasAttribute('data-speed-controls')) { // 防止重复添加
                    var speedContainer = document.createElement('div');
                    speedContainer.style.position = 'absolute';
                    const position = getSavedPosition();
                    speedContainer.style.top = position.top;
                    speedContainer.style.right = position.right;
                    speedContainer.style.zIndex = '1000';
                    speedContainer.style.backgroundColor = 'rgba(0,0,0,0.5)';
                    speedContainer.style.color = 'white';
                    speedContainer.style.padding = '3px';
                    speedContainer.style.borderRadius = '5px';
                    speedContainer.style.display = 'flex';
                    speedContainer.style.flexWrap = 'wrap'; // 使按钮换行
                    speedContainer.style.alignItems = 'center'; // 垂直居中
                    speedContainer.style.overflow = 'hidden'; // 确保容器不会超出范围
                    speedContainer.style.width = '45px'; // 设置容器宽度,以便让按钮能够换行
                    speedContainer.style.transition = 'width 0.3s ease'; // 宽度动画过渡效果
                    speedContainer.style.whiteSpace = 'nowrap'; // 防止按钮换行

                    // 通用按钮样式
                    const buttonStyle = {
                        backgroundColor: 'rgba(0, 0, 0, 0.7)',
                        color: 'white',
                        border: 'none',
                        padding: '3px 6px',
                        borderRadius: '5px',
                        cursor: 'pointer',
                        fontSize: '14px',
                        margin: '3px',
                        fontFamily: 'Arial, sans-serif', // 统一字体
                    };

                    // 创建当前倍速按钮
                    var currentSpeedButton = document.createElement('button');
                    currentSpeedButton.innerText = `${video.playbackRate}x`;
                    Object.assign(currentSpeedButton.style, buttonStyle);

                    // 点击当前倍速按钮时,切换到其他倍速
                    currentSpeedButton.addEventListener('click', function() {
                        const speeds = [0.5, 1.0, 2.0, 2.5, 3.0, 5.0];
                        let currentIndex = speeds.indexOf(video.playbackRate);
                        if (currentIndex === -1) currentIndex = 1; // 默认到1倍速

                        // 循环倍速
                        const nextSpeed = speeds[(currentIndex + 1) % speeds.length];
                        video.playbackRate = nextSpeed;
                        currentSpeedButton.innerText = `${nextSpeed}x`; // 更新按钮显示的倍速
                    });
                    // 添加当前倍速按钮到容器
                    speedContainer.appendChild(currentSpeedButton);

                    // 播放速度选项按钮
                    const speeds = [0.5, 1.0, 2.0, 2.5, 3.0, 5.0];
                    speeds.forEach(function(speed) {
                        var speedButton = document.createElement('button');
                        speedButton.innerText = `${speed}x`;
                        Object.assign(speedButton.style, buttonStyle); // 使用统一的样式

                        // 点击按钮时改变播放速度
                        speedButton.addEventListener('click', function() {
                            const wasPaused = video.paused; // 记录视频是否暂停
                            const wasPlaying = !wasPaused;

                            // 设置播放速度
                            video.playbackRate = speed;
                            currentSpeedButton.innerText = `${speed}x`; // 更新当前倍速按钮显示的倍速

                            // 通过 requestAnimationFrame 确保播放不会中断
                            requestAnimationFrame(() => {
                                if (wasPlaying) {
                                    video.play();
                                }
                            });
                        });

                        // 初始时隐藏其他按钮
                        speedButton.style.display = 'none'; // 关键修改:初始时隐藏
                        speedContainer.appendChild(speedButton); // 将按钮添加到容器中
                    });

                    // 鼠标悬停时展开按钮,鼠标移开时收缩
                    speedContainer.addEventListener('mouseenter', function() {
                        const buttons = speedContainer.querySelectorAll('button');
                        buttons.forEach(button => button.style.display = 'block'); // 显示所有按钮
                        speedContainer.style.width = '270px'; // 扩展容器宽度,显示更多按钮
                    });

                    speedContainer.addEventListener('mouseleave', function() {
                        const buttons = speedContainer.querySelectorAll('button');
                        buttons.forEach((button, index) => {
                            if (index !== 0) button.style.display = 'none'; // 只保留第一个按钮(当前倍速)
                        });
                        speedContainer.style.width = '45px'; // 恢复容器宽度
                    });
                     /**
                    // 创建悬浮窗)

                    **/

                    const floatingWindow = document.createElement('div');
                    floatingWindow.style.position = 'absolute';
                    floatingWindow.style.top = '150px';
                    floatingWindow.style.right = '-10px';
                    floatingWindow.style.zIndex = '1000';
                    floatingWindow.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
                    floatingWindow.style.padding = '15px';
                    floatingWindow.style.borderRadius = '15px';
                    floatingWindow.style.transition = 'opacity 0.3s ease';
                    floatingWindow.style.opacity = '0.3';



                    // 鼠标悬停显示按钮
                    floatingWindow.addEventListener('mouseenter', function() {
                        floatingWindow.style.opacity = '1';
                        positionButtons.forEach(button => button.style.display = 'block');
                    });

                    // 鼠标移出隐藏按钮
                    floatingWindow.addEventListener('mouseleave', function() {
                        floatingWindow.style.opacity = '0.3';
                        positionButtons.forEach(button => button.style.display = 'none');
                    });

                    const positions = [
                        { label: '左上', top: '10px', left: '10px' },
                        { label: '右上', top: '10px', right: '10px' },
                        { label: '左下', bottom: '10px', left: '10px' },
                        { label: '右下', bottom: '10px', right: '10px' }
                    ];

                    const positionButtons = [];
                    positions.forEach(position => {
                        const positionButton = document.createElement('button');
                        positionButton.innerText = position.label;
                        Object.assign(positionButton.style, buttonStyle);
                        positionButton.style.display = 'none';
                        positionButton.style.marginBottom = '5px';

                        positionButton.addEventListener('click', function() {
                            speedContainer.style.top = position.top || '';
                            speedContainer.style.right = position.right || '';
                            speedContainer.style.bottom = position.bottom || '';
                            speedContainer.style.left = position.left || '';
                            savePosition(position);
                        });

                        floatingWindow.appendChild(positionButton);
                        positionButtons.push(positionButton);
                    });



                    // 将按钮容器插入到视频元素之前
                    video.parentNode.insertBefore(speedContainer, video);
                    video.parentNode.insertBefore(floatingWindow, video);
                    video.setAttribute('data-speed-controls', 'true'); // 防止重复添加
                }
            });
        }

        // 创建 MutationObserver 监听 video 元素的变化
        const videoObserver = new MutationObserver(addSpeedControlsToVideos);
        videoObserver.observe(document.body, {
            childList: true,
            subtree: true
        });

        // 初始执行一次
        addSpeedControlsToVideos();
    });
})();