您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Auto select the highest quality on YouTube
- // ==UserScript==
- // @name YouTube Auto HD and FPS
- // @namespace https://github.com/jlhg/youtube-auto-hd
- // @license GPL-3.0
- // @version 0.1.0
- // @description Auto select the highest quality on YouTube
- // @description:zh-TW YouTube 自動選最高畫質
- // @author jlhg
- // @homepage https://github.com/jlhg/youtube-auto-hd
- // @supportURL https://github.com/jlhg/youtube-auto-hd/issues
- // @match https://www.youtube.com/watch*
- // @grant none
- // ==/UserScript==
- (function() {
- 'use strict';
- const SELECTORS = {
- buttonSettings: '.ytp-settings-button',
- video: 'video',
- player: '.html5-video-player:not(#inline-preview-player)',
- menuOption: '.ytp-settings-menu[data-layer] .ytp-menuitem',
- menuOptionContent: ".ytp-menuitem-content",
- optionQuality: ".ytp-settings-menu[data-layer] .ytp-menuitem:last-child",
- panelHeaderBack: ".ytp-panel-header button",
- labelPremium: '.ytp-premium-label'
- };
- const OBSERVER_OPTIONS = {
- childList: true,
- subtree: true
- };
- const SUFFIX_EBR = 'ebr';
- const fpsSupported = [60, 50, 30];
- const qualities = [4320, 2160, 1440, 1080, 720, 480, 360, 240, 144];
- function isElementVisible(element) {
- return element?.offsetWidth > 0 && element?.offsetHeight > 0;
- }
- async function getCurrentQualityElements() {
- return waitElement(SELECTORS.player).then((el) => {
- const elMenuOptions = [...el.querySelectorAll(SELECTORS.menuOption)];
- return elMenuOptions.filter(getIsQualityElement);
- });
- }
- function convertQualityToNumber(elQuality) {
- const isPremiumQuality = Boolean(elQuality.querySelector(SELECTORS.labelPremium));
- const qualityNumber = parseInt(elQuality.textContent);
- if (isPremiumQuality) {
- return (qualityNumber + SUFFIX_EBR);
- }
- return qualityNumber;
- }
- async function getAvailableQualities() {
- const elQualities = await getCurrentQualityElements();
- return elQualities.map(convertQualityToNumber);
- }
- function getPlayerDiv(elVideo) {
- return elVideo.closest(SELECTORS.player);
- }
- function getVideoFPS() {
- const elQualities = getCurrentQualityElements();
- const labelQuality = elQualities[0]?.textContent;
- if (!labelQuality) {
- return 30;
- }
- const fpsMatch = labelQuality.match(/[ps](\d+)/);
- return fpsMatch ? Number(fpsMatch[1]) : 30;
- }
- function getFpsFromRange(qualities, fpsToCheck) {
- const fpsList = Object.keys(qualities)
- .map(fps => parseInt(fps))
- .sort((a, b) => b - a);
- return fpsList.find(fps => fps <= fpsToCheck) || fpsList.at(-1);
- }
- function getIsQualityElement(element) {
- const isQuality = Boolean(element.textContent.match(/\d/));
- const isHasChildren = element.children.length > 1;
- return isQuality && !isHasChildren;
- }
- async function getIsSettingsMenuOpen() {
- waitElement(SELECTORS.buttonSettings).then((el) => {
- const elButtonSettings = el;
- return elButtonSettings?.ariaExpanded === "true";
- });
- }
- function getIsLastOptionQuality(elVideo) {
- const elOptionInSettings = getPlayerDiv(elVideo).querySelector(SELECTORS.optionQuality);
- if (!elOptionInSettings) {
- return false;
- }
- const elQualityName = elOptionInSettings.querySelector(SELECTORS.menuOptionContent);
- // If the video is a channel trailer, the last option is initially the speed one,
- // and the speed setting can only be a single digit
- const matchNumber = elQualityName?.textContent?.match(/\d+/);
- if (!matchNumber) {
- return false;
- }
- const numberString = matchNumber[0];
- const minQualityCharLength = 3; // e.g. 3 characters in 720p
- return numberString.length >= minQualityCharLength;
- }
- async function changeQualityAndClose(elVideo, elPlayer) {
- await changeQualityWhenPossible(elVideo);
- await closeMenu(elPlayer);
- }
- function openQualityMenu(elVideo) {
- const elSettingQuality = getPlayerDiv(elVideo).querySelector(SELECTORS.optionQuality);
- elSettingQuality.click();
- }
- async function changeQuality() {
- const elQualities = await getCurrentQualityElements();
- const qualitiesAvailable = await getAvailableQualities();
- const applyQuality = (iQuality) => {
- elQualities[iQuality]?.click();
- };
- const isQualityPreferredEBR = qualitiesAvailable[0].toString().endsWith(SUFFIX_EBR);
- if (isQualityPreferredEBR) {
- applyQuality(0);
- return;
- }
- const iQualityFallback = qualitiesAvailable.findIndex(quality => !quality.toString().endsWith(SUFFIX_EBR));
- applyQuality(iQualityFallback);
- }
- async function changeQualityWhenPossible(elVideo) {
- if (!getIsLastOptionQuality(elVideo)) {
- elVideo.addEventListener("canplay", () => changeQualityWhenPossible(elVideo), { once: true });
- return;
- }
- openQualityMenu(elVideo);
- await changeQuality();
- }
- async function closeMenu(elPlayer) {
- const clickPanelBackIfPossible = () => {
- const elPanelHeaderBack = elPlayer.querySelector(SELECTORS.panelHeaderBack);
- if (elPanelHeaderBack) {
- elPanelHeaderBack.click();
- return true;
- }
- return false;
- };
- if (clickPanelBackIfPossible()) {
- return;
- }
- new MutationObserver((_, observer) => {
- if (clickPanelBackIfPossible()) {
- observer.disconnect();
- }
- }).observe(elPlayer, OBSERVER_OPTIONS);
- }
- function waitElement(selector) {
- return new Promise(resolve => {
- let element = [...document.querySelectorAll(selector)]
- .find(isElementVisible);
- if (element) {
- return resolve(element);
- }
- const observer = new MutationObserver(mutations => {
- let element = [...document.querySelectorAll(selector)]
- .find(isElementVisible);
- if (element) {
- observer.disconnect();
- resolve(element);
- }
- });
- observer.observe(document.body, OBSERVER_OPTIONS);
- });
- }
- waitElement(SELECTORS.video).then(async (elVideo) => {
- const elPlayer = getPlayerDiv(elVideo);
- const elSettings = elPlayer.querySelector(SELECTORS.buttonSettings);
- if (!elSettings) {
- return;
- }
- const isSettingsMenuOpen = await getIsSettingsMenuOpen();
- if (!isSettingsMenuOpen) {
- elSettings.click();
- }
- elSettings.click();
- await changeQualityAndClose(elVideo, elPlayer);
- elPlayer.querySelector(SELECTORS.buttonSettings).blur();
- });
- })();