Greasy Fork is available in English.
为Youtube Shorts提供更多的控制功能,包括音量控制,进度条,自动滚动,快捷键等等。
当前为
// ==UserScript==
// @name Youtube Shorts Improved
// @name:zh-CN 更好的Youtube Shorts
// @name:zh-TW 更好的Youtube Shorts
// @name:ja より良いYoutube Shorts
// @namespace Violentmonkey Scripts
// @version 1.1.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
// @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%;
}`
);
var vid = null;
var reel = null;
var progbar = null;
var seekMouseDown = false;
var bytsVol = null;
var bytsTimeInfo = null;
var lastCurSeconds = 0;
var progress = null;
var progressTime = 0;
var shortsAndPlayerReady = 0;
var autoScrollVal = true;
var autoscrollInput = null;
var constantVolume = false;
GM_registerMenuCommand(constantVolume ? "Constant Volume: On" : "Constant Volume: Off", function () {
constantVolume = !constantVolume;
GM_setValue("bytsConstantVolume", constantVolume);
});
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
try {
if (
node.tagName === "YTD-SHORTS" ||
node.classList.contains("html5-video-player")
) {
shortsAndPlayerReady++;
if (shortsAndPlayerReady === 2) {
observer.disconnect();
autoScrollVal = GM_getValue("bytsAutoscroll", true);
constantVolume = GM_getValue("bytsConstantVolume", false);
updateVidElemWithRAF();
addEventListener("keydown", function (e) {
switch (e.key.toUpperCase()) {
case "ARROWLEFT":
vid.currentTime -= 2;
break;
case "ARROWRIGHT":
vid.currentTime += 2;
break;
default:
break;
}
});
}
}
} catch (_) {}
}
}
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
function padTo2Digits(num) {
return num.toString().padStart(2, "0");
}
function updateVidElemWithRAF() {
updateVidElem();
requestAnimationFrame(updateVidElemWithRAF);
}
function navigationButtonDown() {
const button = document
.querySelector("#navigation-button-down")
.querySelector("button");
if (button !== null) {
button.click();
}
}
function updateVidElem() {
vid = document.querySelector(".html5-video-player video");
reel = vid.closest("ytd-reel-video-renderer");
if (vid === null || reel === null) {
return;
}
// Volume Slide
if (reel.querySelector("#byts-vol") === null) {
if (document.querySelector("#byts-vol") === null) {
const volSlider = document.createElement("input");
volSlider.style.cssText = `user-select: none; width: 100px; left: 0px; background-color: transparent; position: absolute; margin-top: ${
reel.offsetHeight + 5
}px;`;
volSlider.type = "range";
volSlider.id = "byts-vol";
volSlider.className = "volslider";
volSlider.name = "vol";
volSlider.min = 0.0;
volSlider.max = 1.0;
volSlider.step = 0.01;
volSlider.value = vid.volume;
volSlider.addEventListener("input", function () {
vid.volume = this.value;
GM_setValue("bytsVolume", this.value);
});
reel.appendChild(volSlider);
} else {
reel.appendChild(document.querySelector("#byts-vol"));
}
bytsVol = document.querySelector("#byts-vol");
} else {
try {
if (constantVolume) {
vid.volume = document.querySelector("#byts-vol").value;
}
document.querySelector("#byts-vol").value = vid.volume;
} catch (_) {}
}
bytsVol.style.marginTop = `${reel.offsetHeight + 5}px`;
// Progress Bar
if (reel.querySelector("#byts-progbar") === null) {
const progressbar = reel.querySelector("#progress-bar");
if (progressbar !== null) {
progressbar.remove();
}
if (document.querySelector("#byts-progbar") === null) {
const progbar = document.createElement("div");
progbar.id = "byts-progbar";
progbar.style.cssText =
"user-select: none; cursor: pointer; width: 98%; height: 6px; background-color: #343434; position: absolute; margin-top: 846px; border-radius: 10px";
reel.appendChild(progbar);
} else {
reel.appendChild(document.querySelector("#byts-progbar"));
}
progbar = document.querySelector("#byts-progbar");
progbar.addEventListener("mousemove", (e) => {
if (seekMouseDown) {
vid.currentTime = ((e.offsetX * 1) / reel.offsetWidth) * vid.duration;
}
});
progbar.addEventListener("mousedown", () => {
seekMouseDown = true;
});
progbar.addEventListener("mouseleave", () => {
seekMouseDown = false;
});
progbar.addEventListener("mouseup", (e) => {
seekMouseDown = false;
vid.currentTime = ((e.offsetX * 1) / reel.offsetWidth) * vid.duration;
});
}
// Progress Bar (Inner Red Bar)
progressTime = (vid.currentTime / vid.duration) * 100;
if ((progress = progbar.querySelector("#byts-progress")) === null) {
progress = document.createElement("div");
progress.id = "byts-progress";
progress.style.cssText = `user-select: none; background-color: #FF0000; height: 100%; border-radius: 10px; width: ${progressTime}%;`;
progress.addEventListener("mouseup", (e) => {
const selected_val = (e.offsetX * 1) / reel.offsetWidth;
vid.currentTime = selected_val * vid.duration;
});
progbar.appendChild(progress);
} else {
progress.style.width = `${progressTime}%`;
}
// Time Info
let durSecs = Math.floor(vid.duration);
let durMinutes = Math.floor(durSecs / 60);
let durSeconds = durSecs % 60;
let curSecs = Math.floor(vid.currentTime);
if (
curSecs != lastCurSeconds ||
reel.querySelector("#byts-timeinfo") === null
) {
lastCurSeconds = curSecs;
let curMinutes = Math.floor(curSecs / 60);
let curSeconds = curSecs % 60;
// TimeInfo Element
if (reel.querySelector("#byts-timeinfo") === null) {
if (document.querySelector("#byts-timeinfo") === null) {
const 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;`;
const 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);
reel.appendChild(timeInfo);
} else {
reel.appendChild(document.querySelector("#byts-timeinfo"));
}
bytsTimeInfo = document.querySelector("#byts-timeinfo");
}
document.querySelector(
"#byts-timeinfo-textdiv"
).textContent = `${curMinutes}:${padTo2Digits(
curSeconds
)} / ${durMinutes}:${padTo2Digits(durSeconds)}`;
}
bytsTimeInfo.style.marginTop = `${reel.offsetHeight + 2}px`;
// AutoScroll
if (reel.querySelector("#byts-autoscroll-div") === null) {
if (document.querySelector("#byts-autoscroll-div") === null) {
let astc = "";
if (autoScrollVal) {
astc = " checked";
}
const 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 + 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";
autoscrollInput = document.createElement("input");
autoscrollInput.id = "byts-autoscroll-input";
autoscrollInput.type = "checkbox";
autoscrollInput.checked = autoScrollVal;
autoscrollInput.addEventListener("input", function () {
autoScrollVal = this.checked;
GM_setValue("bytsAutoscroll", this.checked);
});
const autoscrollSlider = document.createElement("span");
autoscrollSlider.className = "slider round";
autoscrollSwitch.appendChild(autoscrollInput);
autoscrollSwitch.appendChild(autoscrollSlider);
autoscrollDiv.appendChild(autoscrollSwitch);
reel.appendChild(autoscrollDiv);
} else {
reel.appendChild(document.querySelector("#byts-autoscroll-div"));
}
bytsVol = document.querySelector("#byts-autoscroll-div");
} else {
if (autoScrollVal == true) {
vid.setAttribute("loop", false);
vid.addEventListener("ended", navigationButtonDown);
} else {
vid.setAttribute("loop", true);
vid.removeEventListener("ended", navigationButtonDown);
}
}
bytsVol.style.marginTop = `${reel.offsetHeight + 2}px`;
}