// ==UserScript== // @name Better Youtube Shorts // @name:zh-CN 更好的 Youtube Shorts // @name:zh-TW 更好的 Youtube Shorts // @namespace Violentmonkey Scripts // @version 2.2.8 // @description Provide more control functions for YouTube Shorts, including automatic/manual redirection to corresponding video pages, volume control, progress bar, auto scrolling, shortcut keys, and more. // @description:zh-CN 为 Youtube Shorts提供更多的控制功能,包括自动/手动跳转到对应视频页面,音量控制,进度条,自动滚动,快捷键等等。 // @description:zh-TW 為 Youtube Shorts提供更多的控制功能,包括自動/手動跳轉到對應影片頁面,音量控制,進度條,自動滾動,快捷鍵等等。 // @author Meriel // @match *://*.youtube.com/* // @exclude *://music.youtube.com/* // @run-at document-start // @grant GM.addStyle // @grant GM.registerMenuCommand // @grant GM.getValue // @grant GM.setValue // @grant GM_info // @license MIT // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com // @homepageURL https://github.com/MerielVaren/better-youtube-shorts // @supportURL https://github.com/MerielVaren/better-youtube-shorts/issues // @downloadURL none // ==/UserScript== (async () => { const shouldNotifyUserAboutChanges = false; const userLanguage = navigator.language || navigator.userLanguage; const i18nText = { zhSimplified: { closeText: `
双击关闭此消息👆`, updateText: `BTYS 版本 ${GM_info.script.version}
我们完善了语言的国际化💬
现在支持简体中文,繁体中文和英文🌎
如果需要支持更多语言
请在tampemonkey的反馈区留言📝
`, newInstallationText: ` 欢迎使用 Better YouTube Shorts🎉
请检查 Tampermonkey 菜单中的设置🛠️
里面还有更多功能📢
下面是快捷键的说明👇

箭头上/下: 向上/向下滚动
箭头左/右: 后退/前进
Shift + 箭头上/左: 音量增加/减少
Shift + 箭头下/右: 音量减少/增加
Alt + 回车: 切换全屏
Alt + W: 在当前标签页中打开观看页面
0~9: 跳转到对应的进度
`, on: "开启", off: "关闭", constantVolume: "恒定音量", operationMode: "快捷键", videoMode: "视频操作模式", shortsMode: "短视频操作模式", continueFromLastCheckpoint: "从上次检查点继续", off: "关闭", temporary: "临时保存", permanent: "永久保存", loopPlayback: "循环播放", openWatchInCurrentTab: "在当前标签页中打开对应视频", doubleClickToFullscreen: "双击全屏", progressBarStyle: "进度条样式", original: "原始", custom: "自定义", autoScroll: "自动滚动", shortsAutoSwitchToVideo: "短视频自动切换到对应视频", }, zhTraditional: { closeText: `
雙擊關閉此消息👆`, updateText: `BTYS 版本 ${GM_info.script.version}
我們完善了語言的國際化💬
現在支持繁體中文,簡體中文以及英文🌎
如果需要支持更多語言
請在tampemonkey的反饋區留言📝
`, newInstallationText: ` 歡迎使用 Better YouTube Shorts🎉
請檢查 Tampermonkey 菜單中的設置🛠️
裡面還有更多功能📢
下面是快捷鍵的說明👇

箭頭上/下: 向上/向下滾動
箭頭左/右: 後退/前進
Shift + 箭頭上/左: 音量增加/減少
Shift + 箭頭下/右: 音量減少/增加
Alt + 回車: 切換全屏
Alt + W: 在當前標籤頁中打開觀看頁面
0~9: 跳轉到對應的進度
`, on: "開啟", off: "關閉", constantVolume: "恆定音量", operationMode: "快捷鍵", videoMode: "視頻操作模式", shortsMode: "短視頻操作模式", continueFromLastCheckpoint: "從上次檢查點繼續", off: "關閉", temporary: "臨時保存", permanent: "永久保存", loopPlayback: "循環播放", openWatchInCurrentTab: "在當前標籤頁中打開對應視頻", doubleClickToFullscreen: "雙擊全屏", progressBarStyle: "進度條樣式", original: "原始", custom: "自定義", autoScroll: "自動滾動", shortsAutoSwitchToVideo: "短視頻自動切換到對應視頻", }, en: { closeText: `
Double click to close this message👆`, updateText: `BTYS Version ${GM_info.script.version}
We have improved the internationalization of languages💬
Now supports English, Chinese Simplified, and Chinese Traditional🌎
If you need support for more languages
Please leave a message in the tampemonkey feedback area📝
`, newInstallationText: ` Welcome to Better YouTube Shorts🎉
Please check the settings in the Tampermonkey menu🛠️
There are more features in it📢
Below is the explanation of the shortcut keys👇

