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