Spaces:
Running
Running
| document.addEventListener('DOMContentLoaded', async function() { | |
| const player = document.getElementById('player'); | |
| const playerOverlay = document.getElementById('player-overlay'); | |
| const channelList = document.getElementById('channel-list'); | |
| const currentChannel = document.getElementById('current-channel'); | |
| const currentProgram = document.getElementById('current-program'); | |
| // Default M3U URL | |
| const m3uUrl = 'https://iptv-org.github.io/iptv/languages/mal.m3u'; | |
| playerOverlay.classList.remove('hidden'); | |
| // Load channels from M3U file | |
| async function loadChannels() { | |
| try { | |
| const response = await fetch(m3uUrl); | |
| const text = await response.text(); | |
| const channels = parseM3U(text); | |
| channelList.innerHTML = ''; | |
| channels.forEach(channel => { | |
| const channelItem = document.createElement('div'); | |
| channelItem.className = 'channel-item bg-gray-700 hover:bg-gray-600 rounded-lg p-3 cursor-pointer flex items-center transition-all'; | |
| channelItem.innerHTML = ` | |
| <div class="w-10 h-10 rounded-full bg-purple-500 flex items-center justify-center mr-3"> | |
| <i data-feather="tv" class="w-4 h-4"></i> | |
| </div> | |
| <div class="flex-1"> | |
| <h3 class="font-medium truncate">${channel.name}</h3> | |
| <p class="text-gray-400 text-xs truncate">${channel.group || 'General'}</p> | |
| </div> | |
| `; | |
| channelItem.addEventListener('click', () => { | |
| playChannel(channel); | |
| }); | |
| channelList.appendChild(channelItem); | |
| }); | |
| feather.replace(); | |
| } catch (error) { | |
| console.error('Error loading channels:', error); | |
| channelList.innerHTML = ` | |
| <div class="text-center py-8 text-gray-400"> | |
| <i data-feather="alert-circle" class="w-12 h-12 mx-auto mb-2"></i> | |
| <p>Failed to load channels. Please try again later.</p> | |
| </div> | |
| `; | |
| feather.replace(); | |
| } | |
| } | |
| // Parse M3U file content | |
| function parseM3U(content) { | |
| const lines = content.split('\n'); | |
| const channels = []; | |
| for (let i = 0; i < lines.length; i++) { | |
| if (lines[i].startsWith('#EXTINF:')) { | |
| const infoLine = lines[i]; | |
| const urlLine = lines[i + 1]; | |
| if (urlLine && !urlLine.startsWith('#')) { | |
| const channel = { | |
| name: extractName(infoLine), | |
| group: extractGroup(infoLine), | |
| url: urlLine.trim() | |
| }; | |
| channels.push(channel); | |
| i++; | |
| } | |
| } | |
| } | |
| return channels; | |
| } | |
| // Extract channel name from EXTINF line | |
| function extractName(extinfLine) { | |
| const match = extinfLine.match(/tvg-name="([^"]*)"/i); | |
| if (match && match[1]) { | |
| return match[1]; | |
| } | |
| // Fallback to last part after comma | |
| const parts = extinfLine.split(','); | |
| return parts[parts.length - 1].trim(); | |
| } | |
| // Extract channel group from EXTINF line | |
| function extractGroup(extinfLine) { | |
| const match = extinfLine.match(/group-title="([^"]*)"/i); | |
| return match && match[1] ? match[1] : null; | |
| } | |
| // Play selected channel | |
| function playChannel(channel) { | |
| playerOverlay.classList.add('hidden'); | |
| currentChannel.textContent = channel.name; | |
| currentProgram.textContent = channel.group ? `Category: ${channel.group}` : 'Live Stream'; | |
| player.pause(); | |
| // Check if HLS.js is needed | |
| if (channel.url.endsWith('.m3u8')) { | |
| if (typeof Hls === 'undefined') { | |
| loadHlsJs().then(() => { | |
| setupHlsPlayer(channel.url); | |
| }); | |
| } else { | |
| setupHlsPlayer(channel.url); | |
| } | |
| } else { | |
| // Direct video source | |
| player.src = channel.url; | |
| player.play(); | |
| } | |
| // Highlight selected channel | |
| const channelItems = document.querySelectorAll('.channel-item'); | |
| channelItems.forEach(item => { | |
| item.classList.remove('bg-rose-500', 'text-white'); | |
| item.classList.add('bg-gray-700', 'hover:bg-gray-600'); | |
| }); | |
| // Find and highlight the clicked channel | |
| const selectedChannel = [...channelItems].find(item => | |
| item.querySelector('h3').textContent === channel.name | |
| ); | |
| if (selectedChannel) { | |
| selectedChannel.classList.remove('bg-gray-700', 'hover:bg-gray-600'); | |
| selectedChannel.classList.add('bg-rose-500', 'text-white'); | |
| } | |
| } | |
| // Load HLS.js dynamically | |
| function loadHlsJs() { | |
| return new Promise((resolve, reject) => { | |
| if (typeof Hls !== 'undefined') { | |
| resolve(); | |
| return; | |
| } | |
| const script = document.createElement('script'); | |
| script.src = 'https://cdn.jsdelivr.net/npm/hls.js@latest'; | |
| script.onload = resolve; | |
| script.onerror = reject; | |
| document.head.appendChild(script); | |
| }); | |
| } | |
| // Setup HLS player | |
| function setupHlsPlayer(url) { | |
| if (Hls.isSupported()) { | |
| const hls = new Hls(); | |
| hls.loadSource(url); | |
| hls.attachMedia(player); | |
| hls.on(Hls.Events.MANIFEST_PARSED, function() { | |
| player.play(); | |
| }); | |
| } else if (player.canPlayType('application/vnd.apple.mpegurl')) { | |
| // For Safari | |
| player.src = url; | |
| player.play(); | |
| } else { | |
| alert('Error: Your browser does not support HLS streaming.'); | |
| } | |
| } | |
| // Initialize the app | |
| loadChannels(); | |
| // Handle volume change from player controls | |
| document.addEventListener('volumechange', (e) => { | |
| player.volume = e.detail.volume; | |
| }); | |
| // Handle play/pause from player controls | |
| document.addEventListener('playpause', () => { | |
| if (player.paused) { | |
| player.play(); | |
| } else { | |
| player.pause(); | |
| } | |
| }); | |
| // Handle fullscreen from player controls | |
| document.addEventListener('fullscreen', () => { | |
| if (player.requestFullscreen) { | |
| player.requestFullscreen(); | |
| } else if (player.webkitRequestFullscreen) { | |
| player.webkitRequestFullscreen(); | |
| } else if (player.msRequestFullscreen) { | |
| player.msRequestFullscreen(); | |
| } | |
| }); | |
| // Channel navigation | |
| let currentChannelIndex = -1; | |
| let channels = []; | |
| document.addEventListener('prevchannel', () => { | |
| if (channels.length === 0) return; | |
| currentChannelIndex = (currentChannelIndex - 1 + channels.length) % channels.length; | |
| playChannel(channels[currentChannelIndex]); | |
| }); | |
| document.addEventListener('nextchannel', () => { | |
| if (channels.length === 0) return; | |
| currentChannelIndex = (currentChannelIndex + 1) % channels.length; | |
| playChannel(channels[currentChannelIndex]); | |
| }); | |
| // Modified loadChannels to store channels globally | |
| async function loadChannels() { | |
| try { | |
| const response = await fetch(m3uUrl); | |
| const text = await response.text(); | |
| channels = parseM3U(text); | |
| channelList.innerHTML = ''; | |
| channels.forEach((channel, index) => { | |
| const channelItem = document.createElement('div'); | |
| channelItem.className = 'channel-item bg-gray-700 hover:bg-gray-600 rounded-lg p-3 cursor-pointer flex items-center transition-all'; | |
| channelItem.innerHTML = ` | |
| <div class="w-10 h-10 rounded-full bg-purple-500 flex items-center justify-center mr-3"> | |
| <i data-feather="tv" class="w-4 h-4"></i> | |
| </div> | |
| <div class="flex-1"> | |
| <h3 class="font-medium truncate">${channel.name}</h3> | |
| <p class="text-gray-400 text-xs truncate">${channel.group || 'General'}</p> | |
| </div> | |
| `; | |
| channelItem.addEventListener('click', () => { | |
| currentChannelIndex = index; | |
| playChannel(channel); | |
| }); | |
| channelList.appendChild(channelItem); | |
| }); | |
| }); |