iptv / index.html
Robshoods's picture
Add 3 files
77d6b31 verified
Raw History Blame Contribute Delete
19.1 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IPTV Player</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.video-container {
aspect-ratio: 16/9;
}
.channel-list {
max-height: calc(100vh - 200px);
overflow-y: auto;
}
.category-item {
transition: all 0.2s ease;
}
.category-item:hover {
transform: translateX(5px);
}
.channel-item {
transition: all 0.2s ease;
}
.channel-item:hover {
background-color: rgba(255, 255, 255, 0.1);
}
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #1a202c;
}
::-webkit-scrollbar-thumb {
background: #4a5568;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #718096;
}
</style>
</head>
<body class="bg-gray-900 text-gray-100">
<div class="container mx-auto px-4 py-6">
<header class="flex justify-between items-center mb-8">
<h1 class="text-3xl font-bold text-blue-400">
<i class="fas fa-tv mr-2"></i> IPTV Player
</h1>
<div class="flex items-center space-x-4">
<div class="relative">
<input type="text" id="searchInput" placeholder="Search channels..."
class="bg-gray-800 text-white px-4 py-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 w-64">
<i class="fas fa-search absolute right-3 top-3 text-gray-400"></i>
</div>
<button id="loadPlaylistBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg flex items-center">
<i class="fas fa-cloud-download-alt mr-2"></i> Load Playlist
</button>
<input type="file" id="fileInput" accept=".m3u,.m3u8" class="hidden">
</div>
</header>
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6">
<!-- Sidebar with categories and channels -->
<div class="lg:col-span-1 bg-gray-800 rounded-xl p-4">
<h2 class="text-xl font-semibold mb-4 flex items-center">
<i class="fas fa-list-alt mr-2 text-blue-400"></i> Categories
</h2>
<div id="categoryList" class="space-y-2 mb-6">
<!-- Categories will be loaded here -->
<div class="text-gray-400 italic">No categories loaded</div>
</div>
<h2 class="text-xl font-semibold mb-4 flex items-center">
<i class="fas fa-tv mr-2 text-blue-400"></i> Channels
</h2>
<div id="channelList" class="channel-list">
<!-- Channels will be loaded here -->
<div class="text-gray-400 italic">Select a category to view channels</div>
</div>
</div>
<!-- Main content area with video player -->
<div class="lg:col-span-3 space-y-6">
<div class="bg-gray-800 rounded-xl overflow-hidden">
<div class="video-container bg-black">
<video id="videoPlayer" controls class="w-full h-full" poster="https://via.placeholder.com/1280x720/1a202c/4a5568?text=Select+a+channel">
Your browser does not support the video tag.
</video>
</div>
</div>
<div class="bg-gray-800 rounded-xl p-4">
<h2 class="text-xl font-semibold mb-4 flex items-center">
<i class="fas fa-info-circle mr-2 text-blue-400"></i> Now Playing
</h2>
<div id="nowPlayingInfo" class="space-y-2">
<div class="text-2xl font-bold" id="currentChannelName">No channel selected</div>
<div class="text-gray-400" id="currentChannelGroup">-</div>
<div class="flex items-center text-gray-400" id="currentChannelUrl">
<i class="fas fa-link mr-2"></i> -
</div>
</div>
</div>
<div class="bg-gray-800 rounded-xl p-4">
<h2 class="text-xl font-semibold mb-4 flex items-center">
<i class="fas fa-history mr-2 text-blue-400"></i> Recently Played
</h2>
<div id="recentChannels" class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
<div class="text-gray-400 italic">No recent channels</div>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const videoPlayer = document.getElementById('videoPlayer');
const loadPlaylistBtn = document.getElementById('loadPlaylistBtn');
const fileInput = document.getElementById('fileInput');
const searchInput = document.getElementById('searchInput');
const categoryList = document.getElementById('categoryList');
const channelList = document.getElementById('channelList');
const currentChannelName = document.getElementById('currentChannelName');
const currentChannelGroup = document.getElementById('currentChannelGroup');
const currentChannelUrl = document.getElementById('currentChannelUrl');
const recentChannels = document.getElementById('recentChannels');
let playlistData = [];
let categories = [];
let recentItems = JSON.parse(localStorage.getItem('recentChannels')) || [];
// Load recent channels
updateRecentChannels();
// Event listeners
loadPlaylistBtn.addEventListener('click', function() {
fileInput.click();
});
fileInput.addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
parseM3UFile(file);
}
});
searchInput.addEventListener('input', function() {
filterChannels(this.value);
});
// Parse M3U file
function parseM3UFile(file) {
const reader = new FileReader();
reader.onload = function(e) {
const content = e.target.result;
const lines = content.split('\n');
playlistData = [];
categories = [];
for (let i = 0; i < lines.length; i++) {
if (lines[i].startsWith('#EXTINF:')) {
const channelInfo = parseExtInf(lines[i]);
const channelUrl = lines[i+1];
if (channelInfo && channelUrl) {
playlistData.push({
...channelInfo,
url: channelUrl.trim()
});
if (channelInfo.group && !categories.includes(channelInfo.group)) {
categories.push(channelInfo.group);
}
}
}
}
displayCategories();
};
reader.readAsText(file);
}
// Parse EXTINF line
function parseExtInf(line) {
const regex = /#EXTINF:-?[0-9]*\s*(?:tvg-id="([^"]*)")?\s*(?:tvg-name="([^"]*)")?\s*(?:tvg-logo="([^"]*)")?\s*(?:group-title="([^"]*)")?\s*(?:,[^"]*)?(.*)/;
const match = line.match(regex);
if (match) {
return {
tvgId: match[1] || '',
tvgName: match[2] || '',
tvgLogo: match[3] || '',
group: match[4] || 'Uncategorized',
name: match[5] || 'Unnamed Channel'
};
}
return null;
}
// Display categories
function displayCategories() {
categoryList.innerHTML = '';
if (categories.length === 0) {
categoryList.innerHTML = '<div class="text-gray-400 italic">No categories found</div>';
return;
}
// Sort categories alphabetically
categories.sort();
// Add "All Channels" option
const allItem = document.createElement('div');
allItem.className = 'category-item cursor-pointer p-2 rounded-lg bg-gray-700 text-white font-medium flex items-center justify-between';
allItem.innerHTML = `
<span>All Channels</span>
<span class="bg-blue-600 text-xs px-2 py-1 rounded-full">${playlistData.length}</span>
`;
allItem.addEventListener('click', function() {
displayChannels(playlistData);
});
categoryList.appendChild(allItem);
// Add each category
categories.forEach(category => {
const count = playlistData.filter(ch => ch.group === category).length;
const item = document.createElement('div');
item.className = 'category-item cursor-pointer p-2 rounded-lg hover:bg-gray-700 text-gray-300 hover:text-white flex items-center justify-between';
item.innerHTML = `
<span>${category}</span>
<span class="bg-gray-600 text-xs px-2 py-1 rounded-full">${count}</span>
`;
item.addEventListener('click', function() {
const categoryChannels = playlistData.filter(ch => ch.group === category);
displayChannels(categoryChannels);
});
categoryList.appendChild(item);
});
}
// Display channels
function displayChannels(channels) {
channelList.innerHTML = '';
if (channels.length === 0) {
channelList.innerHTML = '<div class="text-gray-400 italic">No channels found</div>';
return;
}
channels.forEach(channel => {
const item = document.createElement('div');
item.className = 'channel-item cursor-pointer p-3 rounded-lg hover:bg-gray-700 flex items-center space-x-3';
item.innerHTML = `
<div class="w-10 h-10 bg-gray-600 rounded-full overflow-hidden flex-shrink-0">
${channel.tvgLogo ?
`<img src="${channel.tvgLogo}" alt="${channel.name}" class="w-full h-full object-cover">` :
`<div class="w-full h-full flex items-center justify-center text-xs">${channel.name.substring(0, 2)}</div>`}
</div>
<div class="flex-1 min-w-0">
<div class="font-medium truncate">${channel.name}</div>
<div class="text-xs text-gray-400 truncate">${channel.group}</div>
</div>
<i class="fas fa-play text-blue-400"></i>
`;
item.addEventListener('click', function() {
playChannel(channel);
});
channelList.appendChild(item);
});
}
// Play channel
function playChannel(channel) {
currentChannelName.textContent = channel.name;
currentChannelGroup.textContent = channel.group;
currentChannelUrl.innerHTML = `<i class="fas fa-link mr-2"></i> ${channel.url}`;
// Try to play the video
videoPlayer.src = channel.url;
videoPlayer.load();
videoPlayer.play().catch(e => {
console.error('Error playing video:', e);
alert('Error playing this channel. It may be unavailable or your browser may not support the stream format.');
});
// Add to recent channels
addToRecent(channel);
}
// Add to recent channels
function addToRecent(channel) {
// Remove if already exists
recentItems = recentItems.filter(item => item.url !== channel.url);
// Add to beginning
recentItems.unshift({
name: channel.name,
group: channel.group,
logo: channel.tvgLogo,
url: channel.url
});
// Keep only last 8 items
if (recentItems.length > 8) {
recentItems = recentItems.slice(0, 8);
}
// Save to localStorage
localStorage.setItem('recentChannels', JSON.stringify(recentItems));
// Update UI
updateRecentChannels();
}
// Update recent channels display
function updateRecentChannels() {
recentChannels.innerHTML = '';
if (recentItems.length === 0) {
recentChannels.innerHTML = '<div class="text-gray-400 italic">No recent channels</div>';
return;
}
recentItems.forEach(channel => {
const item = document.createElement('div');
item.className = 'cursor-pointer group';
item.innerHTML = `
<div class="aspect-video bg-gray-700 rounded-lg overflow-hidden relative group-hover:opacity-90 transition-opacity">
${channel.logo ?
`<img src="${channel.logo}" alt="${channel.name}" class="w-full h-full object-cover">` :
`<div class="w-full h-full flex items-center justify-center text-gray-400">
<i class="fas fa-tv text-3xl"></i>
</div>`}
<div class="absolute inset-0 bg-black bg-opacity-40 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
<i class="fas fa-play text-white text-2xl"></i>
</div>
</div>
<div class="mt-2">
<div class="font-medium text-sm truncate">${channel.name}</div>
<div class="text-xs text-gray-400 truncate">${channel.group}</div>
</div>
`;
item.addEventListener('click', function() {
playChannel(channel);
});
recentChannels.appendChild(item);
});
}
// Filter channels by search term
function filterChannels(term) {
if (!term) {
if (categoryList.querySelector('.bg-gray-700')) {
// If a category is selected, show its channels
categoryList.querySelector('.bg-gray-700').click();
} else {
// Otherwise show all channels
displayChannels(playlistData);
}
return;
}
const filtered = playlistData.filter(channel =>
channel.name.toLowerCase().includes(term.toLowerCase()) ||
channel.group.toLowerCase().includes(term.toLowerCase())
);
displayChannels(filtered);
}
// Example: Load a sample playlist URL
function loadSamplePlaylist() {
const sampleUrl = 'http://cdn.sasdscg.com/p/883696456724/389011997309/m3u';
fetch(sampleUrl)
.then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.text();
})
.then(text => {
// Simulate file parsing
const blob = new Blob([text], {type: 'text/plain'});
const file = new File([blob], 'sample.m3u');
parseM3UFile(file);
})
.catch(error => {
console.error('Error loading sample playlist:', error);
alert('Error loading sample playlist. Please try loading your own M3U file.');
});
}
// Uncomment to enable loading a sample playlist on startup
// loadSamplePlaylist();
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Robshoods/iptv" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>