i only need the complete updated files.

{
"manifest_version": 3,
"name": "Media Loader",
"version": "1.4",
"description": "Load and play music and videos from YouTube and Vimeo",
"permissions": ["storage", "tabs"],
"host_permissions": [
"https://www.youtube.com/",
"https://youtu.be/",
"https://player.vimeo.com/*"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "Open Media Loader",
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
},
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'; frame-src https://www.youtube.com https://youtu.be https://player.vimeo.com"
},
"web_accessible_resources": [
{
"resources": ["lib/player.js"],
"matches": [""]
},
{
"resources": ["youtube-sandbox.html"],
"matches": [""]
}
]
}

// background.js

chrome.action.onClicked.addListener(() => {
const dashboardURL = chrome.runtime.getURL("dashboard.html");


// Query for any existing tabs with the dashboard URL
chrome.tabs.query({ url: dashboardURL }, (tabs) => {
    if (chrome.runtime.lastError) {
        console.error("Tab Query Error:", chrome.runtime.lastError);
        return;
    }

    if (tabs.length > 0) {
        // Activate the first existing dashboard tab
        chrome.tabs.update(tabs[0].id, { active: true }, (updatedTab) => {
            if (chrome.runtime.lastError) {
                console.error("Tab Update Error:", chrome.runtime.lastError);
            }
        });
    } else {
        // Create a new dashboard tab
        chrome.tabs.create({ url: dashboardURL, active: true }, (newTab) => {
            if (chrome.runtime.lastError) {
                console.error("Tab Creation Error:", chrome.runtime.lastError);
            }
        });
    }
});
});

Settings
SyncWave

    <!-- Load Audio Section -->
    <section class="section" id="load-music-section">
        <fieldset>
            <legend>Load Audio</legend>
            <label for="music-url">Audio URL</label>
            <input type="text" id="music-url" name="music-url" placeholder="Enter YouTube or Vimeo URL" aria-label="Enter YouTube or Vimeo Audio URL">

            <label for="music-file">Local Audio File</label>
            <input type="file" id="music-file" name="music-file" accept="audio/*" aria-label="Select Local Audio File">

            <label for="music-start">Start Time (seconds)</label>
            <input type="number" id="music-start" name="music-start" placeholder="Start Time (seconds)" min="0" aria-label="Set Start Time in Seconds">

            <button type="button" id="load-music" class="load-button">Load Audio</button>
        </fieldset>
        <div id="music-player-container" class="player-container" aria-live="polite"></div>
    </section>

    <!-- Load Video Section -->
    <section class="section" id="load-video-section">
        <fieldset>
            <legend>Load Video</legend>
            <label for="video-url">Video URL</label>
            <input type="text" id="video-url" name="video-url" placeholder="Enter YouTube or Vimeo URL" aria-label="Enter YouTube or Vimeo Video URL">

            <label for="video-file">Local Video File</label>
            <input type="file" id="video-file" name="video-file" accept="video/*" aria-label="Select Local Video File">

            <label for="video-start">Start Time (seconds)</label>
            <input type="number" id="video-start" name="video-start" placeholder="Start Time (seconds)" min="0" aria-label="Set Start Time in Seconds">

            <button type="button" id="load-video" class="load-button">Load Video</button>
        </fieldset>
        <div id="video-player-container" class="player-container" aria-live="polite"></div>
    </section>

    <!-- Playback Controls -->
    <section class="controls">
        <button type="button" id="play-button" class="play-button" disabled aria-label="Play from Start">Play from Start</button>
        <button type="button" id="pause-button" class="pause-button" disabled aria-label="Pause">Pause</button>
        <button type="button" id="resume-button" class="resume-button" disabled aria-label="Resume">Resume</button>
    </section>

    <!-- Message Container -->
    <section id="message-container" class="message-container" role="alert" aria-live="assertive"></section>

    <!-- Settings Modal -->
    <div id="settings-modal" class="modal" role="dialog" aria-labelledby="settings-modal-title" aria-modal="true" hidden>
        <div class="modal-content">
            <button class="close-button" aria-label="Close Settings Modal">&times;</button>
            <h2 id="settings-modal-title">Settings</h2>
            <form>
                <label for="fullscreen-toggle">
                    <input type="checkbox" id="fullscreen-toggle" name="fullscreen-toggle">
                    Full Screen on Play
                </label>
                <!-- Additional settings can go here -->
            </form>
        </div>
    </div>
</main>

<!-- YouTube Sandbox IFrame -->
<iframe id="youtube-sandbox-iframe" src="youtube-sandbox.html" sandbox="allow-scripts" style="display:none;"></iframe>

<!-- Local Vimeo Player API -->
<script src="lib/player.js"></script>

<!-- Your Dashboard JS -->
<script src="dashboard.js"></script>
// dashboard.js

"use strict";

let youtubePlayers = {
music: null,
video: null
};

// Function to send messages to the YouTube sandbox iframe
function sendMessageToYouTubeSandbox(message) {
const iframe = document.getElementById('youtube-sandbox-iframe');
iframe.contentWindow.postMessage(message, '*');
}

