YouTube Volume Booster 600% (v3.9.18 Resize & Idle-Hide Fix)

Adds a floating volume slider with up to 600% boost. Features: Global Volume, Remember Per Video, One-Time Restore, Draggable UI. Fixes resize and idle-hide bugs.

当前为 2025-06-07 提交的版本,查看 最新版本

// ==UserScript==
// @name        YouTube Volume Booster 600% (v3.9.18 Resize & Idle-Hide Fix)
// @name:vi     YouTube - Tăng Âm Lượng 600% (v3.9.18 Sửa lỗi Resize & Ẩn khi không hoạt động)
// @namespace   http://tampermonkey.net/
// @version     3.9.18 // FIX: Panel now resizes correctly and hides with native YouTube controls.
// @description Adds a floating volume slider with up to 600% boost. Features: Global Volume, Remember Per Video, One-Time Restore, Draggable UI. Fixes resize and idle-hide bugs.
// @description:vi Thêm thanh trượt âm lượng nổi, tăng âm lượng đến 600%. Các tính năng: Âm lượng toàn tab, Ghi nhớ từng video, Khôi phục một lần, Giao diện kéo thả. Sửa lỗi thay đổi kích thước và ẩn khi không hoạt động.
// @author      Gemini & Developer
// @match       *://*.youtube.com/*
// @grant       GM_addStyle
// @grant       GM_setValue
// @grant       GM_getValue
// @icon        https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// ==/UserScript==

