aboutsummaryrefslogtreecommitdiffstats
path: root/src/features/messages.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-18 22:39:39 -0700
committerAdam Soltys <[email protected]>2026-10-18 22:39:39 -0700
commit15f3b1f80a5ed99e7d2db17e46318f4abe3797ce (patch)
treec5cb883a829375b2c9b2fd563e2dbb6a877c3c4e /src/features/messages.js
parent1842b5ebbfd9e47323f6175babc222970a21a966 (diff)
downloadcoinosv3-15f3b1f80a5ed99e7d2db17e46318f4abe3797ce.tar.gz
coinosv3-15f3b1f80a5ed99e7d2db17e46318f4abe3797ce.zip
Going live: when the server can't take a dropped stream back (it restarted) and starts a new one, the app now announces the new one, once it plays, and says the old one is over (no recording: there's nothing in it). It went on announcing the dead address, and viewers saw "The stream isn't playing right now" while the camera sent to an address nobody knew. The streamer's own view follows.
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
Diffstat (limited to 'src/features/messages.js')
-rw-r--r--src/features/messages.js34
1 files changed, 26 insertions, 8 deletions
diff --git a/src/features/messages.js b/src/features/messages.js
index 4820baa..c82670b 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -8975,7 +8975,20 @@ export function messagesFeature(ctx) {
b.ws = ws;
bcRecord(b);
b.reconnecting = false;
- if (info.id !== b.id) toast(t('goLiveRestarted'));
+ // The server couldn't take us back (it restarted, or we were gone
+ // too long): this is a new stream at a new address. The old one is
+ // over — said so, with no recording (there's next to nothing in
+ // it) — and the new one is announced as soon as it plays.
+ if (info.id !== b.id) {
+ toast(t('goLiveRestarted'));
+ const old = { ...b, recording: null };
+ Object.assign(b, { id: info.id, liveUrl: info.live, recording: info.recording, a: LIVE_KIND + ':' + b.pk + ':' + info.id });
+ bcAnnounce(old, 'ended').catch(() => {});
+ bcPlaying(b).then(() => (bc === b && b.phase === 'live' ? bcAnnounce(b, 'live') : null)).then(() => {
+ // the streamer's own view of it follows to the new address
+ if (bc === b && b.ev && ui.liveStream && ui.liveStream.a === old.a) { ui.liveStream = { a: b.a, seed: b.ev }; render(); }
+ }).catch(() => {});
+ }
render();
return;
} catch {}
@@ -8994,14 +9007,23 @@ export function messagesFeature(ctx) {
['p', id.pubkey, '', 'Host'], ['relays', ...relays.slice(0, 6)], CLIENT_TAG];
if (me.picture) tags.push(['image', me.picture]);
if (status === 'live' && b.viewers != null) tags.push(['current_participants', String(b.viewers)]);
- if (status === 'ended') tags.push(['ends', String(Math.floor(Date.now() / 1000))], ['recording', b.recording]);
+ if (status === 'ended') tags.push(['ends', String(Math.floor(Date.now() / 1000))]);
+ if (status === 'ended' && b.recording) tags.push(['recording', b.recording]);
const partial = { kind: LIVE_KIND, content: '', created_at: Math.floor(Date.now() / 1000), tags };
const evt = id.signer instanceof Uint8Array ? finalizeEvent(partial, id.signer) : await id.signer.signEvent(partial);
takeLive([evt]);
- b.ev = evt;
+ if (bc === b) b.ev = evt;
scheduleRepaint();
return publishOn(relays, evt);
}
+ async function bcPlaying(b) {
+ const url = b.liveUrl;
+ for (let i = 0; i < 40 && bc === b && b.phase === 'live' && b.liveUrl === url; i++) {
+ await new Promise((r) => setTimeout(r, 500));
+ const pl = await fetch(url, { cache: 'no-store' }).then((r) => r.text()).catch(() => '');
+ if ((pl.match(/\.ts\s*$/gm) || []).length >= 3) break;
+ }
+ }
async function startBroadcast() {
const b = bc;
if (!b || b.phase !== 'setup' || !b.media) return;
@@ -9019,11 +9041,7 @@ export function messagesFeature(ctx) {
// three segments — Chrome's own player won't start a live playlist with
// fewer (it fails to parse it), so a viewer who comes at once would get
// an error, not the stream
- for (let i = 0; i < 40 && bc === b && b.phase === 'live'; i++) {
- await new Promise((r) => setTimeout(r, 500));
- const pl = await fetch(b.liveUrl, { cache: 'no-store' }).then((r) => r.text()).catch(() => '');
- if ((pl.match(/\.ts\s*$/gm) || []).length >= 3) break;
- }
+ await bcPlaying(b);
if (bc !== b || b.phase !== 'live') return;
await bcAnnounce(b, 'live');
b.tick = setInterval(() => bcTick(b), 60_000);