Adjusting comments into files.
This commit is contained in:
+2
-2
@@ -2,7 +2,7 @@ const EPG_SOURCES = <?= json_encode(array_values($epg_sources)) ?>;
|
||||
const L = <?= json_encode($L) ?>;
|
||||
const ALLOW_PERSONAL_EPG = <?= $allow_personal_epg ? 'true' : 'false' ?>;
|
||||
|
||||
// Source active (index)
|
||||
// Index of the currently selected source.
|
||||
let activeSourceIndex = 0;
|
||||
|
||||
const DEFAULT_EPG_URL = EPG_SOURCES[0]?.epg_url ?? '';
|
||||
@@ -11,7 +11,7 @@ const PX_PER_MIN = 5;
|
||||
const GRID_HOURS = 72;
|
||||
const CORS_PROXY = 'https://corsproxy.io/?';
|
||||
|
||||
// Ancre : 2h avant maintenant, arrondi au quart d'heure inférieur
|
||||
// Anchor the grid 2 hours before "now", rounded down to the previous quarter-hour.
|
||||
const _d = new Date();
|
||||
_d.setMinutes(Math.floor(_d.getMinutes()/15)*15, 0, 0);
|
||||
const GRID_START = new Date(_d.getTime() - 2*3600000);
|
||||
|
||||
+1
-2
@@ -73,7 +73,7 @@ function renderPrograms() {
|
||||
inner.style.width = TOTAL_WIDTH_PX+'px';
|
||||
inner.style.height = totalH+'px';
|
||||
|
||||
// Remettre la now-line (détruite par innerHTML='')
|
||||
// Recreate the now-line because it was removed by innerHTML = ''.
|
||||
const nowLine = document.createElement('div');
|
||||
nowLine.className = 'now-line'; nowLine.id = 'nowLine';
|
||||
nowLine.style.height = totalH + 'px';
|
||||
@@ -141,4 +141,3 @@ function shiftView(mins) {
|
||||
area.scrollLeft = Math.max(0, area.scrollLeft + mins * PX_PER_MIN);
|
||||
document.querySelectorAll('.nav-btn').forEach(b => b.classList.remove('active'));
|
||||
}
|
||||
|
||||
|
||||
+5
-6
@@ -1,8 +1,8 @@
|
||||
// ── 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
|
||||
.replace(/^\d+\s*/, '') // Strip any leading channel number.
|
||||
.replace(/[^a-z0-9]/g, ''); // Keep only alphanumeric characters.
|
||||
}
|
||||
|
||||
async function loadM3U(url) {
|
||||
@@ -25,7 +25,7 @@ function parseM3U(text) {
|
||||
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
|
||||
// Extract the display name from the last comma-separated segment.
|
||||
const comma = line.lastIndexOf(',');
|
||||
currentName = comma !== -1 ? line.slice(comma + 1).trim() : null;
|
||||
} else if (line && !line.startsWith('#') && currentName) {
|
||||
@@ -42,16 +42,15 @@ function getStreamUrl(channelName) {
|
||||
if (m3uStreams[slug]) {
|
||||
url = m3uStreams[slug];
|
||||
} else {
|
||||
// Fallback : chercher une clé qui contient le slug ou l'inverse
|
||||
// Fallback: try a partial match in either direction.
|
||||
for (const [key, u] of Object.entries(m3uStreams)) {
|
||||
if (key.includes(slug) || slug.includes(key)) { url = u; break; }
|
||||
}
|
||||
}
|
||||
if (!url) return null;
|
||||
// Passer par proxy.php pour éviter le mixed content HTTP/HTTPS
|
||||
// Route plain HTTP streams through proxy.php to avoid mixed-content issues.
|
||||
if (url.startsWith('http://')) {
|
||||
return 'proxy.php?url=' + encodeURIComponent(url);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
|
||||
+2
-3
@@ -12,7 +12,7 @@ function openPip(channelName, programTitle) {
|
||||
err.style.display = 'none';
|
||||
pip.classList.add('visible');
|
||||
|
||||
// Détruire l'instance HLS précédente
|
||||
// Tear down the previous HLS instance before opening another stream.
|
||||
if (hlsInstance) { hlsInstance.destroy(); hlsInstance = null; }
|
||||
video.src = '';
|
||||
|
||||
@@ -39,7 +39,7 @@ function openPip(channelName, programTitle) {
|
||||
}
|
||||
});
|
||||
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
||||
// Safari natif HLS
|
||||
// Safari can play HLS natively without hls.js.
|
||||
video.src = url;
|
||||
video.addEventListener('loadedmetadata', () => video.play().catch(() => {}));
|
||||
} else {
|
||||
@@ -55,4 +55,3 @@ function closePip() {
|
||||
if (hlsInstance) { hlsInstance.destroy(); hlsInstance = null; }
|
||||
video.pause(); video.src = '';
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -13,7 +13,7 @@ function openProgramModal(ch, p) {
|
||||
document.getElementById('pm-desc').textContent = p.desc || '';
|
||||
document.getElementById('pm-desc').style.display = p.desc ? 'block' : 'none';
|
||||
|
||||
// Bouton Watch now — seulement si live ET M3U fourni pour la source active
|
||||
// Only show "Watch now" when the program is live and the active source has M3U data.
|
||||
const watchBtn = document.getElementById('pm-watch');
|
||||
if (isLive && currentM3uUrl) {
|
||||
watchBtn.style.display = 'inline-flex';
|
||||
@@ -22,7 +22,7 @@ function openProgramModal(ch, p) {
|
||||
watchBtn.style.display = 'none';
|
||||
}
|
||||
|
||||
// Bouton IMDb
|
||||
// IMDb search shortcut.
|
||||
document.getElementById('pm-imdb').href =
|
||||
`https://www.imdb.com/find/?q=${encodeURIComponent(p.title)}&s=tt`;
|
||||
|
||||
@@ -33,13 +33,13 @@ function closeProgramModal() {
|
||||
document.getElementById('programModal').classList.remove('visible');
|
||||
}
|
||||
|
||||
// Fermer sur clic overlay
|
||||
// Close when the overlay itself is clicked.
|
||||
document.addEventListener('click', e => {
|
||||
const modal = document.getElementById('programModal');
|
||||
if (e.target === modal) closeProgramModal();
|
||||
});
|
||||
|
||||
// Fermer sur Escape
|
||||
// Close on Escape.
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === 'Escape') closeProgramModal();
|
||||
});
|
||||
|
||||
+8
-8
@@ -31,7 +31,7 @@ function onSearchInput(val) {
|
||||
|
||||
function applySearch(q) {
|
||||
if (!q) {
|
||||
// Remettre toutes les chaines
|
||||
// Restore the full channel list when the query is cleared.
|
||||
renderAll();
|
||||
return;
|
||||
}
|
||||
@@ -40,11 +40,11 @@ function applySearch(q) {
|
||||
today.setHours(0,0,0,0);
|
||||
const tomorrow = new Date(today.getTime() + 24*3600000);
|
||||
|
||||
// Filtrer les chaines : nom OU programme aujourd'hui correspond
|
||||
// Keep channels whose name matches or that have a matching program today.
|
||||
const filtered = channels.filter(ch => {
|
||||
// Match sur le nom de la chaine
|
||||
// Match against the channel name.
|
||||
if (ch.name.toLowerCase().includes(q)) return true;
|
||||
// Match sur les programmes d'aujourd'hui (titre + desc)
|
||||
// Match against today's programs (title + description).
|
||||
return (programs[ch.id]||[]).some(p => {
|
||||
if (p.stop < today || p.start > tomorrow) return false;
|
||||
if (p.title.toLowerCase().includes(q)) return true;
|
||||
@@ -72,7 +72,7 @@ function renderFiltered(filteredChannels, q) {
|
||||
inner.style.width = TOTAL_WIDTH_PX + 'px';
|
||||
inner.style.height = (filteredChannels.length * ROW_H) + 'px';
|
||||
|
||||
// Remettre la now-line
|
||||
// Recreate the now-line after clearing the container.
|
||||
const nowLine = document.createElement('div');
|
||||
nowLine.className = 'now-line'; nowLine.id = 'nowLine';
|
||||
nowLine.style.height = (filteredChannels.length * ROW_H) + 'px';
|
||||
@@ -81,7 +81,7 @@ function renderFiltered(filteredChannels, q) {
|
||||
const now = new Date();
|
||||
|
||||
filteredChannels.forEach((ch, i) => {
|
||||
// Colonne chaîne
|
||||
// Channel column.
|
||||
const cell = document.createElement('div');
|
||||
cell.className = 'channel-cell';
|
||||
if (ch.icon) {
|
||||
@@ -96,7 +96,7 @@ function renderFiltered(filteredChannels, q) {
|
||||
cell.addEventListener('click', () => openPip(ch.name, getNowPlaying(ch.id)));
|
||||
list.appendChild(cell);
|
||||
|
||||
// Programmes
|
||||
// Programs row.
|
||||
const row = document.createElement('div');
|
||||
row.className = 'program-row';
|
||||
row.style.cssText = `top:${i*ROW_H}px;position:absolute;left:0;right:0;`;
|
||||
@@ -109,7 +109,7 @@ function renderFiltered(filteredChannels, q) {
|
||||
const block = document.createElement('div');
|
||||
block.className = 'program-block';
|
||||
|
||||
// Highlight si le programme matche la recherche
|
||||
// Highlight programs that match the current search query.
|
||||
const matchesProg = p.title.toLowerCase().includes(q) || (p.desc && p.desc.toLowerCase().includes(q));
|
||||
if (matchesProg) block.classList.add('search-match');
|
||||
|
||||
|
||||
+4
-4
@@ -21,7 +21,7 @@ function switchSource(value) {
|
||||
}
|
||||
|
||||
function updateCopyButtons() {
|
||||
// Rien à faire visuellement, copyUrl() lit currentEpgUrl/currentM3uUrl
|
||||
// No visual update is needed here because copyUrl() reads the current URLs directly.
|
||||
}
|
||||
|
||||
function openPersonalEpgModal() {
|
||||
@@ -35,7 +35,7 @@ function openPersonalEpgModal() {
|
||||
|
||||
function closePersonalEpgModal(applied) {
|
||||
document.getElementById('personalEpgModal').classList.remove('visible');
|
||||
// Si annulé (pas applied), remettre le select sur la source précédente
|
||||
// If the modal was cancelled, restore the previously selected source.
|
||||
if (!applied) {
|
||||
const sel = document.getElementById('sourceSelect');
|
||||
if (sel) sel.value = localStorage.getItem('gridtv-active-source') === 'personal' ? 'personal' : activeSourceIndex;
|
||||
@@ -67,7 +67,7 @@ function closeSourceDropdown() {
|
||||
document.getElementById('sourceDropdownMenu')?.classList.remove('open');
|
||||
}
|
||||
|
||||
// Fermer si clic en dehors
|
||||
// Close the dropdown when clicking outside of it.
|
||||
document.addEventListener('click', e => {
|
||||
if (!e.target.closest('.source-dropdown')) closeSourceDropdown();
|
||||
});
|
||||
@@ -75,7 +75,7 @@ document.addEventListener('click', e => {
|
||||
function updateSourceDropdownLabel(label) {
|
||||
const el = document.getElementById('sourceDropdownLabel');
|
||||
if (el) el.textContent = label;
|
||||
// Mettre à jour la classe active
|
||||
// Update the active item styling in the dropdown.
|
||||
document.querySelectorAll('.source-dropdown-item').forEach((btn, i) => {
|
||||
btn.classList.toggle('active', i === activeSourceIndex);
|
||||
});
|
||||
|
||||
+1
-2
@@ -1,4 +1,4 @@
|
||||
// ── THÈMES ────────────────────────────────────────────────────────────────────
|
||||
// ── THEMES ────────────────────────────────────────────────────────────────────
|
||||
function setTheme(theme) {
|
||||
document.getElementById('themeStylesheet').href = 'themes/' + theme + '.css';
|
||||
localStorage.setItem('gridtv-theme', theme);
|
||||
@@ -17,4 +17,3 @@ function startLiveUpdates() {
|
||||
setInterval(() => { if (channels.length) renderAll(); }, 60000);
|
||||
setInterval(() => loadEPG(DEFAULT_EPG_URL), 30*60*1000);
|
||||
}
|
||||
|
||||
|
||||
+7
-6
@@ -1,5 +1,5 @@
|
||||
|
||||
// Echapper les donnees issues de l'EPG pour eviter les injections HTML
|
||||
// Escape EPG-provided strings before injecting them into HTML.
|
||||
function esc(str) {
|
||||
const d = document.createElement('div');
|
||||
d.textContent = str || '';
|
||||
@@ -8,8 +8,8 @@ function esc(str) {
|
||||
|
||||
let channels = [];
|
||||
let programs = {};
|
||||
let m3uStreams = {}; // slug normalisé → url stream
|
||||
let scrollListenerAdded = false; // sync scroll vertical chaînes
|
||||
let m3uStreams = {}; // Normalized slug -> stream URL.
|
||||
let scrollListenerAdded = false; // Keeps the channel column vertically synced with the timeline.
|
||||
|
||||
function pad(n) { return String(n).padStart(2,'0'); }
|
||||
|
||||
@@ -35,6 +35,8 @@ function parseXMLTVDate(str) {
|
||||
if (!str) return null;
|
||||
const m = str.match(/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\s*([+-]\d{4})?/);
|
||||
if (!m) return null;
|
||||
// XMLTV timestamps use a compact timezone suffix like +0200, so we rebuild
|
||||
// the UTC instant manually before letting Date convert it to local time.
|
||||
const offset = m[7] ? parseInt(m[7]) : 0;
|
||||
const offsetMs = (Math.floor(Math.abs(offset)/100)*60 + (Math.abs(offset)%100)) * 60000 * (offset<0?-1:1);
|
||||
return new Date(Date.UTC(+m[1],+m[2]-1,+m[3],+m[4],+m[5],+m[6]) - offsetMs);
|
||||
@@ -78,7 +80,7 @@ function loadFromInput() {
|
||||
}
|
||||
|
||||
function parseEpisode(p) {
|
||||
// Format xmltv_ns : "S.E.part" (0-indexed) ex: "0.2.0/1" = S01E03
|
||||
// xmltv_ns format: "S.E.part" (0-indexed), e.g. "0.2.0/1" -> S01E03
|
||||
const ns = p.querySelector('episode-num[system="xmltv_ns"]')?.textContent;
|
||||
if (ns) {
|
||||
const parts = ns.split('.');
|
||||
@@ -87,7 +89,7 @@ function parseEpisode(p) {
|
||||
const e = ePart !== '' ? parseInt(ePart) + 1 : null;
|
||||
if (s !== null || e !== null) return { season: s, episode: e };
|
||||
}
|
||||
// Format onscreen : "S01E03" ou "s1e3"
|
||||
// onscreen format: "S01E03" or "s1e3"
|
||||
const os = p.querySelector('episode-num[system="onscreen"]')?.textContent;
|
||||
if (os) {
|
||||
const m = os.match(/[Ss](\d+)[Ee](\d+)/);
|
||||
@@ -136,4 +138,3 @@ function renderAll() {
|
||||
if (isMobile()) { renderMobile(); }
|
||||
else { renderGrid(); setTimeout(centerNow, 150); }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user