diff --git a/index.php b/index.php
index 570ec47..fb97ee1 100644
--- a/index.php
+++ b/index.php
@@ -631,6 +631,7 @@ $m3u_url = htmlspecialchars($config["m3u_url"] ?? "");
#navBtns { display: none !important; }
}
+
@@ -690,13 +691,24 @@ foreach ($theme_files as $file) {
preg_match('/@emoji\s+(\S+)/u', $css, $m_emoji);
$name = isset($m_name[1]) ? trim($m_name[1]) : ucfirst($slug);
$emoji = isset($m_emoji[1]) ? trim($m_emoji[1]) : '๐จ';
- echo "
@@ -734,6 +746,7 @@ function msToX(ms) { return (ms - GRID_START.getTime()) / 60000 * PX_PER_MIN; }
let channels = [];
let programs = {};
+let m3uStreams = {}; // slug normalisรฉ โ url stream
let scrollListenerAdded = false; // sync scroll vertical chaรฎnes
function pad(n) { return String(n).padStart(2,'0'); }
@@ -891,6 +904,7 @@ function renderChannels() {
const name = document.createElement('div');
name.className = 'channel-name'; name.textContent = ch.name; name.title = ch.name;
cell.appendChild(name);
+ cell.addEventListener('click', () => openPip(ch.name, getNowPlaying(ch.id)));
list.appendChild(cell);
});
}
@@ -953,6 +967,9 @@ function renderPrograms() {
block.addEventListener('mouseenter', e=>showTooltip(e,p));
block.addEventListener('mousemove', e=>moveTooltip(e));
block.addEventListener('mouseleave', hideTooltip);
+ if (p.start <= now && p.stop > now) {
+ block.addEventListener('click', () => openPip(ch.name, p.title));
+ }
row.appendChild(block);
});
inner.appendChild(row);
@@ -1042,6 +1059,13 @@ function renderMobile() {
});
}
+// โโ HELPERS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+function getNowPlaying(chId) {
+ const now = new Date();
+ const p = (programs[chId] || []).find(p => p.start <= now && p.stop > now);
+ return p ? p.title : null;
+}
+
// โโ TOOLTIP โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function showTooltip(e, p) {
const ep = fmtEpisode(p.season, p.episode);
@@ -1060,6 +1084,105 @@ function moveTooltip(e) {
function hideTooltip() { document.getElementById('tooltip').classList.remove('visible'); }
+
+// โโ M3U PARSER โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+function slugify(str) {
+ return str.toLowerCase()
+ .replace(/^\d+\s*/, '') // virer le numรฉro de chaรฎne en dรฉbut
+ .replace(/[^a-z0-9]/g, ''); // garder que alphanum
+}
+
+async function loadM3U(url) {
+ if (!url) return;
+ try {
+ let text;
+ try {
+ const r = await fetch(url); text = await r.text();
+ } catch(e) {
+ const r = await fetch(CORS_PROXY + encodeURIComponent(url)); text = await r.text();
+ }
+ parseM3U(text);
+ } catch(e) { console.warn('M3U load failed:', e); }
+}
+
+function parseM3U(text) {
+ m3uStreams = {};
+ const lines = text.split('\n');
+ let currentName = null;
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i].trim();
+ if (line.startsWith('#EXTINF')) {
+ // Extraire le nom : derniรจre partie aprรจs la virgule
+ const comma = line.lastIndexOf(',');
+ currentName = comma !== -1 ? line.slice(comma + 1).trim() : null;
+ } else if (line && !line.startsWith('#') && currentName) {
+ m3uStreams[slugify(currentName)] = line;
+ currentName = null;
+ }
+ }
+ console.log(`M3U: ${Object.keys(m3uStreams).length} streams chargรฉs`);
+}
+
+function getStreamUrl(channelName) {
+ const slug = slugify(channelName);
+ if (m3uStreams[slug]) return m3uStreams[slug];
+ // Fallback : chercher une clรฉ qui contient le slug ou l'inverse
+ for (const [key, url] of Object.entries(m3uStreams)) {
+ if (key.includes(slug) || slug.includes(key)) return url;
+ }
+ return null;
+}
+
+// โโ PLAYER PiP โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+let hlsInstance = null;
+
+function openPip(channelName, programTitle) {
+ const url = getStreamUrl(channelName);
+ const pip = document.getElementById('pip');
+ const video = document.getElementById('pipVideo');
+ const err = document.getElementById('pipError');
+
+ document.getElementById('pipChannel').textContent = channelName;
+ document.getElementById('pipProgram').textContent = programTitle || '';
+ err.style.display = 'none';
+ pip.classList.add('visible');
+
+ // Dรฉtruire l'instance HLS prรฉcรฉdente
+ if (hlsInstance) { hlsInstance.destroy(); hlsInstance = null; }
+ video.src = '';
+
+ if (!url) {
+ err.style.display = 'block';
+ err.textContent = 'โ Aucun stream trouvรฉ pour cette chaรฎne';
+ return;
+ }
+
+ if (Hls.isSupported()) {
+ hlsInstance = new Hls({ enableWorker: true });
+ hlsInstance.loadSource(url);
+ hlsInstance.attachMedia(video);
+ hlsInstance.on(Hls.Events.MANIFEST_PARSED, () => video.play().catch(() => {}));
+ hlsInstance.on(Hls.Events.ERROR, (e, data) => {
+ if (data.fatal) { err.style.display = 'block'; err.textContent = 'โ Erreur de lecture du stream'; }
+ });
+ } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
+ // Safari natif HLS
+ video.src = url;
+ video.addEventListener('loadedmetadata', () => video.play().catch(() => {}));
+ } else {
+ err.style.display = 'block';
+ err.textContent = 'โ HLS non supportรฉ sur ce navigateur';
+ }
+}
+
+function closePip() {
+ const pip = document.getElementById('pip');
+ const video = document.getElementById('pipVideo');
+ pip.classList.remove('visible');
+ if (hlsInstance) { hlsInstance.destroy(); hlsInstance = null; }
+ video.pause(); video.src = '';
+}
+
// โโ THรMES โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function setTheme(theme) {
document.getElementById('themeStylesheet').href = 'themes/' + theme + '.css';
@@ -1087,7 +1210,7 @@ window.addEventListener('resize', () => {
if (m !== lastMobile) { lastMobile=m; scrollListenerAdded=false; if (channels.length) renderAll(); }
});
-window.addEventListener('load', () => loadEPG(DEFAULT_EPG_URL));
+window.addEventListener('load', () => { loadEPG(DEFAULT_EPG_URL); loadM3U(DEFAULT_M3U_URL); });