diff --git a/src/templates/index.html b/src/templates/index.html
index 6ede01a..399d717 100644
--- a/src/templates/index.html
+++ b/src/templates/index.html
@@ -2,210 +2,315 @@
-
-
- Pi Camera
-
+ #status {
+ font-size: 0.85rem;
+ color: #888;
+ min-height: 1.2em;
+ }
+
- Pi Camera Stream
+ Pi Camera
-
-
Stream not started
-
+
-
-
-
-
+
-
+
-
-
-
+
+ } else if (video.canPlayType("application/vnd.apple.mpegurl")) {
+ video.src = src;
+ video.play().catch(e => console.warn("Autoplay blocked:", e));
+ showLive();
+ setStatus("Streaming");
+ } else {
+ setStatus("HLS not supported in this browser");
+ }
+ }
+
+ function stopHls() {
+ if (hls) { hls.destroy(); hls = null; }
+ video.pause();
+ video.src = "";
+ video.style.display = "none";
+ }
+
+ // ── Status polling ───────────────────────────────────────────────────────
+
+ async function fetchStatus() {
+ const res = await fetch("/camera/status");
+ return res.json();
+ }
+
+ function setStatus(msg) {
+ statusEl.textContent = msg;
+ }
+
+ async function waitForReady(maxAttempts = 40) {
+ for (let i = 0; i < maxAttempts; i++) {
+ const data = await fetchStatus();
+ if (data.ready) return true;
+ await new Promise(r => setTimeout(r, 500));
+ }
+ return false;
+ }
+
+ // Called on page load and after stream errors — attach if already live
+ async function attachToStream() {
+ const data = await fetchStatus();
+ if (data.ready) {
+ startHls();
+ renderControls(true);
+ } else if (data.running) {
+ setStatus("Stream starting...");
+ placeholderText.textContent = "Stream starting...";
+ const ready = await waitForReady();
+ if (ready) {
+ startHls();
+ renderControls(true);
+ } else {
+ showOffline("Stream timed out");
+ renderControls(false);
+ }
+ } else {
+ showOffline("Stream is offline");
+ renderControls(false);
+ }
+ }
+
+ function startOfflinePoll() {
+ stopOfflinePoll();
+ pollInterval = setInterval(async () => {
+ const data = await fetchStatus();
+ if (data.running || data.ready) {
+ stopOfflinePoll();
+ await attachToStream();
+ }
+ }, 5000);
+ }
+
+ function stopOfflinePoll() {
+ if (pollInterval) { clearInterval(pollInterval); pollInterval = null; }
+ }
+
+ // ── Stream start / stop ──────────────────────────────────────────────────
+
+ async function startStream() {
+ // guard: re-check status in case another client just started it
+ const current = await fetchStatus();
+ if (current.running || current.ready) {
+ await attachToStream();
+ return;
+ }
+
+ const btn = document.getElementById("btn-start");
+ if (btn) btn.disabled = true;
+ setStatus("Starting camera...");
+ placeholderText.textContent = "Starting...";
+
+ await fetch("/camera/start", { method: "POST" });
+
+ setStatus("Waiting for first segment...");
+ const ready = await waitForReady();
+
+ if (!ready) {
+ showOffline("Camera timed out — check Pi logs");
+ renderControls(false);
+ startOfflinePoll();
+ return;
+ }
+
+ startHls();
+ renderControls(true);
+ stopOfflinePoll();
+ }
+
+ async function stopStream() {
+ stopHls();
+ showOffline("Stream is offline");
+ setStatus("Stopping...");
+ renderControls(false);
+ await fetch("/camera/stop", { method: "POST" });
+ setStatus("Stream stopped");
+ startOfflinePoll();
+ }
+
+ // ── Init ────────────────────────────────────────────────────────────────
+
+ (async () => {
+ await attachToStream();
+ const data = await fetchStatus();
+ if (!data.running && !data.ready) {
+ startOfflinePoll();
+ }
+ })();
+
\ No newline at end of file