YouTubeTV Volume Control with Memory

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

Szkript telepítése?
A szerző által javasolt szkript

Ez is érdekelhet: YouTube Volume Control with Memory

Szkript telepítése
// ==UserScript==
// @name         YouTubeTV Volume Control with Memory
// @namespace    https://github.com/Nick2bad4u/UserStyles
// @version      4.0
// @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
// @tag          youtube
// ==/UserScript==

(function () {
  'use strict';

  // Wait for the YouTube player and controls to load
  const playerReady = setInterval(() => {
    const videoPlayer = document.querySelector('video');
    const leftControls = document.querySelector('.ytp-left-controls');
    const volumeSliderHandle = document.querySelector('.ytp-volume-slider-handle');
    const volumePanel = document.querySelector('.ytp-volume-panel');
    const muteButton = document.querySelector('.ytp-mute-button');

    if (videoPlayer && leftControls && volumeSliderHandle && muteButton) {
      clearInterval(playerReady);

      // Retrieve the saved volume level from localStorage
      let ytVolumeData = localStorage.getItem('yt-player-volume');
      let savedVolume = videoPlayer.volume;
      let savedMuted = videoPlayer.muted;

      if (ytVolumeData) {
        try {
          ytVolumeData = JSON.parse(ytVolumeData);
          const data = JSON.parse(ytVolumeData.data);
          savedVolume = data.volume / 100; // YouTube stores volume from 0 to 100
          savedMuted = data.muted;
        } catch (e) {
          console.error('Failed to parse yt-player-volume:', e);
        }
      }

      // Ensure savedVolume is within [0, 1] range
      videoPlayer.volume = Math.max(0, Math.min(1, savedVolume));
      videoPlayer.muted = savedMuted;

      // Update the slider handle position
      const updateSliderHandle = () => {
        if (videoPlayer.muted) {
          volumeSliderHandle.style.left = `0%`;
        } else {
          volumeSliderHandle.style.left = `${videoPlayer.volume * 100}%`;
        }
      };
      updateSliderHandle();

      // Set the aria-valuenow attribute on the volume panel
      if (volumePanel) {
        volumePanel.setAttribute('aria-valuenow', videoPlayer.volume * 100);
      }

      // Create input element for volume control
      const volumeInput = document.createElement('input');
      volumeInput.type = 'number';
      volumeInput.min = 0;
      volumeInput.max = 100;
      volumeInput.value = videoPlayer.muted ? 0 : Math.round(videoPlayer.volume * 100);

      // Style the input field
      Object.assign(volumeInput.style, {
        width: '40px',
        marginLeft: '10px',
        backgroundColor: 'rgba(255, 255, 255, 0.0)',
        color: 'white',
        border: '0px solid rgba(255, 255, 255, 0.0)',
        borderRadius: '4px',
        zIndex: 9999,
        height: '24px',
        fontSize: '16px',
        padding: '0 4px',
        transition: 'border-color 0.3s, background-color 0.3s',
        outline: 'none',
        position: 'relative',
        top: '13px',
      });

      // Prevent hotkeys from interfering with the input
      volumeInput.addEventListener('keydown', (e) => e.stopPropagation());

      // Input focus and hover styling
      volumeInput.addEventListener(
        'focus',
        () => (volumeInput.style.borderColor = 'rgba(255, 255, 255, 0.6)')
      );
      volumeInput.addEventListener(
        'blur',
        () => (volumeInput.style.borderColor = 'rgba(255, 255, 255, 0.3)')
      );
      volumeInput.addEventListener(
        'mouseenter',
        () => (volumeInput.style.backgroundColor = 'rgba(0, 0, 0, 0.8)')
      );
      volumeInput.addEventListener(
        'mouseleave',
        () => (volumeInput.style.backgroundColor = 'rgba(255, 255, 255, 0.0)')
      );

      // Handle volume change from input
      let lastSetVolume = videoPlayer.volume;
      volumeInput.addEventListener('input', () => {
        let volume = parseInt(volumeInput.value, 10);
        volume = isNaN(volume) ? 100 : Math.max(0, Math.min(100, volume)); // Clamp between 0 and 100

        videoPlayer.volume = volume / 100; // Convert to [0, 1] range

        if (volume === 0) {
          videoPlayer.muted = true;
        } else {
          videoPlayer.muted = false;
        }

        lastSetVolume = videoPlayer.volume;

        // Update the slider handle position
        updateSliderHandle();

        // Save the new volume to localStorage
        const ytVolumeObject = {
          data: JSON.stringify({
            volume: volume, // Volume from 0 to 100
            muted: videoPlayer.muted,
          }),
          expiration: Date.now() + 2592000000, // Expires in 30 days
          creation: Date.now(),
        };
        const ytVolumeString = JSON.stringify(ytVolumeObject);
        localStorage.setItem('yt-player-volume', ytVolumeString);
      });

      // Update input value when volume changes from other controls
      let previousMutedState = videoPlayer.muted;

      videoPlayer.addEventListener('volumechange', () => {
        if (previousMutedState && !videoPlayer.muted) {
          // Player was muted and is now unmuted
          videoPlayer.volume = lastSetVolume;
          volumeInput.value = Math.round(videoPlayer.volume * 100);
          updateSliderHandle();
        }

        previousMutedState = videoPlayer.muted;

        // Update lastSetVolume if the volume changed and not muted
        if (!videoPlayer.muted) lastSetVolume = videoPlayer.volume;

        volumeInput.value = videoPlayer.muted ? 0 : Math.round(videoPlayer.volume * 100);

        // Update the slider handle position
        updateSliderHandle();

        // Save the volume to localStorage
        const volumePercent = Math.round(videoPlayer.volume * 100);
        const ytVolumeObject = {
          data: JSON.stringify({
            volume: volumePercent,
            muted: videoPlayer.muted,
          }),
          expiration: Date.now() + 2592000000,
          creation: Date.now(),
        };
        const ytVolumeString = JSON.stringify(ytVolumeObject);
        localStorage.setItem('yt-player-volume', ytVolumeString);
      });

      // Insert the input into the left controls
      leftControls.appendChild(volumeInput);
    }
  }, 500);
})();