document.addEventListener('DOMContentLoaded', () => {
// === Element References ===
const elements = {
music: {
urlInput: document.getElementById('music-url'),
fileInput: document.getElementById('music-file'),
startInput: document.getElementById('music-start'),
loadButton: document.getElementById('load-music'),
playerContainer: document.getElementById('music-player-container')
},
video: {
urlInput: document.getElementById('video-url'),
fileInput: document.getElementById('video-file'),
startInput: document.getElementById('video-start'),
loadButton: document.getElementById('load-video'),
playerContainer: document.getElementById('video-player-container')
},
controls: {
playButton: document.getElementById('play-button'),
pauseButton: document.getElementById('pause-button'),
resumeButton: document.getElementById('resume-button')
},
messageContainer: document.getElementById('message-container'),
settings: {
button: document.getElementById('settings-button'),
modal: document.getElementById('settings-modal'),
closeButton: document.querySelector('.close-button'),
fullscreenToggle: document.getElementById('fullscreen-toggle')
},
youtubeSandbox: document.getElementById('youtube-sandbox-iframe')
};


// === State Management ===
const state = {
    music: null, // { type: 'file' | 'youtube' | 'vimeo', element: HTMLAudioElement | Vimeo.Player | YT.Player, start: Number }
    video: null, // { type: 'file' | 'youtube' | 'vimeo', element: HTMLVideoElement | Vimeo.Player | YT.Player, start: Number }
    isPaused: false,
    fullscreenOnPlay: false
};

// === Utility Functions ===

/**
 * Displays a message to the user.
 * @param {string} message - The message to display.
 * @param {string} type - The type of message ('success', 'error', 'info').
 */
function showMessage(message, type) {
    elements.messageContainer.innerHTML = '';
    const msgDiv = document.createElement('div');
    msgDiv.textContent = message;
    msgDiv.classList.add('message', type); // 'success', 'error', 'info'
    elements.messageContainer.appendChild(msgDiv);

    // Automatically hide message after 5 seconds
    setTimeout(() => {
        if (elements.messageContainer.contains(msgDiv)) {
            elements.messageContainer.removeChild(msgDiv);
        }
    }, 5000);
}

/**
 * Shows a loading spinner in the specified container.
 * @param {HTMLElement} container - The container to show the loader in.
 */
function showLoading(container) {
    container.innerHTML = '';
    const loader = document.createElement('div');
    loader.classList.add('loader'); // Define loader styles in CSS
    container.appendChild(loader);
}

/**
 * Hides the loading spinner from the specified container.
 * @param {HTMLElement} container - The container to hide the loader from.
 */
function hideLoading(container) {
    const loader = container.querySelector('.loader');
    if (loader) {
        container.removeChild(loader);
    }
}

/**
 * Traps keyboard focus within a specified element.
 * @param {HTMLElement} element - The element to trap focus within.
 */
function trapFocus(element) {
    const focusableElementsString = 'a[href], area[href], input:not([disabled]), select:not([disabled]),' +
        ' textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex="0"],' +
        ' [contenteditable]';
    const focusableElements = element.querySelectorAll(focusableElementsString);
    const firstTabStop = focusableElements[0];
    const lastTabStop = focusableElements[focusableElements.length - 1];

    // Listen for and trap the keyboard
    element.addEventListener('keydown', trapTabKey);

    function trapTabKey(e) {
        // Check for TAB key press
        if (e.key === 'Tab') {
            // SHIFT + TAB
            if (e.shiftKey) {
                if (document.activeElement === firstTabStop) {
                    e.preventDefault();
                    lastTabStop.focus();
                }
            } else { // TAB
                if (document.activeElement === lastTabStop) {
                    e.preventDefault();
                    firstTabStop.focus();
                }
            }
        }
    }

    // Remove trap when modal is closed
    function removeTrapKey(e) {
        element.removeEventListener('keydown', trapTabKey);
    }

    // Store the function reference to remove it later
    element.removeTrapKey = removeTrapKey;
}

function removeTrapFocus() {
    if (elements.settings.modal.removeTrapKey) {
        elements.settings.modal.removeTrapKey();
    }
}

// === Initialization ===
loadSettings();

// === Event Listeners ===
elements.music.loadButton.addEventListener('click', handleLoadMusic);
elements.video.loadButton.addEventListener('click', handleLoadVideo);
elements.controls.playButton.addEventListener('click', handlePlay);
elements.controls.pauseButton.addEventListener('click', handlePause);
elements.controls.resumeButton.addEventListener('click', handleResume);

// Settings Modal Event Listeners
elements.settings.button.addEventListener('click', openSettingsModal);
elements.settings.closeButton.addEventListener('click', closeSettingsModal);
window.addEventListener('click', (event) => {
    if (event.target === elements.settings.modal) {
        closeSettingsModal();
    }
});
document.addEventListener('keydown', (event) => {
    if (event.key === 'Escape' && !elements.settings.modal.hasAttribute('hidden')) {
        closeSettingsModal();
    }
});
elements.settings.fullscreenToggle.addEventListener('change', (event) => {
    state.fullscreenOnPlay = event.target.checked;
    saveSettings();
});

// Listen for messages from YouTube Sandbox
window.addEventListener('message', (event) => {
    // For security, you can check event.origin here if necessary
    const data = event.data;
    if (data.type === 'playerReady') {
        // YouTube Player is ready
        showMessage(`YouTube ${capitalize(data.mediaType)} loaded and ready.`, 'success');
        hideLoading(elements[data.mediaType].playerContainer);
    } else if (data.type === 'playerError') {
        showMessage(`Error loading YouTube ${capitalize(data.mediaType)}.`, 'error');
        hideLoading(elements[data.mediaType].playerContainer);
    }
});

// === Handler Functions ===

// Load Music Handler
function handleLoadMusic() {
    const url = elements.music.urlInput.value.trim();
    const file = elements.music.fileInput.files[0];
    const start = parseInt(elements.music.startInput.value) || 0;

    // Show loading indicator
    showLoading(elements.music.playerContainer);

    // Unload existing music if any
    if (state.music) {
        unloadMedia('music');
    }

    if (file) {
        // Handle local audio file
        const fileUrl = URL.createObjectURL(file);
        const audioElement = document.createElement('audio');
        audioElement.controls = true;
        audioElement.src = fileUrl;
        audioElement.id = 'loaded-music';
        audioElement.setAttribute('aria-label', 'Loaded Audio Player');
        audioElement.style.width = '100%';
        audioElement.style.height = 'auto';

        // Listen for metadata to set start time
        audioElement.addEventListener('loadedmetadata', () => {
            if (start > 0 && audioElement.duration > start) {
                audioElement.currentTime = start;
            }
        });

        elements.music.playerContainer.innerHTML = '';
        elements.music.playerContainer.appendChild(audioElement);

        state.music = {
            type: 'file',
            element: audioElement,
            start: start
        };
        showMessage('Local audio file loaded.', 'success');
        hideLoading(elements.music.playerContainer);
    } else if (url && isValidMediaURL(url)) {
        const mediaInfo = extractMediaInfo(url);
        if (mediaInfo) {
            if (mediaInfo.platform === 'youtube') {
                embedYouTubePlayer(mediaInfo.id, start, 'music');
            } else if (mediaInfo.platform === 'vimeo') {
                embedVimeoPlayer(mediaInfo.id, start, 'music');
            }
            // Loading will be handled inside embed functions
        } else {
            showMessage('Invalid URL. Please enter a valid YouTube or Vimeo URL.', 'error');
            hideLoading(elements.music.playerContainer);
        }
    } else {
        showMessage('Please enter a URL or select a local audio file.', 'error');
        hideLoading(elements.music.playerContainer);
    }

    checkReadyToPlay();
    saveSettings();
}

// Load Video Handler
function handleLoadVideo() {
    const url = elements.video.urlInput.value.trim();
    const file = elements.video.fileInput.files[0];
    const start = parseInt(elements.video.startInput.value) || 0;

    // Show loading indicator
    showLoading(elements.video.playerContainer);

    // Unload existing video if any
    if (state.video) {
        unloadMedia('video');
    }

    if (file) {
        // Handle local video file
        const fileUrl = URL.createObjectURL(file);
        const videoElement = document.createElement('video');
        videoElement.controls = true;
        videoElement.src = fileUrl;
        videoElement.id = 'loaded-video';
        videoElement.setAttribute('aria-label', 'Loaded Video Player');
        videoElement.style.width = '100%';
        videoElement.style.height = 'auto';

        // Listen for metadata to set start time
        videoElement.addEventListener('loadedmetadata', () => {
            if (start > 0 && videoElement.duration > start) {
                videoElement.currentTime = start;
            }
        });

        elements.video.playerContainer.innerHTML = '';
        elements.video.playerContainer.appendChild(videoElement);

        state.video = {
            type: 'file',
            element: videoElement,
            start: start
        };
        showMessage('Local video file loaded.', 'success');
        hideLoading(elements.video.playerContainer);
    } else if (url && isValidMediaURL(url)) {
        const mediaInfo = extractMediaInfo(url);
        if (mediaInfo) {
            if (mediaInfo.platform === 'youtube') {
                embedYouTubePlayer(mediaInfo.id, start, 'video');
            } else if (mediaInfo.platform === 'vimeo') {
                embedVimeoPlayer(mediaInfo.id, start, 'video');
            }
            // Loading will be handled inside embed functions
        } else {
            showMessage('Invalid URL. Please enter a valid YouTube or Vimeo URL.', 'error');
            hideLoading(elements.video.playerContainer);
        }
    } else {
        showMessage('Please enter a URL or select a local video file.', 'error');
        hideLoading(elements.video.playerContainer);
    }

    checkReadyToPlay();
    saveSettings();
}

// Play Handler
function handlePlay() {
    // Play Music
    if (state.music) {
        if (state.music.type === 'file') {
            state.music.element.play();
            if (state.fullscreenOnPlay) {
                enterFullscreen(state.music.element);
            }
        } else if (state.music.type === 'vimeo') {
            state.music.element.play().catch(error => {
                console.error('Error playing Vimeo audio:', error);
                showMessage('Failed to play Vimeo audio.', 'error');
            });
        } else if (state.music.type === 'youtube') {
            sendMessageToYouTubeSandbox({ type: 'play' });
        }
    }

    // Play Video
    if (state.video) {
        if (state.video.type === 'file') {
            state.video.element.play();
            if (state.fullscreenOnPlay) {
                enterFullscreen(state.video.element);
            }
        } else if (state.video.type === 'vimeo') {
            state.video.element.play().catch(error => {
                console.error('Error playing Vimeo video:', error);
                showMessage('Failed to play Vimeo video.', 'error');
            });
        } else if (state.video.type === 'youtube') {
            sendMessageToYouTubeSandbox({ type: 'play' });
        }
    }

    state.isPaused = false;
    elements.controls.pauseButton.disabled = false;
    elements.controls.resumeButton.disabled = true;
    elements.controls.pauseButton.textContent = 'Pause';
    showMessage('Playback started!', 'success');
}

// Pause Handler
function handlePause() {
    if (state.isPaused) return;

    // Pause Music
    if (state.music) {
        if (state.music.type === 'file') {
            state.music.element.pause();
        } else if (state.music.type === 'vimeo') {
            state.music.element.pause().catch(error => {
                console.error('Error pausing Vimeo audio:', error);
                showMessage('Failed to pause Vimeo audio.', 'error');
            });
        } else if (state.music.type === 'youtube') {
            sendMessageToYouTubeSandbox({ type: 'pause' });
        }
    }

    // Pause Video
    if (state.video) {
        if (state.video.type === 'file') {
            state.video.element.pause();
        } else if (state.video.type === 'vimeo') {
            state.video.element.pause().catch(error => {
                console.error('Error pausing Vimeo video:', error);
                showMessage('Failed to pause Vimeo video.', 'error');
            });
        } else if (state.video.type === 'youtube') {
            sendMessageToYouTubeSandbox({ type: 'pause' });
        }
    }

    state.isPaused = true;
    elements.controls.resumeButton.disabled = false;
    elements.controls.pauseButton.disabled = true;
    elements.controls.pauseButton.textContent = 'Paused';
    showMessage('Playback paused.', 'info');
}

// Resume Handler
function handleResume() {
    if (!state.isPaused) return;

    // Resume Music
    if (state.music) {
        if (state.music.type === 'file') {
            state.music.element.play();
            if (state.fullscreenOnPlay) {
                enterFullscreen(state.music.element);
            }
        } else if (state.music.type === 'vimeo') {
            state.music.element.play().catch(error => {
                console.error('Error resuming Vimeo audio:', error);
                showMessage('Failed to resume Vimeo audio.', 'error');
            });
        } else if (state.music.type === 'youtube') {
            sendMessageToYouTubeSandbox({ type: 'play' });
        }
    }

    // Resume Video
    if (state.video) {
        if (state.video.type === 'file') {
            state.video.element.play();
            if (state.fullscreenOnPlay) {
                enterFullscreen(state.video.element);
            }
        } else if (state.video.type === 'vimeo') {
            state.video.element.play().catch(error => {
                console.error('Error resuming Vimeo video:', error);
                showMessage('Failed to resume Vimeo video.', 'error');
            });
        } else if (state.video.type === 'youtube') {
            sendMessageToYouTubeSandbox({ type: 'play' });
        }
    }

    state.isPaused = false;
    elements.controls.resumeButton.disabled = true;
    elements.controls.pauseButton.disabled = false;
    elements.controls.pauseButton.textContent = 'Pause';
    showMessage('Playback resumed.', 'success');
}

// === Settings Modal Functions ===
function openSettingsModal() {
    elements.settings.modal.removeAttribute('hidden');
    elements.settings.modal.setAttribute('aria-hidden', 'false');
    elements.settings.modal.querySelector('.close-button').focus();
    trapFocus(elements.settings.modal);
}

function closeSettingsModal() {
    elements.settings.modal.setAttribute('hidden', '');
    elements.settings.modal.setAttribute('aria-hidden', 'true');
    elements.settings.button.focus();
    removeTrapFocus();
}

// === Media Embedding Functions ===

/**
 * Embeds a YouTube video by sending an initialization message to the sandboxed iframe.
 * @param {string} videoId - The YouTube video ID.
 * @param {number} start - The start time in seconds.
 * @param {string} mediaType - 'music' or 'video'.
 */
function embedYouTubePlayer(videoId, start, mediaType) {
    const container = elements[mediaType].playerContainer;
    container.innerHTML = ''; // Clear previous content

    showLoading(container);

    // Send initialization message to sandbox
    sendMessageToYouTubeSandbox({
        type: 'init',
        videoId: videoId,
        startTime: start,
        mediaType: mediaType
    });

    state[mediaType] = {
        type: 'youtube',
        element: null, // Will be set when sandbox responds
        start: start
    };
}

/**
 * Embeds a Vimeo video using the Player API.
 * @param {string} videoId - The Vimeo video ID.
 * @param {number} start - The start time in seconds.
 * @param {string} mediaType - 'music' or 'video'.
 */
function embedVimeoPlayer(videoId, start, mediaType) {
    const container = elements[mediaType].playerContainer;
    container.innerHTML = ''; // Clear previous content

    const iframe = document.createElement('iframe');
    const src = `https://player.vimeo.com/video/${videoId}?muted=1&autoplay=0&start=${start}`;
    iframe.src = src;
    iframe.width = '640';
    iframe.height = '360';
    iframe.frameBorder = '0';
    iframe.allow = 'autoplay; fullscreen; picture-in-picture';
    iframe.setAttribute('title', 'Vimeo video player');

    container.appendChild(iframe);

    const player = new Vimeo.Player(iframe, {
        id: videoId,
        width: 640,
        muted: true,
        autoplay: false,
        start: start
    });

    state[mediaType] = {
        type: 'vimeo',
        element: player,
        start: start
    };

    // Buffering: play briefly, then pause and set start time
    bufferAndPause('vimeo', mediaType, start);

    hideLoading(container);
}

/**
 * Implements the buffering process: plays briefly, then pauses and sets to start time.
 * @param {string} platform - 'youtube' or 'vimeo'.
 * @param {string} mediaType - 'music' or 'video'.
 * @param {number} startTime - The start time in seconds.
 */
function bufferAndPause(platform, mediaType, startTime) {
    if (platform === 'vimeo') {
        const player = state[mediaType].element;
        player.play().then(() => {
            // Wait for at least 125ms
            setTimeout(() => {
                player.pause().then(() => {
                    player.setCurrentTime(startTime).then(() => {
                        showMessage(`Vimeo ${capitalize(mediaType)} buffered and set to ${startTime} seconds.`, 'success');
                    }).catch(error => {
                        console.error(`Error setting start time for Vimeo ${mediaType}:`, error);
                        showMessage(`Failed to set start time for Vimeo ${mediaType}.`, 'error');
                    });
                }).catch(error => {
                    console.error(`Error pausing Vimeo ${mediaType}:`, error);
                    showMessage(`Failed to pause Vimeo ${mediaType}.`, 'error');
                });
            }, 125);
        }).catch(error => {
            console.error(`Error playing Vimeo ${mediaType}:`, error);
            showMessage(`Failed to play Vimeo ${mediaType}.`, 'error');
        });
    }
}

// === Settings Persistence ===

/**
 * Loads settings from Chrome storage.
 */
function loadSettings() {
    try {
        chrome.storage.local.get(['mediaSettings'], (result) => {
            if (chrome.runtime.lastError) {
                throw new Error(chrome.runtime.lastError);
            }

            if (result.mediaSettings) {
                const { music, video, fullscreenOnPlay: fsPlay } = result.mediaSettings;

                if (music) {
                    if (music.type === 'youtube' || music.type === 'vimeo') {
                        const url = music.type === 'youtube'
                            ? `https://www.youtube.com/watch?v=${music.id}`
                            : `https://vimeo.com/${music.id}`;
                        elements.music.urlInput.value = url;
                        elements.music.startInput.value = music.start || 0;
                        // Optionally, auto-load the media by triggering the load button
                        // handleLoadMusic();
                    }
                    // Handle file-based music loading if necessary
                }

                if (video) {
                    if (video.type === 'youtube' || video.type === 'vimeo') {
                        const url = video.type === 'youtube'
                            ? `https://www.youtube.com/watch?v=${video.id}`
                            : `https://vimeo.com/${video.id}`;
                        elements.video.urlInput.value = url;
                        elements.video.startInput.value = video.start || 0;
                        // Optionally, auto-load the media by triggering the load button
                        // handleLoadVideo();
                    }
                    // Handle file-based video loading if necessary
                }

                if (typeof fsPlay === 'boolean') {
                    state.fullscreenOnPlay = fsPlay;
                    elements.settings.fullscreenToggle.checked = fsPlay;
                }
            }
        });
    } catch (error) {
        console.error('Error loading settings:', error);
        showMessage('Failed to load settings.', 'error');
    }
}

/**
 * Saves settings to Chrome storage.
 */
function saveSettings() {
    const settings = {
        music: state.music,
        video: state.video,
        fullscreenOnPlay: state.fullscreenOnPlay
    };
    try {
        chrome.storage.local.set({ mediaSettings: settings }, () => {
            if (chrome.runtime.lastError) {
                throw new Error(chrome.runtime.lastError);
            }
            console.log('Settings saved.');
        });
    } catch (error) {
        console.error('Error saving settings:', error);
        showMessage('Error saving settings.', 'error');
    }
}

/**
 * Unloads media from the specified type.
 * @param {string} mediaType - 'music' or 'video'.
 */
function unloadMedia(mediaType) {
    if (state[mediaType]) {
        if (state[mediaType].type === 'file') {
            if (state[mediaType].element.src) {
                URL.revokeObjectURL(state[mediaType].element.src);
                state[mediaType].element.src = '';
                state[mediaType].element.remove();
            }
        } else if (state[mediaType].type === 'youtube') {
            // No direct access to destroy YouTube player from main
            // Send a message to sandbox to destroy player if needed
            // For simplicity, reload the sandbox iframe
            const iframe = document.getElementById('youtube-sandbox-iframe');
            iframe.src = iframe.src;
        } else if (state[mediaType].type === 'vimeo') {
            const player = state[mediaType].element;
            player.unload().catch(error => {
                console.error(`Error unloading Vimeo ${mediaType}:`, error);
            });
        }
        state[mediaType] = null;
    }
}

// === Playback Controls ===

/**
 * Enables or disables playback controls based on media loading state.
 */
function checkReadyToPlay() {
    // Enable buttons only when at least one of audio or video is loaded
    const readyAudio = state.music
        && (state.music.type === 'file'
            || state.music.type === 'youtube'
            || state.music.type === 'vimeo');
    const readyVideo = state.video
        && (state.video.type === 'file'
            || state.video.type === 'youtube'
            || state.video.type === 'vimeo');
    const ready = readyAudio || readyVideo;

    elements.controls.playButton.disabled = !ready;
    elements.controls.pauseButton.disabled = !ready;
    elements.controls.resumeButton.disabled = true; // Start with Resume disabled
}

/**
 * Validates whether the URL is a valid YouTube or Vimeo URL.
 * @param {string} url - The URL to validate.
 * @returns {boolean} True if valid, else false.
 */
function isValidMediaURL(url) {
    try {
        const parsedUrl = new URL(url);
        const hostname = parsedUrl.hostname.toLowerCase();
        const pathname = parsedUrl.pathname;

        // YouTube RegEx Patterns
        const youtubeRegex = /^\/(?:watch(?:\?v=|v\/)|embed\/|v\/)?([a-zA-Z0-9_-]{11})$/;
        // Vimeo RegEx Patterns
        const vimeoRegex = /^\/(?:video\/)?(\d+)$/;

        if (hostname.includes('youtube.com') || hostname === 'youtu.be') {
            // Extract YouTube video ID
            let videoId = '';
            if (hostname === 'youtu.be') {
                videoId = pathname.slice(1);
            } else {
                videoId = parsedUrl.searchParams.get('v') || '';
            }
            return youtubeRegex.test(`/${videoId}`);
        } else if (hostname.includes('vimeo.com')) {
            // Extract Vimeo video ID
            const match = pathname.match(vimeoRegex);
            return match && match[1].length > 0;
        }

        return false;
    } catch (e) {
        return false;
    }
}

/**
 * Extracts media information from the URL.
 * @param {string} url - The media URL.
 * @returns {Object|null} An object with platform and id, or null if invalid.
 */
function extractMediaInfo(url) {
    try {
        const parsedUrl = new URL(url);
        const hostname = parsedUrl.hostname.toLowerCase();
        const pathname = parsedUrl.pathname;

        // YouTube Patterns
        if (hostname.includes('youtube.com') || hostname === 'youtu.be') {
            let videoId = '';
            if (hostname === 'youtu.be') {
                videoId = pathname.slice(1);
            } else {
                videoId = parsedUrl.searchParams.get('v') || '';
            }

            // Validate videoId length
            if (videoId.length === 11) {
                return { platform: 'youtube', id: videoId };
            }
        }

        // Vimeo Patterns
        if (hostname.includes('vimeo.com')) {
            const vimeoMatch = pathname.match(/^\/(?:video\/)?(\d+)$/);
            if (vimeoMatch && vimeoMatch[1]) {
                return { platform: 'vimeo', id: vimeoMatch[1] };
            }
        }

        return null;
    } catch (e) {
        return null;
    }
}

/**
 * Capitalize the first letter of a string.
 * @param {string} text - The string to capitalize.
 * @returns {string} Capitalized string.
 */
function capitalize(text) {
    if (!text) return '';
    return text.charAt(0).toUpperCase() + text.slice(1);
}

/**
 * Enters fullscreen mode for the specified element.
 * @param {HTMLElement} element - The element to enter fullscreen.
 */
function enterFullscreen(element) {
    if (element && element.requestFullscreen) {
        element.requestFullscreen().then(() => {
            showMessage('Entered fullscreen mode.', 'success');
        }).catch(err => {
            console.error(`Error attempting to enable full-screen mode: ${err.message} (${err.name})`);
            showMessage('Failed to enter fullscreen mode.', 'error');
        });
    } else if (element && element.parentElement && element.parentElement.requestFullscreen) {
        // Fallback to parent element
        element.parentElement.requestFullscreen().then(() => {
            showMessage('Entered fullscreen mode.', 'success');
        }).catch(err => {
            console.error(`Error attempting to enable full-screen mode: ${err.message} (${err.name})`);
            showMessage('Failed to enter fullscreen mode.', 'error');
        });
    } else {
        showMessage('Fullscreen not supported on this element.', 'error');
    }
}

/**
 * Sends a message to the YouTube sandbox iframe.
 * @param {Object} message - The message object to send.
 */
function sendMessageToYouTubeSandbox(message) {
    sendMessageToYouTubeSandboxFrame(message);
}

/**
 * Directly sends a message to the YouTube sandbox iframe.
 * @param {Object} message - The message object to send.
 */
function sendMessageToYouTubeSandboxFrame(message) {
    const iframe = document.getElementById('youtube-sandbox-iframe');
    iframe.contentWindow.postMessage(message, '*');
}

// === YouTube Sandbox Communication Handlers ===

// No additional code here as messaging is handled via event listeners above

// === Initialize ready state ===
checkReadyToPlay();

// Ensure buttons are correctly enabled/disabled on load
if (state.music || state.video) {
    elements.controls.playButton.disabled = false;
}
});

