Redesign hosted web as broadcast console
This commit is contained in:
parent
bd5d9857ed
commit
5d6f77f868
5 changed files with 469 additions and 300 deletions
|
|
@ -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", () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue