更好的Youtube Shorts

为Youtube Shorts提供更多的控制功能,包括音量控制,进度条,自动滚动,快捷键等等。

目前为 2024-02-14 提交的版本。查看 最新版本

// ==UserScript==
// @name               Youtube Shorts Improved
// @name:zh-CN         更好的Youtube Shorts
// @name:zh-TW         更好的Youtube Shorts
// @name:ja            より良いYoutube Shorts
// @namespace          Violentmonkey Scripts
// @version            1.0.4
// @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/*
// @require            http://code.jquery.com/jquery-latest.js
// @run-at             document-start
// @grant              GM_addStyle
// @grant              GM_getValue
// @grant              GM_setValue
// @license            GPLv3
// @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 $ = window.jQuery;
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;

// Storage
var savedVolume = 1.0;
var autoScrollVal = true;

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++;
        }
      } catch (_) {}
      if (shortsAndPlayerReady === 2) {
        observer.disconnect();
        loadSettings();
        updateVidElemWithRAF();
        addEventListener("keydown", function (e) {
          switch (e.key.toUpperCase()) {
            case "ARROWLEFT":
              $(vid).prop("currentTime", $(vid).prop("currentTime") - 2);
              break;
            case "ARROWRIGHT":
              $(vid).prop("currentTime", $(vid).prop("currentTime") + 2);
              break;
            default:
              break;
          }
        });
      }
    }
  }
});
observer.observe(document.documentElement, {
  childList: true,
  subtree: true,
});

function loadSettings() {
  savedVolume = GM_getValue("bytsVolume", 1.0);
  autoScrollVal = GM_getValue("bytsAutoscroll", true);
}

function padTo2Digits(num) {
  return num.toString().padStart(2, "0");
}

function updateVidElemWithRAF() {
  updateVidElem();
  requestAnimationFrame(updateVidElemWithRAF);
}