/* styles.css */

/* General Styles */
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
}

.container {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
max-width: 1200px; /* Increased max-width for larger displays */
margin: 0 auto;
}

.section {
width: 100%;
margin-bottom: 20px;
background-color: #fff;
padding: 20px; /* Increased padding for better spacing */
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

/* Header with Settings Button and Title /
.header {
width: 100%;
display: flex;
align-items: center;
justify-content: center; / Center the content /
margin-bottom: 30px; / Increased margin for better spacing */
position: relative;
}

.settings-button {
position: absolute;
left: 0;
background: #fff;
border: 1px solid #ccc;
border-radius: 5px;
padding: 8px;
cursor: pointer;
width: 50px; /* Increased size for better touch targets /
height: 50px; / Increased size for better touch targets */
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.3s ease, border-color 0.3s ease;
}

.settings-button:hover {
background-color: #f0f0f0;
border-color: #999;
}

.settings-icon {
width: 24px;
height: 24px;
}

/* Main Title with Animation /
.main-title {
font-size: 3em; / Increased font size for better visibility */
font-weight: bold;
margin: 0;
animation: titleAnimation 3s infinite;
}

/* Title Animation Keyframes */
@keyframes titleAnimation {
0% { color: #4285f4; transform: scale(1); }
50% { color: #ea4335; transform: scale(1.05); }
100% { color: #4285f4; transform: scale(1); }
}

/* Section Headers */
.section > fieldset > legend {
font-size: 1.5em;
font-weight: bold;
margin-bottom: 15px;
}

/* Controls /
.controls {
display: flex;
gap: 15px; / Increased gap for better spacing /
margin-bottom: 30px; / Increased margin for better spacing */
}

.controls button {
padding: 15px 30px; /* Increased padding for better accessibility /
cursor: pointer;
border: none;
border-radius: 8px;
background-color: #4285f4;
color: #fff;
transition: background-color 0.3s ease;
font-size: 1.2em; / Increased font size for better readability */
}

.controls button:disabled {
background-color: #a0c3ff;
cursor: not-allowed;
}

.controls button:hover:not(:disabled) {
background-color: #357ae8;
}

/* Play Button Specific Styles /
.play-button {
background-color: #34a853; / Green */
}

.play-button:hover:not(:disabled) {
background-color: #2c8e46;
}

/* Pause Button Specific Styles /
.pause-button {
background-color: #fbbc05; / Yellow */
}

