// Time zone configuration const TIME_ZONES = { 'UTC/GMT': ['UTC'], 'Americas': [ 'America/Los_Angeles', 'America/Vancouver', 'America/Denver', 'America/Chicago', 'America/New_York', 'America/Sao_Paulo', 'America/Buenos_Aires' ], 'Europe & Africa': [ 'Europe/London', 'Europe/Paris', 'Europe/Berlin', 'Europe/Moscow', 'Africa/Cairo', 'Africa/Johannesburg' ], 'Asia & Pacific': [ 'Asia/Dubai', 'Asia/Singapore', 'Asia/Tokyo', 'Asia/Shanghai', 'Australia/Sydney', 'Pacific/Auckland' ] }; function getInputElement(id) { const element = document.getElementById(id); return element instanceof HTMLInputElement ? element : null; } function getSelectElement(id) { const element = document.getElementById(id); return element instanceof HTMLSelectElement ? element : null; } function isApiResponse(data) { return typeof data === 'object' && data !== null && 'success' in data; } function formatDate(ms, timeZone) { return new Intl.DateTimeFormat('en-US', { timeZone, year: 'numeric', month: 'long', day: 'numeric' }).format(ms); } function formatTime(ms, timeZone) { return new Intl.DateTimeFormat('en-US', { timeZone, hour: '2-digit', minute: '2-digit', hour12: false }).format(ms); } // Store initial UTC milliseconds from server let currentTimeMs = 1740629640000; function updateBrowserTime() { const now = new Date(); const browserTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; const offset = -now.getTimezoneOffset() / 60; const offsetStr = offset >= 0 ? `+${offset}` : `${offset}`; const timeStr = now.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' }); const dateStr = now.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); const browserTimeEl = document.getElementById('browserTime'); const browserTimeZoneEl = document.getElementById('browserTimeZone'); if (browserTimeEl) { browserTimeEl.textContent = `${dateStr} ${timeStr}`; } if (browserTimeZoneEl) { browserTimeZoneEl.textContent = `${browserTimeZone} (GMT${offsetStr})`; } } function updatePreviews() { // Get input elements const timeZoneInput = getSelectElement('timeZone'); const windowSizeInput = getInputElement('windowSize'); if (!timeZoneInput || !windowSizeInput) { console.error('Required input elements not found'); return; } try { const timeZone = timeZoneInput.value; // Format for display using UTC milliseconds directly const dateStr = formatDate(currentTimeMs, timeZone); const timeStr = formatTime(currentTimeMs, timeZone); const zoneStr = timeZone.split('/').pop()?.replace('_', ' ') || timeZone; // Update preview elements const datePreview = document.getElementById('datePreview'); const timePreview = document.getElementById('timePreview'); const windowPreview = document.getElementById('windowPreview'); if (datePreview && timePreview && windowPreview) { datePreview.textContent = dateStr; timePreview.textContent = `${timeStr} (${zoneStr})`; windowPreview.textContent = `${windowSizeInput.value} segments`; } } catch (error) { console.error('Error updating preview:', error); } } function handleTimeChange() { // Get input elements const dateInput = getInputElement('streamStartDate'); const hourInput = getSelectElement('streamHour'); const minuteInput = getSelectElement('streamMinute'); const timeZoneInput = getSelectElement('timeZone'); if (!dateInput || !hourInput || !minuteInput || !timeZoneInput) { console.error('Required input elements not found'); return; } try { // Get input values const [year, month, day] = dateInput.value.split('-').map(Number); const hour = parseInt(hourInput.value); const minute = parseInt(minuteInput.value); const timeZone = timeZoneInput.value; // Create a temporary date in UTC const utcDate = new Date(Date.UTC(year, month - 1, day)); // Format the date in the target time zone to get the offset const formatter = new Intl.DateTimeFormat('en-US', { timeZone, timeZoneName: 'longOffset' }); const tzOffset = formatter.format(utcDate).split(' ').pop(); const offsetHours = parseInt(tzOffset.slice(4)) * (tzOffset[3] === '+' ? 1 : -1); // Adjust UTC time based on time zone offset currentTimeMs = Date.UTC(year, month - 1, day, hour - offsetHours, minute); // Update preview updatePreviews(); console.log('Time debug:', { input: { year, month, day, hour, minute, timeZone }, tzOffset, offsetHours, utc: new Date(currentTimeMs).toISOString(), display: formatTime(currentTimeMs, timeZone) }); } catch (error) { console.error('Error handling time change:', error); } } function validateConfig() { // Get input elements const masterPathInput = getInputElement('masterPlaylistPath'); const dateInput = getInputElement('streamStartDate'); const hourInput = getSelectElement('streamHour'); const minuteInput = getSelectElement('streamMinute'); if (!masterPathInput || !dateInput || !hourInput || !minuteInput) { console.error('Required input elements not found'); return false; } // Validate master playlist path const masterPath = masterPathInput.value; if (!masterPath) { alert('Master playlist path cannot be empty'); return false; } if (masterPath.includes('..')) { alert('Master playlist path cannot contain parent directory references'); return false; } if (masterPath.includes('//')) { alert('Master playlist path cannot contain double slashes'); return false; } if (!masterPath.endsWith('master.m3u8')) { alert('Master playlist path must end with master.m3u8'); return false; } // Validate date const date = dateInput.value; if (!date) { alert('Start date is required'); return false; } if (!date.match(/^\d{4}-\d{2}-\d{2}$/)) { alert('Invalid date format (YYYY-MM-DD required)'); return false; } // Validate time values const hour = parseInt(hourInput.value); const minute = parseInt(minuteInput.value); if (isNaN(hour) || hour < 0 || hour > 23) { alert('Invalid hour (must be 0-23)'); return false; } if (isNaN(minute) || minute < 0 || minute > 59) { alert('Invalid minute (must be 0-59)'); return false; } // Check if date/time is in future if (currentTimeMs > Date.now()) { alert('Start date/time cannot be in the future'); return false; } return true; } function saveConfig() { if (!validateConfig()) return; // Get input elements const timeZoneInput = getSelectElement('timeZone'); const windowSizeInput = getInputElement('windowSize'); const masterPathInput = getInputElement('masterPlaylistPath'); if (!timeZoneInput || !windowSizeInput || !masterPathInput) { console.error('Required input elements not found'); return; } try { const config = { stream: { startTimeMs: currentTimeMs, timeZone: timeZoneInput.value }, hls: { windowSize: parseInt(windowSizeInput.value) }, masterPlaylistPath: masterPathInput.value }; // Log the configuration with human-readable time console.log('Saving config:', { ...config, stream: { ...config.stream, formattedTime: `${formatDate(currentTimeMs, config.stream.timeZone)} ${formatTime(currentTimeMs, config.stream.timeZone)}` } }); fetch('/api/config/update', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(config) }) .then(response => response.json()) .then(data => { if (isApiResponse(data) && data.success) { alert('Configuration updated successfully'); } else { alert(data.error || 'Failed to update configuration'); } }) .catch(error => { console.error('Error:', error); alert('Error updating configuration'); }); } catch (error) { console.error('Error saving config:', error); alert('Error saving configuration'); } } // Initialize when DOM is ready document.addEventListener('DOMContentLoaded', () => { // Add event listeners for all time inputs ['streamStartDate', 'streamHour', 'streamMinute', 'timeZone'].forEach(id => { const element = document.getElementById(id); if (element) { element.addEventListener('change', handleTimeChange); } }); const windowSize = document.getElementById('windowSize'); if (windowSize) { windowSize.addEventListener('input', updatePreviews); } // Initialize preview using server's UTC time updatePreviews(); // Start browser time display updateBrowserTime(); setInterval(updateBrowserTime, 1000); }); // Export functions for use in onclick handlers window.saveConfig = saveConfig;