Twitter Video Player Volume Fix

the script saves and applies your set volume level, when it is off or suddenly too loud!

// ==UserScript==
// @name         Twitter Video Player Volume Fix
// @namespace    http://tampermonkey.net/
// @version      1.03.25
// @description  the script saves and applies your set volume level, when it is off or suddenly too loud!
// @author       6iri6r6r
// @match        https://x.com/*
// @grant        GM_getValue
// @grant        GM_setValue
// @license      MIT
// @icon         https://avatars.mds.yandex.net/i?id=062579e45305ee7dff7c7b26c4dd7bdced335bbc-16398073-images-thumbs&n=13
// ==/UserScript==

(function () {
    const VOLUME_STORAGE_KEY = "XcomTwitter_video_player_MasterVolume";
    const MUTED_STORAGE_KEY = "XcomTwitter_video_MasterVolume_Muted";
    let savedVolume = GM_getValue(VOLUME_STORAGE_KEY, 0.5); // По умолчанию 50%
    let savedMuted = GM_getValue(MUTED_STORAGE_KEY, false); // По умолчанию звук включен

    // Логируем загруженные значения
    console.log("[TwitterVolumeFix] Загружена громкость:", savedVolume);
    console.log("[TwitterVolumeFix] Загружено состояние muted:", savedMuted);

    // Сохраняем громкость
    function saveVolume(volume) {
        if (volume !== savedVolume) {
            savedVolume = volume;
            GM_setValue(VOLUME_STORAGE_KEY, volume);
            console.log("[TwitterVolumeFix] Сохранена громкость:", volume);
        }
    }

    // Сохраняем состояние muted
    function saveMuted(muted) {
        if (muted !== savedMuted) {
            savedMuted = muted;
            GM_setValue(MUTED_STORAGE_KEY, muted);
            console.log("[TwitterVolumeFix] Сохранено состояние muted:", muted);
        }
    }

    // Применяем сохранённые настройки к видео
    function applySettings(video) {
        if (!video) return;
        video.volume = savedVolume; // Применяем точное значение громкости
        video.muted = savedMuted; // Применяем сохранённое состояние muted
        console.log("[TwitterVolumeFix] Применены настройки: volume =", video.volume, ", muted =", video.muted);
    }

    // Отслеживаем клики по кнопке "Игнорировать/Не игнорировать"
    function attachButtonHandlers() {
        document.querySelectorAll('button[aria-label="Игнорировать"], button[aria-label="Не игнорировать"]').forEach((button) => {
            if (!button.dataset.muteHandlerAttached) {
                button.dataset.muteHandlerAttached = "true";
                button.addEventListener("click", () => {
                    const isMuted = button.getAttribute("aria-label") === "Игнорировать";
                    document.querySelectorAll("video").forEach((video) => {
                        video.muted = isMuted;
                        if (!isMuted) {
                            // При unmute восстанавливаем сохранённую громкость
                            video.volume = savedVolume;
                        }
                        saveMuted(isMuted);
                    });
                    console.log("[TwitterVolumeFix] Кнопка нажата, muted =", isMuted, ", volume =", savedVolume);
                });
            }
        });
    }

    // Отслеживаем клики по кнопке "Назад"
    function attachBackButtonHandlers() {
        document.querySelectorAll('button[aria-label="Назад"]').forEach((button) => {
            if (!button.dataset.backHandlerAttached) {
                button.dataset.backHandlerAttached = "true";
                button.addEventListener("click", () => {
                    // При нажатии "Назад" принудительно применяем сохранённые настройки ко всем видео
                    setTimeout(() => {
                        document.querySelectorAll("video").forEach(applySettings);
                        console.log("[TwitterVolumeFix] Нажата кнопка 'Назад', применены настройки: volume =", savedVolume, ", muted =", savedMuted);
                    }, 100); // Задержка для ожидания загрузки видео после навигации
                });
            }
        });
    }

    // Подключаем обработчики к конкретному видео
    function attachVideoHandlers(video) {
        if (!video.dataset.volumeHandlerAttached) {
            video.dataset.volumeHandlerAttached = "true";

            // Применяем настройки при загрузке нового ролика
            video.addEventListener("loadedmetadata", () => applySettings(video));
            video.addEventListener("play", () => applySettings(video));

            // Сохраняем громкость при изменении через слайдер
            video.addEventListener("volumechange", () => {
                if (video.volume !== savedVolume && !video.muted) {
                    saveVolume(video.volume);
                }
            });

            // Сразу применяем для уже загруженного видео
            applySettings(video);
        }
    }

    // Ищем все видео и кнопки на странице
    function processElements() {
        document.querySelectorAll("video").forEach(attachVideoHandlers);
        attachButtonHandlers();
        attachBackButtonHandlers();
    }

    // Наблюдаем за изменениями DOM (SPA-переходы)
    const observer = new MutationObserver(() => processElements());
    observer.observe(document.body, { childList: true, subtree: true });

    // На случай, если элементы уже есть при загрузке
    processElements();
})();