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则快退相应值

// ==UserScript==
// @name          视频倍速与快进-快捷方式
// @version       1.2.8
// @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);
        }
        console.log("videoWithSrc");
        console.log(videoWithSrc);
        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 contentHeight = window.innerHeight;
                console.log("网页高度:", contentHeight);
                playlist.style.maxHeight = PLAYLIST_HEIGHT_RATIO * contentHeight + "px";
            }
        }

        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();


}();