YouTubeTV Volume Control with Memory

Remembers and controls volume levels on YouTube TV with keyboard shortcuts and a UI for manual input

当前为 2024-11-04 提交的版本,查看 最新版本

// ==UserScript==
// @name         YouTubeTV Volume Control with Memory
// @namespace    https://github.com/Nick2bad4u/UserStyles
// @version      3.3
// @description  Remembers and controls volume levels on YouTube TV with keyboard shortcuts and a UI for manual input
// @author       Nick2bad4u
// @match        *://tv.youtube.com/*
// @grant        GM.setValue
// @grant        GM.getValue
// @icon         https://www.google.com/s2/favicons?sz=64&domain=tv.youtube.com
// @license      UnLicense
// ==/UserScript==

(() => {
  "use strict";

  const VOLUME_KEY = "youtubeTVVolume";
  const DEFAULT_VOLUME = 5;

  const createVolumeUI = async () => {
    const sliderContainer = document.querySelector(
      ".ytu-player-controls.style-scope.ypcs-volume-control-slot.ypcs-control .ytu-volume-slider.style-scope.volume-button-slot",
    );
    if (!sliderContainer) {
      console.error("Volume slider container not found, cannot create UI");
      return;
    }

    const savedVolume = await GM.getValue(VOLUME_KEY, DEFAULT_VOLUME);

    // Create input box
    const volumeInput = document.createElement("input");
    volumeInput.type = "number";
    volumeInput.min = 0;
    volumeInput.max = 100;
    volumeInput.value = savedVolume;
    volumeInput.style.width = "35px";
    volumeInput.style.height = "24px";
    volumeInput.style.marginLeft = "10px";
    volumeInput.style.marginRight = "10px";
    volumeInput.style.border = "1px solid #ccc";
    volumeInput.style.background = "#1c1c1c";
    volumeInput.style.color = "#fff";
    volumeInput.style.textAlign = "center";
    volumeInput.style.borderRadius = "4px";
    volumeInput.style.outline = "none";

    // Create a wrapper to position it properly
    const wrapper = document.createElement("div");
    wrapper.style.display = "flex";
    wrapper.style.alignItems = "center";
    wrapper.style.marginRight = "10px";

    // Insert the input box into the correct container
    wrapper.appendChild(volumeInput);
    sliderContainer.parentNode.insertBefore(wrapper, sliderContainer);

    // Prevent key events from propagating while typing
    volumeInput.addEventListener("keydown", (e) => {
      e.stopPropagation();
    });

    // Event listener for manual input changes
    volumeInput.addEventListener("input", (e) => {
      let newValue = parseFloat(e.target.value);
      if (newValue < 0) newValue = 0;
      if (newValue > 100) newValue = 100;
      setVolume(newValue);
    });

    // Function to update input when slider moves
    const slider = document.querySelector(
      'tp-yt-paper-slider[role="slider"].ytu-volume-slider',
    );
    const updateInput = () => {
      volumeInput.value = slider.value;
    };

    // Observe slider changes
    const observer = new MutationObserver(updateInput);
    observer.observe(slider, {
      attributes: true,
      attributeFilter: ["value"],
    });
  };

  const setVolume = async (value) => {
    const slider = document.querySelector(
      'tp-yt-paper-slider[role="slider"].ytu-volume-slider',
    );
    if (slider) {
      console.log(`Setting volume to: ${value}`);
      slider.value = value;
      slider.setAttribute("value", value);
      slider.dispatchEvent(new Event("input", { bubbles: true }));
      slider.dispatchEvent(new Event("change", { bubbles: true }));
    } else {
      console.error("Volume slider not found");
    }
    await GM.setValue(VOLUME_KEY, value);
  };

  const loadSavedVolume = async () => {
    const savedVolume = await GM.getValue(VOLUME_KEY, DEFAULT_VOLUME);
    const slider = document.querySelector(
      'tp-yt-paper-slider[role="slider"].ytu-volume-slider',
    );
    if (slider) {
      console.log(`Applying saved volume: ${savedVolume}`);
      setVolume(savedVolume);
    } else {
      console.log("Slider not ready, retrying in 500ms");
      setTimeout(loadSavedVolume, 500);
    }
  };

  const tryLoadVolume = () => {
    const slider = document.querySelector(
      'tp-yt-paper-slider[role="slider"].ytu-volume-slider',
    );
    const sliderContainer = document.querySelector(
      ".ytu-player-controls.style-scope.ypcs-volume-control-slot.ypcs-control .ytu-volume-slider.style-scope.volume-button-slot",
    );

    if (slider && sliderContainer) {
      loadSavedVolume().then(createVolumeUI);
    } else {
      console.error(
        "Volume slider or container not found, retrying in 3 seconds",
      );
      setTimeout(tryLoadVolume, 3000);
    }
  };

  const observeSliderChanges = () => {
    const slider = document.querySelector(
      'tp-yt-paper-slider[role="slider"].ytu-volume-slider',
    );
    if (slider) {
      console.log("Observing the volume slider for changes");
      const observer = new MutationObserver(async () => {
        const currentValue = slider.value;
        console.log(`Current volume changed to: ${currentValue}`);
        await GM.setValue(VOLUME_KEY, currentValue);
      });
      observer.observe(slider, {
        attributes: true,
      });
    } else {
      console.error("Volume slider not found during initialization");
      setTimeout(observeSliderChanges, 1000);
    }
  };

  window.addEventListener("load", () => {
    console.log("YouTube TV Volume Rememberer script with UI loaded");
    setTimeout(() => {
      tryLoadVolume();
      observeSliderChanges();
    }, 1000);
  });

  window.addEventListener("keydown", (event) => {
    const slider = document.querySelector(
      'tp-yt-paper-slider[role="slider"].ytu-volume-slider',
    );
    if (!slider) return;

    const isShiftPressed = event.shiftKey;

    if (isShiftPressed && event.key === "ArrowUp") {
      const newValue = Math.min(parseFloat(slider.value) + 5, 100);
      setVolume(newValue);
    } else if (isShiftPressed && event.key === "ArrowDown") {
      const newValue = Math.max(parseFloat(slider.value) - 5, 0);
      setVolume(newValue);
    }
  });
})();

QingJ © 2025

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