Greasy Fork

Greasy Fork is available in English.

更好的Youtube Shorts

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

当前为 2024-02-14 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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.3
// @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) {
    if (mutation.addedNodes.length > 0) {
      for (const node of mutation.addedNodes) {
        if (
          node.tagName === "YTD-SHORTS" ||
          node?.className?.includes?.("html5-video-player")
        ) {
          shortsAndPlayerReady++;
        }
        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, { 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);
}