您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
记住上次使用的播放速度,并改造YouTube的速度调整滑杆,最高支持8倍速。
当前为
// ==UserScript== // @name Youtube Remember Speed // @name:zh-TW YouTube 播放速度記憶 // @name:zh-CN YouTube 播放速度记忆 // @name:ja YouTube 再生速度メモリー // @icon https://www.youtube.com/img/favicon_48.png // @author ElectroKnight22 // @namespace electroknight22_youtube_remember_playback_rate_namespace // @version 2.0.0 // @match *://www.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 // @license MIT // @description Remembers the speed that you last used. Now hijacks YouTube's custom speed slider and gives you up to 8x speed. // @description:zh-TW 記住上次使用的播放速度,並改造YouTube的速度調整滑桿,最高支援8倍速。 // @description:zh-CN 记住上次使用的播放速度,并改造YouTube的速度调整滑杆,最高支持8倍速。 // @description:ja 最後に使った再生速度を覚えておき、YouTubeの速度スライダーを改造して最大8倍速まで対応させます。 // ==/UserScript== /*jshint esversion: 11 */ (function() { "use strict"; const DEFAULT_SETTINGS = { targetSpeed: 1 }; let userSettings = { ...DEFAULT_SETTINGS }; let shouldInitialize = false; const maxSpeed = 8; function setSpeed(targetSpeed) { try { let video = document.querySelector('video'); video.playbackRate = targetSpeed; } catch (error) { console.error("Error when trying to set speed. Error: " + error); } } function overrideSpeedElements() { if (!window.location.pathname.startsWith('/watch')) return; const parentSelector = '.ytp-popup.ytp-settings-menu'; const sliderSelector = 'input.ytp-input-slider.ytp-speedslider'; let speedTextElement = null; let speedLabel = null function setSpeedText(targetString) { try { const text = speedTextElement.textContent; const newValue = targetString; speedTextElement.textContent = /\(.*?\)/.test(text) ? text.replace(/\(.*?\)/, `(${newValue})`) : newValue; } catch (error) { console.error("Error when trying to set speed text. Error: " + error); } } function overrideSpeedLabel(sliderElement) { try { console.log('overrideSpeedLabel'); if (!speedLabel || !speedLabel.isConnected) { speedLabel = sliderElement.closest('.ytp-menuitem-with-footer').querySelector('.ytp-menuitem-label'); } if (speedLabel?.textContent) { speedLabel.textContent = speedLabel.textContent.replace(/\(.*?\)/, `(${sliderElement.value})`); } } catch (error) { console.error("Error when trying to override speed label. Error: " + error); } } function overrideSliderStyle(sliderElement) { if (!sliderElement) return; const speedMenuItems = sliderElement.closest('.ytp-panel-menu').children; if (speedMenuItems[0].classList.contains('ytp-menuitem-with-footer')) { Array.from(speedMenuItems).forEach(item => { item.setAttribute('aria-checked', 'false'); }); speedMenuItems[0].setAttribute('aria-checked', 'true'); } overrideSpeedLabel(sliderElement); sliderElement.style = `--yt-slider-shape-gradient-percent: ${sliderElement.value / maxSpeed * 100}%;`; document.querySelector('.ytp-speedslider-text').textContent = sliderElement.value + 'x'; } function overrideSliderFunction() { const sliderElement = parentMenu.querySelector(sliderSelector); if (!sliderElement || !shouldInitialize) return; overrideSpeedLabel(sliderElement); shouldInitialize = false; if (sliderElement.initialized) return; sliderElement.max = maxSpeed.toString(); sliderElement.setAttribute('value', userSettings.targetSpeed.toString()); setSpeed(userSettings.targetSpeed); overrideSliderStyle(sliderElement); sliderElement.addEventListener('input', () => { const newSpeed = parseFloat(sliderElement.value); updateSavedSpeed(newSpeed); setSpeed(newSpeed); overrideSliderStyle(sliderElement) }); // Since we are using the html speed control we should suppress youtube's own speed control to prevent unwanted updates. sliderElement.addEventListener('change', (event) => { setSpeedText(sliderElement.value); event.stopImmediatePropagation(); }, true); sliderElement.initialized = true; } async function findSpeedTextElement() { const youtubeApi = document.querySelector('#movie_player'); await youtubeApi.setPlaybackRate(1.05); const settingItems = document.querySelectorAll('.ytp-menuitem'); const matchingItem = Array.from(settingItems).find(item => item.textContent.includes('1.05') ); speedTextElement = matchingItem?.querySelector('.ytp-menuitem-content'); } const speedTextObserver = new MutationObserver(initializeSpeedTextElement); async function initializeSpeedTextElement() { try { if (speedTextElement) { if (speedTextElement.initialized) return; const youtubeApi = document.querySelector('#movie_player'); youtubeApi?.addEventListener('onPlaybackRateChange', updateSavedSpeed, true); speedTextElement.initialized = true; setSpeedText(userSettings.targetSpeed); speedTextObserver.disconnect(); } else { const requestedSpeed = userSettings.targetSpeed; await findSpeedTextElement(); setSpeed(requestedSpeed); updateSavedSpeed(requestedSpeed); setSpeedText(requestedSpeed); } } catch (error) { console.error("Error when trying to initialize speed text element. Error: " + error); } } const parentMenu = document.querySelector(parentSelector); if (parentMenu) { const sliderObserver = new MutationObserver(overrideSliderFunction); sliderObserver.observe(parentMenu, { childList: true, subtree: true, }); speedTextObserver.observe(parentMenu, { childList: true, subtree: true, attributes: true, }); } else { console.error('Whoops! The parent menu was not found. The script can\'t run. 😥'); } } // syncs the user's settings on load async function applySettings() { try { const storedValues = await GM.listValues(); await Promise.all(Object.entries(DEFAULT_SETTINGS).map(async ([key, value]) => { if (!storedValues.includes(key)) { await GM.setValue(key, value); } })); await Promise.all(storedValues.map(async key => { if (!(key in DEFAULT_SETTINGS)) { await GM.deleteValue(key); } })); await Promise.all( storedValues.map(key => GM.getValue(key).then(value => [key, value])) ).then(keyValuePairs => keyValuePairs.forEach(([newKey, newValue]) => { userSettings[newKey] = newValue; })); console.log(Object.entries(userSettings).map(([key, value]) => key + ": " + value).join(", ")); } catch (error) { console.error("Error when applying settings: " + error.message); } } function updateSavedSpeed(speed) { userSettings.targetSpeed = speed; GM.setValue('targetSpeed', userSettings.targetSpeed); } function handleNewVideoLoad() { shouldInitialize = true; setSpeed(userSettings.targetSpeed); overrideSpeedElements(); } function main() { window.addEventListener("pageshow", () => { handleNewVideoLoad(); window.addEventListener('yt-player-updated', () => { handleNewVideoLoad(); }, true); }, true); } applySettings().then(main); })();