CoolFace
Apppublic

multimodalart/StreamDiffusionV2-Realtime

sourceHugging Faceupdated 3mo agoView on Hugging Face
0likes
index.html202 linesDownload Raw Back to root
1<!DOCTYPE html>2<html lang="en">3<head>4<meta charset="utf-8" />5<meta name="viewport" content="width=device-width, initial-scale=1" />6<title>LiveEdit · Realtime</title>7<style>8  :root { --bg:#0e0f13; --panel:#171922; --line:#2a2e3a; --fg:#e8e8ee; --accent:#c084fc; --good:#86efac; }9  * { box-sizing: border-box; }10  body { margin:0; background:var(--bg); color:var(--fg); font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif; }11  .wrap { max-width:1100px; margin:0 auto; padding:24px 18px 60px; }12  h1 { font-size:1.5rem; margin:0 0 4px; }13  .sub { color:#9aa0ad; font-size:.95rem; margin:0 0 18px; line-height:1.5; }14  .sub a { color:var(--accent); }15  .controls { display:flex; gap:10px; flex-wrap:wrap; align-items:center; margin-bottom:16px; }16  input[type=text] { flex:1; min-width:260px; background:var(--panel); border:1px solid var(--line); color:var(--fg); padding:12px 14px; border-radius:10px; font-size:1rem; }17  button { background:var(--accent); color:#1a1024; border:0; padding:12px 18px; border-radius:10px; font-size:1rem; font-weight:600; cursor:pointer; }18  button:disabled { opacity:.5; cursor:not-allowed; }19  .timer { font-family:ui-monospace,Menlo,monospace; color:var(--good); background:#11210f; border:1px solid #234; padding:8px 12px; border-radius:8px; display:none; }20  .grid { display:grid; grid-template-columns:1fr 1fr; gap:14px; }21  .card { background:var(--panel); border:1px solid var(--line); border-radius:14px; overflow:hidden; }22  .card h2 { font-size:.85rem; text-transform:uppercase; letter-spacing:.05em; color:#9aa0ad; margin:0; padding:10px 14px; border-bottom:1px solid var(--line); }23  .media { aspect-ratio:832/480; background:#000; display:flex; align-items:center; justify-content:center; }24  .media video, .media img { width:100%; height:100%; object-fit:cover; display:block; }25  .placeholder { color:#5a6070; font-size:.9rem; }26  .status { margin-top:14px; color:#9aa0ad; font-size:.9rem; min-height:1.2em; }27  @media (max-width:780px){ .grid{ grid-template-columns:1fr; } }28</style>29</head>30<body>31<div class="wrap">32  <h1>🌀 StreamDiffusionV2 · Realtime Webcam Diffusion</h1>33  <p class="sub">34    Live demo of <a href="https://streamdiffusionv2.github.io/" target="_blank">StreamDiffusionV2</a>35    (MLSys 2026 Best Paper) on Wan2.1-T2V-1.3B. It streams your webcam through a causal video-diffusion36    model with a <b>sink-token rolling KV cache</b> &mdash; built for <i>continuous</i> streaming, so it37    keeps flowing without the window-shift burst. Type a style prompt, click <b>Start</b> to grab ZeroGPU38    for ~60s. <b>Change the prompt anytime</b> &mdash; it updates the live stream.39  </p>40 41  <div class="controls">42    <input id="instruction" type="text" placeholder="style prompt · e.g. psychedelic neon dream · van gogh · cyberpunk city" />43    <button id="startBtn">▶ Start session</button>44    <span id="timer" class="timer">⏱ <span id="count">58</span>s</span>45  </div>46 47  <div class="grid">48    <div class="card">49      <h2>Your webcam</h2>50      <div class="media"><video id="cam" autoplay muted playsinline></video></div>51    </div>52    <div class="card">53      <h2>Edited (live)</h2>54      <div class="media"><img id="out" alt="" /><span id="outPh" class="placeholder">edited stream appears here</span></div>55    </div>56  </div>57 58  <div id="status" class="status"></div>59</div>60 61<canvas id="grab" width="640" height="360" style="display:none"></canvas>62 63<script type="module">64import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";65 66const camEl = document.getElementById("cam");67const outEl = document.getElementById("out");68const outPh = document.getElementById("outPh");69const startBtn = document.getElementById("startBtn");70const instr = document.getElementById("instruction");71const timerEl = document.getElementById("timer");72const countEl = document.getElementById("count");73const statusEl = document.getElementById("status");74const grab = document.getElementById("grab");75const gctx = grab.getContext("2d");76 77let client = null;78let stream = null;79let captureTimer = null;80let countdownTimer = null;81let running = false;82 83const FPS = 30;                // webcam frames sent per second (backend drops backlog)84const SESSION_SECONDS = 58;85 86// --- low-latency player: StreamDiffusionV2 streams steadily (~14fps), so we do87// NOT jitter-buffer. Keep a tiny queue and aggressively drop the backlog so the88// preview always shows the most recent edit (minimal action->reaction delay).89let playQueue = [];90const MAX_QUEUE = 3;           // ~0.2s worth; drop older frames past this91let lastShown = 0;92function playLoop(ts){93  if (playQueue.length > MAX_QUEUE) playQueue = playQueue.slice(-MAX_QUEUE);94  if (playQueue.length && ts - lastShown >= 1000 / 30){95    outEl.src = playQueue.shift();96    outPh.style.display = "none";97    lastShown = ts;98  }99  requestAnimationFrame(playLoop);100}101requestAnimationFrame(playLoop);102 103function setStatus(t){ statusEl.textContent = t; }104 105async function ensureClient(){106  if (!client) client = await Client.connect(window.location.origin);107  return client;108}109 110async function ensureCam(){111  if (stream) return;112  stream = await navigator.mediaDevices.getUserMedia({ video: { width: 832, height: 480 }, audio: false });113  camEl.srcObject = stream;114  await camEl.play().catch(()=>{});115}116 117async function sendInstruction(){118  try {119    await fetch("/instruction", {120      method:"POST", headers:{ "Content-Type":"application/json" },121      body: JSON.stringify({ instruction: instr.value || "" })122    });123  } catch(e){}124}125 126function startCapture(){127  captureTimer = setInterval(() => {128    if (!camEl.videoWidth) return;129    gctx.drawImage(camEl, 0, 0, grab.width, grab.height);130    grab.toBlob(async (blob) => {131      if (!blob) return;132      try { await fetch("/frame", { method:"POST", body: blob }); } catch(e){}133    }, "image/jpeg", 0.6);134  }, 1000 / FPS);135}136 137function stopAll(){138  running = false;139  if (captureTimer) { clearInterval(captureTimer); captureTimer = null; }140  if (countdownTimer) { clearInterval(countdownTimer); countdownTimer = null; }141  timerEl.style.display = "none";142  startBtn.disabled = false;143  startBtn.textContent = "▶ Start session";144}145 146function startCountdown(){147  let r = SESSION_SECONDS;148  countEl.textContent = r;149  timerEl.style.display = "inline-block";150  countdownTimer = setInterval(() => {151    r -= 1; countEl.textContent = Math.max(0, r);152    if (r <= 0) clearInterval(countdownTimer);153  }, 1000);154}155 156instr.addEventListener("change", () => { if (running) sendInstruction(); });157instr.addEventListener("input", () => { if (running) sendInstruction(); });158 159startBtn.addEventListener("click", async () => {160  if (running) return;161  startBtn.disabled = true;162  try {163    setStatus("Requesting webcam…");164    await ensureCam();165    setStatus("Connecting…");166    await ensureClient();167    running = true;168    playQueue = [];169    startBtn.textContent = "◌ Acquiring ZeroGPU…";170    await sendInstruction();171    setStatus("Queued for ZeroGPU — webcam streaming starts once the GPU is acquired…");172 173    const job = client.submit("/run_session", {});174    let frames = 0;175    for await (const msg of job) {176      if (msg.type !== "data" || !msg.data || msg.data[0] == null) continue;177      const payload = msg.data[0];178      if (payload === "__READY__") {179        // GPU is now allocated — only now start capturing & sending frames.180        startBtn.textContent = "● Live";181        await sendInstruction();182        startCapture();183        startCountdown();184        setStatus("ZeroGPU acquired — streaming your webcam through LiveEdit…");185        continue;186      }187      playQueue.push(payload);          // jitter buffer paces actual display188      frames += 1;189      if (frames % 12 === 0) setStatus(`Streaming… ${frames} edited frames`);190    }191    setStatus(frames ? "Session ended. Click Start to run another ~60s session."192                     : "Session ended before any frames were produced — try again.");193  } catch (e) {194    setStatus("Error: " + (e && e.message ? e.message : e));195  } finally {196    stopAll();197  }198});199</script>200</body>201</html>202