.pause-button:hover:not(:disabled) {
background-color: #e0a800;
}

/* Resume Button Specific Styles /
.resume-button {
background-color: #ea4335; / Red */
}

.resume-button:hover:not(:disabled) {
background-color: #c33c2c;
}

/* Modal Styles */
.modal[hidden] {
display: none;
}

.modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6); /* Darker backdrop for better contrast /
display: flex; / Visible when not hidden */
align-items: center;
justify-content: center;
z-index: 1000;
}

.modal-content {
background-color: #fff;
padding: 25px 35px; /* Increased padding for better spacing /
border-radius: 10px; / More pronounced rounding /
width: 90%;
max-width: 600px; / Increased max-width for better layout */
position: relative;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
}

.close-button {
position: absolute;
top: 15px;
right: 20px;
background: none;
border: none;
font-size: 28px;
cursor: pointer;
color: #333;
}

.modal-content h2 {
margin-top: 0;
font-size: 2em; /* Increased font size for better visibility */
}

/* Form Elements */
fieldset {
border: none;
padding: 0;
}

fieldset label {
display: block;
margin-bottom: 8px;
font-weight: bold;
}

fieldset input[type="text"],
fieldset input[type="number"],
fieldset input[type="file"] {
width: 100%;
padding: 12px; /* Increased padding for better usability /
margin-bottom: 20px; / Increased margin for better spacing /
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 6px;
font-size: 1em; / Increased font size for better readability */
}

