Greasy Fork

Greasy Fork is available in English.

视频倍速与快进-快捷方式

快捷键倍速播放、快进(支持B站、腾爱优、百度网盘、油管)。【倍速键】C 加速0.25,S 加速0.5;X 减速0.25,A 减速0.5。数字键1 2 3分别直达对应速率。【快进键】数字7/9分别快进30/60秒,6/8则快退相应值

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name          视频倍速与快进-快捷方式
// @version       1.2.9
// @description   快捷键倍速播放、快进(支持B站、腾爱优、百度网盘、油管)。【倍速键】C 加速0.25,S 加速0.5;X 减速0.25,A 减速0.5。数字键1 2 3分别直达对应速率。【快进键】数字7/9分别快进30/60秒,6/8则快退相应值
// @author        interest2
// @license       GPL-3.0-only
// @match         *://www.bilibili.com/video/*
// @match         *://www.bilibili.com/list/*
// @match         *://www.bilibili.com/cheese/*
// @match         *://v.qq.com/x/page/*
// @match         *://v.qq.com/x/cover/*
// @match         https://www.iqiyi.com/*
// @match         https://v.youku.com/*
// @match         https://pan.baidu.com/pfile/video?path=*
// @match         https://www.youtube.com/*
// @grant         GM_addStyle
// @namespace http://greasyfork.icu/users/1034870
// ==/UserScript==
/* globals $ waitForKeyElements */
!function() {
    "use strict";
    // 自定义的基础速度
    const BASE_RATE = 1.25;

//     快进、快退的快捷键的自定义修改
    const BACKWARD_30 = '6';
    const FORWARD_30 = '7';
    const BACKWARD_60 = '8';
    const FORWARD_60 = '9';
    const BUTTON_TRIPLE = '0';

//     加速、减速的快捷键的自定义修改
    const DEC_SMALL = 'x';
    const INC_SMALL = 'c';
    const DEC_BIG = 'a';
    const INC_BIG = 's';
    const BASIC_SPEED = 'z';

    // 速度提示框的显示时长(毫秒)
    const DURATION = 1200;
    // B站合集视频列表展示高度占据网页高度的比例
    const PLAYLIST_HEIGHT_RATIO = 0.6;

    const url = window.location.href;
    console.log("url: "+url);

    let site = 0;
    if(url.indexOf("bilibili") > -1){
        site = 0;
    }else if(url.indexOf("qq.com") > -1){
        site = 1;
    }else if(url.indexOf("iqiyi.com") > -1){
        site = 2;
    }else if(url.indexOf("baidu.com") > -1){
        site = 3;
    }else if(url.indexOf("youtube.com") > -1){
        site = 4;
    }else if(url.indexOf("youku.com") > -1){
        site = 5;
    }
    console.log("site: "+site);

    var box = getBox();
    console.log("box: "+box);

    function getBox(){
        var box = document.createElement("div");
        box.classList.add("tool-box");
        box.id = "tool-box";
        return box;
    }

    function getVideo(){
        const videoWithSrc = Array.from(document.querySelectorAll('video')).find(video => {
            let src = video.getAttribute('src');
            return src && typeof src === 'string' && src.trim() !== '';
        });
        let toolBox = document.getElementById("tool-box");
        if(isEmpty(toolBox) && !isEmpty(videoWithSrc)){
            console.log("插入box");
            if(site === 4){
                box.style.position = "relative";
                box.style.bottom = "-15rem";
                box.style.fontSize = "2rem";
                console.log(box);
            }
            videoWithSrc.insertAdjacentElement("afterend", box);
        }
        return videoWithSrc;
    }

    const intervalTime = 100; // 每次检查的间隔时间(毫秒)
    const maxChecks = 5000/intervalTime; // 最大检查次数

    let objCss = "";
    let checkCount = 0;
    let specificSite = [3, 4];
    let rawRateBarSite = [1, 3];

    const intervalId = setInterval(() => {
        const element = getVideo();
        checkCount++;

        if (!isEmpty(element)) {
            if(!specificSite.includes(site)){
                setRate();
                console.log("元素已找到:", element);
                console.log(checkCount);
                clearInterval(intervalId); // 停止循环
            }
        } else if (checkCount >= maxChecks) {
            console.log("已达到最大检查次数,未找到元素");
            clearInterval(intervalId); // 停止循环
        }
    }, intervalTime);

    // 特殊处理:① 首次延迟 ② 每隔1秒重设速度,避免暂停键的异常影响
    if(specificSite.includes(site)){
        setTimeout(setRate, 2000);
        setInterval(function() {
            setRecentRate();
        }, 1000);
    }

    let playbackRate = BASE_RATE;

    function setRecentRate(){
        let recentRate = sessionStorage.getItem("recentRate");
        if(!isEmpty(recentRate)){
            playbackRate = parseFloat(recentRate);
        }
        let video = getVideo();
//         百网盘切集后刚取到可能空,再取一次
        if(isEmpty(video)){
            video = getVideo();
            if(isEmpty(video)){
                console.log("video is empty");
                return;
            }

        }
        setTimeout(function(){
            // 重新取一次,否则src属性可能消失
            video = getVideo();
            video.playbackRate = playbackRate;
            showBaiduRateBar(playbackRate);
        }, 1000);
        console.log("playbackRate after: "+playbackRate);
    }

        // 百度网盘倍速条文字提示
    function showBaiduRateBar(playbackRate){
        const pattern = /倍速|X/i;
        let rateCss="vp-video__control-bar--button is-text";
        let rateItems=document.getElementsByClassName(rateCss);
        if(rateItems.length>0){
            for (let i = 0; i < rateItems.length; i++) {
                console.log(rateItems[i].textContent);
                if(pattern.test(rateItems[i].textContent)){
                    rateItems[i].textContent=playbackRate+"X";
                    break;
                }
            }
        }
    }

    // 覆盖默认的url改变触发的方法
    (function(history) {
        var pushState = history.pushState;
        var replaceState = history.replaceState;
        let video = getVideo();

        history.pushState = function(state) {
            pushState.apply(history, arguments);
            console.log('pushState URL changed to:', window.location.href);
            setRecentRate();
        };

        history.replaceState = function(state) {
            replaceState.apply(history, arguments);
            console.log('replaceState URL changed to:', window.location.href);
            setRecentRate();
        };
    })(window.history);

    let likeCss = "#arc_toolbar_report > div.video-toolbar-left > div > div:nth-child(1) > div";

    function setRate(){
        //B站右侧如果有分集列表,则给予更多展示高度
        if(site === 0){
            let playlist = document.querySelector("#mirror-vdcon > div.right-container > div > div.rcmd-tab > div.video-pod.video-pod > div.video-pod__body");
            if(!isEmpty(playlist)){
                const pageHeight = window.innerHeight;
                console.log("网页高度:", pageHeight);
                let top = playlist.getBoundingClientRect().top;

                playlist.style.maxHeight = pageHeight - top - 80 + "px";
                console.log(pageHeight, top);

            }
        }

        let video = getVideo();

        // 初始化倍速
        setRecentRate();
        let toolBox= document.getElementById("tool-box");

        // 监听键盘事件
        document.addEventListener('keydown', (event) => {
            let displayRateFlag = false;
            let setSessionRateFlag = true;
            let likeItem;

            switch (event.key.toLowerCase()) {
                case BASIC_SPEED:
                    console.log(video.playbackRate);
                    // 如果已经是基础速度了,则切回上次的倍速
                    if(video.playbackRate === BASE_RATE){
                        let recentRate = sessionStorage.getItem("recentRate");
                        if(!isEmpty(recentRate)){
                            playbackRate = parseFloat(recentRate);
                            displayRateFlag = true;
                        }
                    }else{
                    // 否则,恢复到自定义的基础速度
                        playbackRate = BASE_RATE;
                    }
                    displayRateFlag = true;
                    setSessionRateFlag = false;
                    break;
                case INC_BIG:
                    displayRateFlag = true;
                    playbackRate += 0.5;
                    break;
                case DEC_BIG:
                    playbackRate -= 0.5;
                    displayRateFlag = true;
                    if (playbackRate < 0.25) {
                        playbackRate = 0.5; // 设置最低倍速
                    }
                    break;
                case INC_SMALL:
                    playbackRate += 0.25;
                    displayRateFlag = true;
                    break;
                case DEC_SMALL:
                    playbackRate -= 0.25;
                    displayRateFlag = true;
                    if (playbackRate < 0.25) {
                        playbackRate = 0.25; // 最低倍速为0.25
                    }
                    break;
                case 'f':
                    if(site !== 0 && site !== 4){ // B站、油管本身支持F全屏键,若不排除会有小问题
                        if(isFullscreen()){
                            document.exitFullscreen();
                        }else{
                            video.requestFullscreen();
                        }
                    }
                    break;
                case '1':
                    playbackRate = 1.0;
                    displayRateFlag = true;
                    break;
                case '2':
                    playbackRate = 2.0;
                    displayRateFlag = true;
                    break;
                case '3':
                    playbackRate = 3.0;
                    displayRateFlag = true;
                    break;
                case '4':
                    playbackRate = 1.5;
                    displayRateFlag = true;
                    break;
                case '5':
                    playbackRate = 2.5;
                    displayRateFlag = true;
                    break;
                case BACKWARD_30:
                    video.currentTime -= 30;
                    break;
                case FORWARD_30:
                    video.currentTime += 30;
                    break;
                case BACKWARD_60:
                    video.currentTime -= 60;
                    break;
                case FORWARD_60:
                    video.currentTime += 60;
                    break;
                case BUTTON_TRIPLE:
                    likeItem = document.querySelector(likeCss);
                    longPress(likeItem);
                    break;
            }
            console.log("now rate is "+playbackRate);
            video = getVideo();
            video.playbackRate = playbackRate;
            if(setSessionRateFlag){
                sessionStorage.setItem("recentRate", playbackRate);
            }

            if(displayRateFlag){
                // 自定义的速度提示
                if(!rawRateBarSite.includes(site)){
                    toolBox.innerText = playbackRate;
                    toolBox.style.display = "block";

                    let showCount = sessionStorage.getItem("show");
                    if(isEmpty(showCount)){
                        sessionStorage.setItem("show", 1);
                    }else{
                        sessionStorage.setItem("show", parseInt(showCount) + 1);
                    }

                    setTimeout(() => {
                        let showCount2 = sessionStorage.getItem("show");
                        if(showCount2 <= 1){
                            toolBox.style.display = "none";
                            sessionStorage.setItem("show", "");
                        }else{
                            sessionStorage.setItem("show", parseInt(showCount2) - 1);
                        }
                    }, DURATION);


                // 原生控制条的速度提示
                }else{
                    showBaiduRateBar(playbackRate);
                    if(site === 1){
                        const element = document.querySelector("#player > div.plugin_ctrl_txp_bottom");
                        element.classList.remove("txp_none");

                        setTimeout(() => {
                            element.classList.add("txp_none");
                        }, DURATION);
                    }
                }

            }

        });
    }

    function isEmpty(item){
        if(item===null || item===undefined || item.length===0){
            return true;
        }else{
            return false;
        }
    }

    function isFullscreen() {
        return (
            document.fullscreenElement ||
            document.msFullscreenElement ||
            document.mozFullScreenElement ||
            document.webkitFullscreenElement ||
            false
        );
    }

    function longPress(element) {
        // 触发鼠标按下事件
        element.dispatchEvent(new MouseEvent('mousedown', { clientX: element.getBoundingClientRect().left + 1, clientY: element.getBoundingClientRect().top + 1 }));

        // 在指定的持续时间后触发鼠标释放事件
        setTimeout(() => {
            element.dispatchEvent(new MouseEvent('mouseup', { clientX: element.getBoundingClientRect().left + 1, clientY: element.getBoundingClientRect().top + 1 }));
        }, 3200);
    }

 function addCustomStyles() {
        var css = `
        .tool-box {
            display: none;   !important;
            position: absolute;   !important;
            bottom: 40%;   !important;
            left: 5%;   !important;
            transform: translate(-50%, 0%);   !important;
            width: 3.3rem;   !important;
            background: rgba(0, 0, 0, 0.7);   !important;
            color: white;   !important;
            padding: 0.5rem;   !important;
            border-radius: 5px;   !important;
            font-size: 1.4rem;   !important;
            text-align: center;   !important;
            z-index: 2147483647   !important;
        }
    `;
        GM_addStyle(css);
    }
    addCustomStyles();


}();