From 0898bb16eb598215ab66c0e86c28a9da063c9357 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Mon, 19 Oct 2026 07:57:04 -0700 Subject: Live streams over WebRTC. Going live also sends the camera to Cloudflare's SFU, which the live server brokers, and a viewer in the app watches from there: 0.1 s behind the broadcaster in go-live-test LATENCY=1, against 3.5 s on HLS. - Switching to the screen and back swaps the track in place. - A reconnect that makes a new stream publishes again, and its viewers follow. - A viewer falls back to HLS if there's no WebRTC for the stream or no connection in 8 s. HLS is still sent, for the recording and the other nostr apps. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg --- tools/go-live-test.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'tools') diff --git a/tools/go-live-test.js b/tools/go-live-test.js index 244ec93..575f3ab 100644 --- a/tools/go-live-test.js +++ b/tools/go-live-test.js @@ -4,7 +4,8 @@ // announced as its kind 30311 pointing at the HLS — and someone else opening // the stream's link watches it. Ending says so in a last 30311 carrying the // recording, and the recording is complete. -// Run: bun tools/go-live-test.js (LIVE_SERVER=path to coinosv3-server/live/server.js; NO_BUILD=1 to reuse dist/) +// Run: bun tools/go-live-test.js (LIVE_SERVER=path to coinosv3-server/live/server.js; NO_BUILD=1 to reuse dist/; +// LIVE_CONFIG=a json with {sfuAppId, sfuSecret} to watch over WebRTC — the real Cloudflare SFU) import puppeteer from './puppeteer.js'; import { cacheKeyFor } from '../src/wallet.js'; import { naddrEncode } from 'nostr-tools/nip19'; @@ -196,6 +197,10 @@ try { if (process.env.LATENCY === '2') console.log(' events:', JSON.stringify(await viewer.evaluate(() => window.__ev.slice(0, 30)))); check('...and it plays', playsOk || await v.waitFor(() => { const x = document.querySelector('video.live-video'); return !!x && !x.paused && x.currentTime > 1 && x.videoWidth > 0; }, 30000), JSON.stringify(await viewer.evaluate(() => { const x = document.querySelector('video.live-video'); return x && { t: x.currentTime, w: x.videoWidth, err: x.error && x.error.code }; }))); + // LIVE_CONFIG with the SFU's key ({sfuAppId, sfuSecret}): the app's viewer + // watches over WebRTC through Cloudflare; without it, HLS + const overRtc = await viewer.evaluate(() => { const x = document.querySelector('video.live-video'); return !!x && x.srcObject instanceof MediaStream; }); + check(process.env.LIVE_CONFIG ? '...over WebRTC (Cloudflare\'s SFU)' : '...over HLS (no SFU key here)', overRtc === !!process.env.LIVE_CONFIG); if (process.env.LATENCY) { await viewer.evaluate(() => { window.__stalls = 0; document.querySelector('video.live-video').addEventListener('waiting', () => window.__stalls++); }); const lags = []; -- cgit v1.2.3