Redgifs volume slider

Real volume slider for redgifs.com

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

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

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

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

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

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.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

// ==UserScript==
// @name         Redgifs volume slider
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Real volume slider for redgifs.com
// @match        *://*.redgifs.com/*
// @author       Piipos_
// @license      WTFPL
// @grant        GM_addStyle
// ==/UserScript==

(function() {
  'use strict';

  GM_addStyle(`
        .volume-slider-wrapper {
            position: relative;
            display: inline-flex;
            align-items: center;
            margin-left: 10px;
            height: 40px;
        }
        .volume-slider-container {
            position: absolute;
            right: 100%;
            top: 50%;
            display: flex;
            align-items: center;
            padding-right: 15px;
            transform: translateY(-50%) translateX(10px);
            opacity: 0;
            pointer-events: none;
            transition: opacity 0.3s, transform 0.3s;
            z-index: 5;
        }
        .volume-slider-wrapper:hover .volume-slider-container,
        .volume-slider-container:hover {
            opacity: 1;
            pointer-events: all;
            transform: translateY(-50%) translateX(0);
        }
        .custom-volume-slider {
            -webkit-appearance: none;
            width: 100px;
            height: 4px;
            border-radius: 2px;
            cursor: pointer;
            background: linear-gradient(to right,
#EFEEF0 var(--vol-percent, 75%), rgba(239,238,240,0.3) var(--vol-percent, 75%), rgba(239,238,240,0.3) 100%);

        }
        .custom-volume-slider::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 12px;
            height: 12px;
            border-radius: 50%;
            background: #EFEEF0;
            cursor: pointer;
            box-shadow: 0 2px 6px rgba(0,0,0,0.5);
        }
        .volume-tooltip {
            font: bold 11px sans-serif;
            color: #fff;
            background: rgba(0,0,0,0.8);
            padding: 4px 6px;
            border-radius: 4px;
            margin-right: 8px;
            min-width: 25px;
            text-align: center;
        }
        .custom-volume-icon {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            display: flex;
            align-items: center;
            justify-content: center;
            pointer-events: none;
            z-index: 1;
        }
        .SoundButton {
            position: relative;
            opacity: 0.01;
        }
        .SoundButton:hover {
            opacity: 0.02;
        }
    `);

  const SVG_MUTED = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M17 15.0002L19.5 12.5002M19.5 12.5002L22 10.0002M19.5 12.5002L17 10.0002M19.5 12.5002L22 15.0002M8.83667 6.74524C10.0339 4.94944 10.6325 4.05155 11.1114 3.81402C12.0579 3.34467 13.2061 3.69233 13.7332 4.60783C14 5.07115 14 6.15029 14 8.30856V15.6919C14 17.8501 14 18.9293 13.7332 19.3926C13.2061 20.3081 12.0579 20.6558 11.1114 20.1864C10.6325 19.9489 10.0339 19.051 8.83667 17.2552C8.60011 16.9004 8.48183 16.7229 8.33865 16.5808C8.06197 16.3062 7.71106 16.1183 7.32906 16.0405C7.13138 16.0002 6.91814 16.0002 6.49167 16.0002H5.2C4.0799 16.0002 3.51984 16.0002 3.09202 15.7822C2.71569 15.5905 2.40973 15.2845 2.21799 14.9082C2 14.4804 2 13.9203 2 12.8002V11.2002C2 10.0801 2 9.52008 2.21799 9.09226C2.40973 8.71593 2.71569 8.40997 3.09202 8.21823C3.51984 8.00024 4.07989 8.00024 5.2 8.00024H6.49167C6.91814 8.00024 7.13138 8.00024 7.32906 7.95995C7.71106 7.88209 8.06197 7.69429 8.33865 7.41963C8.48183 7.2775 8.60011 7.10008 8.83667 6.74524Z" stroke="#EFEEF0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>`;

  const SVG_LOW = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8.83667 6.74524C10.0339 4.94944 10.6325 4.05155 11.1114 3.81402C12.0579 3.34467 13.2061 3.69233 13.7332 4.60783C14 5.07115 14 6.15029 14 8.30856V15.6919C14 17.8501 14 18.9293 13.7332 19.3926C13.2061 20.3081 12.0579 20.6558 11.1114 20.1864C10.6325 19.9489 10.0339 19.051 8.83667 17.2552C8.60011 16.9004 8.48183 16.7229 8.33865 16.5808C8.06197 16.3062 7.71106 16.1183 7.32906 16.0405C7.13138 16.0002 6.91814 16.0002 6.49167 16.0002H5.2C4.0799 16.0002 3.51984 16.0002 3.09202 15.7822C2.71569 15.5905 2.40973 15.2845 2.21799 14.9082C2 14.4804 2 13.9203 2 12.8002V11.2002C2 10.0801 2 9.52008 2.21799 9.09226C2.40973 8.71593 2.71569 8.40997 3.09202 8.21823C3.51984 8.00024 4.07989 8.00024 5.2 8.00024H6.49167C6.91814 8.00024 7.13138 8.00024 7.32906 7.95995C7.71106 7.88209 8.06197 7.69429 8.33865 7.41963C8.48183 7.2775 8.60011 7.10008 8.83667 6.74524Z" stroke="#EFEEF0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path><path d="M15 9.5C16.5 11 16.5 13.5 15 15" stroke="#EFEEF0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>`;

  const SVG_MEDIUM = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8.83667 6.74524C10.0339 4.94944 10.6325 4.05155 11.1114 3.81402C12.0579 3.34467 13.2061 3.69233 13.7332 4.60783C14 5.07115 14 6.15029 14 8.30856V15.6919C14 17.8501 14 18.9293 13.7332 19.3926C13.2061 20.3081 12.0579 20.6558 11.1114 20.1864C10.6325 19.9489 10.0339 19.051 8.83667 17.2552C8.60011 16.9004 8.48183 16.7229 8.33865 16.5808C8.06197 16.3062 7.71106 16.1183 7.32906 16.0405C7.13138 16.0002 6.91814 16.0002 6.49167 16.0002H5.2C4.0799 16.0002 3.51984 16.0002 3.09202 15.7822C2.71569 15.5905 2.40973 15.2845 2.21799 14.9082C2 14.4804 2 13.9203 2 12.8002V11.2002C2 10.0801 2 9.52008 2.21799 9.09226C2.40973 8.71593 2.71569 8.40997 3.09202 8.21823C3.51984 8.00024 4.07989 8.00024 5.2 8.00024H6.49167C6.91814 8.00024 7.13138 8.00024 7.32906 7.95995C7.71106 7.88209 8.06197 7.69429 8.33865 7.41963C8.48183 7.2775 8.60011 7.10008 8.83667 6.74524Z" stroke="#EFEEF0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path><path d="M15 9.5C16.5 11 16.5 13.5 15 15" stroke="#EFEEF0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path><path d="M18 7C21 10 21 14.5 18 17.5" stroke="#EFEEF0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>`;

  const SVG_HIGH = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8.83667 6.74524C10.0339 4.94944 10.6325 4.05155 11.1114 3.81402C12.0579 3.34467 13.2061 3.69233 13.7332 4.60783C14 5.07115 14 6.15029 14 8.30856V15.6919C14 17.8501 14 18.9293 13.7332 19.3926C13.2061 20.3081 12.0579 20.6558 11.1114 20.1864C10.6325 19.9489 10.0339 19.051 8.83667 17.2552C8.60011 16.9004 8.48183 16.7229 8.33865 16.5808C8.06197 16.3062 7.71106 16.1183 7.32906 16.0405C7.13138 16.0002 6.91814 16.0002 6.49167 16.0002H5.2C4.0799 16.0002 3.51984 16.0002 3.09202 15.7822C2.71569 15.5905 2.40973 15.2845 2.21799 14.9082C2 14.4804 2 13.9203 2 12.8002V11.2002C2 10.0801 2 9.52008 2.21799 9.09226C2.40973 8.71593 2.71569 8.40997 3.09202 8.21823C3.51984 8.00024 4.07989 8.00024 5.2 8.00024H6.49167C6.91814 8.00024 7.13138 8.00024 7.32906 7.95995C7.71106 7.88209 8.06197 7.69429 8.33865 7.41963C8.48183 7.2775 8.60011 7.10008 8.83667 6.74524Z" stroke="#EFEEF0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path><path d="M15 9.5C16.5 11 16.5 13.5 15 15" stroke="#EFEEF0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path><path d="M18 7C21 10 21 14.5 18 17.5" stroke="#EFEEF0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path><path d="M21 4.5C25 8.5 25 15.5 21 19.5" stroke="#EFEEF0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>`;

  const getVolumeSVG = (volume) => {
    if (volume === 0) {
      return SVG_MUTED;
    } else if (volume <= 0.33) {
      return SVG_LOW;
    } else if (volume <= 0.66) {
      return SVG_MEDIUM;
    } else {
      return SVG_HIGH;
    }
  };

  const videoVolumeMap = new WeakMap();
  const videoLastVolumeMap = new WeakMap();
  const videoComponentsMap = new WeakMap();

  const updateSliderUI = (slider, tooltip, volume) => {
    const percent = Math.round(volume * 100);
    slider.value = volume;
    slider.style.setProperty('--vol-percent', percent + '%');
    if (tooltip) {
      tooltip.textContent = percent + '%';
    }
  };

  const updateCustomIcon = (customIcon, volume) => {
    if (customIcon) {
      customIcon.innerHTML = getVolumeSVG(volume);
    }
  };

  const toggleMuteIfNeeded = (video, button, newVolume, oldVolume) => {

    if (newVolume === 0 && oldVolume > 0 && !video.muted) {
      if (button && typeof button.click === 'function') {
        button.click();
        return true;
      }
    } else if (newVolume > 0 && oldVolume === 0 && video.muted) {
      if (button && typeof button.click === 'function') {
        button.click();
        return true;
      }
    }
    return false;
  };

  const observer = new MutationObserver(() => {
    document.querySelectorAll('.GifPreview_isActive').forEach(gifPreview => {
      const video = gifPreview.querySelector('video');
      const button = gifPreview.querySelector('.SoundButton');
      if (!video || !button || button.dataset.volumeEnhanced) return;

      button.dataset.volumeEnhanced = 'true';

      const wrapper = document.createElement('div');
      wrapper.className = 'volume-slider-wrapper';

      button.parentNode.insertBefore(wrapper, button);

      wrapper.appendChild(button);

      const customIcon = document.createElement('div');
      customIcon.className = 'custom-volume-icon';

      const sliderContainer = document.createElement('div');
      sliderContainer.className = 'volume-slider-container';

      const tooltip = document.createElement('div');
      tooltip.className = 'volume-tooltip';

      const slider = document.createElement('input');
      slider.type = 'range';
      slider.min = 0;
      slider.max = 1;
      slider.step = 0.01;
      slider.className = 'custom-volume-slider';

      if (!videoVolumeMap.has(video)) {
        const initialVolume = video.muted ? 0 : video.volume;
        videoVolumeMap.set(video, initialVolume);
        if (!video.muted && video.volume > 0) {
          videoLastVolumeMap.set(video, video.volume);
        }
      }

      videoComponentsMap.set(video, {
        slider,
        tooltip,
        button,
        customIcon
      });

      const initialVolume = videoVolumeMap.get(video);
      updateSliderUI(slider, tooltip, initialVolume);
      updateCustomIcon(customIcon, initialVolume);

      let lastVolume = videoVolumeMap.get(video);

      const onSliderInput = (e) => {
        const newVolume = parseFloat(e.target.value);

        if (newVolume > 0) {
          videoLastVolumeMap.set(video, newVolume);
        }

        const wasToggled = toggleMuteIfNeeded(video, button, newVolume, lastVolume);

        videoVolumeMap.set(video, newVolume);

        if (!wasToggled) {
          video.volume = newVolume;
        }

        updateSliderUI(slider, tooltip, newVolume);
        updateCustomIcon(customIcon, newVolume);

        lastVolume = newVolume;
      };

      slider.addEventListener('input', onSliderInput);

      const buttonObserver = new MutationObserver(() => {

        setTimeout(() => {
          const currentVolume = video.muted ? 0 : video.volume;
          videoVolumeMap.set(video, currentVolume);

          updateSliderUI(slider, tooltip, currentVolume);
          updateCustomIcon(customIcon, currentVolume);

          lastVolume = currentVolume;

          if (!video.muted && video.volume > 0) {
            videoLastVolumeMap.set(video, video.volume);
          }
        }, 50);
      });

      buttonObserver.observe(button, {
        childList: true,
        subtree: true
      });

      [slider, sliderContainer].forEach(el => {
        el.addEventListener('click', e => e.stopPropagation());
        el.addEventListener('mousedown', e => e.stopPropagation());
        el.addEventListener('mouseup', e => e.stopPropagation());
      });

      wrapper.appendChild(customIcon);
      sliderContainer.appendChild(tooltip);
      sliderContainer.appendChild(slider);
      wrapper.appendChild(sliderContainer);

      const onVolumeChange = () => {

        if (!video.dataset.userInteracting) {
          const currentVolume = video.muted ? 0 : video.volume;
          videoVolumeMap.set(video, currentVolume);
          updateSliderUI(slider, tooltip, currentVolume);
          updateCustomIcon(customIcon, currentVolume);
          lastVolume = currentVolume;

          if (!video.muted && video.volume > 0) {
            videoLastVolumeMap.set(video, video.volume);
          }
        }
      };

      video.addEventListener('volumechange', onVolumeChange);

      slider.addEventListener('mousedown', () => {
        video.dataset.userInteracting = 'true';
        lastVolume = videoVolumeMap.get(video);
      });

      slider.addEventListener('mouseup', () => {
        setTimeout(() => {
          delete video.dataset.userInteracting;
        }, 100);
      });

      const cleanup = () => {
        video.removeEventListener('volumechange', onVolumeChange);
        buttonObserver.disconnect();
        clearInterval(backupInterval);
        videoComponentsMap.delete(video);

        if (wrapper && wrapper.parentNode) {
          wrapper.parentNode.insertBefore(button, wrapper);
          wrapper.remove();
        }
      };

      const backupInterval = setInterval(() => {
        if (!document.body.contains(video)) {
          cleanup();
          return;
        }

        const targetVolume = videoVolumeMap.get(video);
        if (targetVolume !== undefined && Math.abs(video.volume - targetVolume) > 0.01) {
          video.volume = targetVolume;
        }
      }, 100);
    });
  });

  observer.observe(document, {
    childList: true,
    subtree: true
  });
})();