m3u-magic-viewer / script.js
vmk1's picture
do not autoplay, add play, pause , next button.
ef0774a verified
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);
});
});