Youtube HD Premium

自動切換到你預先設定的畫質。會優先使用Premium位元率。

安裝腳本?
作者推薦腳本

您可能也會喜歡 更佳 YouTube 劇場模式

安裝腳本
// ==UserScript==
// @name                Youtube HD Premium
// @name:zh-TW          Youtube HD Premium
// @name:zh-CN          Youtube HD Premium
// @name:ja             Youtube HD Premium
// @icon                https://www.youtube.com/img/favicon_48.png
// @author              ElectroKnight22
// @namespace           electroknight22_youtube_hd_namespace
// @version             2025.04.07
// I would prefer semantic versioning but it's a bit too late to change it at this point. Calendar versioning was originally chosen to maintain similarity to the adisib's code.
// @match               *://www.youtube.com/*
// @match               *://m.youtube.com/*
// @match               *://www.youtube-nocookie.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
// @grant               GM.notification
// @grant               GM_getValue
// @grant               GM_setValue
// @grant               GM_deleteValue
// @grant               GM_listValues
// @grant               GM_registerMenuCommand
// @grant               GM_unregisterMenuCommand
// @grant               GM_notification
// @license             MIT
// @description         Automatically switches to your pre-selected resolution. Enables premium when possible.
// @description:zh-TW   自動切換到你預先設定的畫質。會優先使用Premium位元率。
// @description:zh-CN   自动切换到你预先设定的画质。会优先使用Premium比特率。
// @description:ja      自動的に設定した画質に替わります。Premiumのビットレートを優先的に選択します。
// @homepage            https://gf.qytechs.cn/en/scripts/498145-youtube-hd-premium
// ==/UserScript==

/*jshint esversion: 11 */

