YouTube 播放速度记忆

自动切换到你预先设定的播放速度。

当前为 2024-08-15 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name                Youtube Remember Speed
// @name:zh-TW          YouTube 播放速度記憶
// @name:zh-CN          YouTube 播放速度记忆
// @name:ja             YouTube 再生速度メモリー
// @icon                data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAqhJREFUaEPtmc9rE0EUxz+DjSh6UAQRxP4F9uhBRKjipef+FwqtoZdYEk3U4jGn0FJ6KrQnj6X0EKVKKIi9tAotPZSCYilFoq0/sK1Z92V329XGENiZSRZ2LtllZ9+8z/e9ncy8UcS8qZj7TwLQ7ggmEUgiEFGB/6aQAxeBq8Al4GxonDPAydD9+dB1qkFfefy9iZ9fgRrwC/jh96v6vz+Bj8B7BduNbBwDcOA6UABuAyciCqTr9d/ACxf0oYI3YaOHAA71KfWpq8QDF6BTP27H9/GRArk+ctSBZ0BGl2SG7YwoyB4COF66lDtY+X/1EPVvKXhVTxUHKsANw6rpNl9RcFM50A1sxEj9QAiJQrcA9LvT5XPd8liy1y8Ad4GSpQF1D3NPAO4DRd2WLdlL6wUYH4dKBSYnLfmPZoDZWejrg/l5GByE5WXTIIYAxO1aDaamYGgIthsuY3TAGQQI3KtWoVCAUgkODnQ4HbZhASAYbnUV0mmYm9MJYREgcHtmxvs+1td1gLQBQNze24OxMchmYXc3CkibAOQDl6k2k4GtrZgBLC56KbSwEMXx4F2LEdjchHweJia8KVZPswCwvw+jo5DLwc6OHrePrBgGKJdhYABWVnQ7bjiF1ta8OV+WFmab5ghMT8PSEhSL3lRpvmkGSKVAct5eqwPEfkMT+y3lZeBDbDf1kq6xLqv4AL3AyxhFQUoqvQpeh2ujI+46cdjeBBJppL9Li34UBCYP5Do4ErKIeiLV82PF3UAPB64Bj4E7biW4K5JO+l6WvajUbqW8/jZsttkBxwWgB7gCnPZfCg4z5P6UH6lzTfyUgxGp7ctBRdBkBxNsjiWXv4Seyd93+DDkG/AJeKfgc6NxOvUcoOXYJQAtS2WoYxIBQ8K2bDaJQMtSGer4B8aT1sve/dr7AAAAAElFTkSuQmCC
// @author              ElectroKnight22
// @namespace           electroknight22_youtube_remember_playback_rate_namespace
// @version             1.0.0
// @match               *://www.youtube.com/*
// @exclude             *://www.youtube.com/live_chat*
// @grant               GM.getValue
// @grant               GM.setValue
// @grant               GM.deleteValue
// @grant               GM.listValues
// @grant               GM_registerMenuCommand
// @grant               GM_unregisterMenuCommand
// @license             MIT
// @description         Automcatically switches to your pre-selected speed.
// @description:zh-TW   自動切換到你預先設定的播放速度。
// @description:zh-CN   自动切换到你预先设定的播放速度。
// @description:ja      自動的に設定した再生速度に替わります。
// ==/UserScript==

/*jshint esversion: 11 */

(function() {
    "use strict";

    const DEBUG = true;

    const DEFAULT_SETTINGS = {
        targetSpeed: 1
    };

    const speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];

    let userSettings = { ...DEFAULT_SETTINGS };
    let menuCommandIds = [];

    let doc = document, win = window;

    // --------------------
    // --- FUNCTIONS ------
    // --------------------

    function debugLog(message, shouldShow = true) {
        if (DEBUG && shouldShow) {
            console.log("YTRS DEBUG | " + message);
        }
    }

    // --------------------
    // Attempt to set the video resolution to target quality or the next best quality
    function setSpeed(targetSpeed) {

        let ytPlayer = doc.getElementById("movie_player") || doc.getElementsByClassName("html5-video-player")[0];

        if (!isValidVideo(ytPlayer)) return;

        ytPlayer.setPlaybackRate(targetSpeed);
        debugLog("Trying to set speed to: " + targetSpeed + "x");

    }

    function isValidVideo(ytPlayer, force) {

        if (!ytPlayer?.getAvailableQualityLabels()[0]) {
            debugLog("Video data missing");
            return false;
        }

        if (win.location.href.startsWith("https://www.youtube.com/shorts/")) {
            debugLog("Skipping Youtube Shorts");
            return false;
        }

        return true;
    }

    // --------------------
    // Functions for the speed selection menu

    function createSpeedMenu() {
        GM_registerMenuCommand("Set Speed (show/hide)", () => {
            menuCommandIds.length ? removeSpeedMenuItems() : showSpeedMenuItems();
        }, {
            autoClose: false
        });
    }

    function showSpeedMenuItems() {
        removeSpeedMenuItems();
        speeds.forEach((speed) => {
            let speedText = speed + "x";
            if (speed === userSettings.targetSpeed) {
                speedText += " (selected)";
            }
            let menuCommandId = GM_registerMenuCommand(speedText, () => {
                setSelectedSpeed(speed);
            }, {
                autoClose: false,
            });
            menuCommandIds.push(menuCommandId);
        });
    }

    function removeSpeedMenuItems() {
        while (menuCommandIds.length) {
            GM_unregisterMenuCommand(menuCommandIds.pop());
        }
    }

    function setSelectedSpeed(speed) {
        if (userSettings.targetSpeed == speed) return;
        userSettings.targetSpeed = speed;
        GM.setValue('targetSpeed', speed);
        removeSpeedMenuItems();
        showSpeedMenuItems();
        setSpeed(speed);
    }

    // --------------------
    // Sync settings with locally stored values
    async function applySettings() {
        try {
            // Get all keys from GM
            const storedValues = await GM.listValues();

            // Write any missing key-value pairs from DEFAULT_SETTINGS to GM
            await Promise.all(Object.entries(DEFAULT_SETTINGS).map(async ([key, value]) => {
                if (!storedValues.includes(key)) {
                    await GM.setValue(key, value);
                }
            }));

            // Delete any extra keys in GM that are not in DEFAULT_SETTINGS
            await Promise.all(storedValues.map(async key => {
                if (!(key in DEFAULT_SETTINGS)) {
                    await GM.deleteValue(key);
                }
            }));

            // Retrieve and update user settings from GM
            await Promise.all(
                storedValues.map(key => GM.getValue(key).then(value => [key, value]))
            ).then(keyValuePairs => keyValuePairs.forEach(([newKey, newValue]) => {
                userSettings[newKey] = newValue;
            }));

            debugLog(Object.entries(userSettings).map(([key, value]) => key + ": " + value).join(", "));
        } catch (error) {
            debugLog("Error when applying settings: " + error.message);
        }
    }

    // --------------------
    // Main function
    function main() {
        if (win.self == win.top) { createSpeedMenu(); }
        setSpeed(userSettings.targetSpeed);
        win.addEventListener("loadstart", () => { setSpeed(userSettings.targetSpeed); }, true);
    }

    // --------------------
    // Entry Point
    applySettings().then(main);
})();