fieldset .load-button {
width: 100%;
padding: 15px;
background-color: #4285f4; /* Blue /
color: #fff;
border: none;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.3s ease;
font-size: 1.2em; / Increased font size for better readability */
}

fieldset .load-button:hover {
background-color: #357ae8;
}

/* Player Containers /
.player-container {
margin-top: 10px;
width: 100%;
max-width: 100%;
min-height: 120px; / Increased minimum height for better visibility /
box-sizing: border-box;
overflow: hidden; / Prevent overflow */
position: relative;
}

/* Responsive Video and Audio Elements /
.player-container video,
.player-container iframe {
width: 100%;
height: 360px; / Fixed height for larger displays */
border-radius: 8px;
display: block;
}

.player-container audio {
width: 100%;
height: auto;
border-radius: 5px;
margin-top: 10px; /* Increased margin for better spacing */
}

/* Messages /
.message-container {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 1001; / Above other content */
width: 90%;
max-width: 700px;
}

.message {
padding: 15px 25px;
border-radius: 8px;
margin-bottom: 15px;
color: #fff;
font-size: 1.1em;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
opacity: 0.95;
animation: fadeInOut 6s forwards;
}

.message.success {
background-color: #4caf50;
}

.message.error {
background-color: #f44336;
}

.message.info {
background-color: #2196f3;
}

