Greasy Fork

Greasy Fork is available in English.

独播库专用视频控制器

快进、快退、音量调节、暂停控制等功能自定义控制,并显示通知。

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         独播库专用视频控制器
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  快进、快退、音量调节、暂停控制等功能自定义控制,并显示通知。
// @author       NB888
// @match        https://www.duboku.tv/*
// @match        https://w.duboku.io/*
// @match        https://v.duboku.io/*
// @match        https://tv.gboku.com/*
// @grant        none
// @license     Proprietary - Only for personal use, no modifications or redistributions allowed. 
//              The code may not be modified, copied, distributed, or used for derivative works without explicit permission.
// ==/UserScript==

(function() {
    'use strict';

    // 获取并设置默认值
    const defaults = {
        fastForwardSeconds: 10, // 快进秒数
        fastRewindSeconds: 10, // 快退秒数
        volumeChange: 5, // 音量增量
        pauseKey: "Space", // 暂停和恢复的键
        forwardKey: "ArrowRight", // 快进的键
        rewindKey: "ArrowLeft", // 快退的键
        volumeUpKey: "ArrowUp", // 音量增加的键
        volumeDownKey: "ArrowDown", // 音量减少的键
        playbackRate: 2.0, // 倍数播放默认值
        enabledSites: "u.duboku.io", // 默认生效的网站
    };

    // 从存储中读取自定义设置
    const getSettings = () => {
        return {
            fastForwardSeconds: GM_getValue('fastForwardSeconds', defaults.fastForwardSeconds),
            fastRewindSeconds: GM_getValue('fastRewindSeconds', defaults.fastRewindSeconds),
            volumeChange: GM_getValue('volumeChange', defaults.volumeChange),
            pauseKey: GM_getValue('pauseKey', defaults.pauseKey),
            forwardKey: GM_getValue('forwardKey', defaults.forwardKey),
            rewindKey: GM_getValue('rewindKey', defaults.rewindKey),
            volumeUpKey: GM_getValue('volumeUpKey', defaults.volumeUpKey),
            volumeDownKey: GM_getValue('volumeDownKey', defaults.volumeDownKey),
            playbackRate: GM_getValue('playbackRate', defaults.playbackRate),
            enabledSites: GM_getValue('enabledSites', defaults.enabledSites),
        };
    };

    // 保存设置到存储
    const saveSettings = (key, value) => {
        GM_setValue(key, value);
    };

    // 显示快进/快退/倍数播放提示
    const showPlaybackHint = (message, direction = 'forward') => {
        const video = document.querySelector('video');
        if (!video) return;

        const hint = document.createElement('div');
        hint.style.position = 'absolute';
        hint.style.top = '50%';
        hint.style.left = '50%';
        hint.style.transform = 'translate(-50%, -50%)';
        hint.style.width = direction === 'playbackRate' || direction === 'playbackRateRewind' ? '72px' : '88px'; // 快进快退提示增大 10%
        hint.style.height = direction === 'playbackRate' || direction === 'playbackRateRewind' ? '72px' : '88px'; // 快进快退提示增大 10%
        hint.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'; // 半透明背景
        hint.style.borderRadius = '50%';
        hint.style.display = 'flex';
        hint.style.flexDirection = 'column';
        hint.style.alignItems = 'center';
        hint.style.justifyContent = 'center';
        hint.style.zIndex = '10000';

        // 添加 3 个三角形
        const triangles = document.createElement('div');
        triangles.style.display = 'flex';
        triangles.style.gap = '5px';
        for (let i = 0; i < 3; i++) {
            const triangle = document.createElement('div');
            triangle.style.width = '0';
            triangle.style.height = '0';
            triangle.style.borderTop = '8px solid transparent';
            triangle.style.borderBottom = '8px solid transparent';
            if (direction === 'rewind' || direction === 'playbackRateRewind') {
                triangle.style.borderRight = '12px solid white'; // 向左倒的三角形
            } else {
                triangle.style.borderLeft = '12px solid white'; // 向右倒的三角形
            }
            triangles.appendChild(triangle);
        }
        hint.appendChild(triangles);

        // 添加秒数提示
        const text = document.createElement('div');
        text.style.color = 'white';
        text.style.fontSize = '14px';
        text.style.marginTop = '5px';
        text.innerText = message;
        hint.appendChild(text);

        video.parentElement.appendChild(hint);

        // 如果是倍数播放提示,设置闪烁效果
        if (direction === 'playbackRate' || direction === 'playbackRateRewind') {
            let isVisible = true;
            const blinkInterval = setInterval(() => {
                hint.style.opacity = isVisible ? '0' : '1';
                isVisible = !isVisible;
            }, 200); // 0.2秒闪烁一次

            // 返回提示元素和闪烁间隔,以便在按键松开时清除
            return { hint, blinkInterval };
        } else {
            // 0.5秒后消失
            setTimeout(() => {
                hint.remove();
            }, 500);
            return null;
        }
    };

    // 显示保存成功提示
    const showSaveSuccessHint = () => {
        const video = document.querySelector('video');
        if (!video) return;

        const hint = document.createElement('div');
        hint.style.position = 'absolute';
        hint.style.top = '50%';
        hint.style.left = '50%';
        hint.style.transform = 'translate(-50%, -50%)';
        hint.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'; // 半透明背景
        hint.style.color = 'white';
        hint.style.padding = '10px';
        hint.style.borderRadius = '5px';
        hint.style.fontSize = '16px';
        hint.style.zIndex = '10000';
        hint.innerText = '保存成功';

        video.parentElement.appendChild(hint);

        // 0.5秒后消失
        setTimeout(() => {
            hint.remove();
        }, 500);
    };

    // 显示音量提示
    const showVolumeHint = (volume) => {
        const video = document.querySelector('video');
        if (!video) return;

        const hint = document.createElement('div');
        hint.style.position = 'absolute';
        hint.style.top = '50%';
        hint.style.left = '50%';
        hint.style.transform = 'translate(-50%, -50%)';
        hint.style.width = '60px';
        hint.style.height = '40px';
        hint.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'; // 半透明背景
        hint.style.borderRadius = '5px';
        hint.style.display = 'flex';
        hint.style.alignItems = 'center';
        hint.style.justifyContent = 'center';
        hint.style.zIndex = '10000';

        const text = document.createElement('div');
        text.style.color = 'white';
        text.style.fontSize = '16px';
        text.innerText = `${Math.round(volume * 100)}%`;
        hint.appendChild(text);

        video.parentElement.appendChild(hint);

        // 0.5秒后消失
        setTimeout(() => {
            hint.remove();
        }, 500);
    };

    // 显示暂停图标
    const showPauseIcon = () => {
        const video = document.querySelector('video');
        if (!video) return;

        const pauseIcon = document.createElement('div');
        pauseIcon.style.position = 'absolute';
        pauseIcon.style.top = '50%';
        pauseIcon.style.left = '50%';
        pauseIcon.style.transform = 'translate(-50%, -50%)';
        pauseIcon.style.width = '60px';
        pauseIcon.style.height = '60px';
        pauseIcon.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'; // 半透明背景
        pauseIcon.style.borderRadius = '50%';
        pauseIcon.style.display = 'flex';
        pauseIcon.style.alignItems = 'center';
        pauseIcon.style.justifyContent = 'center';
        pauseIcon.style.zIndex = '10000';

        const triangle = document.createElement('div');
        triangle.style.width = '0';
        triangle.style.height = '0';
        triangle.style.borderTop = '15px solid transparent';
        triangle.style.borderBottom = '15px solid transparent';
        triangle.style.borderLeft = '25px solid white';
        triangle.style.transform = 'translateX(3px)'; // 向右偏移

        pauseIcon.appendChild(triangle);
        video.parentElement.appendChild(pauseIcon);

        // 监听播放事件,移除图标
        const onPlay = () => {
            pauseIcon.remove();
            video.removeEventListener('play', onPlay);
        };
        video.addEventListener('play', onPlay);
    };

    // 长按快进/快退倍数播放
    let isKeyHeld = false;
    let playbackHint = null; // 存储倍数播放提示

    const startKeyHold = (video, direction, settings) => {
        if (isKeyHeld) return;
        isKeyHeld = true;

        // 设置播放速度
        video.playbackRate = settings.playbackRate;
        playbackHint = showPlaybackHint(`${settings.playbackRate}x`, direction === 'rewind' ? 'playbackRateRewind' : 'playbackRate');
    };

    const stopKeyHold = (video) => {
        if (!isKeyHeld) return;
        isKeyHeld = false;
        video.playbackRate = 1.0; // 恢复默认播放速度

        // 清除倍数播放提示
        if (playbackHint) {
            clearInterval(playbackHint.blinkInterval);
            playbackHint.hint.remove();
            playbackHint = null;
        }
    };

    // 事件监听器:绑定快捷键事件
    let currentKeyListener = null; // 用于存储当前的事件监听器
    const bindKeys = (settings) => {
        // 移除旧的事件监听器
        if (currentKeyListener) {
            document.removeEventListener('keydown', currentKeyListener);
            document.removeEventListener('keyup', currentKeyListener);
        }

        // 创建新的事件监听器
        currentKeyListener = (event) => {
            const video = document.querySelector('video');
            if (!video) return;

            // 阻止页面滚动行为
            if (event.key === settings.volumeUpKey || event.key === settings.volumeDownKey) {
                event.preventDefault();
            }

            switch (event.key) {
                case settings.pauseKey: // 空格键暂停/恢复
                    if (event.type === 'keydown') {
                        if (video.paused) {
                            video.play();
                        } else {
                            video.pause();
                            showPauseIcon(); // 显示暂停图标
                        }
                    }
                    break;
                case settings.forwardKey: // 快进
                    if (event.type === 'keydown') {
                        if (event.repeat) {
                            startKeyHold(video, 'forward', settings);
                        } else {
                            video.currentTime += settings.fastForwardSeconds;
                            showPlaybackHint(`快进 ${settings.fastForwardSeconds} 秒`, 'forward');
                        }
                    } else if (event.type === 'keyup') {
                        stopKeyHold(video);
                    }
                    break;
                case settings.rewindKey: // 快退
                    if (event.type === 'keydown') {
                        if (event.repeat) {
                            startKeyHold(video, 'rewind', settings);
                        } else {
                            video.currentTime -= settings.fastRewindSeconds;
                            showPlaybackHint(`快退 ${settings.fastRewindSeconds} 秒`, 'rewind');
                        }
                    } else if (event.type === 'keyup') {
                        stopKeyHold(video);
                    }
                    break;
                case settings.volumeUpKey: // 音量增加
                    if (event.type === 'keydown') {
                        video.volume = Math.min(1, video.volume + settings.volumeChange / 100);
                        showVolumeHint(video.volume);
                    }
                    break;
                case settings.volumeDownKey: // 音量减少
                    if (event.type === 'keydown') {
                        video.volume = Math.max(0, video.volume - settings.volumeChange / 100);
                        showVolumeHint(video.volume);
                    }
                    break;
            }
        };

        // 绑定新的事件监听器
        document.addEventListener('keydown', currentKeyListener);
        document.addEventListener('keyup', currentKeyListener);
    };

    // 监听全屏状态变化
    document.addEventListener('fullscreenchange', () => {
        const isFullscreen = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement;
        if (isFullscreen) {
            console.log('进入全屏模式');
        } else {
            console.log('退出全屏模式');
        }
    });

    // 创建设置面板
    const createSettingsPanel = () => {
        const settings = getSettings();
        const panel = document.createElement('div');
        panel.style.position = 'absolute';
        panel.style.top = '10px';
        panel.style.left = '10px';
        panel.style.background = 'rgba(0, 0, 0, 0.7)';
        panel.style.color = '#fff';
        panel.style.padding = '10px';
        panel.style.borderRadius = '5px';
        panel.style.zIndex = '10000';
        panel.innerHTML = `
            <h3>自定义视频控制</h3>
            <label>快进秒数: <input type="number" id="fastForwardSeconds" value="${settings.fastForwardSeconds}" /></label><br>
            <label>快退秒数: <input type="number" id="fastRewindSeconds" value="${settings.fastRewindSeconds}" /></label><br>
            <label>音量增量: <input type="number" id="volumeChange" value="${settings.volumeChange}" /></label><br>
            <label>倍数播放: <input type="number" id="playbackRate" value="${settings.playbackRate}" step="0.1" /></label><br>
            <label>暂停/恢复键: <input type="text" id="pauseKey" value="${settings.pauseKey}" readonly /></label><br>
            <label>快进键: <input type="text" id="forwardKey" value="${settings.forwardKey}" readonly /></label><br>
            <label>快退键: <input type="text" id="rewindKey" value="${settings.rewindKey}" readonly /></label><br>
            <label>音量加键: <input type="text" id="volumeUpKey" value="${settings.volumeUpKey}" readonly /></label><br>
            <label>音量减键: <input type="text" id="volumeDownKey" value="${settings.volumeDownKey}" readonly /></label><br>
            <label>生效网站: <textarea id="enabledSites" rows="3">${settings.enabledSites}</textarea></label><br>
            <div style="display: flex; gap: 10px; margin-top: 10px;">
                <button id="saveSettings" style="background-color: green; color: white; padding: 5px 10px; border: none; border-radius: 3px; cursor: pointer;">保存设置</button>
                <button id="exitSettings" style="background-color: red; color: white; padding: 5px 10px; border: none; border-radius: 3px; cursor: pointer;">退出设置</button>
            </div>
        `;

        // 只在视频区域显示面板
        const videoArea = document.querySelector('video');
        if (videoArea) {
            videoArea.parentElement.appendChild(panel);
        }

        // 为输入框绑定按键监听
        const keyInputs = panel.querySelectorAll('input[type="text"]');
        keyInputs.forEach(input => {
            input.addEventListener('click', () => {
                input.value = '按下按键...';
                const keyListener = (event) => {
                    event.preventDefault();
                    input.value = event.key;
                    document.removeEventListener('keydown', keyListener);
                };
                document.addEventListener('keydown', keyListener);
            });
        });

        // 保存设置
        document.getElementById('saveSettings').addEventListener('click', () => {
            // 保存用户设置
            saveSettings('fastForwardSeconds', parseInt(document.getElementById('fastForwardSeconds').value));
            saveSettings('fastRewindSeconds', parseInt(document.getElementById('fastRewindSeconds').value));
            saveSettings('volumeChange', parseInt(document.getElementById('volumeChange').value));
            saveSettings('playbackRate', parseFloat(document.getElementById('playbackRate').value));
            saveSettings('pauseKey', document.getElementById('pauseKey').value);
            saveSettings('forwardKey', document.getElementById('forwardKey').value);
            saveSettings('rewindKey', document.getElementById('rewindKey').value);
            saveSettings('volumeUpKey', document.getElementById('volumeUpKey').value);
            saveSettings('volumeDownKey', document.getElementById('volumeDownKey').value);
            saveSettings('enabledSites', document.getElementById('enabledSites').value);

            // 显示保存成功提示
            showSaveSuccessHint();

            // 重新绑定事件监听器
            const updatedSettings = getSettings();
            bindKeys(updatedSettings);

            // 关闭设置面板
            panel.remove();
        });

        // 退出设置
        document.getElementById('exitSettings').addEventListener('click', () => {
            panel.remove(); // 关闭设置面板
        });
    };

    // 在 Tampermonkey 扩展菜单中注册设置按钮
    GM_registerMenuCommand("打开设置", () => {
        createSettingsPanel();
    });

    // 检查当前网站是否在生效网站列表中
    const settings = getSettings();
    const enabledSites = settings.enabledSites.split('\n').map(site => site.trim());
    const currentSite = window.location.hostname;

    if (enabledSites.includes(currentSite)) {
        bindKeys(settings);
    }
})();