黄金右键-灵活控制视频倍速-Golden Right-Flexibly control the playback rate of videos

按住"→"键倍速播放, 松开"→"键恢复原速, 灵活追剧看视频~ 支持所有H5视频的网站(YouTube、腾讯视频、优酷、番剧等); Press and hold the right arrow key (→) to set the video playback rate faster. Release the key to restore the original rate

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         黄金右键-灵活控制视频倍速-Golden Right-Flexibly control the playback rate of videos
// @description  按住"→"键倍速播放, 松开"→"键恢复原速, 灵活追剧看视频~ 支持所有H5视频的网站(YouTube、腾讯视频、优酷、番剧等); Press and hold the right arrow key (→) to set the video playback rate faster. Release the key to restore the original rate
// @namespace    http://tampermonkey.net/
// @homepage     https://github.com/SkyJinXX/Golden-Right
// @version      1.2.0
// @author       SkyJin
// @include     http://*
// @include     https://*
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// ==/UserScript==

(function () {
    "use strict";

    let down_count = 0;
    let faster_rate = GM_getValue("faster_rate", 3);
    let normal_rate = 1;
    let add_time = GM_getValue("add_time", 7);
    let page_video;

    // 注册菜单命令
    GM_registerMenuCommand("⚙️ Golden Right Settings", showSettings);

    // 设置界面
    function showSettings() {
        const currentFasterRate = GM_getValue("faster_rate", 3);
        const currentAddTime = GM_getValue("add_time", 7);
        
        // 创建设置对话框 - 使用 DOM API 避免 TrustedHTML 问题
        const overlay = document.createElement('div');
        overlay.style.cssText = `
            position: fixed; top: 0; left: 0; width: 100%; height: 100%; 
            background: rgba(0,0,0,0.5); z-index: 999999; display: flex; 
            align-items: center; justify-content: center;
        `;
        
        const dialog = document.createElement('div');
        dialog.style.cssText = `
            background: white; padding: 25px; border-radius: 8px; 
            box-shadow: 0 4px 20px rgba(0,0,0,0.3); min-width: 400px; max-width: 500px;
            font-family: Arial, sans-serif; line-height: 1.6;
        `;
        
        // 标题
        const title = document.createElement('h3');
        title.textContent = 'Golden Right - Settings';
        title.style.cssText = 'margin-top: 0; color: #333;';
        dialog.appendChild(title);
        
        // 倍速设置区域
        const fasterRateDiv = document.createElement('div');
        fasterRateDiv.style.cssText = 'margin: 15px 0;';
        
        const fasterRateLabel = document.createElement('label');
        fasterRateLabel.textContent = 'Playback Speed Rate:';
        fasterRateLabel.style.cssText = 'display: block; margin-bottom: 5px; font-weight: bold;';
        fasterRateDiv.appendChild(fasterRateLabel);
        
        const fasterRateInput = document.createElement('input');
        fasterRateInput.type = 'number';
        fasterRateInput.id = 'fasterRate';
        fasterRateInput.value = currentFasterRate;
        fasterRateInput.min = '1';
        fasterRateInput.max = '10';
        fasterRateInput.step = '0.1';
        fasterRateInput.style.cssText = 'width: 100px; padding: 5px; border: 1px solid #ccc; border-radius: 3px;';
        fasterRateDiv.appendChild(fasterRateInput);
        
        const fasterRateHint = document.createElement('span');
        fasterRateHint.textContent = 'Recommended: 2-4x';
        fasterRateHint.style.cssText = 'color: #666; font-size: 12px; margin-left: 10px;';
        fasterRateDiv.appendChild(fasterRateHint);
        
        dialog.appendChild(fasterRateDiv);
        
        // 快进设置区域
        const addTimeDiv = document.createElement('div');
        addTimeDiv.style.cssText = 'margin: 15px 0;';
        
        const addTimeLabel = document.createElement('label');
        addTimeLabel.textContent = 'Skip Forward Seconds:';
        addTimeLabel.style.cssText = 'display: block; margin-bottom: 5px; font-weight: bold;';
        addTimeDiv.appendChild(addTimeLabel);
        
        const addTimeInput = document.createElement('input');
        addTimeInput.type = 'number';
        addTimeInput.id = 'addTime';
        addTimeInput.value = currentAddTime;
        addTimeInput.min = '1';
        addTimeInput.max = '60';
        addTimeInput.style.cssText = 'width: 100px; padding: 5px; border: 1px solid #ccc; border-radius: 3px;';
        addTimeDiv.appendChild(addTimeInput);
        
        const addTimeHint = document.createElement('span');
        addTimeHint.textContent = 'Seconds to skip when single-clicking right arrow';
        addTimeHint.style.cssText = 'color: #666; font-size: 12px; margin-left: 10px;';
        addTimeDiv.appendChild(addTimeHint);
        
        dialog.appendChild(addTimeDiv);
        
        // 使用说明区域
        const helpDiv = document.createElement('div');
        helpDiv.style.cssText = 'margin: 20px 0; padding: 10px; background: #f0f8ff; border-left: 4px solid #1e90ff; border-radius: 3px;';
        
        const helpTitle = document.createElement('strong');
        helpTitle.textContent = 'Usage Instructions:';
        helpDiv.appendChild(helpTitle);
        
        const helpBr1 = document.createElement('br');
        helpDiv.appendChild(helpBr1);
        
        const helpText1 = document.createTextNode('• Single-click right arrow → Skip forward by specified seconds');
        helpDiv.appendChild(helpText1);
        
        const helpBr2 = document.createElement('br');
        helpDiv.appendChild(helpBr2);
        
        const helpText2 = document.createTextNode('• Hold right arrow → Fast playback, release to restore normal speed');
        helpDiv.appendChild(helpText2);
        
        dialog.appendChild(helpDiv);
        
        // 按钮区域
        const buttonDiv = document.createElement('div');
        buttonDiv.style.cssText = 'text-align: right; margin-top: 20px;';
        
        const cancelBtn = document.createElement('button');
        cancelBtn.textContent = 'Cancel';
        cancelBtn.style.cssText = 'margin-right: 10px; padding: 8px 15px; border: 1px solid #ccc; background: #f8f8f8; border-radius: 3px; cursor: pointer;';
        buttonDiv.appendChild(cancelBtn);
        
        const saveBtn = document.createElement('button');
        saveBtn.textContent = 'Save Settings';
        saveBtn.style.cssText = 'padding: 8px 15px; border: none; background: #4CAF50; color: white; border-radius: 3px; cursor: pointer;';
        buttonDiv.appendChild(saveBtn);
        
        dialog.appendChild(buttonDiv);
        
        overlay.appendChild(dialog);
        document.body.appendChild(overlay);
        
        // 事件处理
        saveBtn.onclick = () => {
            const newFasterRate = parseFloat(fasterRateInput.value);
            const newAddTime = parseInt(addTimeInput.value);
            
            if (newFasterRate > 0 && newAddTime > 0) {
                GM_setValue("faster_rate", newFasterRate);
                GM_setValue("add_time", newAddTime);
                
                // 更新当前变量
                faster_rate = newFasterRate;
                add_time = newAddTime;
                
                alert('Settings saved! Please refresh the page for changes to take effect.');
                document.body.removeChild(overlay);
            } else {
                alert('Please enter valid values!');
            }
        };
        
        cancelBtn.onclick = () => {
            document.body.removeChild(overlay);
        };
        
        // 点击遮罩关闭
        overlay.onclick = (e) => {
            if (e.target === overlay) {
                document.body.removeChild(overlay);
            }
        };
    }

    function makeArray(arr) {
        if (arr.item) {
            var len = arr.length;
            var array = [];
            while (len--) {
                array[len] = arr[len];
            }
            return array;
        }
        return Array.prototype.slice.call(arr);
    }
    const init = () => {
        if (location.origin.indexOf("youtube.com") > -1) {
            document.body.addEventListener("keydown", downEvent_YT, true);
            document.body.parentElement.addEventListener("keyup", upEvent_YT, true);
        } else {
            document.body.addEventListener("keydown", downEvent, true);
            document.body.parentElement.addEventListener("keyup", upEvent, true);
        }
    };
/*
.............########..#######..########......#######..########.##.....##.########.########...######.
.............##.......##.....##.##.....##....##.....##....##....##.....##.##.......##.....##.##....##
.............##.......##.....##.##.....##....##.....##....##....##.....##.##.......##.....##.##......
.............######...##.....##.########.....##.....##....##....#########.######...########...######.
.............##.......##.....##.##...##......##.....##....##....##.....##.##.......##...##.........##
.............##.......##.....##.##....##.....##.....##....##....##.....##.##.......##....##..##....##
.............##........#######..##.....##.....#######.....##....##.....##.########.##.....##..######.
*/
    const getPageVideo = () => {
        console.log("Finding available Video Element...");
        const allVideoElementArray = makeArray(
            document.getElementsByTagName("video")
        ).concat(makeArray(document.getElementsByTagName("bwp-video")));
        console.log(allVideoElementArray);
        const page_video = Array.prototype.find.call(
            allVideoElementArray,
            (e) => {
                if (checkPageVideo(e)) return e;
            }
        );

        if (page_video) {
            console.log("Found the Video Element!");
            return page_video;
        } else {
            console.log("找不到正在播放的Video Element");
        }
    };
    const checkPageVideo = (v) => {
        if (v) {
            return v.offsetWidth > 9 && !v.paused;
        } else {
            return false;
        }
    };
    const relativeEvent = {
        _stopper: (e) => e.stopPropagation(),
        // 目前针对腾讯视频
        shouldPrevent:
            location.origin.indexOf("qq.com") > -1 ||
            location.origin.indexOf("wetv.vip") > -1,
        prevent() {
            document.body.addEventListener("ratechange", this._stopper, true);
            document.body.addEventListener("timeupdate", this._stopper, true);
        },
        allow() {
            document.body.removeEventListener(
                "ratechange",
                this._stopper,
                true
            );
            document.body.removeEventListener(
                "timeupdate",
                this._stopper,
                true
            );
        },
    };
    const downEvent = (e) => {
        if (e.keyCode !== 39) return;
        e.stopPropagation();

        // 计数+1
        down_count++;

        // 长按右键-开始
        if (down_count === 2) {
            if (checkPageVideo(page_video) || (page_video = getPageVideo())) {
                relativeEvent.shouldPrevent && relativeEvent.prevent();
                normal_rate = page_video.playbackRate;
                page_video.playbackRate = faster_rate;
                console.log("加速播放中...");
            }
        }
    };
    const upEvent = (e) => {
        if (e.keyCode !== 39) return;
        e.stopPropagation();

        // 单击右键时
        if (down_count === 1) {
            if (checkPageVideo(page_video) || (page_video = getPageVideo())) {
                page_video.currentTime += add_time;
                console.log("前进" + add_time + "秒");
            }
        }

        // 长按右键-结束
        if (page_video && page_video.playbackRate !== normal_rate) {
            page_video.playbackRate = normal_rate;
            relativeEvent.shouldPrevent && relativeEvent.allow();
        }

        // 计数-重置
        down_count = 0;
    };
/*
    .########..#######..########.....##....##..#######..##.....##.########.##.....##.########..########
    .##.......##.....##.##.....##.....##..##..##.....##.##.....##....##....##.....##.##.....##.##......
    .##.......##.....##.##.....##......####...##.....##.##.....##....##....##.....##.##.....##.##......
    .######...##.....##.########........##....##.....##.##.....##....##....##.....##.########..######..
    .##.......##.....##.##...##.........##....##.....##.##.....##....##....##.....##.##.....##.##......
    .##.......##.....##.##....##........##....##.....##.##.....##....##....##.....##.##.....##.##......
    .##........#######..##.....##.......##.....#######...#######.....##.....#######..########..########
*/
    const getPageVideo_YT = () => {
        console.log("Finding available Video Element...");
        let pv;

        if (document.getElementById("ytd-player") && checkPageVideo_YT(document.getElementById("ytd-player").player_))
            pv = document.getElementById("ytd-player").player_

        if (pv) {
            console.log("Found the Video Element!");
            return pv;
        } else {
            console.log("找不到正在播放的Video Element");
        }
    };
    const checkPageVideo_YT = (v) => {
        if (v) {
            return v.getPlayerState() == 1;
        } else {
            return false;
        }
    };
    const downEvent_YT = (e) => {
        if (e.keyCode !== 39) return;
        e.stopPropagation();

        // 计数+1
        down_count++;

        // 长按右键-开始
        if (down_count === 2) {
            if (checkPageVideo_YT(page_video) || (page_video = getPageVideo_YT())) {
                normal_rate = page_video.getPlaybackRate();
                page_video.setPlaybackRate(faster_rate);
                console.log("加速播放中...");
            }
        }
    };
    const upEvent_YT = (e) => {
        if (e.keyCode !== 39) return;
        e.stopPropagation();

        // 单击右键时
        if (down_count === 1) {
            if (checkPageVideo_YT(page_video) || (page_video = getPageVideo_YT())) {
                page_video.seekToStreamTime(page_video.getCurrentTime() + add_time);
                console.log("前进" + add_time + "秒");
            }
        }

        // 长按右键-结束
        if (page_video && page_video.getPlaybackRate() !== normal_rate) {
            page_video.setPlaybackRate(normal_rate);
        }

        // 计数-重置
        down_count = 0;
    };
/*
.............####.##....##.####.########.####....###....########.########
..............##..###...##..##.....##.....##....##.##......##....##......
..............##..####..##..##.....##.....##...##...##.....##....##......
..............##..##.##.##..##.....##.....##..##.....##....##....######..
..............##..##..####..##.....##.....##..#########....##....##......
..............##..##...###..##.....##.....##..##.....##....##....##......
.............####.##....##.####....##....####.##.....##....##....########
*/
    init();
})();