Redesign hosted web as broadcast console
Some checks are pending
ci-gates / checks (push) Waiting to run
deploy-cloudflare / checks (push) Waiting to run
deploy-cloudflare / deploy (push) Blocked by required conditions

This commit is contained in:
every.channel 2026-05-04 01:06:41 -07:00
parent bd5d9857ed
commit 5d6f77f868
No known key found for this signature in database
5 changed files with 469 additions and 300 deletions

View file

@ -110,19 +110,19 @@ function bindPlayerSignals(watch, name, extraCleanup) {
if (status === "loading") {
clearOfflineTimer();
sawLoading = true;
setHint(`Connecting to relay and subscribing: ${name}`, "ok");
setHint(`Tuning ${name}...`, "ok");
return;
}
if (status === "live") {
clearOfflineTimer();
setHint(`Live: subscribed to ${name}`, "ok");
setHint(`On air: ${name}`, "ok");
return;
}
if (status === "offline" && sawLoading) {
clearOfflineTimer();
// Avoid flashing a false negative during short reconnect windows.
offlineTimer = window.setTimeout(() => {
setHint(`Connection interrupted, retrying: ${name}`, "warn");
setHint(`Signal faded, retrying ${name}...`, "warn");
offlineTimer = null;
}, 8000);
}
@ -133,7 +133,7 @@ function bindPlayerSignals(watch, name, extraCleanup) {
const hasVideo = Boolean(catalog.video && catalog.video.renditions);
const hasAudio = Boolean(catalog.audio && catalog.audio.renditions);
if (hasVideo || hasAudio) {
setHint(`Live: subscribed to ${name}`, "ok");
setHint(`On air: ${name}`, "ok");
}
});
@ -197,7 +197,7 @@ function mountPlayer(relayUrl, name) {
forceAudioOn();
watch.backend?.paused?.set?.(true);
watch.backend?.paused?.set?.(false);
setHint(`Live: subscribed to ${name} (audio unlocked)`, "ok");
setHint(`On air: ${name} (sound on)`, "ok");
};
document.addEventListener("pointerdown", unlockAudio, { once: true });
canvas.addEventListener("pointerdown", unlockAudio, { once: true });
@ -205,7 +205,7 @@ function mountPlayer(relayUrl, name) {
document.removeEventListener("pointerdown", unlockAudio);
canvas.removeEventListener("pointerdown", unlockAudio);
});
setHint(`Live: subscribed to ${name} (tap player to unmute)`, "warn");
setHint(`On air: ${name} (tap picture for sound)`, "warn");
bindPlayerSignals(watch, name, cleanup);
}
@ -285,7 +285,7 @@ async function mountArchivePlayer(name) {
video.playsInline = true;
video.addEventListener("error", () => {
setHint(
"Archive replay bytes are not browser-HLS compatible yet (legacy container); timeline is available, live path is unaffected.",
"This replay is not ready for browser playback yet. Live tuning is unaffected.",
"warn",
);
});
@ -312,7 +312,7 @@ async function mountArchivePlayer(name) {
hls.on(HlsCtor.Events.ERROR, (_event, data) => {
if (!data?.fatal) return;
if (data.type === HlsCtor.ErrorTypes.NETWORK_ERROR) {
setHint("Archive network hiccup, retrying…", "warn");
setHint("Replay hiccup, retrying...", "warn");
try {
hls.startLoad();
} catch (_) {
@ -321,7 +321,7 @@ async function mountArchivePlayer(name) {
return;
}
if (data.type === HlsCtor.ErrorTypes.MEDIA_ERROR) {
setHint("Archive media hiccup, recovering…", "warn");
setHint("Replay picture hiccup, recovering...", "warn");
try {
hls.recoverMediaError();
} catch (_) {
@ -329,7 +329,7 @@ async function mountArchivePlayer(name) {
}
return;
}
setHint(`Archive playback error: ${data.type || "fatal"}`, "warn");
setHint(`Replay unavailable: ${data.type || "fatal"}`, "warn");
});
hls.loadSource(archiveUrl);
hls.attachMedia(video);
@ -392,10 +392,10 @@ function renderLiveList(entries, onWatchLive, onWatchArchive) {
const mount = $("liveList");
mount.textContent = "";
if (!entries.length) {
setListHint("No public streams announced yet.", "");
setListHint("No stations are on air yet.", "");
return;
}
setListHint(`${entries.length} live`, "ok");
setListHint(`${entries.length} on air`, "ok");
for (const entry of entries) {
const row = document.createElement("div");
@ -409,7 +409,9 @@ function renderLiveList(entries, onWatchLive, onWatchArchive) {
const meta = document.createElement("div");
meta.className = "liveMeta";
meta.textContent = `${entry.broadcast_name || ""} @ ${entry.relay_url || DEFAULT_RELAY_URL}`;
meta.textContent = entry.broadcast_name
? `Channel key: ${entry.broadcast_name}`
: "Ready to tune";
info.appendChild(meta);
const actions = document.createElement("div");
@ -417,14 +419,14 @@ function renderLiveList(entries, onWatchLive, onWatchArchive) {
const watchBtn = document.createElement("button");
watchBtn.className = "btn secondary";
watchBtn.textContent = "Live";
watchBtn.textContent = "Watch";
watchBtn.addEventListener("click", () => {
onWatchLive(entry);
});
const archiveBtn = document.createElement("button");
archiveBtn.className = "btn secondary";
archiveBtn.textContent = "Archive";
archiveBtn.textContent = "DVR";
archiveBtn.addEventListener("click", () => {
onWatchArchive(entry);
});
@ -480,18 +482,18 @@ function main() {
updateSharePreview();
if (!name) {
setHint("Enter a broadcast name to watch.", "warn");
setHint("Pick a station or enter a channel key.", "warn");
return;
}
if (mode === "archive") {
writeParams(relayUrl, name, mode);
setHint(`Loading archive DVR: ${name}`, "ok");
setHint(`Loading replay: ${name}`, "ok");
try {
await mountArchivePlayer(name);
} catch (e) {
setHint(
`Archive playback unavailable: ${String(e)}. Ensure /api/archive is configured.`,
`Replay unavailable: ${String(e)}.`,
"warn",
);
}
@ -500,7 +502,7 @@ function main() {
if (!hasWebTransport()) {
setHint(
"WebTransport is not available in this browser. Try Chrome or Firefox Nightly. Safari support is still incomplete.",
"This browser cannot tune the live player yet. Try Chrome or Edge.",
"warn",
);
return;
@ -510,14 +512,14 @@ function main() {
await ensureMoqWatchElement();
} catch (e) {
setHint(
`Failed to load MoQ web player dependency: ${String(e)}. Disable script blockers for esm.sh/jsdelivr/unpkg and retry.`,
`The player did not load: ${String(e)}. Try again after allowing the page scripts.`,
"warn",
);
return;
}
writeParams(relayUrl, name, mode);
setHint(`Connecting to relay and subscribing: ${name}`, "ok");
setHint(`Tuning ${name}...`, "ok");
mountPlayer(relayUrl, name);
}
@ -537,7 +539,7 @@ function main() {
const name = normalizeName(nameInput.value);
const mode = archiveModeInput.checked ? "archive" : "live";
if (!name) {
setHint("Enter a broadcast name first.", "warn");
setHint("Pick a station first.", "warn");
return;
}
const link = currentShareLink(relayUrl, name, mode);
@ -552,7 +554,7 @@ function main() {
});
async function refreshLiveList() {
setListHint("Loading live streams...", "");
setListHint("Scanning stations...", "");
try {
const entries = await fetchLiveList();
renderLiveList(
@ -574,7 +576,7 @@ function main() {
);
} catch (e) {
$("liveList").textContent = "";
setListHint(`Live list error: ${String(e)}`, "warn");
setListHint("Station guide is unavailable right now.", "warn");
}
}
refreshListBtn.addEventListener("click", () => {