Spaces:
Paused
Paused
File size: 3,710 Bytes
cb118ea | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | 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");
}
} |