openvibers/openvibe.live Commits

While live, every other page shows "You're live · Back to the studio" (#474)

OP OpenVibers committed 13 days ago · 9afa7db258 · 3 files, +38 −1

Plan L23 R5 (owner, 2026-10-31: "if they're going to the home page… keep the stream live… where possible"). Moving
around the site never stops a broadcast, but off the studio page nothing said the stream was still on. Now a red
pill under the navbar does, with a pulsing dot (static with reduced motion), and one tap goes back to the studio.
It is checked each second with the rest of the studio and hides on the studio page and when the stream ends.
test/broadcast-always-live.test.js covers it.

Changes

ddiff --git a/public/css/features/broadcast.css b/public/css/features/broadcast.css
iindex b20511c9..982429a8 100644
---- a/public/css/features/broadcast.css
++++ b/public/css/features/broadcast.css
@@@ -2788,6 +2788,19 @@ body.bc-studio-on #bc-chat-messages .chat-conn-status { display: block !importan
background: #e11d48; color: #fff; font-weight: 700; box-shadow: 0 8px 24px rgba(0,0,0,0.35); cursor: pointer;
}
.bcs-return[hidden] { display: none; }
++/* "You're live · Back to the studio" on every other page while the broadcast runs (broadcast-studio.js syncLivePill). */
++.bc-live-pill {
++ position: fixed; top: calc(var(--navbar-h, 56px) + 8px + env(safe-area-inset-top, 0px)); left: 50%; transform: translateX(-50%); z-index: 1001;
++ display: inline-flex; align-items: center; gap: 8px; min-height: 40px; max-width: calc(100vw - 32px); padding: 0 16px; border: 0; border-radius: 999px;
++ background: #e11d48; color: #fff; font: inherit; font-size: 0.86rem; white-space: nowrap; box-shadow: 0 8px 24px rgba(0,0,0,0.3); cursor: pointer;
++}
++.bc-live-pill[hidden] { display: none; }
++.bc-live-pill b { font-weight: 800; }
++.bc-live-pill span:last-child { opacity: 0.9; }
++.bc-live-pill-dot { width: 8px; height: 8px; border-radius: 50%; background: #fff; animation: bcLivePillPulse 1.6s ease-in-out infinite; }
++@keyframes bcLivePillPulse { 50% { opacity: 0.35; } }
++.bc-live-pill:focus-visible { outline: 3px solid #93c5fd; outline-offset: 2px; }
++@media (prefers-reduced-motion: reduce) { .bc-live-pill-dot { animation: none; } }
@media (prefers-reduced-motion: reduce) {
.bcs-live-dot, .bcs-sheet, .bcs-sheet-wrap { animation: none; }
}
ddiff --git a/public/js/broadcast-studio.js b/public/js/broadcast-studio.js
iindex 82767aa4..163fe31e 100644
---- a/public/js/broadcast-studio.js
++++ b/public/js/broadcast-studio.js
@@@ -684,6 +684,22 @@
syncDesktopButtons();
if (target) { const n = document.querySelector(target); if (n) n.scrollIntoView({ behavior: 'smooth', block: 'start' }); }
}
++ /* "● You're live · Back to the studio" (plan L23 R5, owner 2026-10-31): moving around the site never stops the
++ broadcast, so on every other page a pill under the navbar says the stream is still on and takes the streamer
++ back in one tap. */
++ function syncLivePill() {
++ const show = !!localStream() && !onBroadcastPage();
++ let pill = $('bc-live-pill');
++ if (!show) { if (pill) pill.hidden = true; return; }
++ if (!pill) {
++ pill = el('button', { type: 'button', class: 'bc-live-pill', id: 'bc-live-pill',
++ onclick: () => { if (typeof navigate === 'function') navigate('/broadcast'); else location.href = '/broadcast'; } },
++ el('span', { class: 'bc-live-pill-dot', 'aria-hidden': 'true' }), el('b', { text: "You're live" }), el('span', { text: 'Back to the studio' }));
++ document.body.append(pill);
++ }
++ pill.hidden = false;
++ }
++
function ensureReturn() {
if ($('bcs-return')) return;
document.body.append(el('button', { type: 'button', class: 'bcs-return', id: 'bcs-return', hidden: true,
@@@ -861,6 +877,7 @@
if (wanted()) { if (!st.root) mount(); refresh(); }
else { if (st.root) unmount(); applyTracks(); syncDesktopButtons(); }
syncGoLiveDock();
++ syncLivePill();
}
setInterval(tick, 1000);
document.addEventListener('visibilitychange', tick);
ddiff --git a/test/broadcast-always-live.test.js b/test/broadcast-always-live.test.js
iindex 7a159bf1..4340ea15 100644
---- a/test/broadcast-always-live.test.js
++++ b/test/broadcast-always-live.test.js
@@@ -32,4 +32,11 @@ const app = read('public/js/app.js');
const teardown = app.slice(app.indexOf('function teardownRoute('), app.indexOf('\n}\n', app.indexOf('function teardownRoute(')));
assert.ok(!/stopBroadcast|cleanupStream|endStream|stopAllStreams/.test(teardown), 'a page change leaves the broadcast running');
--console.log('always live: 5 min grace, viewers wait and say so, the studio reconnects at once, leave asks first');
++// On every other page while live: "You're live · Back to the studio", one tap back.
++const studio = read('public/js/broadcast-studio.js');
++assert.match(studio, /function syncLivePill\(\) \{\s*const show = !!localStream\(\) && !onBroadcastPage\(\);/, 'the pill shows off the studio page while live');
++assert.match(studio, /navigate\('\/broadcast'\)/, 'and takes the streamer back');
++assert.match(studio, /syncGoLiveDock\(\);\s*syncLivePill\(\);/, 'checked every second with the rest of the studio');
++assert.match(read('public/css/features/broadcast.css'), /\.bc-live-pill \{[^}]*position: fixed;[^}]*min-height: 40px;/);
++
++console.log('always live: 5 min grace, viewers wait and say so, the studio reconnects at once, leave asks first, a pill back to the studio');