您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
为Youtube Shorts提供更多的控制功能,包括音量控制,进度条,自动滚动,快捷键等等。
当前为
// ==UserScript== // @name Better Youtube Shorts // @name:zh-CN 更好的Youtube Shorts // @name:zh-TW 更好的Youtube Shorts // @name:ja より良いYoutube Shorts // @namespace Violentmonkey Scripts // @version 1.2.8 // @description Provides more control features for Youtube Shorts, including volume control, progress bar, auto-scroll, hotkeys, and more. // @description:zh-CN 为Youtube Shorts提供更多的控制功能,包括音量控制,进度条,自动滚动,快捷键等等。 // @description:zh-TW 為Youtube Shorts提供更多的控制功能,包括音量控制,進度條,自動滾動,快捷鍵等等。 // @description:ja Youtube Shortsに音量コントロール、プログレスバー、自動スクロール、ホットキーなどの機能を提供します。 // @author Meriel // @match *://www.youtube.com/shorts/* // @run-at document-start // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_notification // @license MIT // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com // ==/UserScript== GM_addStyle( `input[type="range"].volslider { height: 14px; -webkit-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: rgb(50 50 50); border-radius: 25px; border: 1px solid #000000; } input[type="range"].volslider::-webkit-slider-thumb { -webkit-appearance: none; width: 20px; height: 20px; margin-top: -7px; border-radius: 0px; background-image: url("https://i.imgur.com/vcQoCVS.png"); background-size: 20px; background-repeat: no-repeat; background-position: 50%; } input[type="range"]:focus::-webkit-slider-runnable-track { background: rgb(50 50 50); } .switch { position: relative; display: inline-block; width: 46px; height: 20px; } .switch input { opacity: 0; width: 0; height: 0; } .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; -webkit-transition: 0.4s; transition: 0.4s; } .slider:before { position: absolute; content: ""; height: 12px; width: 12px; left: 4px; bottom: 4px; background-color: white; -webkit-transition: 0.4s; transition: 0.4s; } input:checked + .slider { background-color: #ff0000; } input:focus + .slider { box-shadow: 0 0 1px #ff0000; } input:checked + .slider:before { -webkit-transform: translateX(26px); -ms-transform: translateX(26px); transform: translateX(26px); } /* Rounded sliders */ .slider.round { border-radius: 12px; } .slider.round:before { border-radius: 50%; }` ); let seekMouseDown = false; let lastCurSeconds = 0; let autoScrollVal = GM_getValue("autoscroll", true); let constantVolume = GM_getValue("constantVolume", false); GM_registerMenuCommand( `Constant Volume: ${constantVolume ? "On" : "Off"}`, function () { constantVolume = !constantVolume; GM_setValue("constantVolume", constantVolume); location.reload(); } ); let shortsReady = false; let videoPlayerReady = false; const observer = new MutationObserver((mutations) => { 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-video-player"); if (shortsReady && videoPlayerReady) { observer.disconnect(); let videoPlayer = document.querySelector(".html5-video-player video"); addShortcuts(videoPlayer); updateVidElemWithRAF(); break outer; } } } }); observer.observe(document.documentElement, { childList: true, subtree: true, }); function addShortcuts(videoPlayer) { addEventListener("keydown", function (e) { switch (e.key.toUpperCase()) { case "ARROWLEFT": videoPlayer.currentTime -= 2; break; case "ARROWRIGHT": videoPlayer.currentTime += 2; break; default: break; } }); } function padTo2Digits(num) { return num.toString().padStart(2, "0"); } function updateVidElemWithRAF() { updateVidElem(); requestAnimationFrame(updateVidElemWithRAF); } function navigationButtonDown() { document.querySelector("#navigation-button-down > button").click(); } function updateVidElem() { const videoPlayer = document.querySelector(".html5-video-player video"); const reelVideoRenderer = videoPlayer.closest("ytd-reel-video-renderer"); if (videoPlayer === null || reelVideoRenderer === null) { return; } // Volume Slider let volumeSlider = document.querySelector("#byts-vol"); if (reelVideoRenderer.querySelector("#byts-vol") === null) { if (volumeSlider === null) { volumeSlider = document.createElement("input"); volumeSlider.style.cssText = `user-select: none; width: 100px; left: 0px; background-color: transparent; position: absolute; margin-top: ${ reelVideoRenderer.offsetHeight + 5 }px;`; 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 = videoPlayer.volume; volumeSlider.addEventListener("input", function () { videoPlayer.volume = this.value; GM_setValue("bytsVolume", this.value); }); } reelVideoRenderer.appendChild(volumeSlider); } if (constantVolume) { videoPlayer.volume = document.querySelector("#byts-vol").value; } document.querySelector("#byts-vol").value = videoPlayer.volume; volumeSlider.style.marginTop = `${reelVideoRenderer.offsetHeight + 5}px`; // Progress Bar let progressBar = document.querySelector("#byts-progbar"); if (reelVideoRenderer.querySelector("#byts-progbar") === null) { const builtinProgressbar = reelVideoRenderer.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: 6px; background-color: #343434; position: absolute; margin-top: 846px; border-radius: 10px"; } reelVideoRenderer.appendChild(progressBar); progressBar.addEventListener("mousemove", (e) => { if (seekMouseDown) { videoPlayer.currentTime = ((e.offsetX * 1) / reelVideoRenderer.offsetWidth) * videoPlayer.duration; } }); progressBar.addEventListener("mousedown", () => { seekMouseDown = true; }); progressBar.addEventListener("mouseleave", () => { seekMouseDown = false; }); progressBar.addEventListener("mouseup", (e) => { seekMouseDown = false; videoPlayer.currentTime = ((e.offsetX * 1) / reelVideoRenderer.offsetWidth) * videoPlayer.duration; }); } // Progress Bar (Inner Red Bar) let progressTime = (videoPlayer.currentTime / videoPlayer.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}%;`; InnerProgressBar.addEventListener("mouseup", (e) => { const selected_val = (e.offsetX * 1) / reelVideoRenderer.offsetWidth; videoPlayer.currentTime = selected_val * videoPlayer.duration; }); progressBar.appendChild(InnerProgressBar); } InnerProgressBar.style.width = `${progressTime}%`; // Time Info let durSecs = Math.floor(videoPlayer.duration); let durMinutes = Math.floor(durSecs / 60); let durSeconds = durSecs % 60; let curSecs = Math.floor(videoPlayer.currentTime); let timeInfo = document.querySelector("#byts-timeinfo"); let timeInfoTextDiv = document.querySelector("#byts-timeinfo-textdiv"); if ( curSecs != lastCurSeconds || reelVideoRenderer.querySelector("#byts-timeinfo") === null ) { lastCurSeconds = curSecs; let curMinutes = Math.floor(curSecs / 60); let curSeconds = curSecs % 60; if (reelVideoRenderer.querySelector("#byts-timeinfo") === 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: ${ reelVideoRenderer.offsetHeight + 2 }px;`; timeInfoTextDiv = document.createElement("div"); timeInfoTextDiv.id = "byts-timeinfo-textdiv"; timeInfoTextDiv.style.cssText = `display: flex; margin-right: 5px; margin-top: 4px; color: white; font-size: 1.2rem;`; timeInfoTextDiv.textContent = `${curMinutes}:${padTo2Digits( curSeconds )} / ${durMinutes}:${padTo2Digits(durSeconds)}`; timeInfo.appendChild(timeInfoTextDiv); } reelVideoRenderer.appendChild(timeInfo); } timeInfoTextDiv.textContent = `${curMinutes}:${padTo2Digits( curSeconds )} / ${durMinutes}:${padTo2Digits(durSeconds)}`; } timeInfo.style.marginTop = `${reelVideoRenderer.offsetHeight + 2}px`; // AutoScroll let autoScrollDiv = document.querySelector("#byts-autoscroll-div"); if (reelVideoRenderer.querySelector("#byts-autoscroll-div") === 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: ${ reelVideoRenderer.offsetHeight + 2 }px;`; const autoScrollTextDiv = document.createElement("div"); autoScrollTextDiv.style.cssText = `display: flex; margin-right: 5px; margin-top: 4px; color: white; font-size: 1.2rem;`; autoScrollTextDiv.textContent = "Auto-Scroll: "; autoScrollDiv.appendChild(autoScrollTextDiv); const autoScrollSwitch = document.createElement("label"); autoScrollSwitch.className = "switch"; const autoscrollInput = document.createElement("input"); autoscrollInput.id = "byts-autoscroll-input"; autoscrollInput.type = "checkbox"; autoscrollInput.checked = autoScrollVal; autoscrollInput.addEventListener("input", function () { autoScrollVal = 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); } reelVideoRenderer.appendChild(autoScrollDiv); } if (autoScrollVal === true) { videoPlayer.removeAttribute("loop"); videoPlayer.removeEventListener("ended", navigationButtonDown); videoPlayer.addEventListener("ended", navigationButtonDown); } else { videoPlayer.setAttribute("loop", true); videoPlayer.removeEventListener("ended", navigationButtonDown); } autoScrollDiv.style.marginTop = `${reelVideoRenderer.offsetHeight + 2}px`; }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址