function updateVidElem() {
  vid = $(".html5-video-player").first().find("video").first();
  reel = $(vid).closest("ytd-reel-video-renderer");
  if ($(vid).length === 0 || $(reel).length === 0) {
    return;
  }

  if (autoScrollVal == true) {
    $(vid).removeAttr("loop");
    $(vid).unbind("ended");
    $(vid).on("ended", function () {
      $("#navigation-button-down").find("button").first().click();
    });
  } else {
    $(vid).attr("loop", true);
    $(vid).unbind("ended");
  }

  // Progress Bar
  if ($(reel).find("#byts-progbar").length === 0) {
    $(reel).find("#progress-bar").remove();
    if ($("#byts-progbar").length === 0) {
      $(reel).append(
        '<div id="byts-progbar" style="user-select: none; cursor: pointer; width: 98%; height: 6px; background-color: #343434; position: absolute; margin-top: 846px; border-radius: 10px"></div>'
      );
    } else {
      $(reel).append($("#byts-progbar"));
    }
    progbar = $("#byts-progbar").first();
    $(progbar).mousemove((e) => {
      if (seekMouseDown) {
        $(vid).prop(
          "currentTime",
          ((e.offsetX * 1) / $(reel).outerWidth()) * $(vid).prop("duration")
        );
      }
    });
    $(progbar).mousedown(() => {
      seekMouseDown = true;
    });
    $(progbar).mouseleave(() => {
      seekMouseDown = false;
    });
    $(progbar).mouseup((e) => {
      seekMouseDown = false;
      $(vid).prop(
        "currentTime",
        ((e.offsetX * 1) / $(reel).outerWidth()) * $(vid).prop("duration")
      );
    });
  }

  // Progress Bar (Inner Red Bar)
  progressTime = ($(vid).prop("currentTime") / $(vid).prop("duration")) * 100;
  if ((progress = $(progbar).find("#byts-progress")).length === 0) {
    progress = $("<div></div>");
    progress.attr("id", "byts-progress");

    progress.css({
      userSelect: "none",
      backgroundColor: "#FF0000",
      height: "100%",
      borderRadius: "10px",
      width: progressTime + "%",
    });

    progress.on("mouseup", function (e) {
      var selected_val = (e.offsetX * 1) / $(reel).outerWidth();
      $(vid).prop("currentTime", selected_val * $(vid).prop("duration"));
    });

    progress.appendTo(progbar);
  } else {
    progress.css("width", progressTime + "%");
  }

  // Time Info
  let durSecs = Math.floor($(vid).prop("duration"));
  let durMinutes = Math.floor(durSecs / 60);
  let durSeconds = durSecs % 60;

  let curSecs = Math.floor($(vid).prop("currentTime"));
  if (
    curSecs != lastCurSeconds ||
    $(reel).find("#byts-timeinfo").length === 0
  ) {
    lastCurSeconds = curSecs;

    let curMinutes = Math.floor(curSecs / 60);
    let curSeconds = curSecs % 60;

    // TimeInfo Element
    if ($(reel).find("#byts-timeinfo").length === 0) {
      if ($("#byts-timeinfo").length === 0) {
        $(reel).append(
          '<div id="byts-timeinfo" style="user-select: none; display: flex; right: auto; left: auto; position: absolute; margin-top: ' +
            ($(reel).height() + 2) +
            'px;"><div id="byts-timeinfo-textdiv" style="display: flex; margin-right: 5px; margin-top: 4px; color: white; font-size: 1.2rem;">' +
            `${curMinutes}:${padTo2Digits(
              curSeconds
            )} / ${durMinutes}:${padTo2Digits(durSeconds)}` +
            "</div></div>"
        );
      } else {
        $(reel).append($("#byts-timeinfo"));
      }
      bytsTimeInfo = $("#byts-timeinfo");
    }

    $("#byts-timeinfo-textdiv").text(
      `${curMinutes}:${padTo2Digits(curSeconds)} / ${durMinutes}:${padTo2Digits(
        durSeconds
      )}`
    );
  }

  $("#byts-timeinfo").css("margin-top", $(reel).height() + 2);

  // Volume Slide
  if ($(reel).find("#byts-vol").length === 0) {
    if ($("#byts-vol").length === 0) {
      $(reel).append(
        '<input style="user-select: none; width: 100px; left: 0px; background-color: transparent; position: absolute; margin-top: ' +
          ($(reel).height() + 5) +
          'px;" type="range" id="byts-vol" class="volslider" name="vol" min="0.0" max="1.0" step="0.05" value="' +
          savedVolume +
          '"></input>'
      );
    } else {
      $(reel).append($("#byts-vol"));
    }
    bytsVol = $("#byts-vol");

    $("#byts-vol").on("input change", function () {
      $(vid).prop("volume", $(this).val());
      GM_setValue("bytsVolume", $(this).val());
    });
  } else {
    $("#byts-vol").val($(vid).prop("volume"));
  }

  $("#byts-vol").css("margin-top", $(reel).height() + 5);

  // AutoScroll
  if ($(reel).find("#byts-autoscroll-div").length === 0) {
    if ($("#byts-autoscroll-div").length === 0) {
      let astc = "";
      if (autoScrollVal) {
        astc = " checked";
      }
      $(reel).append(
        '<div id="byts-autoscroll-div" style="user-select: none; display: flex; right: 0px; position: absolute; margin-top: ' +
          ($(reel).height() + 2) +
          'px;"><div style="display: flex; margin-right: 5px; margin-top: 4px; color: white; font-size: 1.2rem;">Auto-Scroll: </div><label class="switch"><input id="byts-autoscroll-input" type="checkbox"' +
          astc +
          '><span class="slider round"></span></label></div>'
      );
    } else {
      $(reel).append($("#byts-autoscroll-div"));
    }
    bytsVol = $("#byts-autoscroll-div");

    $("#byts-autoscroll-input").on("input change", function () {
      GM_setValue("bytsAutoscroll", $(this).is(":checked"));

      if ($(this).is(":checked")) {
        autoScrollVal = true;
      } else {
        autoScrollVal = false;
      }
      if (autoScrollVal == true) {
        $(vid).removeAttr("loop");
        $(vid).unbind("ended");
        $(vid).on("ended", function () {
          $("#navigation-button-down").find("button").first().click();
        });
      } else {
        $(vid).attr("loop", true);
        $(vid).unbind("ended");
      }
    });
  }

  $("#byts-autoscroll-div").css("margin-top", $(reel).height() + 2);
}

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址