/* Loader /
.loader {
border: 8px solid #f3f3f3; / Light grey /
border-top: 8px solid #4285f4; / Blue /
border-radius: 50%;
width: 80px; / Increased size for better visibility /
height: 80px; / Increased size for better visibility */
animation: spin 1s linear infinite;
margin: 0 auto;
}

@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

/* Fade In and Out for Messages */
@keyframes fadeInOut {
0% { opacity: 0; transform: translateY(-20px); }
10% { opacity: 0.95; transform: translateY(0); }
90% { opacity: 0.95; transform: translateY(0); }
100% { opacity: 0; transform: translateY(-20px); }
}

/* Responsive Design */
@media (max-width: 1000px) {
.container {
padding: 25px;
}


.controls {
    flex-direction: column;
    gap: 10px;
}

.controls button {
    width: 100%;
}

.main-title {
    font-size: 2.5em;
}

.player-container video,
.player-container iframe {
    height: 280px; /* Adjusted height for smaller screens */
}

.loader {
    width: 60px; /* Adjusted size for smaller screens */
    height: 60px; /* Adjusted size for smaller screens */
}
}

@media (max-width: 700px) {
.section {
padding: 15px;
}


.modal-content {
    padding: 20px 25px;
}

.main-title {
    font-size: 2em;
}

.player-container video,
.player-container iframe {
    height: 220px; /* Further adjusted height for even smaller screens */
}

.loader {
    width: 50px; /* Further adjusted size for even smaller screens */
    height: 50px; /* Further adjusted size for even smaller screens */
}
}

