YouTube Volume Control with Memory

Set YouTube volume manually on a scale of 1-100, remember last set volume, and inject the UI to the left of the volume slider on the video player. Syncs the slider, disables invalid inputs, and adds debugging.

目前为 2024-11-04 提交的版本。查看 最新版本

// ==UserScript==
// @name         YouTube Volume Control with Memory
// @namespace    https://github.com/Nick2bad4u/UserStyles
// @version      4.0
// @description  Set YouTube volume manually on a scale of 1-100, remember last set volume, and inject the UI to the left of the volume slider on the video player. Syncs the slider, disables invalid inputs, and adds debugging.
// @author       Nick2bad4u
// @match        *://www.youtube.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant        GM.getValue
// @grant        GM.setValue
// @license      UnLicense
// ==/UserScript==

(async function () {
  "use strict";

  // Default volume if none is saved
  let previousVolume = await GM.getValue("youtubeVolume", 5);

  // Create input element for volume control
  const volumeInput = document.createElement("input");
  volumeInput.type = "number";
  volumeInput.min = 0;
  volumeInput.max = 100;
  volumeInput.value = previousVolume;

  // Set input field styles to resemble YouTube's UI
  volumeInput.style.width = "30px";
  volumeInput.style.marginRight = "10px";
  volumeInput.style.backgroundColor = "rgba(255, 255, 255, 0.0)";
  volumeInput.style.color = "white";
  volumeInput.style.border = "0px solid rgba(255, 255, 255, 0.0)";
  volumeInput.style.borderRadius = "4px";
  volumeInput.style.zIndex = 9999;
  volumeInput.style.height = "24px";
  volumeInput.style.fontSize = "16px";
  volumeInput.style.padding = "0 4px";
  volumeInput.style.transition = "border-color 0.3s, background-color 0.3s";
  volumeInput.style.outline = "none";
  volumeInput.style.position = "relative";
  volumeInput.style.top = "13px";

  // Change border color on focus
  volumeInput.addEventListener("focus", () => {
    volumeInput.style.borderColor = "rgba(255, 255, 255, 0.6)";
  });

  volumeInput.addEventListener("blur", () => {
    volumeInput.style.borderColor = "rgba(255, 255, 255, 0.3)";
  });

  // Change background color on hover
  volumeInput.addEventListener("mouseenter", () => {
    volumeInput.style.backgroundColor = "rgba(0, 0, 0, 0.8)";
  });

  volumeInput.addEventListener("mouseleave", () => {
    volumeInput.style.backgroundColor = "rgba(255, 255, 255, 0.0)";
  });

  // Prevent YouTube hotkeys when typing in the input
  volumeInput.addEventListener("keydown", function (event) {
    event.stopPropagation();
    console.log("Keydown event in volume input, stopping propagation.");
  });

  // Function to set the volume based on input value
  async function setVolume(volumeValue) {
    const player = document.querySelector("video");
    if (player) {
      // Validate input (must be between 0 and 100)
      if (volumeValue < 0) volumeValue = 0;
      if (volumeValue > 100) volumeValue = 100;
      volumeInput.value = volumeValue;

      // Set the player volume and save to Tampermonkey storage
      player.volume = volumeValue / 100;
      await GM.setValue("youtubeVolume", volumeValue);
      console.log(
        `Volume set to ${volumeValue} and saved to Tampermonkey storage.`,
      );

      // Sync YouTube's volume slider UI
      const volumeSlider = document.querySelector(".ytp-volume-slider-handle");
      if (volumeSlider) {
        volumeSlider.style.left = `${volumeValue}%`;
        console.log("YouTube volume slider updated.");
      }
    }
  }

  // Event listener for input change (manually changing the volume in the input box)
  volumeInput.addEventListener("input", () => setVolume(volumeInput.value));

  // Function to update the input field when YouTube's player volume is changed
  async function updateVolumeInput() {
    const player = document.querySelector("video");
    if (player) {
      const currentVolume = Math.round(player.volume * 100);
      volumeInput.value = currentVolume;
      await GM.setValue("youtubeVolume", currentVolume);
      console.log(
        `Volume input updated to ${currentVolume} from video player.`,
      );

      // Show 0 if the video is muted
      if (player.muted) {
        volumeInput.value = 0;
      }
    }
  }

  // Function to handle mute changes
  async function handleMuteChange() {
    const player = document.querySelector("video");
    if (player) {
      if (player.muted) {
        volumeInput.value = 0; // Show 0 when muted
      } else {
        volumeInput.value = previousVolume; // Restore previous volume when unmuted
        player.volume = previousVolume / 100; // Set the player volume back to previous
      }
      console.log(`Mute state changed: muted = ${player.muted}`);
    }
  }

  // Inject the input box into YouTube's control bar
  function injectVolumeControl() {
    const volumeSliderPanel = document.querySelector(".ytp-volume-panel");
    if (volumeSliderPanel) {
      volumeSliderPanel.parentNode.insertBefore(volumeInput, volumeSliderPanel);
      setVolume(previousVolume); // Set initial volume
      const player = document.querySelector("video");
      if (player) {
        player.addEventListener("volumechange", updateVolumeInput);
        player.addEventListener("mute", handleMuteChange);
        player.addEventListener("unmute", handleMuteChange);
        console.log("Volume input injected and event listeners attached.");
      }
    } else {
      console.log("Volume panel not found, retrying...");
      setTimeout(injectVolumeControl, 500);
    }
  }

  // Inject the volume control when the page is ready
  injectVolumeControl();
})();