Loading TV Guide...
// Global variables let selectedDate = new Date(); let programData = null; let activeCategory = 'All'; let categories = new Set(['All']); let manifestData = null; // Initialize the guide function initializeGuide() { createDateBar(); initializeScrollButtons(); fetchGuide().then(() => { scrollToCurrentTime(); }); // Set up periodic updates setInterval(checkTimelineShift, 1000); setInterval(() => { updateSeekerLine(); updateCurrentPrograms(); }, 1000); setInterval(fetchGuide, 3600000); // Refresh data hourly } // Date-related functions function createDateBar() { const dateScroll = document.getElementById('dateScroll'); dateScroll.innerHTML = ''; for (let i = 0; i < 5; i++) { const date = new Date(); date.setDate(date.getDate() + i); const dateItem = document.createElement('div'); dateItem.className = `date-item ${i === 0 ? 'today' : ''} ${isSameDate(date, selectedDate) ? 'active' : ''}`; const dayName = i === 0 ? 'Today' : i === 1 ? 'Tomorrow' : date.toLocaleDateString('en-US', { weekday: 'short' }); dateItem.innerHTML = `
${dayName}
${date.toLocaleDateString('en-US', { day: 'numeric', month: 'short' })}
`; dateItem.addEventListener('click', () => { document.querySelectorAll('.date-item').forEach(item => item.classList.remove('active')); dateItem.classList.add('active'); selectedDate = date; fetchGuide(); }); dateScroll.appendChild(dateItem); } } function isSameDate(date1, date2) { return date1.getDate() === date2.getDate() && date1.getMonth() === date2.getMonth() && date1.getFullYear() === date2.getFullYear(); } function initializeScrollButtons() { const dateScroll = document.getElementById('dateScroll'); const scrollLeft = document.querySelector('.scroll-left'); const scrollRight = document.querySelector('.scroll-right'); scrollLeft.addEventListener('click', () => { dateScroll.scrollBy({ left: -200, behavior: 'smooth' }); }); scrollRight.addEventListener('click', () => { dateScroll.scrollBy({ left: 200, behavior: 'smooth' }); }); } // Category-related functions function extractCategories(manifest) { const uniqueCategories = new Set(['All']); Object.values(manifest).forEach(channel => { if (channel.category) { uniqueCategories.add(channel.category); } }); return Array.from(uniqueCategories); } function createCategoryFilters(categories) { const filterContainer = document.getElementById('category-filters'); filterContainer.innerHTML = ''; categories.forEach(category => { const filterButton = document.createElement('button'); filterButton.className = `category-filter ${category === activeCategory ? 'active' : ''}`; filterButton.textContent = category; filterButton.onclick = () => { document.querySelectorAll('.category-filter').forEach(btn => btn.classList.remove('active')); filterButton.classList.add('active'); activeCategory = category; fetchGuide(); }; filterContainer.appendChild(filterButton); }); } // Time and program-related functions function getCurrentTimeBase() { // If we're near the end of the day (after 8 PM), adjust the base time // to ensure we show programs from the next day const now = new Date(selectedDate); const currentHour = now.getHours(); // If we're approaching the end of the day, adjust the time window if (currentHour >= 20) { // After 8 PM // Move the time window forward to show more of tomorrow's content now.setHours(now.getHours() - 2); // Set base time 2 hours earlier to show more future content } const minutes = now.getMinutes(); const roundedMinutes = Math.floor(minutes / 30) * 30; return new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), roundedMinutes, 0, 0); } function formatDateTime(timestamp) { if (!timestamp) return ''; const year = timestamp.substring(0, 4); const month = timestamp.substring(4, 6); const day = timestamp.substring(6, 8); const hour = timestamp.substring(8, 10); const minute = timestamp.substring(10, 12); return `${year}-${month}-${day}T${hour}:${minute}:00`; } function calculateProgramPosition(startTime, endTime) { const baseTime = getCurrentTimeBase(); const adjustedStartTime = startTime < baseTime ? baseTime : startTime; const timeDiff = adjustedStartTime - baseTime; const duration = (endTime - adjustedStartTime) / (1000 * 60 * 30); const position = (timeDiff / (30 * 60 * 1000)) * 120; return { left: Math.max(0, position), width: Math.max(duration * 120, 10) }; } function checkTimelineShift() { const now = new Date(); const currentMinutes = now.getMinutes(); const currentSeconds = now.getSeconds(); if ((currentMinutes === 29 || currentMinutes === 59) && currentSeconds === 59) { shiftTimeline(); } } function shiftTimeline() { const timeHeader = document.querySelector('.time-header'); const programContainers = document.querySelectorAll('.programs-container'); timeHeader.classList.add('shifting'); programContainers.forEach(container => { container.classList.add('shifting'); }); setTimeout(() => { timeHeader.classList.remove('shifting'); programContainers.forEach(container => { container.classList.remove('shifting'); }); updateTimeCells(); updateProgramPositions(); fetchGuide(); }, 1000); } function updateTimeCells() { const timeHeader = document.querySelector('.time-header'); const timeCells = Array.from(timeHeader.querySelectorAll('.time-cell:not(:first-child)')); if (timeCells.length > 0) { timeCells[0].remove(); } const lastCell = timeCells[timeCells.length - 1]; if (lastCell) { const baseTime = getCurrentTimeBase(); const lastTime = new Date(baseTime.getTime() + (parseInt(lastCell.dataset.minuteOffset) + 30) * 60 * 1000); const newCell = document.createElement('div'); newCell.className = 'time-cell'; newCell.dataset.minuteOffset = (parseInt(lastCell.dataset.minuteOffset) + 30).toString(); newCell.textContent = lastTime.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false }); if (lastTime.getMinutes() === 0) { newCell.style.fontWeight = 'bold'; newCell.style.backgroundColor = '#2d2d2d'; } // Add visual indicator for next day's time slots const nextDay = new Date(baseTime); nextDay.setDate(nextDay.getDate() + 1); nextDay.setHours(0, 0, 0, 0); if (lastTime >= nextDay) { newCell.style.borderTop = '2px solid #e53e3e'; } timeHeader.appendChild(newCell); } } function updateProgramPositions() { const programs = document.querySelectorAll('.program'); const baseTime = getCurrentTimeBase(); programs.forEach(program => { const startTime = new Date(program.dataset.start); const endTime = new Date(program.dataset.end); if (endTime < baseTime) { program.remove(); } else { const position = calculateProgramPosition(startTime, endTime); program.style.left = `${position.left}px`; program.style.width = `${position.width}px`; } }); } function createTimeHeader() { const timeHeader = document.createElement('div'); timeHeader.className = 'time-header'; const channelHeaderCell = document.createElement('div'); channelHeaderCell.className = 'time-cell'; channelHeaderCell.textContent = 'Channels'; timeHeader.appendChild(channelHeaderCell); const baseTime = getCurrentTimeBase(); const intervalMinutes = 30; // Increase the number of intervals to show more future time slots const totalIntervals = 48; // 24 hours of 30-minute intervals for (let i = 0; i < totalIntervals; i++) { const timeSlot = new Date(baseTime.getTime() + (i * intervalMinutes * 60 * 1000)); const timeCell = document.createElement('div'); timeCell.className = 'time-cell'; timeCell.dataset.minuteOffset = i * intervalMinutes; // Add day indicator for times past midnight const nextDay = new Date(baseTime); nextDay.setDate(nextDay.getDate() + 1); nextDay.setHours(0, 0, 0, 0); const formattedTime = timeSlot.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false }); // Add visual indicator for next day's time slots if (timeSlot >= nextDay) { timeCell.style.borderTop = '2px solid #e53e3e'; timeCell.textContent = formattedTime; } else { timeCell.textContent = formattedTime; } if (timeSlot.getMinutes() === 0) { timeCell.style.fontWeight = 'bold'; timeCell.style.backgroundColor = '#2d2d2d'; } timeHeader.appendChild(timeCell); } return timeHeader; } function updateCurrentPrograms() { const now = new Date(); const programs = document.querySelectorAll('.program'); programs.forEach(program => { const startTime = new Date(program.dataset.start); const endTime = new Date(program.dataset.end); if (now >= startTime && now < endTime) { program.classList.add('current'); const progress = ((now - startTime) / (endTime - startTime)) * 100; let progressBar = program.querySelector('.progress-bar'); if (!progressBar) { progressBar = document.createElement('div'); progressBar.className = 'progress-bar'; program.appendChild(progressBar); } progressBar.style.width = `${progress}%`; } else { program.classList.remove('current'); const progressBar = program.querySelector('.progress-bar'); if (progressBar) { progressBar.remove(); } } }); } function updateSeekerLine() { const now = new Date(); const guideContainer = document.querySelector('.guide-container'); let seeker = document.querySelector('.seeker-line'); if (!seeker && guideContainer) { seeker = document.createElement('div'); seeker.className = 'seeker-line'; const seekerTime = document.createElement('div'); seekerTime.className = 'seeker-time'; seeker.appendChild(seekerTime); guideContainer.appendChild(seeker); } if (seeker) { const baseTime = getCurrentTimeBase(); const timeDiff = now - baseTime; const position = (timeDiff / (30 * 60 * 1000)) * 120; seeker.style.left = `${200 + position}px`; const timeDisplay = now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false }); seeker.querySelector('.seeker-time').textContent = timeDisplay; const timeCells = document.querySelectorAll('.time-cell'); const currentMinutes = now.getHours() * 60 + now.getMinutes(); timeCells.forEach(cell => { cell.classList.remove('current-time'); const cellMinutes = parseInt(cell.dataset.minuteOffset || 0); const baseMinutes = baseTime.getHours() * 60 + baseTime.getMinutes(); if (currentMinutes >= baseMinutes + cellMinutes && currentMinutes < baseMinutes + cellMinutes + 30) { cell.classList.add('current-time'); } }); } updateCurrentPrograms(); } function scrollToCurrentTime() { const guideContainer = document.querySelector('.guide-container'); const now = new Date(); const baseTime = getCurrentTimeBase(); const timeDiff = now - baseTime; const position = (timeDiff / (30 * 60 * 1000)) * 120; guideContainer.scrollLeft = Math.max(0, position - 240); } // Main fetch function async function fetchGuide() { const loading = document.getElementById('loading'); const container = document.getElementById('guide-container'); try { const [xmlResponse, manifestResponse] = await Promise.all([ fetch('https://azimabid00.github.io/epg/astro_epg.xml'), fetch('manifest.json') ]); if (!xmlResponse.ok || !manifestResponse.ok) { throw new Error('Failed to fetch data'); } const xmlText = await xmlResponse.text(); manifestData = await manifestResponse.json(); if (categories.size === 1) { categories = new Set(extractCategories(manifestData)); createCategoryFilters(Array.from(categories)); } const parser = new DOMParser(); const xmlDoc = parser.parseFromString(xmlText, "text/xml"); const guideContainer = document.createElement('div'); guideContainer.className = 'guide-container'; guideContainer.appendChild(createTimeHeader()); // Get the current time-based window instead of just the selected day const startOfDay = new Date(selectedDate); startOfDay.setHours(0, 0, 0, 0); // Create a window that extends 24 hours from the current time // This ensures programs from the next day appear as current day ends const currentTime = new Date(); const endTime = new Date(currentTime); endTime.setHours(currentTime.getHours() + 24); // 24-hour window const channelIds = Object.keys(manifestData).sort(); for (let channelId of channelIds) { const channelInfo = manifestData[channelId]; if (activeCategory !== 'All' && channelInfo.category !== activeCategory) { continue; } const channelRow = document.createElement('div'); channelRow.className = 'channel-row'; const channelCell = document.createElement('div'); channelCell.className = 'channel-cell'; channelCell.innerHTML = ` ${channelInfo.logo ? `` : ''}
${channelId}
`; channelCell.addEventListener('click', (e) => { e.stopPropagation(); window.location.href = `channel.html?id=${encodeURIComponent(channelId)}`; }); channelRow.appendChild(channelCell); const programsContainer = document.createElement('div'); programsContainer.className = 'programs-container'; // Get all programs for this channel const programmes = Array.from(xmlDoc.querySelectorAll(`programme[channel="${channelId}"]`)); let hasCurrentOrUpcoming = false; // First, sort programs by start time to ensure proper ordering programmes.sort((a, b) => { const startA = new Date(formatDateTime(a.getAttribute('start'))); const startB = new Date(formatDateTime(b.getAttribute('start'))); return startA - startB; }); for (let program of programmes) { const startTime = new Date(formatDateTime(program.getAttribute('start'))); const endTime = new Date(formatDateTime(program.getAttribute('stop'))); // Include programs that: // 1. Start on the selected day OR // 2. End on the selected day OR // 3. Span across the selected day (start before, end after) // 4. Also include programs from next day if we're approaching end of day const isInSelectedDay = (startTime >= startOfDay && startTime < endTime) || (startTime < startOfDay && endTime > startOfDay); // Also include programs in our extended 24hr window const isInExtendedWindow = startTime < endTime; if (isInSelectedDay || isInExtendedWindow) { hasCurrentOrUpcoming = true; const title = program.getElementsByTagName('title')[0]?.textContent || 'No Title'; const desc = program.getElementsByTagName('desc')[0]?.textContent || ''; const position = calculateProgramPosition(startTime, endTime); const programDiv = document.createElement('div'); programDiv.className = 'program'; programDiv.style.left = `${position.left}px`; programDiv.style.width = `${position.width}px`; programDiv.dataset.start = startTime; programDiv.dataset.end = endTime; programDiv.title = `${title}\n\n${desc}`; // Add a visual indicator for programs from the next day const nextDay = new Date(startOfDay); nextDay.setDate(nextDay.getDate() + 1); if (startTime >= nextDay) { programDiv.style.borderLeftColor = '#e53e3e'; programDiv.style.borderLeftWidth = '3px'; } programDiv.innerHTML = `
${title}
${startTime.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false })} - ${endTime.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false })}
`; programsContainer.appendChild(programDiv); } } if (hasCurrentOrUpcoming) { channelRow.appendChild(programsContainer); guideContainer.appendChild(channelRow); } } container.innerHTML = ''; container.appendChild(guideContainer); loading.style.display = 'none'; updateSeekerLine(); } catch (error) { loading.textContent = `Error loading TV Guide: ${error.message}`; loading.style.backgroundColor = '#dc2626'; loading.style.padding = '20px'; loading.style.borderRadius = '8px'; } } // Initialize when DOM is loaded document.addEventListener('DOMContentLoaded', initializeGuide);