您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
自动切换到你预先设定的画質。会优先使用Premium比特率。
// ==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.google.com/s2/favicons?sz=64&domain=youtube.com // @author ElectroKnight22 // @namespace electroknight22_youtube_hd_namespace // @version 2025.08.02 // @note 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.addValueChangeListener // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_listValues // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_notification // @grant GM_addValueChangeListener // @run-at document-idle // @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://greasyfork.org/scripts/498145-youtube-hd-premium // ==/UserScript== /*jshint esversion: 11 */ (function () { 'use strict'; const DEFAULT_SETTINGS = { targetResolution: 'hd2160', expandMenu: false, }; const QUALITIES = { highres: 4320, hd2160: 2160, hd1440: 1440, hd1080: 1080, hd720: 720, large: 480, medium: 360, small: 240, tiny: 144, }; const PREMIUM_INDICATOR = 'Premium'; const REQUIRED_TAMPERMONKEY_VERSION = '5.4.624'; 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 ${REQUIRED_TAMPERMONKEY_VERSION} or later.`, qualityMenu: 'Quality Menu', }, 'zh-TW': { tampermonkeyOutdatedAlertMessage: `看起來您正在使用較舊版本的篡改猴,可能會導致選單問題。為了獲得最佳體驗,請更新至 ${REQUIRED_TAMPERMONKEY_VERSION} 或更高版本。`, qualityMenu: '畫質選單', }, 'zh-CN': { tampermonkeyOutdatedAlertMessage: `看起来您正在使用旧版本的篡改猴,这可能会导致菜单问题。为了获得最佳体验,请更新到 ${REQUIRED_TAMPERMONKEY_VERSION} 或更高版本。`, qualityMenu: '画质菜单', }, ja: { tampermonkeyOutdatedAlertMessage: `ご利用のTampermonkeyのバージョンが古いため、メニューに問題が発生する可能性があります。より良い体験のため、バージョン${REQUIRED_TAMPERMONKEY_VERSION}以上に更新してください。`, qualityMenu: '画質メニュー', }, }; const state = { userSettings: { ...DEFAULT_SETTINGS }, isIframe: window.self !== window.top, isOldTampermonkey: false, useCompatibilityMode: typeof GM === 'undefined' && typeof GM_info !== 'undefined', moviePlayer: null, registeredMenuIds: [], }; const GM_API = { registerMenuCommand: state.useCompatibilityMode ? GM_registerMenuCommand : GM.registerMenuCommand, unregisterMenuCommand: state.useCompatibilityMode ? GM_unregisterMenuCommand : GM.unregisterMenuCommand, getValue: state.useCompatibilityMode ? GM_getValue : GM.getValue, setValue: state.useCompatibilityMode ? GM_setValue : GM.setValue, deleteValue: state.useCompatibilityMode ? GM_deleteValue : GM.deleteValue, listValues: state.useCompatibilityMode ? GM_listValues : GM.listValues, notification: state.useCompatibilityMode ? GM_notification : GM.notification, addValueChangeListener: state.useCompatibilityMode ? GM_addValueChangeListener : GM.addValueChangeListener, }; const getLocalizedText = () => { const lang = navigator.language || navigator.userLanguage; const preferredLang = lang.startsWith('zh') && lang !== 'zh-TW' ? 'zh-CN' : lang; return TRANSLATIONS[preferredLang] || TRANSLATIONS['en-US']; }; function resolveOptimalQuality(videoQualityData, targetResolutionString) { const availableQualities = [...new Set(videoQualityData.map((q) => q.quality))]; const targetValue = QUALITIES[targetResolutionString]; const bestQualityString = availableQualities .filter((q) => QUALITIES[q] <= targetValue) .sort((a, b) => QUALITIES[b] - QUALITIES[a])[0]; if (!bestQualityString) return null; let normalCandidate = null; let premiumCandidate = null; for (const quality of videoQualityData) { if (quality.quality === bestQualityString && quality.isPlayable) { if (quality.qualityLabel?.trim().endsWith(PREMIUM_INDICATOR)) { premiumCandidate = quality; } else { normalCandidate = quality; } } } return premiumCandidate || normalCandidate; } function setResolution() { try { if (!state.moviePlayer) throw new Error('Movie player not found.'); const videoQualityData = state.moviePlayer.getAvailableQualityData(); if (!videoQualityData.length) { // Fallback for non-auto-playing videos that have preloading disabled try { const videoElement = state.moviePlayer.querySelector('video'); state.moviePlayer.setPlaybackQualityRange(state.userSettings.targetResolution); // Instant, but may break UI temporarily videoElement.addEventListener('play', setResolution, { once: true }); // Slower, but guarantees correct quality and fixes UI } catch { console.error('Failed to set resolution using fallback method.'); } return; } const optimalQuality = resolveOptimalQuality(videoQualityData, state.userSettings.targetResolution); if (optimalQuality) { state.moviePlayer.setPlaybackQualityRange(optimalQuality.quality, optimalQuality.quality, optimalQuality.formatId); } } catch (error) { console.error('Did not set resolution.', error); } } function handlePlayerStateChange(playerState) { const playerElement = document.getElementById('movie_player'); if (!playerElement) return; if (playerState === -1 && playerElement.hasAttribute('resolution-set')) { playerElement.removeAttribute('resolution-set'); } if (playerState === 1 && !playerElement.hasAttribute('resolution-set')) { playerElement.setAttribute('resolution-set', ''); setResolution(); } } function processVideoLoad(event = null) { state.moviePlayer = event?.target?.player_ ?? document.querySelector('#movie_player'); setResolution(); const playerElement = document.getElementById('movie_player'); if (playerElement && !playerElement.hasAttribute('state-change-listener-added')) { playerElement.addEventListener('onStateChange', handlePlayerStateChange); playerElement.setAttribute('state-change-listener-added', 'true'); } } function addEventListeners() { const playerUpdateEvent = window.location.hostname === 'm.youtube.com' ? 'state-navigateend' : 'yt-player-updated'; window.addEventListener(playerUpdateEvent, processVideoLoad, true); window.addEventListener('pageshow', processVideoLoad, true); // Sync settings across tabs GM_API.addValueChangeListener('settings', (key, oldValue, newValue, remote) => { if (!remote) return; state.userSettings = newValue; showMenuOptions(); const applyResolutionChange = () => { if (!document.hidden) setResolution(); }; if (document.hidden) { window.addEventListener('visibilitychange', applyResolutionChange, { once: true }); } else { applyResolutionChange(); } }); } function removeMenuOptions() { while (state.registeredMenuIds.length) { GM_API.unregisterMenuCommand(state.registeredMenuIds.pop()); } } function showMenuOptions() { removeMenuOptions(); const shouldUseFallback = state.isOldTampermonkey || state.isIframe; const localizedText = getLocalizedText(); const menuItems = [ !shouldUseFallback && { label: () => `${localizedText.qualityMenu} ${state.userSettings.expandMenu ? '🔼' : '🔽'}`, menuId: 'menuExpandBtn', alwaysShow: true, async handleClick() { state.userSettings.expandMenu = !state.userSettings.expandMenu; await updateSetting('expandMenu', state.userSettings.expandMenu); showMenuOptions(); }, }, ...Object.entries(QUALITIES).map(([label, resolution]) => ({ label: () => `${resolution}p ${label === state.userSettings.targetResolution ? '✅' : ''}`, menuId: label, alwaysShow: false, async handleClick() { if (state.userSettings.targetResolution === label) return; state.userSettings.targetResolution = label; await updateSetting('targetResolution', label); setResolution(); showMenuOptions(); }, })), ]; menuItems.forEach((item) => { if (!item) return; // Skip invalid items if (item.alwaysShow || state.userSettings.expandMenu || shouldUseFallback) { GM_API.registerMenuCommand(item.label(), item.handleClick, { id: item.menuId, autoClose: shouldUseFallback, }); state.registeredMenuIds.push(item.menuId); } }); } async function updateSetting(key, value) { try { const currentSettings = (await GM_API.getValue('settings')) || {}; currentSettings[key] = value; await GM_API.setValue('settings', currentSettings); } catch (error) { console.error('Error updating setting:', error); } } async function loadUserSettings() { const storedSettings = (await GM_API.getValue('settings')) || {}; state.userSettings = { ...DEFAULT_SETTINGS, ...storedSettings }; if (!QUALITIES[state.userSettings.targetResolution]) { state.userSettings.targetResolution = DEFAULT_SETTINGS.targetResolution; } await GM_API.setValue('settings', state.userSettings); } async function cleanupOldStorage() { try { const allowedKeys = ['settings', 'versionWarningShown']; const keys = await GM_API.listValues(); for (const key of keys) { if (!allowedKeys.includes(key)) { await GM_API.deleteValue(key); } } } catch (error) { console.error('Error cleaning up old storage keys:', error); } } function compareVersions(v1, v2) { 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; } function checkTampermonkeyVersion() { if (state.useCompatibilityMode || GM_info.scriptHandler !== 'Tampermonkey') return; if (compareVersions(GM_info.version, REQUIRED_TAMPERMONKEY_VERSION) < 0) { state.isOldTampermonkey = true; const versionWarningShown = GM_API.getValue('versionWarningShown', false); if (!versionWarningShown) { GM_API.setValue('versionWarningShown', true); GM_API.notification({ text: getLocalizedText().tampermonkeyOutdatedAlertMessage, timeout: 15000, }); } } } async function initialize() { if (state.useCompatibilityMode) console.warn('Running in Greasemonkey compatibility mode. Some features might be limited.'); try { await cleanupOldStorage(); await loadUserSettings(); checkTampermonkeyVersion(); } catch (error) { console.error(`Error during initialization: ${error}. Loading with default settings.`); } addEventListeners(); showMenuOptions(); } initialize(); })();