document.addEventListener('DOMContentLoaded', () => { // --- 1. Element Grouping & State Management --- const elements = { media: { player: document.getElementById('mediaPlayer'), customControls: document.getElementById('customControls'), playPauseBtn: document.getElementById('playPauseBtn'), seekBar: document.getElementById('seekBar'), timeDisplay: document.getElementById('timeDisplay'), }, volume: { volumeBtn: document.getElementById('volumeBtn'), volumeBar: document.getElementById('volumeBar'), }, dialogs: { mainOverlay: document.getElementById('dialogOverlay'), urlOverlay: document.getElementById('urlDialogOverlay'), settingsOverlay: document.getElementById('settingsDialogOverlay'), subtitlesOverlay: document.getElementById('subtitlesOverlay'), }, buttons: { chooseFile: document.getElementById('chooseFileBtn'), enterUrl: document.getElementById('enterUrlBtn'), submitUrl: document.getElementById('submitUrlBtn'), cancelUrl: document.getElementById('cancelUrlBtn'), settings: document.getElementById('settingsBtn'), saveSettings: document.getElementById('saveSettingsBtn'), cc: document.getElementById('ccBtn'), submitSubtitles: document.getElementById('submitSubtitlesBtn'), cancelSubtitles: document.getElementById('cancelSubtitlesBtn'), showDialog: document.getElementById('showDialogBtn'), hideDialog: document.getElementById('hideDialogBtn'), fullscreen: document.getElementById('fullscreenBtn'), }, inputs: { fileInput: document.getElementById('fileInput'), urlInput: document.getElementById('urlInput'), subtitlesInput: document.getElementById('subtitlesInput'), autoplayCheckbox: document.getElementById('autoplayCheckbox'), loopCheckbox: document.getElementById('loopCheckbox'), controlsCheckbox: document.getElementById('controlsCheckbox'), colorsCheckbox: document.getElementById('colorsCheckbox'), }, }; const state = { hls: null, dashPlayer: null, previousObjectURL: null, }; // --- 2. Utility Functions --- const formatTime = (time) => { const minutes = Math.floor(time / 60) || 0; const seconds = Math.floor(time % 60) || 0; return `${minutes}:${seconds.toString().padStart(2, '0')}`; }; const setVisibility = (element, isVisible) => { element.style.display = isVisible ? 'block' : 'none'; }; const toggleControls = (show) => { elements.media.customControls.style.display = show ? 'flex' : 'none'; }; const resetMediaPlayers = () => { if (state.hls) { state.hls.destroy(); state.hls = null; } if (state.dashPlayer) { state.dashPlayer.reset(); state.dashPlayer = null; } }; const updatePlayPauseButton = () => { elements.media.playPauseBtn.textContent = elements.media.player.paused ? 'Play' : 'Pause'; }; const updateVolumeButton = () => { const isMuted = elements.volume.volumeBar.value === 0 || elements.media.player.muted; elements.volume.volumeBtn.textContent = isMuted ? '🔇' : '🔊'; }; const addSubtitles = (url) => { // Remove old subtitle tracks Array.from(elements.media.player.getElementsByTagName('track')).forEach(track => track.remove()); if (!url) return; const track = document.createElement('track'); track.kind = 'subtitles'; track.label = 'English'; track.srclang = 'en'; track.src = url; elements.media.player.appendChild(track); track.track.mode = 'showing'; }; const detectStreamType = async (url) => { try { const response = await fetch(url, { method: 'HEAD' }); const contentType = response.headers.get('Content-Type') || ''; const isHLS = url.toLowerCase().endsWith('.m3u8') || contentType.includes('mpegurl'); const isDASH = url.toLowerCase().endsWith('.mpd') || contentType.includes('dash+xml'); return { isHLS, isDASH }; } catch (err) { console.error("Failed to detect stream type:", err); return { isHLS: false, isDASH: false }; } }; const playMedia = (source) => { elements.media.player.src = source; elements.media.player.load(); if (elements.inputs.autoplayCheckbox.checked) { elements.media.player.play(); } toggleControls(true); setVisibility(elements.dialogs.mainOverlay, false); setVisibility(elements.dialogs.urlOverlay, false); }; // --- 3. Event Handlers --- const handleUrlSubmit = async () => { let url = elements.inputs.urlInput.value; if (url && !url.startsWith('http')) { url = 'https://' + url; } if (!url) return; resetMediaPlayers(); addSubtitles(); const { isHLS, isDASH } = await detectStreamType(url); elements.media.player.style.display = 'flex'; if (isHLS && typeof Hls !== 'undefined' && Hls.isSupported()) { state.hls = new Hls(); state.hls.loadSource(url); state.hls.attachMedia(elements.media.player); state.hls.on(Hls.Events.MANIFEST_PARSED, () => { if (elements.inputs.autoplayCheckbox.checked) { elements.media.player.play(); } }); } else if (isDASH && typeof dashjs !== 'undefined') { state.dashPlayer = dashjs.MediaPlayer().create(); state.dashPlayer.initialize(elements.media.player, url, true); if (elements.inputs.autoplayCheckbox.checked) { elements.media.player.play(); } } else { playMedia(url); } elements.inputs.urlInput.value = ''; toggleControls(true); setVisibility(elements.dialogs.urlOverlay, false); setVisibility(elements.dialogs.mainOverlay, false); }; const handleFileChange = (event) => { resetMediaPlayers(); addSubtitles(); if (state.previousObjectURL) { URL.revokeObjectURL(state.previousObjectURL); } const file = event.target.files[0]; if (!file) return; const fileURL = URL.createObjectURL(file); playMedia(fileURL); state.previousObjectURL = fileURL; }; const handleSettingsSave = () => { elements.media.player.autoplay = elements.inputs.autoplayCheckbox.checked; elements.media.player.loop = elements.inputs.loopCheckbox.checked; elements.media.player.controls = elements.inputs.controlsCheckbox.checked; const filter = elements.inputs.colorsCheckbox.checked ? 'contrast(1.1) saturate(1.15) brightness(1.03)' : ''; elements.media.player.style.filter = filter; setVisibility(elements.dialogs.settingsOverlay, false); }; // --- 4. Event Listener Setup --- const setupEventListeners = () => { // Media & Controls elements.media.player.addEventListener('timeupdate', () => { elements.media.seekBar.max = elements.media.player.duration || 0; elements.media.seekBar.value = elements.media.player.currentTime; elements.media.timeDisplay.textContent = `${formatTime(elements.media.player.currentTime)} / ${formatTime(elements.media.player.duration)}`; }); elements.media.player.addEventListener('play', updatePlayPauseButton); elements.media.player.addEventListener('pause', updatePlayPauseButton); elements.media.player.addEventListener('volumechange', updateVolumeButton); elements.media.playPauseBtn.addEventListener('click', () => { elements.media.player.paused ? elements.media.player.play() : elements.media.player.pause(); }); elements.media.seekBar.addEventListener('input', () => { elements.media.player.currentTime = elements.media.seekBar.value; }); elements.volume.volumeBar.addEventListener('input', () => { elements.media.player.volume = elements.volume.volumeBar.value; }); elements.volume.volumeBtn.addEventListener('click', () => { elements.media.player.muted = !elements.media.player.muted; updateVolumeButton(); }); // Dialogs & Buttons elements.buttons.chooseFile.addEventListener('click', () => elements.inputs.fileInput.click()); elements.buttons.enterUrl.addEventListener('click', () => setVisibility(elements.dialogs.urlOverlay, true)); elements.buttons.cancelUrl.addEventListener('click', () => setVisibility(elements.dialogs.urlOverlay, false)); elements.buttons.submitUrl.addEventListener('click', handleUrlSubmit); elements.inputs.urlInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') handleUrlSubmit(); }); elements.inputs.fileInput.addEventListener('change', handleFileChange); elements.buttons.settings.addEventListener('click', () => setVisibility(elements.dialogs.settingsOverlay, true)); elements.buttons.saveSettings.addEventListener('click', handleSettingsSave); elements.buttons.cc.addEventListener('click', () => setVisibility(elements.dialogs.subtitlesOverlay, true)); elements.buttons.cancelSubtitles.addEventListener('click', () => setVisibility(elements.dialogs.subtitlesOverlay, false)); elements.buttons.submitSubtitles.addEventListener('click', () => { addSubtitles(elements.inputs.subtitlesInput.value); elements.inputs.subtitlesInput.value = ''; setVisibility(elements.dialogs.subtitlesOverlay, false); }); elements.inputs.subtitlesInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') elements.buttons.submitSubtitles.click(); }); elements.buttons.showDialog.addEventListener('click', () => setVisibility(elements.dialogs.mainOverlay, true)); elements.buttons.hideDialog.addEventListener('click', () => setVisibility(elements.dialogs.mainOverlay, false)); elements.buttons.fullscreen.addEventListener('click', () => { if (!document.fullscreenElement) { elements.media.player.requestFullscreen(); } else { document.exitFullscreen(); } }); // Initialize window.onload = () => setVisibility(elements.dialogs.mainOverlay, true); }; setupEventListeners(); });