@media (max-width: 500px) {
.container {
padding: 15px;
}


.controls button {
    padding: 12px;
    font-size: 1em;
}

.modal-content {
    padding: 15px 20px;
}

.main-title {
    font-size: 1.8em;
}

.player-container video,
.player-container iframe {
    height: 180px; /* Further adjusted height for very small screens */
}

.loader {
    width: 40px; /* Further adjusted size for very small screens */
    height: 40px; /* Further adjusted size for very small screens */
}
}


<!-- YouTube IFrame API -->
<script src="https://www.youtube.com/iframe_api"></script>
<script>
    let player;
    let mediaType = ''; // 'music' or 'video'

    // Function called by YouTube API once it's ready
    function onYouTubeIframeAPIReady() {
        // Player will be created upon receiving initialization message
    }

    // Listen for messages from the parent
    window.addEventListener('message', (event) => {
        // For security, you can check event.origin here if necessary
        const data = event.data;
        if (data.type === 'init') {
            mediaType = data.mediaType; // 'music' or 'video'
            player = new YT.Player('youtube-player', {
                height: '360',
                width: '640',
                videoId: data.videoId,
                playerVars: {
                    'autoplay': 0,
                    'controls': 1,
                    'start': data.startTime,
                    'mute': 1,
                    'rel': 0,
                    'disablekb': 1,
                    'modestbranding': 1,
                    'iv_load_policy': 3,
                    'fs': 1
                },
                events: {
                    'onReady': () => {
                        // Notify the parent that the player is ready
                        window.parent.postMessage({
                            type: 'playerReady',
                            mediaType: mediaType
                        }, '*');
                    },
                    'onError': (error) => {
                        window.parent.postMessage({
                            type: 'playerError',
                            mediaType: mediaType,
                            error: error
                        }, '*');
                    }
                }
            });
        } else if (data.type === 'play') {
            if (player && typeof player.playVideo === 'function') {
                player.playVideo();
            }
        } else if (data.type === 'pause') {
            if (player && typeof player.pauseVideo === 'function') {
                player.pauseVideo();
            }
        } else if (data.type === 'seek') {
            if (player && typeof player.seekTo === 'function') {
                player.seekTo(data.time, true);
            }
        }
    });
