Spaces:
Running
Running
Update index.html
Browse files- index.html +208 -120
index.html
CHANGED
|
@@ -677,6 +677,61 @@
|
|
| 677 |
box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.1);
|
| 678 |
}
|
| 679 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 680 |
.quick-presets {
|
| 681 |
margin-top: 1.5rem;
|
| 682 |
}
|
|
@@ -1143,68 +1198,6 @@
|
|
| 1143 |
right: 1rem;
|
| 1144 |
z-index: 100;
|
| 1145 |
}
|
| 1146 |
-
|
| 1147 |
-
/* TV Guide (Mini EPG) */
|
| 1148 |
-
.tv-guide {
|
| 1149 |
-
position: absolute;
|
| 1150 |
-
bottom: 5rem;
|
| 1151 |
-
left: 2rem;
|
| 1152 |
-
right: 2rem;
|
| 1153 |
-
background: var(--glass-bg);
|
| 1154 |
-
backdrop-filter: blur(40px);
|
| 1155 |
-
border-radius: 16px;
|
| 1156 |
-
border: 1px solid var(--border);
|
| 1157 |
-
padding: 1.5rem;
|
| 1158 |
-
display: none;
|
| 1159 |
-
z-index: 50;
|
| 1160 |
-
box-shadow: var(--shadow-xl);
|
| 1161 |
-
}
|
| 1162 |
-
|
| 1163 |
-
.guide-header {
|
| 1164 |
-
display: flex;
|
| 1165 |
-
justify-content: space-between;
|
| 1166 |
-
align-items: center;
|
| 1167 |
-
margin-bottom: 1rem;
|
| 1168 |
-
}
|
| 1169 |
-
|
| 1170 |
-
.guide-title {
|
| 1171 |
-
font-size: 1rem;
|
| 1172 |
-
font-weight: 600;
|
| 1173 |
-
display: flex;
|
| 1174 |
-
align-items: center;
|
| 1175 |
-
gap: 0.5rem;
|
| 1176 |
-
}
|
| 1177 |
-
|
| 1178 |
-
.guide-timeline {
|
| 1179 |
-
display: flex;
|
| 1180 |
-
gap: 1rem;
|
| 1181 |
-
overflow-x: auto;
|
| 1182 |
-
padding-bottom: 0.5rem;
|
| 1183 |
-
}
|
| 1184 |
-
|
| 1185 |
-
.guide-item {
|
| 1186 |
-
min-width: 200px;
|
| 1187 |
-
padding: 1rem;
|
| 1188 |
-
background: rgba(255, 255, 255, 0.03);
|
| 1189 |
-
border-radius: 12px;
|
| 1190 |
-
border: 1px solid var(--border);
|
| 1191 |
-
}
|
| 1192 |
-
|
| 1193 |
-
.guide-time {
|
| 1194 |
-
font-size: 0.85rem;
|
| 1195 |
-
color: var(--text-secondary);
|
| 1196 |
-
margin-bottom: 0.5rem;
|
| 1197 |
-
}
|
| 1198 |
-
|
| 1199 |
-
.guide-show {
|
| 1200 |
-
font-weight: 600;
|
| 1201 |
-
margin-bottom: 0.5rem;
|
| 1202 |
-
}
|
| 1203 |
-
|
| 1204 |
-
.guide-desc {
|
| 1205 |
-
font-size: 0.85rem;
|
| 1206 |
-
color: var(--text-secondary);
|
| 1207 |
-
}
|
| 1208 |
</style>
|
| 1209 |
</head>
|
| 1210 |
<body>
|
|
@@ -1346,6 +1339,16 @@
|
|
| 1346 |
</div>
|
| 1347 |
</div>
|
| 1348 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1349 |
<div class="quick-presets">
|
| 1350 |
<div class="preset-title">
|
| 1351 |
<i class="fas fa-bolt"></i>
|
|
@@ -1370,7 +1373,7 @@
|
|
| 1370 |
<div class="action-buttons">
|
| 1371 |
<button class="tv-btn tv-btn-primary" id="loadPlaylistBtn">
|
| 1372 |
<i class="fas fa-play-circle"></i>
|
| 1373 |
-
<span>LOAD</span>
|
| 1374 |
</button>
|
| 1375 |
<button class="tv-btn tv-btn-secondary" id="clearBtn">
|
| 1376 |
<i class="fas fa-trash"></i>
|
|
@@ -1403,7 +1406,7 @@
|
|
| 1403 |
<i class="fas fa-broadcast-tower"></i>
|
| 1404 |
</div>
|
| 1405 |
<h3>No Channels Loaded</h3>
|
| 1406 |
-
<p>Enter a playlist URL
|
| 1407 |
</div>
|
| 1408 |
</div>
|
| 1409 |
</div>
|
|
@@ -1439,26 +1442,6 @@
|
|
| 1439 |
</div>
|
| 1440 |
</div>
|
| 1441 |
</aside>
|
| 1442 |
-
|
| 1443 |
-
<!-- TV Guide Overlay -->
|
| 1444 |
-
<div class="tv-guide" id="tvGuide">
|
| 1445 |
-
<div class="guide-header">
|
| 1446 |
-
<div class="guide-title">
|
| 1447 |
-
<i class="fas fa-tv"></i>
|
| 1448 |
-
<span>NOW PLAYING</span>
|
| 1449 |
-
</div>
|
| 1450 |
-
<button class="tv-control-btn" id="closeGuide">
|
| 1451 |
-
<i class="fas fa-times"></i>
|
| 1452 |
-
</button>
|
| 1453 |
-
</div>
|
| 1454 |
-
<div class="guide-timeline" id="guideTimeline">
|
| 1455 |
-
<div class="guide-item">
|
| 1456 |
-
<div class="guide-time">NOW</div>
|
| 1457 |
-
<div class="guide-show">Select a channel</div>
|
| 1458 |
-
<div class="guide-desc">No program information available</div>
|
| 1459 |
-
</div>
|
| 1460 |
-
</div>
|
| 1461 |
-
</div>
|
| 1462 |
</div>
|
| 1463 |
|
| 1464 |
<!-- Loading Overlay -->
|
|
@@ -1492,13 +1475,17 @@
|
|
| 1492 |
bitrate: 0,
|
| 1493 |
buffer: 0,
|
| 1494 |
health: 100
|
| 1495 |
-
}
|
|
|
|
| 1496 |
};
|
| 1497 |
|
| 1498 |
// DOM Elements
|
| 1499 |
this.elements = {
|
| 1500 |
video: document.getElementById('videoPlayer'),
|
| 1501 |
playlistUrl: document.getElementById('playlistUrl'),
|
|
|
|
|
|
|
|
|
|
| 1502 |
loadPlaylistBtn: document.getElementById('loadPlaylistBtn'),
|
| 1503 |
clearBtn: document.getElementById('clearBtn'),
|
| 1504 |
channelsList: document.getElementById('channelsList'),
|
|
@@ -1524,8 +1511,8 @@
|
|
| 1524 |
initialLoadOverlay: document.getElementById('initialLoadOverlay'),
|
| 1525 |
favoritesBtn: document.getElementById('favoritesBtn'),
|
| 1526 |
guideBtn: document.getElementById('guideBtn'),
|
| 1527 |
-
|
| 1528 |
-
|
| 1529 |
};
|
| 1530 |
|
| 1531 |
// Initialize
|
|
@@ -1560,11 +1547,43 @@
|
|
| 1560 |
}
|
| 1561 |
|
| 1562 |
setupEventListeners() {
|
| 1563 |
-
// Load playlist button
|
| 1564 |
this.elements.loadPlaylistBtn.addEventListener('click', () => {
|
| 1565 |
this.loadPlaylistFromUrl(this.elements.playlistUrl.value);
|
| 1566 |
});
|
| 1567 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1568 |
// Clear button
|
| 1569 |
this.elements.clearBtn.addEventListener('click', () => {
|
| 1570 |
this.clearPlaylist();
|
|
@@ -1629,7 +1648,7 @@
|
|
| 1629 |
});
|
| 1630 |
|
| 1631 |
// Fullscreen
|
| 1632 |
-
|
| 1633 |
this.toggleFullscreen();
|
| 1634 |
});
|
| 1635 |
|
|
@@ -1639,16 +1658,6 @@
|
|
| 1639 |
this.elements.channelsSidebar.style.display === 'none' ? 'flex' : 'none';
|
| 1640 |
});
|
| 1641 |
|
| 1642 |
-
// Guide toggle
|
| 1643 |
-
this.elements.guideBtn.addEventListener('click', () => {
|
| 1644 |
-
this.elements.tvGuide.style.display =
|
| 1645 |
-
this.elements.tvGuide.style.display === 'block' ? 'none' : 'block';
|
| 1646 |
-
});
|
| 1647 |
-
|
| 1648 |
-
this.elements.closeGuide.addEventListener('click', () => {
|
| 1649 |
-
this.elements.tvGuide.style.display = 'none';
|
| 1650 |
-
});
|
| 1651 |
-
|
| 1652 |
// Video events
|
| 1653 |
this.elements.video.addEventListener('play', () => {
|
| 1654 |
this.state.isPlaying = true;
|
|
@@ -1704,26 +1713,9 @@
|
|
| 1704 |
}, 3000);
|
| 1705 |
});
|
| 1706 |
|
| 1707 |
-
//
|
| 1708 |
-
this.elements.
|
| 1709 |
-
|
| 1710 |
-
e.currentTarget.style.borderColor = 'var(--primary)';
|
| 1711 |
-
e.currentTarget.style.background = 'rgba(0, 102, 255, 0.1)';
|
| 1712 |
-
});
|
| 1713 |
-
|
| 1714 |
-
this.elements.playlistUrl.addEventListener('dragleave', (e) => {
|
| 1715 |
-
e.currentTarget.style.borderColor = 'var(--border)';
|
| 1716 |
-
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.05)';
|
| 1717 |
-
});
|
| 1718 |
-
|
| 1719 |
-
this.elements.playlistUrl.addEventListener('drop', (e) => {
|
| 1720 |
-
e.preventDefault();
|
| 1721 |
-
const text = e.dataTransfer.getData('text');
|
| 1722 |
-
if (text) {
|
| 1723 |
-
this.elements.playlistUrl.value = text;
|
| 1724 |
-
}
|
| 1725 |
-
e.currentTarget.style.borderColor = 'var(--border)';
|
| 1726 |
-
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.05)';
|
| 1727 |
});
|
| 1728 |
}
|
| 1729 |
|
|
@@ -1776,12 +1768,44 @@
|
|
| 1776 |
if (document.fullscreenElement) {
|
| 1777 |
document.exitFullscreen();
|
| 1778 |
}
|
| 1779 |
-
this.elements.tvGuide.style.display = 'none';
|
| 1780 |
break;
|
| 1781 |
}
|
| 1782 |
});
|
| 1783 |
}
|
| 1784 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1785 |
async loadPlaylistFromUrl(url) {
|
| 1786 |
if (!url) {
|
| 1787 |
this.showNotification('Please enter a playlist URL', 'error');
|
|
@@ -2215,6 +2239,7 @@
|
|
| 2215 |
this.state.channels = [];
|
| 2216 |
this.state.filteredChannels = [];
|
| 2217 |
this.state.currentChannel = null;
|
|
|
|
| 2218 |
|
| 2219 |
if (this.state.hls) {
|
| 2220 |
this.state.hls.destroy();
|
|
@@ -2223,6 +2248,8 @@
|
|
| 2223 |
|
| 2224 |
this.elements.video.pause();
|
| 2225 |
this.elements.video.src = '';
|
|
|
|
|
|
|
| 2226 |
|
| 2227 |
this.renderChannels();
|
| 2228 |
this.elements.channelCount.textContent = '0';
|
|
@@ -2239,6 +2266,46 @@
|
|
| 2239 |
this.elements.loadingOverlay.style.display = show ? 'flex' : 'none';
|
| 2240 |
}
|
| 2241 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2242 |
showNotification(message, type = 'info') {
|
| 2243 |
// Create notification
|
| 2244 |
const notification = document.createElement('div');
|
|
@@ -2285,6 +2352,27 @@
|
|
| 2285 |
}
|
| 2286 |
}, 3000);
|
| 2287 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2288 |
}
|
| 2289 |
|
| 2290 |
// Initialize Clarke Player Ultra
|
|
|
|
| 677 |
box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.1);
|
| 678 |
}
|
| 679 |
|
| 680 |
+
/* File Upload Area */
|
| 681 |
+
.file-upload-area {
|
| 682 |
+
border: 2px dashed var(--border);
|
| 683 |
+
border-radius: 12px;
|
| 684 |
+
padding: 2rem 1rem;
|
| 685 |
+
text-align: center;
|
| 686 |
+
cursor: pointer;
|
| 687 |
+
transition: var(--transition);
|
| 688 |
+
background: rgba(255, 255, 255, 0.02);
|
| 689 |
+
position: relative;
|
| 690 |
+
overflow: hidden;
|
| 691 |
+
}
|
| 692 |
+
|
| 693 |
+
.file-upload-area:hover {
|
| 694 |
+
border-color: var(--primary);
|
| 695 |
+
background: rgba(255, 255, 255, 0.05);
|
| 696 |
+
}
|
| 697 |
+
|
| 698 |
+
.file-upload-area.dragover {
|
| 699 |
+
border-color: var(--primary);
|
| 700 |
+
background: rgba(0, 102, 255, 0.1);
|
| 701 |
+
box-shadow: var(--glow-primary);
|
| 702 |
+
}
|
| 703 |
+
|
| 704 |
+
.file-upload-area i {
|
| 705 |
+
font-size: 2.5rem;
|
| 706 |
+
color: var(--text-muted);
|
| 707 |
+
margin-bottom: 1rem;
|
| 708 |
+
display: block;
|
| 709 |
+
}
|
| 710 |
+
|
| 711 |
+
.file-upload-area p {
|
| 712 |
+
font-size: 0.9rem;
|
| 713 |
+
color: var(--text-secondary);
|
| 714 |
+
margin: 0;
|
| 715 |
+
line-height: 1.4;
|
| 716 |
+
}
|
| 717 |
+
|
| 718 |
+
.file-upload-area .file-name {
|
| 719 |
+
font-size: 0.85rem;
|
| 720 |
+
color: var(--primary-light);
|
| 721 |
+
margin-top: 0.5rem;
|
| 722 |
+
font-weight: 500;
|
| 723 |
+
}
|
| 724 |
+
|
| 725 |
+
.file-input {
|
| 726 |
+
position: absolute;
|
| 727 |
+
width: 100%;
|
| 728 |
+
height: 100%;
|
| 729 |
+
top: 0;
|
| 730 |
+
left: 0;
|
| 731 |
+
opacity: 0;
|
| 732 |
+
cursor: pointer;
|
| 733 |
+
}
|
| 734 |
+
|
| 735 |
.quick-presets {
|
| 736 |
margin-top: 1.5rem;
|
| 737 |
}
|
|
|
|
| 1198 |
right: 1rem;
|
| 1199 |
z-index: 100;
|
| 1200 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1201 |
</style>
|
| 1202 |
</head>
|
| 1203 |
<body>
|
|
|
|
| 1339 |
</div>
|
| 1340 |
</div>
|
| 1341 |
|
| 1342 |
+
<!-- File Upload Area -->
|
| 1343 |
+
<div class="input-group">
|
| 1344 |
+
<div class="file-upload-area" id="fileUploadArea">
|
| 1345 |
+
<i class="fas fa-file-upload"></i>
|
| 1346 |
+
<p>Drag & drop .m3u/.m3u8 file here<br>or click to browse</p>
|
| 1347 |
+
<div class="file-name" id="fileName"></div>
|
| 1348 |
+
<input type="file" id="fileInput" class="file-input" accept=".m3u,.m3u8">
|
| 1349 |
+
</div>
|
| 1350 |
+
</div>
|
| 1351 |
+
|
| 1352 |
<div class="quick-presets">
|
| 1353 |
<div class="preset-title">
|
| 1354 |
<i class="fas fa-bolt"></i>
|
|
|
|
| 1373 |
<div class="action-buttons">
|
| 1374 |
<button class="tv-btn tv-btn-primary" id="loadPlaylistBtn">
|
| 1375 |
<i class="fas fa-play-circle"></i>
|
| 1376 |
+
<span>LOAD URL</span>
|
| 1377 |
</button>
|
| 1378 |
<button class="tv-btn tv-btn-secondary" id="clearBtn">
|
| 1379 |
<i class="fas fa-trash"></i>
|
|
|
|
| 1406 |
<i class="fas fa-broadcast-tower"></i>
|
| 1407 |
</div>
|
| 1408 |
<h3>No Channels Loaded</h3>
|
| 1409 |
+
<p>Enter a playlist URL or upload a file to begin</p>
|
| 1410 |
</div>
|
| 1411 |
</div>
|
| 1412 |
</div>
|
|
|
|
| 1442 |
</div>
|
| 1443 |
</div>
|
| 1444 |
</aside>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1445 |
</div>
|
| 1446 |
|
| 1447 |
<!-- Loading Overlay -->
|
|
|
|
| 1475 |
bitrate: 0,
|
| 1476 |
buffer: 0,
|
| 1477 |
health: 100
|
| 1478 |
+
},
|
| 1479 |
+
currentFile: null
|
| 1480 |
};
|
| 1481 |
|
| 1482 |
// DOM Elements
|
| 1483 |
this.elements = {
|
| 1484 |
video: document.getElementById('videoPlayer'),
|
| 1485 |
playlistUrl: document.getElementById('playlistUrl'),
|
| 1486 |
+
fileInput: document.getElementById('fileInput'),
|
| 1487 |
+
fileUploadArea: document.getElementById('fileUploadArea'),
|
| 1488 |
+
fileName: document.getElementById('fileName'),
|
| 1489 |
loadPlaylistBtn: document.getElementById('loadPlaylistBtn'),
|
| 1490 |
clearBtn: document.getElementById('clearBtn'),
|
| 1491 |
channelsList: document.getElementById('channelsList'),
|
|
|
|
| 1511 |
initialLoadOverlay: document.getElementById('initialLoadOverlay'),
|
| 1512 |
favoritesBtn: document.getElementById('favoritesBtn'),
|
| 1513 |
guideBtn: document.getElementById('guideBtn'),
|
| 1514 |
+
settingsBtn: document.getElementById('settingsBtn'),
|
| 1515 |
+
fullscreenBtn: document.getElementById('fullscreenBtn')
|
| 1516 |
};
|
| 1517 |
|
| 1518 |
// Initialize
|
|
|
|
| 1547 |
}
|
| 1548 |
|
| 1549 |
setupEventListeners() {
|
| 1550 |
+
// Load playlist from URL button
|
| 1551 |
this.elements.loadPlaylistBtn.addEventListener('click', () => {
|
| 1552 |
this.loadPlaylistFromUrl(this.elements.playlistUrl.value);
|
| 1553 |
});
|
| 1554 |
|
| 1555 |
+
// File input change
|
| 1556 |
+
this.elements.fileInput.addEventListener('change', (e) => {
|
| 1557 |
+
if (e.target.files.length > 0) {
|
| 1558 |
+
this.handleFileSelect(e.target.files[0]);
|
| 1559 |
+
}
|
| 1560 |
+
});
|
| 1561 |
+
|
| 1562 |
+
// Drag and drop for file upload
|
| 1563 |
+
this.elements.fileUploadArea.addEventListener('dragover', (e) => {
|
| 1564 |
+
e.preventDefault();
|
| 1565 |
+
e.currentTarget.classList.add('dragover');
|
| 1566 |
+
});
|
| 1567 |
+
|
| 1568 |
+
this.elements.fileUploadArea.addEventListener('dragleave', (e) => {
|
| 1569 |
+
e.preventDefault();
|
| 1570 |
+
e.currentTarget.classList.remove('dragover');
|
| 1571 |
+
});
|
| 1572 |
+
|
| 1573 |
+
this.elements.fileUploadArea.addEventListener('drop', (e) => {
|
| 1574 |
+
e.preventDefault();
|
| 1575 |
+
e.currentTarget.classList.remove('dragover');
|
| 1576 |
+
|
| 1577 |
+
if (e.dataTransfer.files.length > 0) {
|
| 1578 |
+
const file = e.dataTransfer.files[0];
|
| 1579 |
+
if (file.name.endsWith('.m3u') || file.name.endsWith('.m3u8')) {
|
| 1580 |
+
this.handleFileSelect(file);
|
| 1581 |
+
} else {
|
| 1582 |
+
this.showNotification('Please select a .m3u or .m3u8 file', 'error');
|
| 1583 |
+
}
|
| 1584 |
+
}
|
| 1585 |
+
});
|
| 1586 |
+
|
| 1587 |
// Clear button
|
| 1588 |
this.elements.clearBtn.addEventListener('click', () => {
|
| 1589 |
this.clearPlaylist();
|
|
|
|
| 1648 |
});
|
| 1649 |
|
| 1650 |
// Fullscreen
|
| 1651 |
+
this.elements.fullscreenBtn.addEventListener('click', () => {
|
| 1652 |
this.toggleFullscreen();
|
| 1653 |
});
|
| 1654 |
|
|
|
|
| 1658 |
this.elements.channelsSidebar.style.display === 'none' ? 'flex' : 'none';
|
| 1659 |
});
|
| 1660 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1661 |
// Video events
|
| 1662 |
this.elements.video.addEventListener('play', () => {
|
| 1663 |
this.state.isPlaying = true;
|
|
|
|
| 1713 |
}, 3000);
|
| 1714 |
});
|
| 1715 |
|
| 1716 |
+
// Settings button
|
| 1717 |
+
this.elements.settingsBtn.addEventListener('click', () => {
|
| 1718 |
+
this.showSettings();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1719 |
});
|
| 1720 |
}
|
| 1721 |
|
|
|
|
| 1768 |
if (document.fullscreenElement) {
|
| 1769 |
document.exitFullscreen();
|
| 1770 |
}
|
|
|
|
| 1771 |
break;
|
| 1772 |
}
|
| 1773 |
});
|
| 1774 |
}
|
| 1775 |
|
| 1776 |
+
handleFileSelect(file) {
|
| 1777 |
+
if (!file.name.endsWith('.m3u') && !file.name.endsWith('.m3u8')) {
|
| 1778 |
+
this.showNotification('Please select a .m3u or .m3u8 file', 'error');
|
| 1779 |
+
return;
|
| 1780 |
+
}
|
| 1781 |
+
|
| 1782 |
+
this.state.currentFile = file;
|
| 1783 |
+
this.elements.fileName.textContent = file.name;
|
| 1784 |
+
this.updateStatus('File selected: ' + file.name);
|
| 1785 |
+
|
| 1786 |
+
// Auto-load the file
|
| 1787 |
+
this.loadPlaylistFromFile(file);
|
| 1788 |
+
}
|
| 1789 |
+
|
| 1790 |
+
async loadPlaylistFromFile(file) {
|
| 1791 |
+
this.updateStatus('Loading playlist file...');
|
| 1792 |
+
this.showLoading(true);
|
| 1793 |
+
|
| 1794 |
+
try {
|
| 1795 |
+
const text = await file.text();
|
| 1796 |
+
await this.parsePlaylist(text, file.name);
|
| 1797 |
+
|
| 1798 |
+
this.showNotification(`Playlist loaded: ${file.name}`, 'success');
|
| 1799 |
+
|
| 1800 |
+
} catch (error) {
|
| 1801 |
+
console.error('Failed to load file:', error);
|
| 1802 |
+
this.showNotification('Failed to load playlist file', 'error');
|
| 1803 |
+
this.updateStatus('Load failed');
|
| 1804 |
+
} finally {
|
| 1805 |
+
this.showLoading(false);
|
| 1806 |
+
}
|
| 1807 |
+
}
|
| 1808 |
+
|
| 1809 |
async loadPlaylistFromUrl(url) {
|
| 1810 |
if (!url) {
|
| 1811 |
this.showNotification('Please enter a playlist URL', 'error');
|
|
|
|
| 2239 |
this.state.channels = [];
|
| 2240 |
this.state.filteredChannels = [];
|
| 2241 |
this.state.currentChannel = null;
|
| 2242 |
+
this.state.currentFile = null;
|
| 2243 |
|
| 2244 |
if (this.state.hls) {
|
| 2245 |
this.state.hls.destroy();
|
|
|
|
| 2248 |
|
| 2249 |
this.elements.video.pause();
|
| 2250 |
this.elements.video.src = '';
|
| 2251 |
+
this.elements.fileName.textContent = '';
|
| 2252 |
+
this.elements.fileInput.value = '';
|
| 2253 |
|
| 2254 |
this.renderChannels();
|
| 2255 |
this.elements.channelCount.textContent = '0';
|
|
|
|
| 2266 |
this.elements.loadingOverlay.style.display = show ? 'flex' : 'none';
|
| 2267 |
}
|
| 2268 |
|
| 2269 |
+
showSettings() {
|
| 2270 |
+
const settings = `
|
| 2271 |
+
<div class="overlay-content" style="max-width: 500px;">
|
| 2272 |
+
<div class="overlay-icon">
|
| 2273 |
+
<i class="fas fa-cog"></i>
|
| 2274 |
+
</div>
|
| 2275 |
+
<h2 class="overlay-title">Settings</h2>
|
| 2276 |
+
<div style="text-align: left; margin-bottom: 2rem;">
|
| 2277 |
+
<div style="margin-bottom: 1rem;">
|
| 2278 |
+
<label style="display: block; margin-bottom: 0.5rem; color: var(--text-secondary);">Video Quality</label>
|
| 2279 |
+
<select style="width: 100%; padding: 0.75rem; background: rgba(255,255,255,0.05); border: 1px solid var(--border); border-radius: 8px; color: var(--text-primary);">
|
| 2280 |
+
<option>Auto</option>
|
| 2281 |
+
<option>1080p</option>
|
| 2282 |
+
<option>720p</option>
|
| 2283 |
+
<option>480p</option>
|
| 2284 |
+
</select>
|
| 2285 |
+
</div>
|
| 2286 |
+
<div style="margin-bottom: 1rem;">
|
| 2287 |
+
<label style="display: block; margin-bottom: 0.5rem; color: var(--text-secondary);">Buffer Size</label>
|
| 2288 |
+
<select style="width: 100%; padding: 0.75rem; background: rgba(255,255,255,0.05); border: 1px solid var(--border); border-radius: 8px; color: var(--text-primary);">
|
| 2289 |
+
<option>30 seconds</option>
|
| 2290 |
+
<option>60 seconds</option>
|
| 2291 |
+
<option>90 seconds</option>
|
| 2292 |
+
</select>
|
| 2293 |
+
</div>
|
| 2294 |
+
</div>
|
| 2295 |
+
<div class="overlay-buttons">
|
| 2296 |
+
<button class="tv-btn tv-btn-primary" style="padding: 0.75rem 2rem;">
|
| 2297 |
+
Save
|
| 2298 |
+
</button>
|
| 2299 |
+
<button class="tv-btn tv-btn-secondary" style="padding: 0.75rem 2rem;">
|
| 2300 |
+
Cancel
|
| 2301 |
+
</button>
|
| 2302 |
+
</div>
|
| 2303 |
+
</div>
|
| 2304 |
+
`;
|
| 2305 |
+
|
| 2306 |
+
this.showOverlay(settings);
|
| 2307 |
+
}
|
| 2308 |
+
|
| 2309 |
showNotification(message, type = 'info') {
|
| 2310 |
// Create notification
|
| 2311 |
const notification = document.createElement('div');
|
|
|
|
| 2352 |
}
|
| 2353 |
}, 3000);
|
| 2354 |
}
|
| 2355 |
+
|
| 2356 |
+
showOverlay(content) {
|
| 2357 |
+
const overlay = document.createElement('div');
|
| 2358 |
+
overlay.className = 'tv-overlay';
|
| 2359 |
+
overlay.style.display = 'flex';
|
| 2360 |
+
overlay.innerHTML = content;
|
| 2361 |
+
|
| 2362 |
+
document.body.appendChild(overlay);
|
| 2363 |
+
|
| 2364 |
+
// Add close button functionality
|
| 2365 |
+
overlay.addEventListener('click', (e) => {
|
| 2366 |
+
if (e.target === overlay || e.target.closest('.tv-btn-secondary')) {
|
| 2367 |
+
overlay.style.animation = 'fadeIn 0.3s ease reverse';
|
| 2368 |
+
setTimeout(() => {
|
| 2369 |
+
if (overlay.parentNode) {
|
| 2370 |
+
overlay.parentNode.removeChild(overlay);
|
| 2371 |
+
}
|
| 2372 |
+
}, 300);
|
| 2373 |
+
}
|
| 2374 |
+
});
|
| 2375 |
+
}
|
| 2376 |
}
|
| 2377 |
|
| 2378 |
// Initialize Clarke Player Ultra
|