Greasy Fork is available in English.

bilibili网页播放器增加倍速按钮

在B站(bilibili)网页播放器上增加一些额外的倍速按钮

// ==UserScript==
// @name         bilibili网页播放器增加倍速按钮
// @namespace    voeoc
// @version      0.0.4
// @description  在B站(bilibili)网页播放器上增加一些额外的倍速按钮
// @author       voeoc
// @include      https://www.bilibili.com/video/*
// @icon         https://www.google.com/s2/favicons?domain=bilibili.com
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-idle
// ==/UserScript==

(function() {
    // 加入的按钮
    const DEFAULT_SPEED_LIST_BUTTON = [0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 3, 4, 5, 10, 100];

    // 存储上一页面倍速的键
    const GMKEY_LAST_SPEED_VALUE = "VOEOC_GMKEY_LAST_SPEED_VALUE";
    // 存储倍速列表的键
    const GMKEY_SPEED_LIST = "VOEOC_GMKEY_SPEED_LIST";
    // 设置是否自动保存倍速
    const GMKEY_AUTO_SAVE_SPEED = "VOEOC_GMKEY_AUTO_SAVE_SPEED";

    function formatSpeedValue(speedValue) {
        if (Number.isInteger(speedValue)) {
            return `${speedValue.toString()}.0`;
        } else {
            return speedValue.toString();
        }
    }

    function setSpeedButtonHighlight(btnSpeed) {
        let nowHighlightButton = document.querySelector('.bilibili-player-active');
        if (nowHighlightButton != null) {
            nowHighlightButton.classList.remove("bilibili-player-active");
        }

        btnSpeed.classList.add("bilibili-player-active");
    }

    function setSpeed(speedValue) {

        document.querySelector('video').playbackRate = speedValue;
        // 修改显示的文字
        let btnShowSpeed = document.querySelector('.bilibili-player-video-btn-speed-name');
        if (speedValue != 1) {
            btnShowSpeed.innerHTML = `${formatSpeedValue(speedValue)}x`;
        } else {
            btnShowSpeed.innerHTML = "倍速";
        }

        console.log(`voeoc: 成功设定倍速${speedValue}`)
    }

    function createThatButton(text) {
        let btn = document.createElement("li");
        btn.classList.add("bilibili-player-video-btn-speed-menu-list");
        btn.classList.add("voeoc-virtual-button");
        btn.innerHTML = text;
        return btn;
    }

    function createSpeedButton(speedValue) {
        let btnSpeed = createThatButton(`${formatSpeedValue(speedValue)}x`);
        btnSpeed.setAttribute("data-value", `${speedValue}`);

        btnSpeed.onclick = function() {
            // 设定视频的倍速
            setSpeed(speedValue);
            console.log(`voeoc: 用户设置倍速${speedValue}`)

            // 刷新高亮
            setSpeedButtonHighlight(btnSpeed);
            // 储存当前倍速
            GM_setValue(GMKEY_LAST_SPEED_VALUE, speedValue);
            // 隐藏弹出菜单
            document.querySelector(".bilibili-player-video-btn.bilibili-player-video-btn-speed").classList.remove("bilibili-player-speed-show");
        };

        return btnSpeed;
    }

    function getIsAutoSaveSpeed() {
        return GM_getValue(GMKEY_AUTO_SAVE_SPEED, false);
    }

    function appendSettingsUI() {
        let isTheFirstRun = true;
        waitElementLoaded(".bilibili-player-video-danmaku-setting", (bilibiliPlayerSettingsPanelButton) => {
            bilibiliPlayerSettingsPanelButton.addEventListener("mouseover", function() {
                if (!isTheFirstRun) {
                    return;
                }
                waitElementLoaded(".bilibili-player-video-danmaku-setting-right", (bilibiliPlayerSettingsPanel) => {
                    let btnSpeedSettings = document.createElement("div");
                    btnSpeedSettings.className = "voeoc-button-speed-settings";
                    btnSpeedSettings.innerHTML = `
        修改倍速表...
        <div id="voeoc-speed-list-settings-panel" class="voeoc-speed-list-settings-panel">
            <div style="margin: 5px;">
            <input id="voeoc-text-speed-list" class="voeoc-text-speed-list" style=""></input>
            <label style="float:left; ">
                <input type="checkbox"  id="voeoc-cbox-auto-save-speed" style="vertical-align:middle;background-color: red;">
                自动播放倍速
            </label>
            <div id="voeoc-button-speed-settings-cancel"class="bui bui-button bui-button-transparent voeoc-button-speed-settings-apply">取消</div>
            <div id="voeoc-button-speed-settings-apply" class="bui bui-button bui-button-transparent voeoc-button-speed-settings-apply">确定</div>
            </div>
        </div>
                    `;

                    function showSettingPanel(isShow) {
                        let settingsPanel = bilibiliPlayerSettingsPanel.querySelector("#voeoc-speed-list-settings-panel");
                        if (isShow) {

                            let inputBoxSpeedList = bilibiliPlayerSettingsPanel.querySelector("#voeoc-text-speed-list");
                            let cboxAutoSaveSpeed = bilibiliPlayerSettingsPanel.querySelector("#voeoc-cbox-auto-save-speed");

                            // 载入倍速表,格式为数组
                            inputBoxSpeedList.value = GM_getValue(GMKEY_SPEED_LIST, DEFAULT_SPEED_LIST_BUTTON).toString();

                            // 载入是否自动保存的选项
                            cboxAutoSaveSpeed.checked = getIsAutoSaveSpeed();

                            settingsPanel.classList.add("voeoc-speed-list-settings-panel-show");
                        } else {
                            settingsPanel.classList.remove("voeoc-speed-list-settings-panel-show");
                        }
                    }

                    waitElementLoaded("#voeoc-button-speed-settings-apply", (btnOk) => {
                        btnOk.onclick = function() {
                            let inputBoxSpeedList = bilibiliPlayerSettingsPanel.querySelector("#voeoc-text-speed-list");

                            // 储存输入的倍速表
                            GM_setValue(GMKEY_SPEED_LIST, inputBoxSpeedList.value.split(" ").join("").replace(",", ",").split(","));
                            // 修改成功
                            // 隐藏设置面板
                            showSettingPanel(false);
                            // 刷新当前网页倍速表
                            reloadSpeedList();
                        };
                    });

                    waitElementLoaded("#voeoc-button-speed-settings-cancel", (btnCancel) => {
                        btnCancel.onclick = function() {
                            showSettingPanel(false);
                        };
                    });

                    waitElementLoaded("#voeoc-cbox-auto-save-speed", (cboxAutoSaveSpeed) => {
                        cboxAutoSaveSpeed.onclick = function() {
                            GM_setValue(GMKEY_AUTO_SAVE_SPEED, cboxAutoSaveSpeed.checked);
                        };
                    });

                    btnSpeedSettings.onmouseenter = function() {
                        showSettingPanel(true);
                    };

                    btnSpeedSettings.onmouseleave = function() {
                        let inputBoxSpeedList = bilibiliPlayerSettingsPanel.querySelector("#voeoc-text-speed-list");
                        if (inputBoxSpeedList != document.activeElement) {
                            // 如果倍速表输入框未获得焦点,取消显示设置面板
                            showSettingPanel(false);
                        }
                    };

                    bilibiliPlayerSettingsPanel.appendChild(btnSpeedSettings);
                });
                isTheFirstRun = false;
            });
        });
    }

    function reloadSpeedList() {
        let btnMenu = document.querySelector(".bilibili-player-video-btn-speed-menu");
        // 读取倍速表
        let oldSpeedList = GM_getValue(GMKEY_SPEED_LIST, DEFAULT_SPEED_LIST_BUTTON);
        // 读取上一页面的倍速设置
        let lastSpeedValue = GM_getValue(GMKEY_LAST_SPEED_VALUE, 1);

        // 清除冗余按钮
        btnMenu.innerHTML = "";

        // 添加按钮节点
        for (let i = 0; i < oldSpeedList.length; ++i) {
            let btnSpeed = createSpeedButton(Number(oldSpeedList[i]));
            // 在最前面插入按钮
            btnMenu.insertBefore(btnSpeed, btnMenu.childNodes[0]);
            // 根据上一页面刷新倍速
            if (getIsAutoSaveSpeed() && Math.abs(oldSpeedList[i] - lastSpeedValue) < 0.001) {
                btnSpeed.click();
            }
        }


        // 第一次加载页面,延时检测视频速度是否正确修改
        if (getIsAutoSaveSpeed()) {
            (function() {
                const TIME_OUT = 10; // 10次放弃
                let findTimeNum = 0; // 记录查找的次数

                let timer = setInterval(() => {
                    if (lastSpeedValue != document.querySelector('video').playbackRate) {
                        // 清除定时器
                        clearInterval(timer);
                        setSpeed(lastSpeedValue);
                    } else {
                        findTimeNum++;
                        if (TIME_OUT < findTimeNum) {
                            // 清除定时器,并且不执行回调
                            clearInterval(timer);
                        }
                    }
                }, 100);
            })();
        }
    }

    function waitElementLoaded(selector, func) {
        const TIME_OUT = 100; // 找100次没有找到就放弃
        let findTimeNum = 0; // 记录查找的次数
        let timer = setInterval(() => {
            let element = document.querySelector(selector);
            console.log(`voeoc: 查找${selector}:${element}`)
            if (element != null) {
                // 清除定时器
                clearInterval(timer);
                func(element);
            } else {
                findTimeNum++;
                if (TIME_OUT < findTimeNum) {
                    // 清除定时器,并且不执行回调
                    clearInterval(timer);
                    console.log(`voeoc: 查找${selector}失败`)
                }
            }
        }, 200);
    }



    // 等待视频组件加载完毕
    waitElementLoaded('video', (bilibiliPlayer) => {
        // 注入自定义样式
        (function() {
            let style = document.createElement("style");
            let cssText = `
.voeoc-virtual-button {}

.voeoc-button-speed-settings {
    margin-top: 9px;
    float: right;
    width: 80px;
    z-index: 999px;
    transform: translateZ(0);
}

.voeoc-speed-list-settings-panel {
    height: 90px;
    width: 240px;
    overflow: hidden;
    top: -90px;
    left: -150px;
    float: right;
    position: absolute;
    visibility: hidden;
    opacity: 0;
    background-color: rgb(59, 59, 59);
    margin: 0 -110px 0 0;
    border-radius: 2px;
}

.voeoc-speed-list-settings-panel.voeoc-speed-list-settings-panel-show {
    visibility: visible;
    opacity: 1;
}

.voeoc-button-speed-settings-apply {
    float: right;
    position: relative;
    top: 25px;
    margin-right: 2px;
}

.voeoc-text-speed-list {
    width: 225px;
    background-color: transparent;
    border-color: white;
    border-width: 0;
    border-bottom-width: 1px;
    border-style: solid;
    line-height: 22px;
}
            `;
            style.appendChild(document.createTextNode(cssText));
            document.getElementsByTagName("head")[0].appendChild(style);
        }());


        let isFristLoad = true;

        function loadData() {
            // 等待视频速率控制组件加载完毕
            waitElementLoaded(".bilibili-player-video-btn-speed-menu", (btnMenu) => {
                // 载入倍速表
                reloadSpeedList();

                // 载入设置界面
                appendSettingsUI();

            });
        };

        loadData();

        bilibiliPlayer.addEventListener("loadedmetadata", function() {
            if (isFristLoad) {
                isFristLoad = false;
                return;
            }
            loadData();
        });
    });
})();