Better Youtube Shorts

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.

ของเมื่อวันที่ 11-07-2024 ดู เวอร์ชันล่าสุด

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey, Greasemonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name               Better Youtube Shorts
// @name:zh-CN         更好的 Youtube Shorts
// @name:zh-TW         更好的 Youtube Shorts
// @namespace          Violentmonkey Scripts
// @version            2.1.1
// @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
// ==/UserScript==

(async () => {
  const isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
  let currentUrl = "";

  const isMobile =
    /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
      navigator.userAgent
    );

  const once = (fn) => {
    let done = false;
    let result;
    return async (...args) => {
      if (done) return result;
      done = true;
      result = await fn(...args);
      return result;
    };
  };

  const infoMainText = `BTYS Version ${GM_info.script.version}<br>
    We support the mobile version of YouTube Shorts now!🎉🎉🎉<br>
    Now you can enjoy BYTS on the mobile web version, go for a try!📱<br>
    Right now it's a beta version<br>
    So features may not work as expected🐛<br>
    Let us know if you find any bugs or have any suggestions🎩<br>
    DOUBLE CLICK this message to close it👋<br>
    `;

  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 infoFn = once(async (reel) => {
    const version = await GM_getValue("version");
    const shouldNotifyUserAboutChanges = true;
    if (
      !version ||
      (typeof version === "string" &&
        higherVersion(GM_info.script.version, version) &&
        shouldNotifyUserAboutChanges)
    ) {
      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 = infoMainText;
      info.appendChild(infoText);
      reel.appendChild(info);
      info.addEventListener("dblclick", () => {
        info.remove();
      });
    }
  });

  let shortsAutoSwitchToVideo = await GM_getValue("shortsAutoSwitchToVideo");
  if (shortsAutoSwitchToVideo === void 0) {
    shortsAutoSwitchToVideo = false;
    GM_setValue("shortsAutoSwitchToVideo", shortsAutoSwitchToVideo);
  }
  GM_registerMenuCommand(
    `Shorts Auto Switch To Video: ${shortsAutoSwitchToVideo ? "on" : "off"}`,
    () => {
      shortsAutoSwitchToVideo = !shortsAutoSwitchToVideo;
      GM_setValue("shortsAutoSwitchToVideo", shortsAutoSwitchToVideo).then(
        () => (location.href = location.href.replace("watch?v=", "shorts/"))
      );
    }
  );

  if (shortsAutoSwitchToVideo && location.href.includes("youtube.com/shorts")) {
    currentUrl = location.href = location.href.replace("shorts/", "watch?v=");
    return;
  }

  const initialize = once(async () => {
    GM_addStyle(
      `input[type="range"].volslider {
          height: 12px;
          -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: 12px;
          height: 12px;
          margin-top: -2px;
          border-radius: 50%;
          background: ${isDarkMode ? "white" : "black"};
        }
        input[type="range"]:focus::-webkit-slider-runnable-track {
          background: rgb(50 50 50);
        }
        .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: #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%;
        }`
    );

    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");
    const checkpointStatusEnum = Object.freeze({
      OFF: 0,
      TEMPORARY: 1,
      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.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.OFF) {
      shortsCheckpoints = await GM_getValue("shortsCheckpoints");
      if (
        shortsCheckpoints === void 0 ||
        continueFromLastCheckpoint === checkpointStatusEnum.TEMPORARY
      ) {
        shortsCheckpoints = {};
        GM_setValue("shortsCheckpoints", shortsCheckpoints);
      }
    }

    GM_registerMenuCommand(
      `Constant Volume: ${constantVolume ? "on" : "off"}`,
      () => {
        constantVolume = !constantVolume;
        GM_setValue("constantVolume", constantVolume).then(() =>
          location.reload()
        );
      }
    );
    GM_registerMenuCommand(`Operating Mode: ${operationMode}`, () => {
      operationMode = operationMode === "video" ? "shorts" : "video";
      GM_setValue("operationMode", operationMode).then(() => location.reload());
    });
    GM_registerMenuCommand(
      `Continue From Last Checkpoint: ${Object.keys(checkpointStatusEnum)
        .find(
          (key) => checkpointStatusEnum[key] === continueFromLastCheckpoint % 3
        )
        .toLowerCase()}`,
      () => {
        continueFromLastCheckpoint = (continueFromLastCheckpoint + 1) % 3;
        GM_setValue(
          "continueFromLastCheckpoint",
          continueFromLastCheckpoint
        ).then(() => location.reload());
      }
    );
    GM_registerMenuCommand(
      `Loop Playback: ${loopPlayback ? "on" : "off"}`,
      () => {
        loopPlayback = !loopPlayback;
        GM_setValue("loopPlayback", loopPlayback).then(() => location.reload());
      }
    );
    GM_registerMenuCommand(
      `Open Watch in Current Tab: ${openWatchInCurrentTab ? "on" : "off"}`,
      () => {
        openWatchInCurrentTab = !openWatchInCurrentTab;
        GM_setValue("openWatchInCurrentTab", openWatchInCurrentTab).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 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",
                  function (e) {
                    videoOperationMode(e);
                    if (constantVolume) {
                      constantVolume = false;
                      requestAnimationFrame(() => (constantVolume = true));
                    }
                  },
                  {
                    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,
          }
        );
      }
      video.addEventListener("dblclick", function () {
        if (document.fullscreenElement) {
          document.exitFullscreen();
        } else {
          document.getElementsByTagName("ytd-app")[0].requestFullscreen();
        }
      });
      document.addEventListener("keydown", function (e) {
        if (
          e.key.toUpperCase() === "ENTER" ||
          e.key.toUpperCase() === "NUMPADENTER"
        ) {
          if (document.fullscreenElement) {
            document.exitFullscreen();
          } else {
            document.getElementsByTagName("ytd-app")[0].requestFullscreen();
          }
        }
      });
      document.addEventListener("keydown", function (e) {
        if (e.key.toUpperCase() === "W") {
          const watchUrl = location.href.replace("shorts/", "watch?v=");
          if (openWatchInCurrentTab) {
            window.location.href = watchUrl;
          } else {
            window.open(watchUrl, "_blank");
          }
        }
      });
    }

    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() {
      if (!isMobile) {
        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;
        }

        const shortsPlayerControls = document.querySelector(
          "#scrubber > ytd-scrubber > shorts-player-controls"
        );
        const scrubber = document.getElementById("scrubber");
        shortsPlayerControls?.remove();
        scrubber?.remove();

        infoFn(reel);

        if (continueFromLastCheckpoint !== checkpointStatusEnum.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);
          audioInitialized = true;
        }
        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`;

        // 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();
          });
          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.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}%;`;
          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: 4px; color: ${
              isDarkMode ? "white" : "black"
            }; font-size: 1.2rem;`;
            autoScrollTextDiv.textContent = "Auto Scroll ";
            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`;
        autoScrollSlider.style.marginTop = `0px`;
        autoScrollSwitch.style.marginTop = "5px";
      } else {
        const reel = document.querySelector(
          "ytm-reel-player-overlay-renderer[data-is-active='true']"
        );
        if (reel === null) {
          return;
        }
        const currentVideo = document.querySelector("video");
        if (video !== currentVideo) {
          video = currentVideo;
        }
        const touchControls = document.querySelector(
          "div.layer.touch-controls > div"
        );
        touchControls?.remove();

        infoFn(reel);

        // 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%; left: 3px; height: 10px; 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();
          });
          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.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}%;`;
          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; justify-content: center; width: 386px; height: 11px; color: white; font-size: 1rem;`;
              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`;
      }
    }
  });
  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);
})();