(function () {
    "use strict";

    // -------------------------------
    // Default settings (for storage key "settings")
    // -------------------------------
    const DEFAULT_SETTINGS = {
        targetResolution: "hd2160",
        expandMenu: false,
        debug: false
    };

    // -------------------------------
    // Other constants and translations
    // -------------------------------
    const BROWSER_LANGUAGE = navigator.language || navigator.userLanguage;
    const GET_PREFERRED_LANGUAGE = () => {
        if (BROWSER_LANGUAGE.startsWith('zh') && BROWSER_LANGUAGE !== 'zh-TW') {
            return 'zh-CN';
        } else {
            return BROWSER_LANGUAGE;
        }
    };

    const TRANSLATIONS = {
        'en-US': {
            tampermonkeyOutdatedAlertMessage: "It looks like you're using an older version of Tampermonkey that might cause menu issues. For the best experience, please update to version 5.4.6224 or later.",
            qualityMenu: 'Quality Menu',
            autoModeName: 'Optimized Auto',
            debug: 'DEBUG'
        },
        'zh-TW': {
            tampermonkeyOutdatedAlertMessage: "看起來您正在使用較舊版本的篡改猴,可能會導致選單問題。為了獲得最佳體驗,請更新至 5.4.6224 或更高版本。",
            qualityMenu: '畫質選單',
            autoModeName: '優化版自動模式',
            debug: '偵錯'
        },
        'zh-CN': {
            tampermonkeyOutdatedAlertMessage: "看起来您正在使用旧版本的篡改猴,这可能会导致菜单问题。为了获得最佳体验,请更新到 5.4.6224 或更高版本。",
            qualityMenu: '画质菜单',
            autoModeName: '优化版自动模式',
            debug: '调试'
        },
        'ja': {
            tampermonkeyOutdatedAlertMessage: "ご利用のTampermonkeyのバージョンが古いため、メニューに問題が発生する可能性があります。より良い体験のため、バージョン5.4.6224以上に更新してください。",
            qualityMenu: '画質メニュー',
            autoModeName: '最適化自動モード',
            debug: 'デバッグ'
        }
    };

    const GET_LOCALIZED_TEXT = () => {
        const language = GET_PREFERRED_LANGUAGE();
        return TRANSLATIONS[language] || TRANSLATIONS['en-US'];
    };

    const QUALITIES = {
        highres: 4320,
        hd2160: 2160,
        hd1440: 1440,
        hd1080: 1080,
        hd720: 720,
        large: 480,
        medium: 360,
        small: 240,
        tiny: 144,
        auto: 0
    };

    const PREMIUM_INDICATOR_LABEL = "Premium";

    // -------------------------------
    // Global variables
    // -------------------------------
    let userSettings = { ...DEFAULT_SETTINGS };
    let useCompatibilityMode = false;
    let menuItems = [];
    let moviePlayer = null;
    let isIframe = false;
    let isOldTampermonkey = false;
    const updatedVersions = {
        Tampermonkey: '5.4.624',
    };
    let isScriptRecentlyUpdated = false;

    // -------------------------------
    // GM FUNCTION OVERRIDES
    // -------------------------------
    const GMCustomRegisterMenuCommand = useCompatibilityMode ? GM_registerMenuCommand : GM.registerMenuCommand;
    const GMCustomUnregisterMenuCommand = useCompatibilityMode ? GM_unregisterMenuCommand : GM.unregisterMenuCommand;
    const GMCustomGetValue = useCompatibilityMode ? GM_getValue : GM.getValue;
    const GMCustomSetValue = useCompatibilityMode ? GM_setValue : GM.setValue;
    const GMCustomDeleteValue = useCompatibilityMode ? GM_deleteValue : GM.deleteValue;
    const GMCustomListValues = useCompatibilityMode ? GM_listValues : GM.listValues;
    const GMCustomNotification = useCompatibilityMode ? GM_notification : GM.notification;

    // -------------------------------
    // Debug logging helper
    // -------------------------------
    function printDebug(consoleMethod = console.log, ...args) {
        if (!userSettings.debug) return;
        if (typeof consoleMethod !== 'function') {
            args.unshift(consoleMethod);
            consoleMethod = console.log;
        }

        consoleMethod(...args);
    }

    // -------------------------------
    // Video quality functions
    // -------------------------------
    function setResolution() {
        try {
            if (!moviePlayer) throw new Error("Movie player not found.");

            const videoQualityData = moviePlayer.getAvailableQualityData();
            const currentPlaybackQuality = moviePlayer.getPlaybackQuality();
            const currentQualityLabel = moviePlayer.getPlaybackQualityLabel();

            if (isIframe && !videoQualityData.length) { // fixes non-auto-playing iframes
                printDebug("Performing iframe magic...");
                const videoElement = moviePlayer.querySelector('video');
                moviePlayer.setPlaybackQualityRange(userSettings.targetResolution); // Force set quality to user preference. Breaks the UI but quality will be mostly correct.
                videoElement.addEventListener('play', setResolution, { once: true }); // Waits for playback to set quality properly. Fixes the UI and guarantees correct quality.
                return;
            }

            if (!videoQualityData.length) throw new Error("Quality options missing.");
            if (userSettings.targetResolution === 'auto') {
                if (!currentPlaybackQuality || !currentQualityLabel) throw new Error("Unable to determine current playback quality.");
                const isOptimalQuality =
                    videoQualityData.filter(q => q.quality == currentPlaybackQuality).length <= 1 ||
                    currentQualityLabel.trim().endsWith(PREMIUM_INDICATOR_LABEL);
                if (!isOptimalQuality) moviePlayer.loadVideoById(moviePlayer.getVideoData().video_id);
                printDebug(`Setting quality to: [${GET_LOCALIZED_TEXT().autoModeName}]`);
            } else {
                let resolvedTarget = findNextAvailableQuality(userSettings.targetResolution, moviePlayer.getAvailableQualityLevels());
                const premiumData = videoQualityData.find(q =>
                    q.quality === resolvedTarget &&
                    q.qualityLabel?.trim().endsWith(PREMIUM_INDICATOR_LABEL) &&
                    q.isPlayable
                );
                moviePlayer.setPlaybackQualityRange(resolvedTarget, resolvedTarget, premiumData?.formatId);
                printDebug(`Setting quality to: [${resolvedTarget}${premiumData ? " Premium" : ""}]`);
            }
        } catch (error) {
            printDebug(console.error, "Did not set resolution. ", error);
        }
    }

    function findNextAvailableQuality(target, availableQualities) {
        const targetValue = QUALITIES[target];
        return availableQualities
            .filter(q => QUALITIES[q] <= targetValue)
            .sort((a, b) => QUALITIES[b] - QUALITIES[a])[0];
    }

    function processVideoLoad(event = null) {
        printDebug('Processing video load...');
        moviePlayer = event?.target?.player_ ?? document.querySelector('#movie_player');
        setResolution();
    }

    // -------------------------------
    // Menu functions
    // -------------------------------
    function processMenuOptions(options, callback) {
        Object.values(options).forEach(option => {
            if (!option.alwaysShow && !userSettings.expandMenu && !isOldTampermonkey) return;
            if (option.items) {
                option.items.forEach(item => callback(item));
            } else {
                callback(option);
            }
        });
    }

    // The menu callbacks now use the helper "updateSetting" to update the stored settings.
    function showMenuOptions() {
        const shouldAutoClose = isOldTampermonkey;
        removeMenuOptions();
        const menuExpandButton = isOldTampermonkey ? {} : {
            expandMenu: {
                alwaysShow: true,
                label: () => `${GET_LOCALIZED_TEXT().qualityMenu} ${userSettings.expandMenu ? "🔼" : "🔽"}`,
                menuId: "menuExpandBtn",
                handleClick: async function () {
                    userSettings.expandMenu = !userSettings.expandMenu;
                    await updateSetting('expandMenu', userSettings.expandMenu);
                    showMenuOptions();
                },
            },
        };
        const menuOptions = {
            ...menuExpandButton,
            qualities: {
                items: Object.entries(QUALITIES).map(([label, resolution]) => ({
                    label: () => `${resolution === 0 ? GET_LOCALIZED_TEXT().autoModeName : resolution + 'p'} ${label === userSettings.targetResolution ? "✅" : ""}`,
                    menuId: label,
                    handleClick: async function () {
                        if (userSettings.targetResolution === label) return;
                        userSettings.targetResolution = label;
                        await updateSetting('targetResolution', label);
                        setResolution();
                        showMenuOptions();
                    },
                })),
            },
            debug: {
                label: () => `${GET_LOCALIZED_TEXT().debug} ${userSettings.debug ? "✅" : ""}`,
                menuId: "debugBtn",
                handleClick: async function () {
                    userSettings.debug = !userSettings.debug;
                    await updateSetting('debug', userSettings.debug);
                    showMenuOptions();
                },
            },
        };

        processMenuOptions(menuOptions, (item) => {
            GMCustomRegisterMenuCommand(item.label(), item.handleClick, {
                id: item.menuId,
                autoClose: shouldAutoClose,
            });
            menuItems.push(item.menuId);
        });
    }

    function removeMenuOptions() {
        while (menuItems.length) {
            GMCustomUnregisterMenuCommand(menuItems.pop());
        }
    }

    // -------------------------------
    // GreaseMonkey / Tampermonkey version checks
    // -------------------------------
    function compareVersions(v1, v2) {
        try {
            if (!v1 || !v2) throw new Error("Invalid version string.");
            if (v1 === v2) return 0;
            const parts1 = v1.split('.').map(Number);
            const parts2 = v2.split('.').map(Number);
            const len = Math.max(parts1.length, parts2.length);
            for (let i = 0; i < len; i++) {
                const num1 = parts1[i] ?? 0;
                const num2 = parts2[i] ?? 0;
                if (num1 > num2) return 1;
                if (num1 < num2) return -1;
            }
            return 0;
        } catch (error) {
            throw new Error("Error comparing versions: " + error);
        }
    }

    function hasGreasyMonkeyAPI() {
        if (typeof GM !== 'undefined') return true;
        if (typeof GM_info !== 'undefined') {
            useCompatibilityMode = true;
            printDebug(console.warn, "Running in compatibility mode.");
            return true;
        }
        return false;
    }

    function CheckTampermonkeyUpdated() {
        if (GM_info.scriptHandler === "Tampermonkey" &&
            compareVersions(GM_info.version, updatedVersions.Tampermonkey) !== 1) {
            isOldTampermonkey = true;
            if (isScriptRecentlyUpdated) {
                GMCustomNotification({
                    text: GET_LOCALIZED_TEXT().tampermonkeyOutdatedAlertMessage,
                    timeout: 15000
                });
            }
        }
    }

    // -------------------------------
    // Storage helper functions
    // -------------------------------

    /**
     * Load user settings from the "settings" key.
     * Ensures that only keys existing in DEFAULT_SETTINGS are kept.
     * If no stored settings are found, defaults are used.
     */
    async function loadUserSettings() {
        try {
            const storedSettings = await GMCustomGetValue('settings', {});
            userSettings = Object.keys(DEFAULT_SETTINGS).reduce((accumulator, key) => {
                accumulator[key] = storedSettings.hasOwnProperty(key) ? storedSettings[key] : DEFAULT_SETTINGS[key];
                return accumulator;
            }, {});
            await GMCustomSetValue('settings', userSettings);
            printDebug(`Loaded user settings: ${JSON.stringify(userSettings)}.`);
        } catch (error) {
            throw error;
        }
    }

    // Update one setting in the stored settings.
    async function updateSetting(key, value) {
        try {
            let currentSettings = await GMCustomGetValue('settings', DEFAULT_SETTINGS);
            currentSettings[key] = value;
            await GMCustomSetValue('settings', currentSettings);
        } catch (error) {
            printDebug(console.error, "Error updating setting: ", error);
        }
    }

    async function updateScriptInfo() {
        try {
            const oldScriptInfo = await GMCustomGetValue('scriptInfo', null);
            printDebug(`Previous script info: ${JSON.stringify(oldScriptInfo)}`);
            const newScriptInfo = {
                version: getScriptVersionFromMeta(),
            };
            await GMCustomSetValue('scriptInfo', newScriptInfo);

            if (!oldScriptInfo || compareVersions(newScriptInfo.version, oldScriptInfo?.version) !== 0) {
                isScriptRecentlyUpdated = true;
            }
            printDebug(`Updated script info: ${JSON.stringify(newScriptInfo)}`);
        } catch (error) {
            printDebug(console.error, "Error updating script info: ", error);
        }
    }

    // Cleanup any leftover keys from previous versions.
    async function cleanupOldStorage() {
        try {
            const allowedKeys = ['settings', 'scriptInfo'];
            const keys = await GMCustomListValues();
            for (const key of keys) {
                if (!allowedKeys.includes(key)) {
                    await GMCustomDeleteValue(key);
                    printDebug(`Deleted leftover key: ${key}`);
                }
            }
        } catch (error) {
            printDebug(console.error, "Error cleaning up old storage keys: ", error);
        }
    }

    // -------------------------------
    // Script metadata extraction
    // -------------------------------
    function getScriptVersionFromMeta() {
        const meta = GM_info.scriptMetaStr;
        const versionMatch = meta?.match(/@version\s+([^\r\n]+)/);
        return versionMatch ? versionMatch[1].trim() : null;
    }

    // -------------------------------
    // Main function: add event listeners and initialize
    // -------------------------------
    function addEventListeners() {
        if (window.location.hostname === "m.youtube.com") {
            window.addEventListener('state-navigateend', processVideoLoad, true);
        } else {
            window.addEventListener('yt-player-updated', processVideoLoad, true);
        }
    }

    async function initialize() {
        try {
            if (!hasGreasyMonkeyAPI()) throw new Error("Did not detect valid Grease Monkey API");
            await cleanupOldStorage();
            await loadUserSettings();
            await updateScriptInfo();
            CheckTampermonkeyUpdated();
        } catch (error) {
            printDebug(console.error, `Error loading user settings: ${error}. Loading with default settings.`);
        }

        window.addEventListener('pageshow', processVideoLoad, true);
        if (window.self === window.top) {
            addEventListeners();
            showMenuOptions();
        } else {
            isIframe = true;
        }
    }

    // -------------------------------
    // Entry Point
    // -------------------------------
    initialize();
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址