</script>
Errors
Refused to load the script 'https://www.youtube.com/iframe_api' because it violates the following Content Security Policy directive: "script-src 'self'". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.
Context
dashboard.html
Stack Trace
youtube-sandbox.html:0 (anonymous function)


To address the Content Security Policy (CSP) error you're encountering, you'll need to adjust your Chrome extension's manifest.json to permit loading the YouTube IFrame API. Additionally, since Chrome extensions enforce strict CSPs that disallow inline scripts, you'll need to externalize any inline scripts present in your youtube-sandbox.html.

Below are the complete updated files with the necessary modifications:

1. manifest.json
Changes Made:

Updated content_security_policy to allow scripts from https://www.youtube.com/iframe_api.
Externalized scripts by removing any inline script allowances.

{
  "manifest_version": 3,
  "name": "Media Loader",
  "version": "1.4",
  "description": "Load and play music and videos from YouTube and Vimeo",
  "permissions": ["storage", "tabs"],
  "host_permissions": [
    "https://www.youtube.com/*",
    "https://youtu.be/*",
    "https://player.vimeo.com/*"
  ],
  "background": {
    "service_worker": "background.js"
  },
  "action": {
    "default_title": "Open Media Loader",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  "content_security_policy": {
    "extension_pages": "script-src 'self' https://www.youtube.com/iframe_api; object-src 'self'; frame-src https://www.youtube.com https://youtu.be https://player.vimeo.com"
  },
  "web_accessible_resources": [
    {
      "resources": ["lib/player.js"],
      "matches": ["<all_urls>"]
    },
    {
      "resources": ["youtube-sandbox.html", "sandbox.js"],
      "matches": ["<all_urls>"]
    }
  ]
}
2. youtube-sandbox.html
Changes Made:

Removed inline scripts and moved them to an external sandbox.js file.
Included the external sandbox.js script to handle all YouTube player functionalities.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>YouTube Sandbox</title>
</head>
<body>
    <!-- YouTube Player Container -->
    <div id="youtube-player"></div>

    <!-- External Sandbox Script -->
    <script src="sandbox.js"></script>
</body>
</html>
3. sandbox.js
New File Created:

Externalized the previously inline JavaScript code from youtube-sandbox.html to comply with CSP restrictions.

"use strict";

let player;
let mediaType = ''; // 'music' or 'video'

// Function called by YouTube API once it's ready
function onYouTubeIframeAPIReady() {
    // Player will be created upon receiving initialization message
}

// Listen for messages from the parent
window.addEventListener('message', (event) => {
    // For security, you can check event.origin here if necessary
    const data = event.data;
    if (data.type === 'init') {
        mediaType = data.mediaType; // 'music' or 'video'
        player = new YT.Player('youtube-player', {
            height: '360',
            width: '640',
            videoId: data.videoId,
            playerVars: {
                'autoplay': 0,
                'controls': 1,
                'start': data.startTime,
                'mute': 1,
                'rel': 0,
                'disablekb': 1,
                'modestbranding': 1,
                'iv_load_policy': 3,
                'fs': 1
            },
            events: {
                'onReady': () => {
                    // Notify the parent that the player is ready
                    window.parent.postMessage({
                        type: 'playerReady',
                        mediaType: mediaType
                    }, '*');
                },
                'onError': (error) => {
                    window.parent.postMessage({
                        type: 'playerError',
                        mediaType: mediaType,
                        error: error
                    }, '*');
                }
            }
        });
    } else if (data.type === 'play') {
        if (player && typeof player.playVideo === 'function') {
            player.playVideo();
        }
    } else if (data.type === 'pause') {
        if (player && typeof player.pauseVideo === 'function') {
            player.pauseVideo();
        }
    } else if (data.type === 'seek') {
        if (player && typeof player.seekTo === 'function') {
            player.seekTo(data.time, true);
        }
    }
});

// Load YouTube IFrame API
const script = document.createElement('script');
script.src = 'https://www.youtube.com/iframe_api';
document.head.appendChild(script);
4. Additional Notes
Permissions: Ensure that all required permissions are correctly specified in the manifest.json. The updated CSP allows the extension to load the YouTube IFrame API without violating security policies.
Security Considerations: Allowing external scripts can introduce security vulnerabilities. Make sure to validate and sanitize all inputs and communications between your extension and external sources. Always verify the origin of messages in postMessage handlers to prevent potential cross-site scripting (XSS) attacks.
Testing: After making these changes, reload your extension in Chrome to apply the updates. Monitor the Chrome Developer Console for any further CSP-related issues or other errors that may arise.
Alternative Approaches: If you continue to face CSP issues or want to enhance security, consider proxying YouTube API requests through your extension or utilize Chrome's native APIs to manage media playback without relying on external scripts.
By implementing these updates, your extension should now successfully load and utilize the YouTube IFrame API without violating Chrome's Content Security Policy. Ensure you thoroughly test all functionalities to confirm that media loading and playback operate as expected.

Failed to load extension
File
~\Desktop\myext
Error
'content_security_policy.extension_pages': Insecure CSP value "https://www.youtube.com/iframe_api" in directive 'script-src'.
Could not load manifest.