Greasy Fork

Greasy Fork is available in English.

bilibili播放视频倍速自定义(原生按钮,支持0-16倍速)

bilibili播放视频倍速自定义,与原生按钮兼容,自动记忆

目前为 2024-09-07 提交的版本,查看 最新版本

// ==UserScript==
// @name         bilibili播放视频倍速自定义(原生按钮,支持0-16倍速)
// @namespace    dzj0821
// @version      1.1
// @description  bilibili播放视频倍速自定义,与原生按钮兼容,自动记忆
// @author       dzj0821
// @include      http*://*bilibili.com/video/*
// @include      http*://*bilibili.com/list/*
// @include      http*://*bilibili.com/bangumi/*
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @license      MIT
// ==/UserScript==

(function () {
    "use strict";
    //bilibili播放器限制,改这里也没用
    let SPEED_MIN_VALUE = 0
    let SPEED_MAX_VALUE = 16

    function getSpeedSetting() {
        let defaultValue = ["0.5", "0.75", "1.0", "1.25", "1.5", "2.0", "3.0", "4.0"]
        try {
            let setting = localStorage.getItem("dz_bilibili_video_custom_speed_setting")
            if (!setting) {
                return defaultValue
            }
            let arr = setting.split(" ")
            for (var value of arr) {
                let speed = parseFloat(value)
                if (isNaN(speed) || speed == 0 || speed < SPEED_MIN_VALUE || speed > SPEED_MAX_VALUE) {
                    return defaultValue
                }
            }
            return arr
        } catch (err) {
            return defaultValue
        }
    }

    function updateSpeedSetting(){
        let input = window.prompt("输入想显示的倍速,以空格分隔:", getSpeedSetting().join(" "))
        let arr = input.split(" ")
        for (var value of arr) {
            let speed = parseFloat(value)
            if (isNaN(speed)) {
                alert("【" + value + "】不是有效的速度")
                return
            }
            if (speed == 0) {
                alert("速度不能为0")
                return
            }
            if (speed < SPEED_MIN_VALUE) {
                alert("播放器速度下限为" + SPEED_MIN_VALUE + ",速度必须小于等于" + SPEED_MIN_VALUE)
                return
            }
            if (speed > SPEED_MAX_VALUE) {
                alert("播放器速度上限为" + SPEED_MAX_VALUE + ",速度必须小于等于" + SPEED_MAX_VALUE)
                return
            }
        }
        localStorage.setItem("dz_bilibili_video_custom_speed_setting", input)
        //重新初始化
        init()
    }

    GM_registerMenuCommand("更新倍速设置", updateSpeedSetting, null)

    function getSetSpeedOnLoadSetting() {
        return localStorage.getItem("dz_bilibili_video_custom_speed_set_speed_on_load") == "true"
    }

    function switchSetSpeedOnLoad() {
        localStorage.setItem("dz_bilibili_video_custom_speed_set_speed_on_load", !getSetSpeedOnLoadSetting())
        GM_unregisterMenuCommand(commandId)
        commandId = GM_registerMenuCommand(getSetSpeedOnLoadSetting() ? "记忆倍速:开启(点击关闭)" : "记忆倍速:关闭(点击开启)", switchSetSpeedOnLoad, null)
    }
    let commandId = GM_registerMenuCommand(getSetSpeedOnLoadSetting() ? "记忆倍速:开启(点击关闭)" : "记忆倍速:关闭(点击开启)", switchSetSpeedOnLoad, null)

    let speed = getSetSpeedOnLoadSetting() ? parseFloat(localStorage.getItem("dz_bilibili_video_custom_speed_value")) : 1
    let cacheItem = undefined
    if (isNaN(speed) || speed < SPEED_MIN_VALUE || speed > SPEED_MAX_VALUE) {
        speed = 1
    }
    update()

    function init() {
        let menu = document.querySelector(".bpx-player-ctrl-playbackrate-menu")
        if (!menu) {
            return false
        }
        if (!cacheItem) {
            let item = menu.children[0]
            if (!item) {
                return false
            }
            //复制一个新的
            item = item.cloneNode(false)
            //可能是激活的,去掉激活状态
            item.classList.remove("bpx-state-active")
            cacheItem = item
        }
        
        while (menu.children.length > 0) {
            menu.removeChild(menu.children[0])
        }
        let setting = getSpeedSetting()
        for (let i = setting.length - 1; i >= 0; i--) {
            let value = setting[i]
            let currentItem = document.importNode(cacheItem, false)
            currentItem.innerText = value + "x"
            currentItem.attributes["data-value"].value = parseFloat(value)
            currentItem.addEventListener("click", function(){
                speed = value
                localStorage.setItem("dz_bilibili_video_custom_speed_value", speed)
                let videoObj = document.querySelector("video") ?? document.querySelector("bwp-video")
                if (videoObj) {
                    videoObj.playbackRate = speed
                }
                // 后加的节点b站也会管理,不需要自行切换状态了
                // document.querySelector(".bpx-state-active").classList.remove("bpx-state-active")
                // currentItem.classList.add("bpx-state-active")
            })
            menu.appendChild(currentItem)
        }
        if (!menu.classList.contains("dz_bilibili_video_custom_speed_initialize")) {
            menu.classList.add("dz_bilibili_video_custom_speed_initialize")
        }
        return true
    }

    function update() {
        let videoObj = document.querySelector("video") ?? document.querySelector("bwp-video")
        if (!videoObj) {
            return
        }
        let menu = document.querySelector(".bpx-player-ctrl-playbackrate-menu")
        if (!menu) {
            return
        }
        if (!menu.classList.contains("dz_bilibili_video_custom_speed_initialize")) {
            //未初始化
            if (!init()) {
                return
            }
        }
        if (speed !== videoObj.playbackRate) {
            //切换到了新页面,重新适用倍速
            videoObj.playbackRate = speed
        }
    }

    setInterval(update, 200)
})();