// Add toast notification animations const style = document.createElement('style'); style.textContent = ` @keyframes slideIn { from { transform: translateX(400px); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes slideOut { from { transform: translateX(0); opacity: 1; } to { transform: translateX(400px); opacity: 0; } } `; document.head.appendChild(style); // Go Live Page State (global scope) let hls = null; let isLive = false; let streamStatus = { isOnline: false }; let regularStreamUrl = '/playlists/active.m3u8'; let liveStreamUrl = null; let isInitialLoad = true; // Track if this is the first load let isMuted = true; // Track mute state // Fetch stream URLs from server async function fetchStreamUrls() { try { const response = await fetch('/api/golive/urls'); const data = await response.json(); regularStreamUrl = data.regularStreamUrl || '/playlists/active.m3u8'; liveStreamUrl = data.liveStreamUrl; console.log('[GoLive] Stream URLs configured:', { regular: regularStreamUrl, live: liveStreamUrl ? 'configured' : 'not configured' }); if (!liveStreamUrl) { showError('Live stream URL not configured'); } } catch (error) { console.error('[GoLive] Error fetching stream URLs:', error); showError('Failed to load stream configuration'); } } // Initialize HLS player function initializePlayer() { const video = document.getElementById('video'); if (!video) { console.error('[GoLive] Video element not found'); return; } if (Hls.isSupported()) { // HLS.js configuration optimized for live streaming hls = new Hls({ debug: false, enableWorker: true, lowLatencyMode: true, liveSyncDurationCount: 3, liveMaxLatencyDurationCount: 10, maxBufferLength: 30, maxMaxBufferLength: 600, fragLoadingMaxRetry: 3, manifestLoadingMaxRetry: 3 }); hls.attachMedia(video); hls.on(Hls.Events.MEDIA_ATTACHED, () => { // Only load regular stream on initial setup if (isInitialLoad) { console.log('[GoLive] Initializing with regular stream'); loadStream(regularStreamUrl); isInitialLoad = false; } }); hls.on(Hls.Events.MANIFEST_PARSED, () => { hideLoading(); // Preserve mute state across stream switches video.muted = isMuted; video.play().then(() => { console.log('[GoLive] Autoplay started', isMuted ? '(muted)' : '(unmuted)'); if (isMuted) { showUnmuteButton(); } }).catch(err => { console.log('[GoLive] Autoplay prevented:', err); hideUnmuteButton(); }); }); hls.on(Hls.Events.ERROR, (event, data) => { console.error('[GoLive] HLS error:', data); if (data.fatal) { handleFatalError(data); } }); } else if (video.canPlayType('application/vnd.apple.mpegurl')) { // Native HLS support (Safari) console.log('[GoLive] Using native HLS support'); video.src = regularStreamUrl; video.addEventListener('loadedmetadata', () => { hideLoading(); // Preserve mute state across stream switches video.muted = isMuted; video.play().then(() => { console.log('[GoLive] Autoplay started', isMuted ? '(muted)' : '(unmuted)'); if (isMuted) { showUnmuteButton(); } }).catch(err => { console.log('[GoLive] Autoplay prevented:', err); hideUnmuteButton(); }); }); } else { showError('HLS is not supported in this browser'); } } // Load a stream source function loadStream(url) { console.log('[GoLive] Loading stream:', url); showLoading('Loading stream...'); if (hls) { hls.loadSource(url); } else { const video = document.getElementById('video'); if (video) { video.src = url; } else { console.error('[GoLive] Video element not found!'); } } updateCurrentSource(url); } // Check stream status async function checkStreamStatus() { if (!liveStreamUrl) { updateStreamStatus({ isOnline: false, error: 'Not configured' }); return; } try { const response = await fetch('/api/golive/status'); const status = await response.json(); console.log('[GoLive] Stream status:', status); // If we're currently playing live stream but it went offline, switch back to regular if (isLive && !status.isOnline) { console.log('[GoLive] Live stream went offline, switching back to regular programming'); loadStream(regularStreamUrl); isLive = false; updateToggleButton(); updatePlaybackStatus(); showInfo('Live stream ended. Switched back to regular programming.'); } streamStatus = status; updateStreamStatus(status); } catch (error) { console.error('[GoLive] Error checking stream status:', error); updateStreamStatus({ isOnline: false, error: 'Check failed' }); } } // Update stream status UI function updateStreamStatus(status) { const statusDot = document.getElementById('streamStatusDot'); const statusText = document.getElementById('streamStatusText'); const statusInfo = document.getElementById('streamStatusInfo'); const toggleButton = document.getElementById('toggleButton'); const qualitiesContainer = document.getElementById('qualitiesContainer'); if (statusDot) { statusDot.className = 'status-dot ' + (status.isOnline ? 'online' : 'offline'); } if (statusText) { statusText.textContent = status.isOnline ? 'ONLINE' : 'OFFLINE'; } if (statusInfo) { if (status.isOnline && status.qualities) { statusInfo.textContent = status.qualities.join(', ') + ' available'; } else if (status.error) { statusInfo.textContent = status.error; } else { statusInfo.textContent = 'No active stream'; } } // Update quality badges if (qualitiesContainer) { if (status.isOnline && status.qualities) { qualitiesContainer.innerHTML = status.qualities .map(q => '' + q + '') .join(''); } else { qualitiesContainer.innerHTML = 'N/A'; } } // Enable/disable toggle button based on stream availability if (toggleButton && !isLive) { toggleButton.disabled = !status.isOnline; } } // Handle toggle button click function handleToggle() { console.log('[GoLive] Toggle button clicked - isLive:', isLive); if (isLive) { // Switch back to regular programming console.log('[GoLive] Switching to regular programming'); loadStream(regularStreamUrl); isLive = false; updateToggleButton(); updatePlaybackStatus(); } else { // Switch to live stream if (!liveStreamUrl) { console.error('[GoLive] Live stream URL not configured!'); showError('Live stream URL is not configured'); return; } if (!streamStatus.isOnline) { console.warn('[GoLive] Live stream is offline'); showError('Live stream is not available. Please start your RTMP stream first.'); return; } console.log('[GoLive] Switching to live stream'); loadStream(liveStreamUrl); isLive = true; updateToggleButton(); updatePlaybackStatus(); } } // Update toggle button appearance function updateToggleButton() { const toggleButton = document.getElementById('toggleButton'); const toggleIcon = document.getElementById('toggleIcon'); const toggleText = document.getElementById('toggleText'); if (toggleButton) { if (isLive) { toggleButton.classList.add('active'); if (toggleIcon) toggleIcon.textContent = '⏹'; if (toggleText) toggleText.textContent = 'END LIVE'; } else { toggleButton.classList.remove('active'); if (toggleIcon) toggleIcon.textContent = '🔴'; if (toggleText) toggleText.textContent = 'GO LIVE'; } } } // Update playback status indicator function updatePlaybackStatus() { const playbackDot = document.getElementById('playbackStatusDot'); const playbackText = document.getElementById('playbackStatusText'); if (playbackDot) { playbackDot.className = 'status-dot ' + (isLive ? 'live' : 'online'); } if (playbackText) { playbackText.textContent = isLive ? 'LIVE STREAM' : 'SCHEDULED PROGRAMMING'; } } // Update current source display function updateCurrentSource(url) { const sourceDisplay = document.getElementById('currentSourceDisplay'); if (sourceDisplay) { const displayUrl = url.length > 60 ? url.substring(0, 57) + '...' : url; sourceDisplay.textContent = displayUrl; } } // Show loading overlay function showLoading(message = 'Loading...') { const overlay = document.getElementById('loadingOverlay'); const text = document.getElementById('loadingText'); if (overlay) { overlay.style.display = 'flex'; } if (text) { text.textContent = message; } } // Hide loading overlay function hideLoading() { const overlay = document.getElementById('loadingOverlay'); if (overlay) { overlay.style.display = 'none'; } } // Show unmute button overlay function showUnmuteButton() { const overlay = document.getElementById('unmuteOverlay'); if (overlay && isMuted) { overlay.classList.remove('hidden'); } } // Hide unmute button overlay function hideUnmuteButton() { const overlay = document.getElementById('unmuteOverlay'); if (overlay) { overlay.classList.add('hidden'); } } // Handle unmute button click function handleUnmute() { const video = document.getElementById('video'); const overlay = document.getElementById('unmuteOverlay'); if (video) { video.muted = false; isMuted = false; console.log('[GoLive] Video unmuted'); } if (overlay) { overlay.classList.add('hidden'); } } // Show error message function showError(message) { console.error('[GoLive] Error:', message); // Create a toast notification instead of alert const toast = document.createElement('div'); toast.style.cssText = 'position: fixed; bottom: 20px; right: 20px; background: rgba(220, 38, 38, 0.95); color: white; padding: 16px 24px; border-radius: 8px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); z-index: 10000; max-width: 400px; font-size: 14px; animation: slideIn 0.3s ease-out;'; toast.textContent = message; document.body.appendChild(toast); // Remove after 5 seconds setTimeout(() => { toast.style.animation = 'slideOut 0.3s ease-out'; setTimeout(() => toast.remove(), 300); }, 5000); } // Show info message function showInfo(message) { console.log('[GoLive] Info:', message); const toast = document.createElement('div'); toast.style.cssText = 'position: fixed; bottom: 20px; right: 20px; background: rgba(59, 130, 246, 0.95); color: white; padding: 16px 24px; border-radius: 8px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); z-index: 10000; max-width: 400px; font-size: 14px; animation: slideIn 0.3s ease-out;'; toast.textContent = message; document.body.appendChild(toast); // Remove after 4 seconds setTimeout(() => { toast.style.animation = 'slideOut 0.3s ease-out'; setTimeout(() => toast.remove(), 300); }, 4000); } // Handle fatal HLS errors function handleFatalError(data) { console.error('[GoLive] Fatal error:', data); switch (data.type) { case Hls.ErrorTypes.NETWORK_ERROR: console.log('[GoLive] Network error, attempting recovery...'); // If we're playing live stream and get network error, try to recover if (isLive) { hls.startLoad(); // If recovery fails after a few seconds, fallback to regular stream setTimeout(() => { if (hls && hls.media && hls.media.paused) { console.log('[GoLive] Recovery failed, switching to regular programming'); loadStream(regularStreamUrl); isLive = false; updateToggleButton(); updatePlaybackStatus(); showInfo('Live stream connection lost. Switched to regular programming.'); } }, 5000); } else { hls.startLoad(); } break; case Hls.ErrorTypes.MEDIA_ERROR: console.log('[GoLive] Media error, attempting recovery...'); hls.recoverMediaError(); break; default: // For other fatal errors on live stream, fallback to regular if (isLive) { console.log('[GoLive] Fatal error on live stream, switching to regular programming'); loadStream(regularStreamUrl); isLive = false; updateToggleButton(); updatePlaybackStatus(); showInfo('Live stream error. Switched to regular programming.'); } else { showError('Playback error: ' + data.type); } break; } } // Initialize when DOM is loaded document.addEventListener('DOMContentLoaded', async function() { console.log('[GoLive] Initializing Go Live page'); // Initialize button state updateToggleButton(); updatePlaybackStatus(); // Get stream URLs from server await fetchStreamUrls(); // Initialize video player initializePlayer(); // Check stream status await checkStreamStatus(); // Set up periodic status checks (every 10 seconds) setInterval(checkStreamStatus, 10000); // Set up toggle button const toggleButton = document.getElementById('toggleButton'); if (toggleButton) { toggleButton.addEventListener('click', handleToggle); console.log('[GoLive] Ready - Toggle button active'); } else { console.error('[GoLive] Toggle button not found!'); } // Set up unmute button const unmuteButton = document.getElementById('unmuteButton'); if (unmuteButton) { unmuteButton.addEventListener('click', handleUnmute); console.log('[GoLive] Unmute button ready'); } // Initially hide unmute button until video starts playing hideUnmuteButton(); });