diff --git a/simpliplay/player.js b/simpliplay/player.js index b8e8f91..4b97cb0 100644 --- a/simpliplay/player.js +++ b/simpliplay/player.js @@ -1,411 +1,264 @@ document.addEventListener('DOMContentLoaded', () => { - const dialogOverlay = document.getElementById('dialogOverlay'); - const chooseFileBtn = document.getElementById('chooseFileBtn'); - const enterUrlBtn = document.getElementById('enterUrlBtn'); - const fileInput = document.getElementById('fileInput'); - const mediaPlayer = document.getElementById('mediaPlayer'); - const playPauseBtn = document.getElementById('playPauseBtn'); - const seekBar = document.getElementById('seekBar'); - const timeDisplay = document.getElementById('timeDisplay'); - const volumeBar = document.getElementById('volumeBar'); - const settingsBtn = document.getElementById('settingsBtn'); - const settingsPanel = document.getElementById('settingsPanel'); - const autoplayCheckbox = document.getElementById('autoplayCheckbox'); - const loopCheckbox = document.getElementById('loopCheckbox'); - const saveSettingsBtn = document.getElementById('saveSettingsBtn'); - const urlDialogOverlay = document.getElementById('urlDialogOverlay'); - const settingsDialogOverlay = document.getElementById('settingsDialogOverlay'); - const urlInput = document.getElementById('urlInput'); - const submitUrlBtn = document.getElementById('submitUrlBtn'); - const cancelUrlBtn = document.getElementById('cancelUrlBtn'); - const ccBtn = document.getElementById('ccBtn'); // CC button - const volumeBtn = document.getElementById("volumeBtn") - const subtitlesOverlay = document.getElementById('subtitlesOverlay'); - const subtitlesInput = document.getElementById('subtitlesInput'); - const submitSubtitlesBtn = document.getElementById('submitSubtitlesBtn'); - const cancelSubtitlesBtn = document.getElementById('cancelSubtitlesBtn'); - const customControls = document.getElementById('customControls'); - let hls = null - let player = null - window.hls = hls; - window.dash = player; + // --- 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'), + }, + }; - async function detectStreamType(url) { - try { - const response = await fetch(url, { method: 'HEAD' }); - const contentType = response.headers.get('Content-Type') || ''; + const state = { + hls: null, + dashPlayer: null, + previousObjectURL: null, + }; - const isHLS = url.toLowerCase().endsWith('.m3u8') || - contentType.includes('application/vnd.apple.mpegurl') || - contentType.includes('application/x-mpegURL'); + // --- 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 isDASH = url.toLowerCase().endsWith('.mpd') || - contentType.includes('application/dash+xml'); + const setVisibility = (element, isVisible) => { + element.style.display = isVisible ? 'block' : 'none'; + }; - return { isHLS, isDASH, contentType }; - } catch (err) { - console.error("Failed to detect stream type:", err); - return { isHLS: false, isDASH: false, contentType: null }; - } -} - - // Update media volume when the slider is moved - volumeBar.addEventListener("input", function () { - mediaPlayer.volume = volumeBar.value; - }); - - // Sync slider with media volume (in case it's changed programmatically) - mediaPlayer.addEventListener("volumechange", function () { - volumeBar.value = mediaPlayer.volume; - if (mediaPlayer.muted || mediaPlayer.volume === 0) { - volumeBtn.textContent = "🔇"; - } else { - volumeBtn.textContent = "🔊"; - } - }); - -// Function to add subtitles dynamically (e.g., after URL input) -function addSubtitles(url) { - // Remove any existing subtitle tracks - const existingTracks = mediaPlayer.getElementsByTagName('track'); - for (let track of existingTracks) { - track.remove(); - } - - // Create a new track for subtitles - const track = document.createElement('track'); - track.kind = 'subtitles'; - track.label = 'English'; - track.srclang = 'en'; - track.src = url; - - // Append the new track - mediaPlayer.appendChild(track); - - // Optionally, enable subtitles by default - track.track.mode = 'showing'; // Enable subtitles by default -} - -// Handle submit subtitle URL -function clearSubtitles() { - const tracks = mediaPlayer.getElementsByTagName('track'); - for (let i = tracks.length - 1; i >= 0; i--) { - tracks[i].remove(); - } -} - -// Use this function when a new video is loaded - -submitSubtitlesBtn.addEventListener('click', () => { - const subtitleUrl = subtitlesInput.value; - if (subtitleUrl) { - addSubtitles(subtitleUrl); - subtitlesOverlay.style.display = 'none'; - subtitlesInput.value = ''; - } -}); - - - - let autoplayEnabled = true; - let loopEnabled = false; - - // Handle submit URL button in custom dialog -submitUrlBtn.addEventListener('click', async () => { - let url = urlInput.value; - - // Check if URL is a valid URL and doesn't contain "http" or "https" - if (url && !url.startsWith('http') && !url.startsWith('https')) { - // Assuming it's a URL and needs the protocol added - url = 'http://' + url; // You can also choose 'https://' if preferred - } - -const { isHLS, isDASH } = await detectStreamType(url); + 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; + } + }; - if (url) { - clearSubtitles(); - if (hls !== null) { - hls.destroy() - hls = null - window.hls = hls + 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 }; } - if (player !== null) { - player.reset() - player = null - window.dash = player + }; + + const playMedia = (source) => { + elements.media.player.src = source; + elements.media.player.load(); + if (elements.inputs.autoplayCheckbox.checked) { + elements.media.player.play(); } - if (url.toLowerCase().endsWith('.m3u8') || url.toLowerCase().endsWith('.m3u') || isHLS) { - // HLS stream - if (Hls.isSupported()) { - mediaPlayer.style.display = 'flex'; // Hide the native video player - hls = new Hls(); - window.hls = hls - mediaPlayer.pause(); - hls.loadSource(url); - hls.attachMedia(mediaPlayer); - hls.on(Hls.Events.MANIFEST_PARSED, function() { - if (autoplayCheckbox.checked) { - mediaPlayer.play(); - } - urlInput.value = ""; - customControls.style.display = 'flex'; - }); - window.hls = hls - } else { - alert("Your device doesn't support HLS."); - customControls.style.display = 'flex'; - urlInput.value = ""; + 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 if (url.toLowerCase().endsWith('.mpd') || isDASH) { - mediaPlayer.style.display = 'flex'; // Hide the native video player - mediaPlayer.pause(); - player = dashjs.MediaPlayer().create(); - window.dash = player - // MPEG-DASH stream - player.initialize(mediaPlayer, url, true); - customControls.style.display = 'flex'; - urlInput.value = ""; - if (autoplayCheckbox.checked) { - mediaPlayer.play(); - } - window.dash = player } else { - mediaPlayer.style.display = 'flex'; // Hide the native video player - mediaPlayer.pause(); - mediaPlayer.src = url; - customControls.style.display = 'flex'; - urlInput.value = ""; - if (autoplayCheckbox.checked) { - mediaPlayer.play(); - } + playMedia(url); } - urlDialogOverlay.style.display = 'none'; - dialogOverlay.style.display = 'none'; - } -}); - - - - // Handle CC button to show subtitle modal - ccBtn.addEventListener('click', () => { - subtitlesOverlay.style.display = 'block'; - }); - - - - // Handle cancel subtitle modal - cancelSubtitlesBtn.addEventListener('click', () => { - subtitlesOverlay.style.display = 'none'; - }); - - - // Show the dialog on page load - window.onload = function () { - dialogOverlay.style.display = 'block'; - }; - - - - // Handle "Choose a File" button - chooseFileBtn.addEventListener('click', () => { - fileInput.click(); - }); - - mediaPlayer.addEventListener("volumechange", function () { - if (mediaPlayer.muted) { - volumeBtn.textContent = "🔇" - } else if (mediaPlayer.volume === 0) { - volumeBtn.textContent = "🔊" - } - }); - - - - - let previousObjectURL = null; // Store the last Object URL - - fileInput.addEventListener('change', (event) => { - if (hls !== null) { - hls.destroy() - hls = null - window.hls = hls - } - if (player !== null) { - player.reset() - player = null - window.dash = player - } - const file = event.target.files[0]; - if (!file) return; - - clearSubtitles(); // Remove any previously loaded subtitles - - // Revoke the previous Object URL if it exists - if (previousObjectURL) { - URL.revokeObjectURL(previousObjectURL); - } - - // Create a new Object URL for the selected file - const fileURL = URL.createObjectURL(file); - mediaPlayer.src = fileURL; - mediaPlayer.load(); - if (autoplayCheckbox.checked) { - mediaPlayer.play(); - } - - // Store the new Object URL for future cleanup - previousObjectURL = fileURL; - - // Hide dialog after selecting a file - dialogOverlay.style.display = 'none'; - }); - - - // Handle "Enter a URL" button - enterUrlBtn.addEventListener('click', () => { - urlDialogOverlay.style.display = 'block'; - }); - - // Handle cancel button in URL dialog - cancelUrlBtn.addEventListener('click', () => { - urlDialogOverlay.style.display = 'none'; - }); - - // Handle custom play/pause button -playPauseBtn.addEventListener('click', () => { - if (mediaPlayer.paused) { - mediaPlayer.play(); - playPauseBtn.textContent = 'Pause'; - } else { - mediaPlayer.pause(); - playPauseBtn.textContent = 'Play'; - } -}); - -// Sync button with the video player when it is paused manually -mediaPlayer.addEventListener('pause', () => { - playPauseBtn.textContent = 'Play'; -}); - -// Sync button with the video player when it is played -mediaPlayer.addEventListener('play', () => { - playPauseBtn.textContent = 'Pause'; -}); - -// Volume button toggling mute/unmute -volumeBtn.addEventListener('click', () => { - if (mediaPlayer.muted || mediaPlayer.volume == 0) { - mediaPlayer.muted = false; - volumeBtn.textContent = '🔊'; // Unmute icon - } else { - mediaPlayer.muted = true; - volumeBtn.textContent = '🔇'; // Mute icon - } -}); - - -// Handle URL input on Enter key -urlInput.addEventListener('keydown', (e) => { - if (e.key === 'Enter') { - submitUrlBtn.click(); - } -}); - -// Handle Subtitles input on Enter key -subtitlesInput.addEventListener('keydown', (e) => { - if (e.key === 'Enter') { - submitSubtitlesBtn.click(); - } -}); - -// Handle URL submission - - - // Update seek bar and time display - mediaPlayer.addEventListener('timeupdate', () => { - seekBar.max = mediaPlayer.duration || 0; - seekBar.value = mediaPlayer.currentTime; - const current = formatTime(mediaPlayer.currentTime); - const total = formatTime(mediaPlayer.duration); - timeDisplay.textContent = `${current} / ${total}`; - }); - - // Seek media - seekBar.addEventListener('input', () => { - mediaPlayer.currentTime = seekBar.value; - }); - - // Handle volume - volumeBar.addEventListener('input', () => { - mediaPlayer.volume = volumeBar.value; - if (volumeBar.value == 0 || mediaPlayer.volume == 0) { - volumeBtn.textContent = "🔇"; - } else { - volumeBtn.textContent = "🔊"; - } - }); - - // Show settings panel - settingsBtn.addEventListener('click', () => { - settingsDialogOverlay.style.display = 'block'; - settingsPanel.style.display = 'block'; - }); - - saveSettingsBtn.addEventListener('click', () => { - autoplayEnabled = autoplayCheckbox.checked; - loopEnabled = loopCheckbox.checked; - mediaPlayer.autoplay = autoplayEnabled; - mediaPlayer.loop = loopEnabled; - - const controlsEnabled = document.getElementById('controlsCheckbox').checked; - const colorsEnabled = document.getElementById('colorsCheckbox').checked; - mediaPlayer.controls = controlsEnabled; - if (colorsEnabled) { - mediaPlayer.style.filter = "contrast(1.1) saturate(1.15) brightness(1.03)"; - } else { - mediaPlayer.style.filter = ""; - } - - settingsPanel.style.display = 'none'; - settingsDialogOverlay.style.display = 'none'; - }); - - -// End of first event listener for DOM content loaded - - // Format time - function formatTime(time) { - const minutes = Math.floor(time / 60) || 0; - const seconds = Math.floor(time % 60) || 0; - return `${minutes}:${seconds.toString().padStart(2, '0')}`; + 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 showDialogBtn = document.getElementById('showDialogBtn'); - const hideDialogBtn = document.getElementById('hideDialogBtn'); - const fullscreenBtn = document.getElementById('fullscreenBtn'); + const file = event.target.files[0]; + if (!file) return; - // Show dialog - showDialogBtn.addEventListener('click', () => { - dialogOverlay.style.display = 'block'; + 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; }); - // Hide dialog - hideDialogBtn.addEventListener('click', () => { - dialogOverlay.style.display = 'none'; + 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(); }); - // Fullscreen functionality - fullscreenBtn.addEventListener('click', () => { + // 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) { - mediaPlayer.requestFullscreen(); + elements.media.player.requestFullscreen(); } else { document.exitFullscreen(); } }); + // Initialize + window.onload = () => setVisibility(elements.dialogs.mainOverlay, true); + }; -// End of code and second event listener for DOM content loaded - + setupEventListeners(); });