Gpsirdb / static /app.js
gopallikehack's picture
Create app.js
cb118ea verified
Raw History Blame Contribute Delete
3.71 kB
const form = document.getElementById("load-form");
const urlInput = document.getElementById("m3u-url");
const statusEl = document.getElementById("status");
const listEl = document.getElementById("channel-list");
const searchEl = document.getElementById("search");
const videoEl = document.getElementById("video");
const nowPlaying = document.querySelector(".np-text");
const overlay = document.getElementById("video-overlay");
let channels = [];
let hls = null;
/* ── Load form ────────────────────────────── */
form.addEventListener("submit", async (e) => {
e.preventDefault();
const url = urlInput.value.trim();
if (!url) return;
setStatus("Loading playlist…", "");
listEl.innerHTML = "";
channels = [];
try {
const r = await fetch(`/api/parse?url=${encodeURIComponent(url)}`);
const data = await r.json();
if (data.error) throw new Error(data.error);
channels = data.channels;
setStatus(`Loaded ${data.count} channels.`, "ok");
renderList(channels);
} catch (err) {
setStatus("Error: " + err.message, "error");
}
});
function setStatus(msg, cls) {
statusEl.textContent = msg;
statusEl.className = "status " + (cls || "");
}
/* ── Render list ──────────────────────────── */
function renderList(items) {
listEl.innerHTML = "";
const frag = document.createDocumentFragment();
items.forEach((ch, i) => {
const li = document.createElement("li");
li.style.animationDelay = Math.min(i * 12, 400) + "ms";
if (ch.logo) {
const img = document.createElement("img");
img.src = ch.logo;
img.loading = "lazy";
img.onerror = () => { img.style.display = "none"; };
li.appendChild(img);
}
const span = document.createElement("span");
span.textContent = ch.name || "(no name)";
li.appendChild(span);
li.addEventListener("click", () => playChannel(ch, li));
frag.appendChild(li);
});
listEl.appendChild(frag);
}
/* ── Search ──────────────────────────────── */
searchEl.addEventListener("input", () => {
const q = searchEl.value.trim().toLowerCase();
if (!q) { renderList(channels); return; }
renderList(channels.filter(c => (c.name || "").toLowerCase().includes(q)));
});
/* ── Play channel ────────────────────────── */
function playChannel(ch, li) {
document.querySelectorAll("#channel-list li").forEach(x => x.classList.remove("active"));
li.classList.add("active");
nowPlaying.textContent = ch.name || "Playing";
overlay.classList.add("hidden");
document.querySelector(".now-playing").classList.add("live");
const proxied = `/api/proxy?url=${encodeURIComponent(ch.url)}`;
if (hls) { hls.destroy(); hls = null; }
if (videoEl.canPlayType("application/vnd.apple.mpegurl")) {
// Safari / iOS native HLS
videoEl.src = proxied;
videoEl.play().catch(() => {});
return;
}
if (window.Hls && Hls.isSupported()) {
hls = new Hls({
lowLatencyMode: true,
backBufferLength: 30,
maxBufferLength: 30,
});
hls.loadSource(proxied);
hls.attachMedia(videoEl);
hls.on(Hls.Events.MANIFEST_PARSED, () => {
videoEl.play().catch(() => {});
});
hls.on(Hls.Events.ERROR, (evt, data) => {
if (data.fatal) {
console.warn("HLS fatal error:", data);
setStatus("Playback error β€” stream may be offline or blocked.", "error");
}
});
} else {
setStatus("HLS not supported in this browser.", "error");
}
}