YouTube Volume Booster 600% (with Clear Storage Menu)

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.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

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.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        YouTube Volume Booster 600% (with Clear Storage Menu)
// @name:vi     YouTube - Tăng Âm Lượng 600% (có Menu Xóa Bộ nhớ)
// @namespace   http://tampermonkey.net/
// @version     3.9.23
// @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
// @grant       GM_registerMenuCommand
// @icon        https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @license MIT2
// ==/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 = 'en'; // Default language

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

    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.',
            clearManualStorageTitle: "Xóa bộ nhớ 'Ghi nhớ từng video'",
            confirmClear: "Bạn có chắc chắn muốn xóa tất cả dữ liệu của 'Ghi nhớ từng video' không? Hành động này không thể hoàn tác.",
            clearManualStorageSuccess: "Đã xóa tất cả dữ liệu của 'Ghi nhớ từng video'.",
            savePositionTitle: 'Lưu vị trí toolbar',
            savePositionHint: 'Lưu vị trí hiện tại của toolbar làm mặc định',
            clearPositionStorageTitle: "Xóa bộ nhớ vị trí toolbar",
            confirmClearPosition: "Bạn có chắc chắn muốn xóa bộ nhớ vị trí toolbar không? Toolbar sẽ quay về vị trí mặc định sau khi reload trang.",
            clearPositionStorageSuccess: "Đã xóa bộ nhớ vị trí toolbar. Reload trang để áp dụng."
        },
        '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.',
            clearManualStorageTitle: "Clear 'Remember Per Video' Storage",
            confirmClear: "Are you sure you want to delete all 'Remember Per Video' data? This action cannot be undone.",
            clearManualStorageSuccess: "Cleared all 'Remember Per Video' data.",
            savePositionTitle: 'Save toolbar position',
            savePositionHint: 'Save current toolbar position as default',
            clearPositionStorageTitle: "Clear toolbar position storage",
            confirmClearPosition: "Are you sure you want to clear the toolbar position storage? Toolbar will reset to default after page reload.",
            clearPositionStorageSuccess: "Cleared toolbar position storage. Reload the page to apply."
        }
    };
    // 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;
        }
        #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.save-position {
            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="M16 9V4l1 0c.55 0 1-.45 1-1s-.45-1-1-1H7c-.55 0-1 .45-1 1s.45 1 1 1l1 0v5c0 .55-.45 1-1 1H5v2h14v-2h-2c-.55 0-1-.45-1-1zM8 19h3v3h2v-3h3l-4-4-4 4z"/></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 || !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 || !isOneTimeRestoreEnabled) return null;
        const oneTimeProcessedKey = SESSION_STORAGE_ONE_TIME_PROCESSED_KEY_PREFIX + videoId;
        if (sessionStorage.getItem(oneTimeProcessedKey)) 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(bottomPercent, rightPercent) {
        await GM_setValue(STORAGE_KEY_TOOLBAR_POSITION, { bottomPercent, rightPercent });
    }

    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;
        }
        const savePositionIcon = document.querySelector('.volume-booster-setting-icon.save-position');
        if (savePositionIcon) {
            savePositionIcon.title = translations[lang].savePositionHint;
        }
    }

    async function createVolumeSliderUI(playerContainer) {
        if (document.getElementById('volume-booster-container-abs')) return;
        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';
        const savePositionIcon = document.createElement('div');
        savePositionIcon.className = 'volume-booster-setting-icon save-position';

        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() {
            settingsIconGlobal.classList.toggle('enabled', isGlobalVolumeEnabled);
        }
        updateGlobalVolumeIconState();

        function updateOneTimeRestoreIconState() {
            oneTimeRestoreToggle.classList.toggle('enabled', isOneTimeRestoreEnabled);
        }
        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);
            settingsIconPerVideo.classList.toggle('enabled', isRememberPerVideoEnabled);
            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);
        });
        savePositionIcon.addEventListener('click', async () => {
            const playerRect = playerContainer.getBoundingClientRect();
            const currentBottom = parseFloat(container.style.bottom);
            const currentRight = parseFloat(container.style.right);
            const bottomPercent = currentBottom / playerRect.height;
            const rightPercent = currentRight / playerRect.width;
            await saveToolbarPosition(bottomPercent, rightPercent);
            savePositionIcon.classList.add('clicked');
            setTimeout(() => savePositionIcon.classList.remove('clicked'), 500);
        });
        container.appendChild(slider);
        container.appendChild(label);
        container.appendChild(settingsIconGlobal);
        container.appendChild(settingsIconPerVideo);
        container.appendChild(saveVolumeIcon);
        container.appendChild(savePositionIcon); // Swapped position with oneTimeRestoreToggle
        container.appendChild(oneTimeRestoreToggle); // Swapped position with savePositionIcon
        container.appendChild(languageToggleIcon);
        playerContainer.appendChild(container);

        // Load and apply saved position as percentages
        const savedPos = await loadToolbarPosition();
        const playerRect = playerContainer.getBoundingClientRect();
        let bottomPercent = savedPos ? savedPos.bottomPercent : (55 / playerRect.height);
        let rightPercent = savedPos ? savedPos.rightPercent : (15 / playerRect.width);
        let bottom = bottomPercent * playerRect.height;
        let right = rightPercent * playerRect.width;
        container.style.bottom = `${bottom}px`;
        container.style.right = `${right}px`;

        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', () => {
            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';
                // Không lưu tự động nữa, chỉ lưu khi bấm nút
            }
        });
        const resizeObserver = new ResizeObserver(() => {
            const playerRect = playerContainer.getBoundingClientRect();
            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');
        const playerContainer = document.querySelector('#movie_player');
        if (!videoElement || !newVideoId || !playerContainer) {
            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(playerContainer);
        applyVolumeToUIAndGain(currentTabVolume);
        if (audioContext && audioContext.state === 'suspended') {
            audioContext.resume();
        }
    }

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

    const observer = new MutationObserver(() => {
        const newVideoIdInURL = getVideoId();
        if (newVideoIdInURL && newVideoIdInURL !== currentVideoId) {
            debouncedInitialize();
        }
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
    window.addEventListener('yt-page-data-updated', debouncedInitialize);
    window.addEventListener('yt-navigate-finish', debouncedInitialize);
    window.addEventListener('load', () => {
        // Chỉ load toolbar và vị trí sau khi page load hoàn toàn
        debouncedInitialize();
    }); // Ensure init after full page load

    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("------------------------------------------");
    };

    // --- NEW FUNCTIONALITY: CLEAR MANUAL STORAGE (WITH CONFIRMATION) ---
    async function clearManualVideoSettings() {
        const isConfirmed = confirm(translations[currentLanguage].confirmClear);

        if (isConfirmed) {
            await GM_setValue(STORAGE_KEY_PER_VIDEO, {});
            alert(translations[currentLanguage].clearManualStorageSuccess);
        }
    }

    // --- NEW: CLEAR TOOLBAR POSITION STORAGE ---
    async function clearToolbarPosition() {
        const isConfirmed = confirm(translations[currentLanguage].confirmClearPosition);

        if (isConfirmed) {
            await GM_setValue(STORAGE_KEY_TOOLBAR_POSITION, null);
            alert(translations[currentLanguage].clearPositionStorageSuccess);
        }
    }

    (async () => {
        currentLanguage = await getFeatureState(STORAGE_KEY_LANGUAGE, 'vi');
        GM_registerMenuCommand(translations[currentLanguage].clearManualStorageTitle, clearManualVideoSettings);
        GM_registerMenuCommand(translations[currentLanguage].clearPositionStorageTitle, clearToolbarPosition);
    })();

})();