(function() {
    'use strict';

    // --- GLOBAL VARIABLES AND SETTINGS ---
    let audioContext, gainNode, sourceNode;
    let currentVideoId = null; // Current video ID
    let previousVideoId = null; // Stores the ID of the video watched just before the current one
    let currentTabVolume = 100; // Default volume for the current tab
    let tabId = null; // Unique ID for the current browser tab

    // Feature states
    let isGlobalVolumeEnabled = false; // Controls if volume is persisted per tab
    let isRememberPerVideoEnabled = false; // Controls if manual save button for per-video is active
    let isOneTimeRestoreEnabled = true; // Controls if one-time restore is active

    let currentLanguage = 'vi'; // Default language

    // Variables for draggable toolbar position
    let isDragging = false; //
    let dragOffsetX, dragOffsetY; //
    const STORAGE_KEY_TOOLBAR_POSITION = 'youtubeBoosterToolbarPosition'; // Key to save toolbar position

    const translations = {
        'vi': {
            globalVolumeTitle: 'Âm lượng toàn tab', //
            globalVolumeEnabled: 'Âm lượng toàn tab: Đã bật (Nhấn để tắt)', //
            globalVolumeDisabled: 'Âm lượng toàn tab: Đã tắt (Nhấn để bật)', //
            rememberPerVideoTitle: 'Ghi nhớ từng video', //
            rememberPerVideoEnabled: 'Ghi nhớ từng video: Đã bật (Nhấn để tắt)', //
            rememberPerVideoDisabled: 'Ghi nhớ từng video: Đã tắt (Nhấn để bật)', //
            savePerVideoTitle: 'Lưu âm lượng cho video này', //
            savePerVideoEnabledHint: 'Lưu âm lượng cho video này (Tính năng ghi nhớ đang hoạt động)', //
            savePerVideoDisabledHint: 'Chỉ hoạt động khi "Ghi nhớ từng video" bật.', //
            oneTimeRestoreTitle: 'Khôi phục một lần', //
            oneTimeRestoreEnabled: 'Khôi phục một lần: Đã bật (Nhấn để tắt)', //
            oneTimeRestoreDisabled: 'Khôi phục một lần: Đã tắt (Nhấn để bật)', //
            languageToggleTitle: 'Ngôn ngữ', //
            languageToggleHint: 'Chuyển đổi ngôn ngữ (hiện tại: Tiếng Việt)', //
            languageToggleHintEnglish: 'Switch language (current: English)', //
            manualSaveSuccess: 'Đã lưu âm lượng thủ công', //
            manualSaveNotAllowed: 'Lưu thủ công không được phép. "Ghi nhớ từng video" đang tắt hoặc không có ID video.', //
            globalVolumeEnabledUser: '"Âm lượng toàn tab" đã BẬT bởi người dùng.', //
            globalVolumeDisabledUser: '"Âm lượng toàn tab" đã TẮT bởi người dùng.', //
            rememberPerVideoEnabledUser: '"Ghi nhớ từng video" đã BẬT.', //
            rememberPerVideoDisabledUser: '"Ghi nhớ từng video" đã TẮT.', //
            oneTimeRestoreEnabledUser: '"Khôi phục một lần" đã BẬT bởi người dùng.', //
            oneTimeRestoreDisabledUser: '"Khôi phục một lần" đã TẮT bởi người dùng.', //
            boosterInitializedGlobal: 'Khởi tạo - Trạng thái tính năng Âm lượng Toàn cầu:', //
            boosterInitializedPerVideo: 'Khởi tạo - Trạng thái chuyển đổi tính năng Mỗi Video:', //
            boosterInitializedOneTime: 'Khởi tạo - Trạng thái chuyển đổi tính năng Khôi phục Một lần:', //
            tabIdNotInitialized: 'ID tab chưa được khởi tạo hoặc không có sẵn.' //
        },
        'en': {
            globalVolumeTitle: 'Global Volume', //
            globalVolumeEnabled: 'Global Volume: Enabled (Click to disable)', //
            globalVolumeDisabled: 'Global Volume: Disabled (Click to enable)', //
            rememberPerVideoTitle: 'Remember Per Video', //
            rememberPerVideoEnabled: 'Remember Per Video: Enabled (Click to disable)', //
            rememberPerVideoDisabled: 'Remember Per Video: Disabled (Click to enable)', //
            savePerVideoTitle: 'Save volume for this video', //
            savePerVideoEnabledHint: 'Save volume for this video (Remember feature is active)', //
            savePerVideoDisabledHint: 'Only active when "Remember Per Video" is on.', //
            oneTimeRestoreTitle: 'One-Time Restore', //
            oneTimeRestoreEnabled: 'One-Time Restore: Enabled (Click to disable)', //
            oneTimeRestoreDisabled: 'One-Time Restore: Disabled (Click to enable)', //
            languageToggleTitle: 'Language', //
            languageToggleHint: 'Switch language (current: Vietnamese)', //
            languageToggleHintEnglish: 'Switch language (current: English)', //
            manualSaveSuccess: 'Manually saved volume', //
            manualSaveNotAllowed: 'Manual save not allowed. "Remember Volume Per Video" is off or no video ID.', //
            globalVolumeEnabledUser: '"Global Volume for Current Tab" feature ENABLED by user.', //
            globalVolumeDisabledUser: '"Global Volume for Current Tab" feature DISABLED by user.', //
            rememberPerVideoEnabledUser: '"Remember Per Video" feature ENABLED.', //
            rememberPerVideoDisabledUser: '"Remember Per Video" feature DISABLED.', //
            oneTimeRestoreEnabledUser: '"One-Time Restore" feature ENABLED by user.', //
            oneTimeRestoreDisabledUser: '"One-Time Restore" feature DISABLED by user.', //
            boosterInitializedGlobal: 'Initializing - Global Volume Feature State:', //
            boosterInitializedPerVideo: 'Initializing - Per Video Feature Toggle State:', //
            boosterInitializedOneTime: 'Initializing - One-Time Restore Feature Toggle State:', //
            tabIdNotInitialized: 'Tab ID not yet initialized or available.' //
        }
    };

    // Tampermonkey storage keys
    const STORAGE_KEY_PER_VIDEO = 'youtubeVolumeSettings_v2'; //
    const STORAGE_KEY_GLOBAL_FEATURE_STATE = 'youtubeGlobalVolumeFeatureState_v2'; //
    const STORAGE_KEY_PER_VIDEO_FEATURE_STATE = 'youtubePerVideoFeatureState_v2'; //
    const STORAGE_KEY_ONE_TIME_RESTORE_FEATURE_STATE = 'youtubeOneTimeRestoreFeatureState_v1'; //
    const STORAGE_KEY_TAB_VOLUME_PREFIX = 'youtubeGlobalVolumePerTab_v2_'; //
    const STORAGE_KEY_LANGUAGE = 'youtubeBoosterLanguage_v1'; //
    const STORAGE_KEY_ONE_TIME_RESTORE_VOLUMES = 'youtubeRestoreVolume_v3_videoId_map'; //

    // Session storage keys
    const SESSION_STORAGE_TAB_ID_KEY = 'youtubeBoosterTabId_v2'; //
    const SESSION_STORAGE_ONE_TIME_PROCESSED_KEY_PREFIX = 'youtubeBoosterOneTimeProcessed_'; //

    // Debounce variables
    let initializeTimeout = null;
    const DEBOUNCE_DELAY = 100; //

    // --- CSS FOR UI ---
    GM_addStyle(`
        #volume-booster-container-abs {
            position: absolute;
            z-index: 9999;
            background-color: rgba(28, 28, 28, 0.85); /*  */
            padding: 6px 12px; /*  */
            border-radius: 8px; /*  */
            display: flex; /*  */
            align-items: center; /*  */
            gap: 8px; /*  */
            opacity: 0; /*  */
            transition: opacity 0.3s ease-in-out, bottom 0.3s ease-in-out, right 0.3s ease-in-out; /*  */
            pointer-events: none; /*  */
            cursor: grab; /*  */
        }
        #volume-booster-container-abs.dragging {
            cursor: grabbing; /*  */
            transition: none; /*  */
        }
        /* FIX: Panel only shows when native controls are visible (player does NOT have .ytp-autohide class) */
        #movie_player:not(.ytp-autohide) #volume-booster-container-abs {
            opacity: 1; /*  */
            pointer-events: auto; /*  */
        }
        .volume-booster-slider-abs {
            -webkit-appearance: none; /*  */
            appearance: none; /*  */
            width: 100px; /*  */
            height: 5px; /*  */
            background: #777; /*  */
            outline: none; /*  */
            cursor: pointer; /*  */
            border-radius: 3px; /*  */
            margin: 0; /*  */
        }
        .volume-booster-slider-abs::-webkit-slider-thumb {
            -webkit-appearance: none; /*  */
            appearance: none; /*  */
            width: 16px; /*  */
            height: 16px; /*  */
            background: #ff0000; /*  */
            cursor: pointer; /*  */
            border-radius: 50%; /*  */
        }
        .volume-booster-slider-abs::-moz-range-thumb {
            width: 16px; /*  */
            height: 16px; /*  */
            background: #ff0000; /*  */
            cursor: pointer; /*  */
            border-radius: 50%; /*  */
            border: none; /*  */
        }
        .volume-booster-label-abs {
            color: white; /*  */
            font-size: 13px; /*  */
            font-weight: bold; /*  */
            min-width: 50px; /*  */
            text-shadow: 1px 1px 2px black; /*  */
            text-align: right; /*  */
        }
        .volume-booster-setting-icon {
            cursor: pointer; /*  */
            width: 20px; /*  */
            height: 20px; /*  */
            background-color: #ffffff; /*  */
            mask-size: contain; /*  */
            mask-repeat: no-repeat; /*  */
            mask-position: center; /*  */
            transition: background-color 0.2s ease-in-out; /*  */
            border-radius: 4px; /*  */
            padding: 2px; /*  */
        }
        .volume-booster-setting-icon.enabled {
            background-color: #4CAF50; /*  */
        }
        .volume-booster-setting-icon.global-volume {
            mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.3-.61-.22l-2.49 1c-.52-.4-1.09-.75-1.72-1.03L13.73 2.2c-.06-.2-.25-.3-.46-.3h-4c-.21 0-.4.1-.46.3L9.23 4.87c-.63.28-1.2.63-1.72 1.03l-2.49-1c-.23-.09-.49 0-.61.22l-2 3.46c-.12.22-.07.49.12.64l2.11 1.65c-.04.32-.07.64-.07.98s.03.66.07-.98l-2.11 1.65c-.19.15-.24-.42-.12-.64l2 3.46c.12.22.39-.3.61-.22l2.49-1c.52.4 1.09.75 1.72 1.03l.44 2.69c.06.2.25.3.46.3h4c.21 0 .4-.1.46-.3l.44-2.69c.63-.28 1.2-.63 1.72-1.03l2.49 1c.23.09.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zm-1.93-1.93c.33.33.56.73.69 1.13l.11.4c.03.1.06.2.06.3s-.03.2-.06.3l-.11-.4c-.13.4-.36.8-.69 1.13-.33.33-.73-.56-1.13-.69l-.4-.11c-.1.03-.2.06-.3.06s-.2-.03-.3-.06l-.4-.11c-.4-.13-.8-.36-1.13-.69-.33-.33-.56-.73-.69-1.13l-.11-.4c-.03-.1-.06-.2-.06-.3s-.03.2.06-.3l-.11-.4c.13-.4.36-.8.69-1.13.33.33.73-.56 1.13-.69l-.4-.11c.1-.03.2-.06.3.06s-.2.03.3.06l-.4.11c.4.13.8.36 1.13.69zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z"/></svg>'); /*  */
        }
        .volume-booster-setting-icon.per-video-toggle {
            mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M17 3H7c-1.1 0-2 .9-2 2v16l7-3 7 3V5c0-1.1-.9-2-2-2z"/></svg>'); /*  */
        }
        .volume-booster-setting-icon.save-per-video {
            mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z"/></svg>'); /*  */
            opacity: 0.5; /*  */
            pointer-events: none; /*  */
        }
        .volume-booster-setting-icon.one-time-restore-toggle {
            mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 4V1l-4 4 4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.01 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8V23l4-4-4-4v3z"/></svg>'); /*  */
        }
        .volume-booster-setting-icon.language-toggle {
            mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-.07-1.74-.27-3.4-.59-4.96C16.39 3.5 17.72 5.06 18.92 8zM12 4.04c.83 1.22 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.74 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2s.06 1.34.14 2H4.26zm.82 2h2.95c.07 1.74-.27 3.4-.59 4.96C7.61 20.5 6.28 18.94 5.08 16zM8.07 19.96c-.83-1.22-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.74-1.91 3.96zM11.99 20c-.83-1.22-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.74-1.91 3.96zM12 19.96c-.83-1.22-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.74-1.91 3.96zM11.99 4.04c-.83-1.22-1.48-2.53-1.91-3.96h3.82c-.43-1.43-1.08 2.74-1.91 3.96z"/></svg>'); /*  */
        }
        .volume-booster-setting-icon.clicked {
            background-color: #007bff; /*  */
        }
    `);

    // --- FUNCTIONS TO SAVE AND LOAD SETTINGS ---

    function getTabId() {
        let id = sessionStorage.getItem(SESSION_STORAGE_TAB_ID_KEY); //
        if (!id) {
            id = crypto.randomUUID(); //
            sessionStorage.setItem(SESSION_STORAGE_TAB_ID_KEY, id); //
        }
        return id; //
    }

    function getVideoId() {
        const urlParams = new URLSearchParams(window.location.search);
        return urlParams.get('v'); //
    }

    async function getVolumeSetting(videoId) {
        if (!videoId) return null; //
        const allSettings = await GM_getValue(STORAGE_KEY_PER_VIDEO, {}); //
        return allSettings[videoId] !== undefined ? allSettings[videoId] : null; //
    }

    async function saveVolumeSetting(videoId, volume) {
        if (!videoId) return; //
        const allSettings = await GM_getValue(STORAGE_KEY_PER_VIDEO, {}); //
        if (volume === 100) {
            delete allSettings[videoId]; //
        } else {
            allSettings[videoId] = volume; //
        }
        await GM_setValue(STORAGE_KEY_PER_VIDEO, allSettings); //
    }

    async function getFeatureState(key, defaultValue = false) {
        return await GM_getValue(key, defaultValue); //
    }

    async function saveFeatureState(key, state) {
        await GM_setValue(key, state); //
    }

    async function saveOneTimeRestoreVolume(videoId, volume) {
        if (!videoId) return; //
        if (!isOneTimeRestoreEnabled) {
            return; //
        }
        let restoreData = await GM_getValue(STORAGE_KEY_ONE_TIME_RESTORE_VOLUMES, {}); //
        restoreData[videoId] = volume; //
        await GM_setValue(STORAGE_KEY_ONE_TIME_RESTORE_VOLUMES, restoreData); //
    }

    async function loadAndClearOneTimeRestoreVolume(videoId) {
        if (!videoId) return null; //
        if (!isOneTimeRestoreEnabled) {
            return null; //
        }
        const oneTimeProcessedKey = SESSION_STORAGE_ONE_TIME_PROCESSED_KEY_PREFIX + videoId; //
        const hasProcessedOneTime = sessionStorage.getItem(oneTimeProcessedKey); //
        if (hasProcessedOneTime) {
            return null; //
        }
        let restoreData = await GM_getValue(STORAGE_KEY_ONE_TIME_RESTORE_VOLUMES, {});
        let restoredVolume = restoreData[videoId]; //
        if (restoredVolume !== undefined) {
            sessionStorage.setItem(oneTimeProcessedKey, 'true'); //
            return restoredVolume; //
        }
        return null; //
    }

    async function clearOneTimeRestoreVolume(videoIdToClear) {
        if (!videoIdToClear) return; //
        let restoreData = await GM_getValue(STORAGE_KEY_ONE_TIME_RESTORE_VOLUMES, {}); //
        if (restoreData.hasOwnProperty(videoIdToClear)) {
            delete restoreData[videoIdToClear]; //
            await GM_setValue(STORAGE_KEY_ONE_TIME_RESTORE_VOLUMES, restoreData); //
        }
        sessionStorage.removeItem(SESSION_STORAGE_ONE_TIME_PROCESSED_KEY_PREFIX + videoIdToClear); //
    }

    async function saveToolbarPosition(bottom, right) {
        await GM_setValue(STORAGE_KEY_TOOLBAR_POSITION, { bottom, right }); //
    }

    async function loadToolbarPosition() {
        return await GM_getValue(STORAGE_KEY_TOOLBAR_POSITION, null); //
    }

    // --- CORE SCRIPT FUNCTIONS ---

    function setupAudioBoosterOnce(videoElement) {
        if (audioContext) return; //
        audioContext = new(window.AudioContext || window.webkitAudioContext)(); //
        sourceNode = audioContext.createMediaElementSource(videoElement); //
        gainNode = audioContext.createGain(); //
        sourceNode.connect(gainNode); //
        gainNode.connect(audioContext.destination); //
    }

    function applyVolumeToUIAndGain(volume) {
        if (!gainNode) return; //
        gainNode.gain.value = volume / 100; //
        const slider = document.querySelector('.volume-booster-slider-abs');
        const label = document.querySelector('.volume-booster-label-abs');
        if (slider) slider.value = volume; //
        if (label) label.textContent = `${volume}%`; //
    }

    function updateUIText() {
        const lang = currentLanguage; //
        const settingsIconGlobal = document.querySelector('.volume-booster-setting-icon.global-volume'); //
        if (settingsIconGlobal) {
            settingsIconGlobal.title = isGlobalVolumeEnabled ?
                translations[lang].globalVolumeEnabled : translations[lang].globalVolumeDisabled; //
        }
        const settingsIconPerVideo = document.querySelector('.volume-booster-setting-icon.per-video-toggle'); //
        if (settingsIconPerVideo) {
            settingsIconPerVideo.title = isRememberPerVideoEnabled ?
                translations[lang].rememberPerVideoEnabled : translations[lang].rememberPerVideoDisabled; //
        }
        const saveVolumeIcon = document.querySelector('.volume-booster-setting-icon.save-per-video'); //
        if (saveVolumeIcon) {
            saveVolumeIcon.title = isRememberPerVideoEnabled ?
                translations[lang].savePerVideoEnabledHint : translations[lang].savePerVideoDisabledHint; //
        }
        const oneTimeRestoreToggle = document.querySelector('.volume-booster-setting-icon.one-time-restore-toggle'); //
        if (oneTimeRestoreToggle) {
            oneTimeRestoreToggle.title = isOneTimeRestoreEnabled ?
                translations[lang].oneTimeRestoreEnabled : translations[lang].oneTimeRestoreDisabled; //
        }
        const languageToggleIcon = document.querySelector('.volume-booster-setting-icon.language-toggle'); //
        if (languageToggleIcon) {
            languageToggleIcon.title = lang === 'vi' ?
                translations['vi'].languageToggleHint : translations['en'].languageToggleHintEnglish; //
        }
    }

    async function createVolumeSliderUI() {
        if (document.getElementById('volume-booster-container-abs')) return; //
        const playerContainer = document.querySelector('#movie_player');
        if (!playerContainer) return;

        const container = document.createElement('div');
        container.id = 'volume-booster-container-abs'; //
        const slider = document.createElement('input'); //
        slider.className = 'volume-booster-slider-abs'; //
        slider.type = 'range'; //
        slider.min = '0'; //
        slider.max = '600'; //
        slider.step = '10'; //
        const label = document.createElement('span'); //
        label.className = 'volume-booster-label-abs'; //

        const settingsIconGlobal = document.createElement('div'); //
        settingsIconGlobal.className = 'volume-booster-setting-icon global-volume'; //
        const settingsIconPerVideo = document.createElement('div'); //
        settingsIconPerVideo.className = 'volume-booster-setting-icon per-video-toggle'; //
        if (isRememberPerVideoEnabled) settingsIconPerVideo.classList.add('enabled'); //
        const saveVolumeIcon = document.createElement('div'); //
        saveVolumeIcon.className = 'volume-booster-setting-icon save-per-video'; //
        const oneTimeRestoreToggle = document.createElement('div'); //
        oneTimeRestoreToggle.className = 'volume-booster-setting-icon one-time-restore-toggle'; //
        if (isOneTimeRestoreEnabled) oneTimeRestoreToggle.classList.add('enabled'); //
        const languageToggleIcon = document.createElement('div'); //
        languageToggleIcon.className = 'volume-booster-setting-icon language-toggle'; //

        function updateSaveButtonState() {
            if (isRememberPerVideoEnabled) {
                saveVolumeIcon.style.opacity = '1'; //
                saveVolumeIcon.style.pointerEvents = 'auto'; //
            } else {
                saveVolumeIcon.style.opacity = '0.5'; //
                saveVolumeIcon.style.pointerEvents = 'none'; //
            }
        }
        updateSaveButtonState(); //

        function updateGlobalVolumeIconState() {
            if (isGlobalVolumeEnabled) {
                settingsIconGlobal.classList.add('enabled'); //
            } else {
                settingsIconGlobal.classList.remove('enabled'); //
            }
        }
        updateGlobalVolumeIconState(); //

        function updateOneTimeRestoreIconState() {
            if (isOneTimeRestoreEnabled) {
                oneTimeRestoreToggle.classList.add('enabled'); //
            } else {
                oneTimeRestoreToggle.classList.remove('enabled'); //
            }
        }
        updateOneTimeRestoreIconState(); //

        slider.addEventListener('input', async () => {
            const boostValue = parseInt(slider.value, 10);
            applyVolumeToUIAndGain(boostValue);
            currentTabVolume = boostValue;
            if (isGlobalVolumeEnabled) {
                await GM_setValue(STORAGE_KEY_TAB_VOLUME_PREFIX + tabId, currentTabVolume); //
            }
            if (isOneTimeRestoreEnabled) {
                await saveOneTimeRestoreVolume(currentVideoId, currentTabVolume); //
            }
        });

        settingsIconGlobal.addEventListener('click', async () => {
            isGlobalVolumeEnabled = !isGlobalVolumeEnabled;
            await saveFeatureState(STORAGE_KEY_GLOBAL_FEATURE_STATE, isGlobalVolumeEnabled);
            updateGlobalVolumeIconState();
            updateUIText();
            settingsIconGlobal.classList.add('clicked'); //
            setTimeout(() => settingsIconGlobal.classList.remove('clicked'), 200); //
            if (isGlobalVolumeEnabled) {
                await GM_setValue(STORAGE_KEY_TAB_VOLUME_PREFIX + tabId, currentTabVolume); //
            }
            debouncedInitialize(); //
        });

        settingsIconPerVideo.addEventListener('click', async () => {
            isRememberPerVideoEnabled = !isRememberPerVideoEnabled;
            await saveFeatureState(STORAGE_KEY_PER_VIDEO_FEATURE_STATE, isRememberPerVideoEnabled);
            if (isRememberPerVideoEnabled) {
                settingsIconPerVideo.classList.add('enabled');
            } else {
                settingsIconPerVideo.classList.remove('enabled');
            }
            updateSaveButtonState();
            updateUIText(); //
            settingsIconPerVideo.classList.add('clicked'); //
            setTimeout(() => settingsIconPerVideo.classList.remove('clicked'), 200); //
            debouncedInitialize(); //
        });

        saveVolumeIcon.addEventListener('click', async () => {
            if (isRememberPerVideoEnabled && currentVideoId) {
                const currentSliderValue = parseInt(slider.value, 10);
                await saveVolumeSetting(currentVideoId, currentSliderValue);
                currentTabVolume = currentSliderValue; //
                applyVolumeToUIAndGain(currentTabVolume);
                if (isOneTimeRestoreEnabled) {
                    await saveOneTimeRestoreVolume(currentVideoId, currentTabVolume); //
                }
                saveVolumeIcon.classList.add('clicked'); //
                setTimeout(() => saveVolumeIcon.classList.remove('clicked'), 500); //
            }
        });

        oneTimeRestoreToggle.addEventListener('click', async () => {
            isOneTimeRestoreEnabled = !isOneTimeRestoreEnabled;
            await saveFeatureState(STORAGE_KEY_ONE_TIME_RESTORE_FEATURE_STATE, isOneTimeRestoreEnabled); //
            updateOneTimeRestoreIconState();
            updateUIText();
            oneTimeRestoreToggle.classList.add('clicked');
            setTimeout(() => oneTimeRestoreToggle.classList.remove('clicked'), 200); //
            if (isOneTimeRestoreEnabled) {
                await saveOneTimeRestoreVolume(currentVideoId, currentTabVolume); //
            } else {
                await clearOneTimeRestoreVolume(currentVideoId); //
            }
            debouncedInitialize(); //
        });

        languageToggleIcon.addEventListener('click', async () => {
            currentLanguage = (currentLanguage === 'vi') ? 'en' : 'vi';
            await saveFeatureState(STORAGE_KEY_LANGUAGE, currentLanguage);
            updateUIText();
            languageToggleIcon.classList.add('clicked'); //
            setTimeout(() => languageToggleIcon.classList.remove('clicked'), 200); //
        });

        container.appendChild(slider); //
        container.appendChild(label); //
        container.appendChild(settingsIconGlobal); //
        container.appendChild(settingsIconPerVideo); //
        container.appendChild(saveVolumeIcon); //
        container.appendChild(oneTimeRestoreToggle); //
        container.appendChild(languageToggleIcon); //
        playerContainer.appendChild(container); //

        const savedPos = await loadToolbarPosition(); //
        if (savedPos) {
            container.style.bottom = `${savedPos.bottom}px`; //
            container.style.right = `${savedPos.right}px`; //
        } else {
            container.style.bottom = '55px'; //
            container.style.right = '15px'; //
        }
        container.style.position = 'absolute'; //

        container.addEventListener('mousedown', (e) => {
            if (e.button === 0 && !e.target.closest('input, .volume-booster-setting-icon')) {
                isDragging = true;
                container.classList.add('dragging');
                const containerRect = container.getBoundingClientRect(); //
                dragOffsetX = e.clientX - containerRect.left;
                dragOffsetY = e.clientY - containerRect.top;
                container.style.transition = 'none'; //
                e.preventDefault(); //
            }
        });

        document.addEventListener('mousemove', (e) => {
            if (!isDragging) return;
            const playerRect = playerContainer.getBoundingClientRect();
            const containerWidth = container.offsetWidth;
            const containerHeight = container.offsetHeight;

            const mouseXRelativeToPlayer = e.clientX - playerRect.left; //
            const mouseYRelativeToPlayer = e.clientY - playerRect.top; //

            let newContainerLeftRelativeToPlayer = mouseXRelativeToPlayer - dragOffsetX; //
            let newContainerTopRelativeToPlayer = mouseYRelativeToPlayer - dragOffsetY; //

            newContainerLeftRelativeToPlayer = Math.max(0, Math.min(newContainerLeftRelativeToPlayer, playerRect.width - containerWidth)); //
            newContainerTopRelativeToPlayer = Math.max(0, Math.min(newContainerTopRelativeToPlayer, playerRect.height - containerHeight)); //

            let newRight = playerRect.width - (newContainerLeftRelativeToPlayer + containerWidth); //
            let newBottom = playerRect.height - (newContainerTopRelativeToPlayer + containerHeight); //

            container.style.right = `${newRight}px`; //
            container.style.bottom = `${newBottom}px`; //
        });

        document.addEventListener('mouseup', async () => {
            if (isDragging) {
                isDragging = false;
                container.classList.remove('dragging');
                container.style.transition = 'opacity 0.3s ease-in-out, bottom 0.3s ease-in-out, right 0.3s ease-in-out'; //
                const currentBottom = parseFloat(container.style.bottom);
                const currentRight = parseFloat(container.style.right);
                await saveToolbarPosition(currentBottom, currentRight);
            }
        });

        // FIX: Add ResizeObserver to handle player size changes
        const resizeObserver = new ResizeObserver(entries => {
            for (let entry of entries) {
                const playerRect = entry.contentRect;
                const containerWidth = container.offsetWidth;
                const containerHeight = container.offsetHeight;
                let currentBottom = parseFloat(container.style.bottom);
                let currentRight = parseFloat(container.style.right);
                let currentTop = playerRect.height - currentBottom - containerHeight;
                let currentLeft = playerRect.width - currentRight - containerWidth;
                const clampedLeft = Math.max(0, Math.min(currentLeft, playerRect.width - containerWidth));
                const clampedTop = Math.max(0, Math.min(currentTop, playerRect.height - containerHeight));
                const newFinalRight = playerRect.width - clampedLeft - containerWidth;
                const newFinalBottom = playerRect.height - clampedTop - containerHeight;
                container.style.right = `${newFinalRight}px`;
                container.style.bottom = `${newFinalBottom}px`;
            }
        });
        resizeObserver.observe(playerContainer);

        updateUIText();
    }

    async function initialize() {
        if (!window.location.pathname.startsWith('/watch')) {
            const container = document.getElementById('volume-booster-container-abs');
            if (container) container.remove(); //
            currentVideoId = null;
            previousVideoId = null;
            return;
        }

        const newVideoId = getVideoId(); //
        const videoElement = document.querySelector('video'); //
        if (!videoElement || !newVideoId) {
            const container = document.getElementById('volume-booster-container-abs');
            if (container) container.remove(); //
            currentVideoId = null;
            previousVideoId = null;
            return;
        }

        if (newVideoId === currentVideoId && currentVideoId !== null) {
            return; //
        }

        if (previousVideoId && previousVideoId !== newVideoId && isOneTimeRestoreEnabled) {
            await clearOneTimeRestoreVolume(previousVideoId); //
        }

        previousVideoId = currentVideoId; //
        currentVideoId = newVideoId; //
        tabId = getTabId(); //

        isGlobalVolumeEnabled = await getFeatureState(STORAGE_KEY_GLOBAL_FEATURE_STATE, false); //
        isRememberPerVideoEnabled = await getFeatureState(STORAGE_KEY_PER_VIDEO_FEATURE_STATE, false); //
        isOneTimeRestoreEnabled = await getFeatureState(STORAGE_KEY_ONE_TIME_RESTORE_FEATURE_STATE, true); //
        currentLanguage = await getFeatureState(STORAGE_KEY_LANGUAGE, 'vi'); //

        let volumeToDetermine = 100; //
        const perVideoVolume = await getVolumeSetting(currentVideoId); //
        const restoredVolumeOnBrowserRestore = await loadAndClearOneTimeRestoreVolume(currentVideoId); //
        const globalTabVolume = await GM_getValue(STORAGE_KEY_TAB_VOLUME_PREFIX + tabId, 100); //

        if (isRememberPerVideoEnabled && perVideoVolume !== null) {
            volumeToDetermine = perVideoVolume; //
        } else if (restoredVolumeOnBrowserRestore !== null) {
            volumeToDetermine = restoredVolumeOnBrowserRestore; //
        } else if (isGlobalVolumeEnabled) {
            volumeToDetermine = globalTabVolume; //
        } else {
            volumeToDetermine = 100; //
        }

        currentTabVolume = volumeToDetermine; //
        if (isGlobalVolumeEnabled) {
            await GM_setValue(STORAGE_KEY_TAB_VOLUME_PREFIX + tabId, currentTabVolume); //
        }
        if (isOneTimeRestoreEnabled) {
            await saveOneTimeRestoreVolume(currentVideoId, currentTabVolume); //
        }

        setupAudioBoosterOnce(videoElement); //
        await createVolumeSliderUI(); //
        applyVolumeToUIAndGain(currentTabVolume); //
        if (audioContext && audioContext.state === 'suspended') {
            audioContext.resume(); //
        }
    }

    function debouncedInitialize() {
        clearTimeout(initializeTimeout); //
        initializeTimeout = setTimeout(initialize, DEBOUNCE_DELAY); //
    }

    const observer = new MutationObserver(mutations => {
        let shouldDebounceInitialize = false;
        const newVideoIdInURL = getVideoId();
        if (newVideoIdInURL && newVideoIdInURL !== currentVideoId) {
            shouldDebounceInitialize = true; //
        }
        if (shouldDebounceInitialize) {
            debouncedInitialize(); //
        }
    });

    observer.observe(document.body, { childList: true, subtree: true });
    window.addEventListener('yt-page-data-updated', debouncedInitialize); //
    window.addEventListener('yt-navigate-finish', debouncedInitialize); //
    debouncedInitialize(); //

    window.checkBoosterStorage = async function() {
        console.log("--- YouTube Volume Booster Storage Inspection ---");
        console.log("Global Volume Feature State:", await GM_getValue(STORAGE_KEY_GLOBAL_FEATURE_STATE, false)); //
        console.log("Remember Per Video Feature State:", await GM_getValue(STORAGE_KEY_PER_VIDEO_FEATURE_STATE, false)); //
        console.log("One-Time Restore Feature State:", await GM_getValue(STORAGE_KEY_ONE_TIME_RESTORE_FEATURE_STATE, true)); //
        console.log("Per Video Saved Volumes:", await GM_getValue(STORAGE_KEY_PER_VIDEO, {})); //
        console.log("One-Time Restore Volumes (Map of videoId -> volume):", await GM_getValue(STORAGE_KEY_ONE_TIME_RESTORE_VOLUMES, {})); //
        if (tabId) {
            console.log(`Global Volume for current tab (${tabId}):`, await GM_getValue(STORAGE_KEY_TAB_VOLUME_PREFIX + tabId, 100)); //
        } else {
            console.log(translations[currentLanguage].tabIdNotInitialized); //
        }
        console.log("Saved Toolbar Position:", await GM_getValue(STORAGE_KEY_TOOLBAR_POSITION, null)); //
        console.log("Current Language:", currentLanguage); //
        console.log("------------------------------------------");
    };
})();