Arrow Up/Down: Scroll up/down
Arrow Left/Right: Seek backward/forward
Shift + Arrow Up/Left: Volume up/backward
Shift + Arrow Down/Right: Volume down/forward
Alt + Enter: Toggle fullscreen
Alt + W: Open watch page in current tab
0~9: Jump to the corresponding progress
`, on: "on", off: "off", constantVolume: "Constant Volume", operationMode: "Operation Mode", videoMode: "video operation mode", shortsMode: "shorts operation mode", continueFromLastCheckpoint: "Continue From Last Checkpoint", off: "off", temporary: "temporary", permanent: "permanent", loopPlayback: "Loop Playback", openWatchInCurrentTab: "Open Watch in Current Tab", doubleClickToFullscreen: "Double Click to Fullscreen", progressBarStyle: "Progress Bar Style", original: "original", custom: "custom", autoScroll: "Auto Scroll", shortsAutoSwitchToVideo: "Shorts Auto Switch To Video", }, }; const i18n = userLanguage.includes("zh") ? userLanguage === "zh-CN" || userLanguage === "zh-SG" ? i18nText.zhSimplified : i18nText.zhTraditional : i18nText.en; const isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches; let currentUrl = ""; const once = (fn) => { let done = false; let result; return async (...args) => { if (done) return result; done = true; result = await fn(...args); return result; }; }; const closeText = i18n.closeText; let updateText = i18n.updateText; let newInstallationText = i18n.newInstallationText; updateText += closeText; newInstallationText += closeText; const higherVersion = (v1, v2) => { const v1Arr = v1.split("."); const v2Arr = v2.split("."); for (let i = 0; i < v1Arr.length; i++) { if (v1Arr[i] > v2Arr[i]) { return true; } else if (v1Arr[i] < v2Arr[i]) { return false; } } return false; }; const version = await GM.getValue("version"); let interval; const checkVideoPaused = (video, waitTime = 100) => { if (!video.paused) { video.pause(); console.log("Video paused"); interval = setTimeout(() => checkVideoPaused(video, waitTime), waitTime); } else { clearTimeout(interval); } }; const newInstallation = once(async (reel, video) => { if (!version) { if (!interval) { interval = setTimeout(() => checkVideoPaused(video, 100), 100); } GM.setValue("version", GM_info.script.version); const info = document.createElement("div"); info.style.cssText = `position: absolute; top: 0; left: 0; right: 0; bottom: 0; display: flex; justify-content: center; align-items: center; background-color: rgba(0, 0, 0, 0.5); z-index: 999; margin: 5px 0; color: black; font-size: 2rem; font-weight: bold; text-align: center; border-radius: 10px; padding: 10px; box-shadow: 0 0 10px 5px rgba(0, 0, 0, 0.5); transition: 0.5s;`; const infoText = document.createElement("div"); infoText.style.cssText = `background-color: white; padding: 10px; border-radius: 10px; font-size: 1.5rem;`; infoText.innerHTML = newInstallationText; info.appendChild(infoText); reel.appendChild(info); info.addEventListener("dblclick", () => { info.remove(); video.play(); }); } }); const update = once(async (reel, video) => { GM.setValue("version", GM_info.script.version); if ( typeof version === "string" && higherVersion(GM_info.script.version, version) && shouldNotifyUserAboutChanges ) { if (!interval) { interval = setTimeout(() => checkVideoPaused(video, 100), 100); } GM.setValue("version", GM_info.script.version); const info = document.createElement("div"); info.style.cssText = `position: absolute; top: 0; left: 0; right: 0; bottom: 0; display: flex; justify-content: center; align-items: center; background-color: rgba(0, 0, 0, 0.5); z-index: 999; margin: 5px 0; color: black; font-size: 2rem; font-weight: bold; text-align: center; border-radius: 10px; padding: 10px; box-shadow: 0 0 10px 5px rgba(0, 0, 0, 0.5); transition: 0.5s;`; const infoText = document.createElement("div"); infoText.style.cssText = `background-color: white; padding: 10px; border-radius: 10px; font-size: 1.5rem;`; infoText.innerHTML = updateText; info.appendChild(infoText); reel.appendChild(info); info.addEventListener("dblclick", () => { info.remove(); video.play(); }); } }); let shortsAutoSwitchToVideo = await GM.getValue("shortsAutoSwitchToVideo"); if (shortsAutoSwitchToVideo === void 0) { shortsAutoSwitchToVideo = false; GM.setValue("shortsAutoSwitchToVideo", shortsAutoSwitchToVideo); } GM.registerMenuCommand( `${i18n.shortsAutoSwitchToVideo}: ${ shortsAutoSwitchToVideo ? i18n.on : i18n.off }`, () => { shortsAutoSwitchToVideo = !shortsAutoSwitchToVideo; GM.setValue("shortsAutoSwitchToVideo", shortsAutoSwitchToVideo).then( () => (location.href = location.href.replace("watch?v=", "shorts/")) ); } ); if (shortsAutoSwitchToVideo) { if (window.location.pathname.match("/shorts/.+")) { window.location.replace( "https://www.youtube.com/watch?v=" + window.location.pathname.split("/shorts/").pop() ); } document.addEventListener("yt-navigate-start", (event) => { const url = event.detail.url.split("/shorts/"); if (url.length > 1) { window.location.replace("https://www.youtube.com/watch?v=" + url.pop()); } }); return; } const initialize = once(async () => { GM.addStyle( `input[type="range"].volslider { height: 12px; -webkit-appearance: none; -moz-appearance: none; /* Firefox */ appearance: none; margin: 10px 0; } input[type="range"].volslider:focus { outline: none; } input[type="range"].volslider::-webkit-slider-runnable-track { height: 8px; cursor: pointer; box-shadow: 0px 0px 0px #000000; background: ${isDarkMode ? "rgb(50, 50, 50)" : "#ccc"}; border-radius: 25px; } input[type="range"].volslider::-webkit-slider-thumb { -webkit-appearance: none; width: 12px; height: 12px; margin-top: -2px; border-radius: 50%; background: ${isDarkMode ? "white" : "black"}; } /* Firefox */ input[type="range"].volslider::-moz-range-track { height: 8px; cursor: pointer; box-shadow: 0px 0px 0px #000000; background: ${isDarkMode ? "rgb(50, 50, 50)" : "#ccc"}; border-radius: 25px; } input[type="range"].volslider::-moz-range-thumb { width: 12px; height: 12px; border: none; border-radius: 50%; background: ${isDarkMode ? "white" : "black"}; } .switch { position: relative; display: inline-block; width: 40px; height: 12px; } .switch input { opacity: 0; width: 0; height: 0; } .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: ${isDarkMode ? "rgb(50, 50, 50)" : "#ccc"}; -webkit-transition: 0.4s; transition: 0.4s; } .slider:before { position: absolute; content: ""; height: 12px; width: 12px; left: 0px; bottom: 0px; background-color: ${isDarkMode ? "white" : "black"}; -webkit-transition: 0.4s; transition: 0.4s; } input:checked + .slider { background-color: #ff0000; } input:focus + .slider { box-shadow: 0 0 0px #ff0000; } input:checked + .slider:before { -webkit-transform: translateX(29px); -ms-transform: translateX(29px); transform: translateX(29px); } /* Rounded sliders */ .slider.round { border-radius: 12px; } .slider.round:before { border-radius: 50%; } /* red progress bar */ #byts-progbar:hover #byts-progress::after, #byts-progbar.show-dot #byts-progress::after { content: ''; position: absolute; top: 50%; right: 0; transform: translate(50%, -50%); width: 15px; height: 15px; background-color: #FF0000; border-radius: 50%; display: block; } ` ); let seekMouseDown = false; let lastCurSeconds = 0; let video = null; let autoScroll = await GM.getValue("autoScroll"); let loopPlayback = await GM.getValue("loopPlayback"); let constantVolume = await GM.getValue("constantVolume"); let operationMode = await GM.getValue("operationMode"); let openWatchInCurrentTab = await GM.getValue("openWatchInCurrentTab"); let doubleClickToFullscreen = await GM.getValue("doubleClickToFullscreen"); let progressBarStyle = await GM.getValue("progressBarStyle"); const checkpointStatusEnum = Object.freeze({ [i18n.off]: 0, [i18n.temporary]: 1, [i18n.permanent]: 2, }); let continueFromLastCheckpoint = await GM.getValue( "continueFromLastCheckpoint" ); let lastShortsId = ""; if (autoScroll === void 0) { autoScroll = true; GM.setValue("autoScroll", autoScroll); } if (constantVolume === void 0) { constantVolume = false; GM.setValue("constantVolume", constantVolume); } if (operationMode === void 0) { operationMode = "Shorts"; GM.setValue("operationMode", operationMode); } if (continueFromLastCheckpoint === void 0) { continueFromLastCheckpoint = checkpointStatusEnum[i18n.off]; GM.setValue("continueFromLastCheckpoint", continueFromLastCheckpoint); } if (loopPlayback === void 0) { loopPlayback = true; GM.setValue("loopPlayback", loopPlayback); } if (openWatchInCurrentTab === void 0) { openWatchInCurrentTab = false; GM.setValue("openWatchInCurrentTab", openWatchInCurrentTab); } let shortsCheckpoints; if (continueFromLastCheckpoint !== checkpointStatusEnum[i18n.off]) { shortsCheckpoints = await GM.getValue("shortsCheckpoints"); if ( shortsCheckpoints === void 0 || continueFromLastCheckpoint === checkpointStatusEnum[i18n.temporary] ) { shortsCheckpoints = {}; GM.setValue("shortsCheckpoints", shortsCheckpoints); } } if (doubleClickToFullscreen === void 0) { doubleClickToFullscreen = true; GM.setValue("doubleClickToFullscreen", doubleClickToFullscreen); } if (progressBarStyle === void 0) { progressBarStyle = "custom"; GM.setValue("progressBarStyle", progressBarStyle); } GM.registerMenuCommand( `${i18n.constantVolume}: ${constantVolume ? i18n.on : i18n.off}`, () => { constantVolume = !constantVolume; GM.setValue("constantVolume", constantVolume).then(() => location.reload() ); } ); GM.registerMenuCommand( `${i18n.operationMode}: ${ operationMode === "Video" ? i18n.videoMode : i18n.shortsMode }`, () => { operationMode = operationMode === "Video" ? "Shorts" : "Video"; GM.setValue("operationMode", operationMode).then(() => location.reload() ); } ); GM.registerMenuCommand( `${i18n.continueFromLastCheckpoint}: ${Object.keys(checkpointStatusEnum) .find( (key) => checkpointStatusEnum[key] === continueFromLastCheckpoint % 3 ) .toLowerCase()}`, () => { continueFromLastCheckpoint = (continueFromLastCheckpoint + 1) % 3; GM.setValue( "continueFromLastCheckpoint", continueFromLastCheckpoint ).then(() => location.reload()); } ); GM.registerMenuCommand( `${i18n.loopPlayback}: ${loopPlayback ? i18n.on : i18n.off}`, () => { loopPlayback = !loopPlayback; GM.setValue("loopPlayback", loopPlayback).then(() => location.reload()); } ); GM.registerMenuCommand( `${i18n.openWatchInCurrentTab}: ${ openWatchInCurrentTab ? i18n.on : i18n.off }`, () => { openWatchInCurrentTab = !openWatchInCurrentTab; GM.setValue("openWatchInCurrentTab", openWatchInCurrentTab).then(() => location.reload() ); } ); GM.registerMenuCommand( `${i18n.doubleClickToFullscreen}: ${ doubleClickToFullscreen ? i18n.on : i18n.off }`, () => { doubleClickToFullscreen = !doubleClickToFullscreen; GM.setValue("doubleClickToFullscreen", doubleClickToFullscreen).then( () => location.reload() ); } ); GM.registerMenuCommand( `${i18n.progressBarStyle}: ${ progressBarStyle === "custom" ? i18n.custom : i18n.original }`, () => { progressBarStyle = progressBarStyle === "custom" ? "original" : "custom"; GM.setValue("progressBarStyle", progressBarStyle).then(() => location.reload() ); } ); const observer = new MutationObserver( async (mutations, shortsReady = false, videoPlayerReady = false) => { outer: for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (!shortsReady) { shortsReady = node.tagName === "YTD-SHORTS"; } if (!videoPlayerReady) { videoPlayerReady = typeof node.className === "string" && node.className.includes("html5-main-video"); } if (shortsReady && videoPlayerReady) { observer.disconnect(); video = node; if (constantVolume) { video.volume = await GM.getValue("volume", 0); } addShortcuts(); updateVidElemWithRAF(); break outer; } } } } ); observer.observe(document.documentElement, { childList: true, subtree: true, }); function videoOperationMode(e) { const volumeSlider = document.getElementById("byts-vol"); if (!e.shiftKey) { if ( e.key.toUpperCase() === "ARROWUP" || e.key.toUpperCase() === "ARROWDOWN" ) { e.stopPropagation(); e.preventDefault(); switch (e.key.toUpperCase()) { case "ARROWUP": video.volume = Math.min(1, video.volume + 0.01); volumeSlider.value = video.volume; break; case "ARROWDOWN": video.volume = Math.max(0, video.volume - 0.01); volumeSlider.value = video.volume; break; default: break; } } else if ( e.key.toUpperCase() === "ARROWLEFT" || e.key.toUpperCase() === "ARROWRIGHT" ) { switch (e.key.toUpperCase()) { case "ARROWLEFT": video.currentTime -= 1; break; case "ARROWRIGHT": video.currentTime += 1; break; default: break; } } } else { switch (e.key.toUpperCase()) { case "ARROWLEFT": case "ARROWUP": navigationButtonUp(); break; case "ARROWRIGHT": case "ARROWDOWN": navigationButtonDown(); break; default: break; } } } function shortsOperationMode(e) { const volumeSlider = document.getElementById("byts-vol"); if ( e.key.toUpperCase() === "ARROWUP" || e.key.toUpperCase() === "ARROWDOWN" ) { e.stopPropagation(); e.preventDefault(); if (e.shiftKey) { switch (e.key.toUpperCase()) { case "ARROWUP": video.volume = Math.min(1, video.volume + 0.02); volumeSlider.value = video.volume; break; case "ARROWDOWN": video.volume = Math.max(0, video.volume - 0.02); volumeSlider.value = video.volume; break; default: break; } } else { switch (e.key.toUpperCase()) { case "ARROWUP": navigationButtonUp(); break; case "ARROWDOWN": navigationButtonDown(); break; default: break; } } } else if ( e.key.toUpperCase() === "ARROWLEFT" || e.key.toUpperCase() === "ARROWRIGHT" ) { if (e.shiftKey) { switch (e.key.toUpperCase()) { case "ARROWLEFT": video.volume = Math.max(0, video.volume - 0.01); volumeSlider.value = video.volume; break; case "ARROWRIGHT": video.volume = Math.min(1, video.volume + 0.01); volumeSlider.value = video.volume; break; default: break; } } else { switch (e.key.toUpperCase()) { case "ARROWLEFT": video.currentTime -= 1; break; case "ARROWRIGHT": video.currentTime += 1; break; default: break; } } } } function handleEvent(e) { videoOperationMode(e); if (constantVolume) { constantVolume = false; requestAnimationFrame(() => (constantVolume = true)); } } function addShortcuts() { if (operationMode === "Video") { const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (node?.id === "byts-vol-div") { document.addEventListener("keydown", handleEvent, { capture: true, }); observer.disconnect(); } } } }); observer.observe(document.documentElement, { childList: true, subtree: true, }); } else { document.addEventListener( "keydown", function (e) { shortsOperationMode(e); if (constantVolume) { constantVolume = false; requestAnimationFrame(() => (constantVolume = true)); } }, { capture: true, } ); } if (doubleClickToFullscreen) { video.addEventListener("dblclick", function () { if (document.fullscreenElement) { document.exitFullscreen(); } else { document.getElementsByTagName("ytd-app")[0].requestFullscreen(); } }); } document.addEventListener("keydown", function (e) { if (e.altKey && e.key.toUpperCase() === "ENTER") { if (document.fullscreenElement) { document.exitFullscreen(); } else { document.getElementsByTagName("ytd-app")[0].requestFullscreen(); } } }); document.addEventListener("keydown", function (e) { if (e.altKey && e.key.toUpperCase() === "W") { const watchUrl = location.href.replace("shorts/", "watch?v="); if (openWatchInCurrentTab) { window.location.href = watchUrl; } else { window.open(watchUrl, "_blank"); } } }); document.addEventListener("keydown", function (e) { if ( (e.key >= "0" && e.key <= "9") || (e.code >= "Numpad0" && e.code <= "Numpad9") ) { video.currentTime = video.duration * (e.key / 10); } }); } function padTo2Digits(num) { return num.toString().padStart(2, "0"); } function updateVidElemWithRAF() { try { if (currentUrl?.includes("youtube.com/shorts")) { updateVidElem(); } } catch (e) { console.error(e); } requestAnimationFrame(updateVidElemWithRAF); } function navigationButtonDown() { document.querySelector("#navigation-button-down button").click(); } function navigationButtonUp() { document.querySelector("#navigation-button-up button").click(); } function setVideoPlaybackTime(event, player) { const rect = player.getBoundingClientRect(); let offsetX = event.clientX - rect.left; if (offsetX < 0) { offsetX = 0; } else if (offsetX > player.offsetWidth) { offsetX = player.offsetWidth - 1; } let currentTime = (offsetX / player.offsetWidth) * video.duration; if (currentTime === 0) currentTime = 1e-6; video.currentTime = currentTime; } async function updateVidElem() { const currentVideo = document.querySelector( "#shorts-player > div.html5-video-container > video" ); if (video !== currentVideo) { video = currentVideo; } if (constantVolume) { video.volume = await GM.getValue("volume", 0); } const reel = document.querySelector("ytd-reel-video-renderer[is-active]"); if (reel === null) { return; } if (progressBarStyle === "custom") { const shortsPlayerControls = document.querySelector( "#scrubber > ytd-scrubber > shorts-player-controls" ); const scrubber = document.getElementById("scrubber"); shortsPlayerControls?.remove(); scrubber?.remove(); } update(reel, video); newInstallation(reel, video); if (continueFromLastCheckpoint !== checkpointStatusEnum[i18n.off]) { const currentSec = Math.floor(video.currentTime); const shortsUrlList = location.href.split("/"); if (!shortsUrlList.includes("shorts")) return; const shortsId = shortsUrlList.pop(); if (shortsId !== lastShortsId) { lastShortsId = shortsId; const checkpoint = shortsCheckpoints[shortsId] || 1e-6; video.pause(); if (checkpoint + 1 >= video.duration) { video.currentTime = 1e-6; } else { video.currentTime = checkpoint; } video.play(); } if (currentSec !== lastCurSeconds && video.currentTime !== 0) { lastCurSeconds = currentSec; shortsCheckpoints[shortsId] = currentSec; GM.setValue("shortsCheckpoints", shortsCheckpoints); } } if (operationMode === "Shorts") { document.removeEventListener("keydown", videoOperationMode, { capture: true, }); document.addEventListener("keydown", shortsOperationMode, {}); } else { document.removeEventListener("keydown", shortsOperationMode, {}); document.addEventListener("keydown", videoOperationMode, { capture: true, }); } // Volume Slider let volumeSliderDiv = document.getElementById("byts-vol-div"); let volumeSlider = document.getElementById("byts-vol"); let volumeTextDiv = document.getElementById("byts-vol-textdiv"); const reelVolumeSliderDiv = reel.querySelector("#byts-vol-div"); if (reelVolumeSliderDiv === null) { if (volumeSliderDiv === null) { volumeSliderDiv = document.createElement("div"); volumeSliderDiv.id = "byts-vol-div"; volumeSliderDiv.style.cssText = `user-select: none; width: 100px; left: 0px; background-color: transparent; position: absolute; margin-left: 5px; margin-top: ${reel.offsetHeight}px;`; volumeSlider = document.createElement("input"); volumeSlider.style.cssText = `user-select: none; width: 80px; left: 0px; background-color: transparent; position: absolute; margin-top: 0px;`; volumeSlider.type = "range"; volumeSlider.id = "byts-vol"; volumeSlider.className = "volslider"; volumeSlider.name = "vol"; volumeSlider.min = 0.0; volumeSlider.max = 1.0; volumeSlider.step = 0.01; volumeSlider.value = video.volume; volumeSlider.addEventListener("input", function () { video.volume = this.value; GM.setValue("volume", this.value); }); volumeSliderDiv.appendChild(volumeSlider); volumeTextDiv = document.createElement("div"); volumeTextDiv.id = "byts-vol-textdiv"; volumeTextDiv.style.cssText = `user-select: none; background-color: transparent; position: absolute; color: ${ isDarkMode ? "white" : "black" }; font-size: 1.2rem; margin-left: ${volumeSlider.offsetWidth + 1}px`; volumeTextDiv.textContent = `${( video.volume.toFixed(2) * 100 ).toFixed()}%`; volumeSliderDiv.appendChild(volumeTextDiv); } reel.appendChild(volumeSliderDiv); } if (constantVolume) { video.volume = volumeSlider.value; } volumeSlider.value = video.volume; volumeTextDiv.textContent = `${( video.volume.toFixed(2) * 100 ).toFixed()}%`; volumeSliderDiv.style.marginTop = `${reel.offsetHeight + 2}px`; volumeTextDiv.style.marginLeft = `${volumeSlider.offsetWidth + 1}px`; if (video.muted) { volumeTextDiv.textContent = "0%"; volumeSlider.value = 0; } else { volumeTextDiv.textContent = `${(video.volume * 100).toFixed()}%`; volumeSlider.value = video.volume; } if (progressBarStyle === "custom") { // Progress Bar let progressBar = document.getElementById("byts-progbar"); const reelProgressBar = reel.querySelector("#byts-progbar"); if (reelProgressBar === null) { const builtinProgressbar = reel.querySelector("#progress-bar"); if (builtinProgressbar !== null) { builtinProgressbar.remove(); } if (progressBar === null) { progressBar = document.createElement("div"); progressBar.id = "byts-progbar"; progressBar.style.cssText = `user-select: none; cursor: pointer; width: 98%; height: 7px; background-color: #343434; position: absolute; border-radius: 10px; margin-top: ${ reel.offsetHeight - 7 }px;`; } reel.appendChild(progressBar); let wasPausedBeforeDrag = false; progressBar.addEventListener("mousedown", function (e) { seekMouseDown = true; wasPausedBeforeDrag = video.paused; setVideoPlaybackTime(e, progressBar); video.pause(); progressBar.classList.add("show-dot"); }); document.addEventListener("mousemove", function (e) { if (!seekMouseDown) return; e.preventDefault(); setVideoPlaybackTime(e, progressBar); if (!video.paused) { video.pause(); } e.preventDefault(); }); document.addEventListener("mouseup", function () { if (!seekMouseDown) return; seekMouseDown = false; if (!wasPausedBeforeDrag) { video.play(); } progressBar.classList.remove("show-dot"); }); } progressBar.style.marginTop = `${reel.offsetHeight - 7}px`; // Progress Bar (Inner Red Bar) const progressTime = (video.currentTime / video.duration) * 100; let InnerProgressBar = progressBar.querySelector("#byts-progress"); if (InnerProgressBar === null) { InnerProgressBar = document.createElement("div"); InnerProgressBar.id = "byts-progress"; InnerProgressBar.style.cssText = ` user-select: none; background-color: #FF0000; height: 100%; border-radius: 10px; width: ${progressTime}%; position: relative; `; progressBar.appendChild(InnerProgressBar); } InnerProgressBar.style.width = `${progressTime}%`; } // Time Info const durSecs = Math.floor(video.duration); const durMinutes = Math.floor(durSecs / 60); const durSeconds = durSecs % 60; const curSecs = Math.floor(video.currentTime); let timeInfo = document.getElementById("byts-timeinfo"); let timeInfoText = document.getElementById("byts-timeinfo-textdiv"); const reelTimeInfo = reel.querySelector("#byts-timeinfo"); if (!Number.isNaN(durSecs) && reelTimeInfo !== null) { timeInfoText.textContent = `${Math.floor(curSecs / 60)}:${padTo2Digits( curSecs % 60 )} / ${durMinutes}:${padTo2Digits(durSeconds)}`; } if (curSecs !== lastCurSeconds || reelTimeInfo === null) { lastCurSeconds = curSecs; const curMinutes = Math.floor(curSecs / 60); const curSeconds = curSecs % 60; if (reelTimeInfo === null) { if (timeInfo === null) { timeInfo = document.createElement("div"); timeInfo.id = "byts-timeinfo"; timeInfo.style.cssText = `user-select: none; display: flex; right: auto; left: auto; position: absolute; margin-top: ${ reel.offsetHeight - 2 }px;`; timeInfoText = document.createElement("div"); timeInfoText.id = "byts-timeinfo-textdiv"; timeInfoText.style.cssText = `display: flex; margin-right: 5px; margin-top: 4px; color: ${ isDarkMode ? "white" : "black" }; font-size: 1.2rem;`; timeInfoText.textContent = `${curMinutes}:${padTo2Digits( curSeconds )} / ${durMinutes}:${padTo2Digits(durSeconds)}`; timeInfo.appendChild(timeInfoText); } reel.appendChild(timeInfo); timeInfoText.textContent = `${curMinutes}:${padTo2Digits( curSeconds )} / ${durMinutes}:${padTo2Digits(durSeconds)}`; } } timeInfo.style.marginTop = `${reel.offsetHeight - 2}px`; // AutoScroll let autoScrollDiv = document.getElementById("byts-autoscroll-div"); const reelAutoScrollDiv = reel.querySelector("#byts-autoscroll-div"); if (reelAutoScrollDiv === null) { if (autoScrollDiv === null) { autoScrollDiv = document.createElement("div"); autoScrollDiv.id = "byts-autoscroll-div"; autoScrollDiv.style.cssText = `user-select: none; display: flex; right: 0px; position: absolute; margin-top: ${ reel.offsetHeight - 3 }px;`; const autoScrollTextDiv = document.createElement("div"); autoScrollTextDiv.style.cssText = `display: flex; margin-right: 5px; margin-top: ${ userLanguage.includes("zh") ? "3px" : "4px" }; color: ${isDarkMode ? "white" : "black"}; font-size: 1.2rem;`; autoScrollTextDiv.textContent = i18n.autoScroll; autoScrollDiv.appendChild(autoScrollTextDiv); const autoScrollSwitch = document.createElement("label"); autoScrollSwitch.className = "switch"; autoScrollSwitch.style.marginTop = "5px"; const autoscrollInput = document.createElement("input"); autoscrollInput.id = "byts-autoscroll-input"; autoscrollInput.type = "checkbox"; autoscrollInput.checked = autoScroll; autoscrollInput.addEventListener("input", function () { autoScroll = this.checked; GM.setValue("autoScroll", this.checked); }); const autoScrollSlider = document.createElement("span"); autoScrollSlider.className = "slider round"; autoScrollSwitch.appendChild(autoscrollInput); autoScrollSwitch.appendChild(autoScrollSlider); autoScrollDiv.appendChild(autoScrollSwitch); } reel.appendChild(autoScrollDiv); } if (autoScroll === true) { video.removeAttribute("loop"); video.removeEventListener("ended", navigationButtonDown); video.addEventListener("ended", navigationButtonDown); } else { if (loopPlayback) { video.setAttribute("loop", true); video.removeEventListener("ended", navigationButtonDown); } else { video.removeAttribute("loop"); video.removeEventListener("ended", navigationButtonDown); } } autoScrollDiv.style.marginTop = `${reel.offsetHeight - 3}px`; } }); const urlChange = (event) => { const destinationUrl = event?.destination?.url || ""; if (destinationUrl.startsWith("about:blank")) return; const href = destinationUrl || location.href; if (href.includes("youtube.com/shorts")) { if (shortsAutoSwitchToVideo) { currentUrl = location.href = href.replace("shorts/", "watch?v="); return; } else { currentUrl = href; initialize(); } } }; urlChange(); unsafeWindow?.navigation?.addEventListener("navigate", urlChange); unsafeWindow.addEventListener("replaceState", urlChange); unsafeWindow.addEventListener("pushState", urlChange); unsafeWindow.addEventListener("popState", urlChange); unsafeWindow.addEventListener("hashchange", urlChange); })();