openvibers/openvibe.live Commits
Broadcast: before going live on a phone, a Go live bar stays in reach while the slot's own button is two screens down (#159)
Changes
@@@ -2646,3 +2646,21 @@ body.bc-studio-on .toast-container { z-index: 1500; }@media (prefers-reduced-motion: reduce) {.bcs-live-dot, .bcs-sheet, .bcs-sheet-wrap { animation: none; }}++++/* "Go live" in reach on a phone before going live (public/js/broadcast-studio.js): pinned while the slot's own Go Live++ button is below the fold. */++.bcs-golive {++ position: fixed; left: 0; right: 0; bottom: 0; z-index: 1050; display: grid; gap: 4px; justify-items: center;++ padding: 10px max(14px, env(safe-area-inset-right, 0px)) calc(10px + env(safe-area-inset-bottom, 0px)) max(14px, env(safe-area-inset-left, 0px));++ background: linear-gradient(to bottom, transparent, var(--bg-primary, #0b0b10) 34%);++}++.bcs-golive[hidden] { display: none; }++.bcs-golive-btn {++ width: 100%; max-width: 520px; min-height: 52px; border: 0; border-radius: 14px; cursor: pointer;++ background: var(--accent-strong, #2563eb); color: var(--on-accent-strong, #fff); font: inherit; font-size: 1.05rem; font-weight: 800;++ box-shadow: 0 10px 30px rgba(0,0,0,0.35);++}++.bcs-golive-btn:disabled { opacity: 0.6; cursor: default; }++.bcs-golive-btn:focus-visible { outline: 3px solid #93c5fd; outline-offset: 2px; }++.bcs-golive-where { color: var(--text-secondary, #a1a1aa); font-size: 0.76rem; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }++body.bc-golive-dock-on .messenger-toggle { display: none !important; }@@@ -13,7 +13,8 @@MediaStreamTrack.enabled = false: viewers get silence or black while the stream and its recording go on. Thechoice is applied again every second, so a replaced track (a camera switch, a recovery) keeps it.-- window.bcStudio (toggleMic, toggleCamera, flip, open) also serves the desktop control row. */++ Before going live, a phone also keeps "Go live" in reach (a bar at the bottom while the slot's own button is off++ screen). window.bcStudio (toggleMic, toggleCamera, flip, open) also serves the desktop control row. */(function () {'use strict';// A phone held upright, or turned sideways (wide but short, with a touch screen).@@@ -381,6 +382,35 @@else if (st.sheet) closeSheet();});++ // ── Before going live on a phone: "Go live" stays in reach ──++ // The slot workspace puts the real Go Live button below the title, method, capture mode, quality and devices,++ // about two screens down on a phone. While it is out of view, a bar at the bottom presses it.++ function goLiveButton() { const b = $('bc-ws-golive-btn'); return b && b.offsetParent ? b : null; }++ function syncGoLiveDock() {++ const real = NARROW.matches && onBroadcastPage() && !localStream() ? goLiveButton() : null;++ let inView = false;++ if (real) { const r = real.getBoundingClientRect(); inView = r.top < window.innerHeight - 8 && r.bottom > 0; }++ const show = !!real && !inView;++ let dock = $('bcs-golive');++ if (show && !dock) {++ dock = el('div', { class: 'bcs-golive', id: 'bcs-golive' },++ el('button', { type: 'button', class: 'bcs-golive-btn', onclick: () => { const b = goLiveButton(); if (b && !b.disabled) b.click(); } },++ icon('fa-tower-broadcast'), ' Go live'),++ el('small', { class: 'bcs-golive-where' }));++ document.body.append(dock);++ }++ if (dock) {++ dock.hidden = !show;++ if (show) {++ const where = real.parentElement && real.parentElement.querySelector('.bc-create-reassurance');++ dock.querySelector('.bcs-golive-where').textContent = where ? where.textContent.trim() : '';++ dock.querySelector('.bcs-golive-btn').disabled = !!real.disabled;++ }++ }++ document.body.classList.toggle('bc-golive-dock-on', show);++ }++ document.addEventListener('scroll', () => { if ($('bcs-golive') || goLiveButton()) syncGoLiveDock(); }, { capture: true, passive: true });++// ── Once a second: mount, update or leave ──function tick() {const has = !!localStream();@@@ -393,6 +423,7 @@}if (wanted()) { if (!st.root) mount(); refresh(); }else { if (st.root) unmount(); applyTracks(); syncDesktopButtons(); }++ syncGoLiveDock();}setInterval(tick, 1000);if (NARROW.addEventListener) NARROW.addEventListener('change', tick);@@@ -6,6 +6,8 @@* first, then ultra-wide and others), else to the next camera; one camera says so; screen sharing says to stop it.* - Mute and camera off are track.enabled = false, and they hold when the track is replaced (a camera switch, a* recovery) because the studio applies them again every second.++ * - Before going live on a phone, a "Go live" bar presses the slot's own Go Live button while that one is two screens++ * down, under the title, method, capture mode, quality and devices.* - The camera buttons are visible once the camera runs: the live view used to show before capture started, which* hid "Switch Cam" for the whole stream.* - The live controls say what they do: "watching" (not "total viewers"), no "WEBRTC" badge, RobotStreamer's line@@@ -164,6 +166,14 @@ function loadStudio({ stream, cameras = [], screenShare = false }) {assert.ok(!/passthrough`|to RS`/.test(rs) && /ms delay/.test(rs) && /reconnected once/.test(rs), 'RobotStreamer in words');});++ await check('before going live on a phone, a "Go live" bar presses the slot\'s own button while it is off screen', async () => {++ const dock = extract(studioSrc, 'syncGoLiveDock');++ assert.ok(/NARROW\.matches && onBroadcastPage\(\) && !localStream\(\)/.test(dock), 'phones, on /broadcast, not live yet');++ assert.ok(/getBoundingClientRect\(\)/.test(dock) && /const show = !!real && !inView/.test(dock), 'only while the real button is out of view');++ assert.ok(/b && !b\.disabled\) b\.click\(\)/.test(dock), 'it clicks the real Go Live button (same checks, same settings)');++ assert.ok(/id="bc-ws-golive-btn"/.test(read('public/js/broadcast-workspace.js')), 'the button it presses exists');++ });++await check('the studio loads with the broadcast feature, after broadcast.js', async () => {const js = features.broadcast.js;assert.ok(js.indexOf('/js/broadcast-studio.js') > js.indexOf('/js/broadcast.js'));
@@ -2646,3 +2646,21 @@ body.bc-studio-on .toast-container { z-index: 1500; }
@@ -2646,3 +2646,21 @@ body.bc-studio-on .toast-container { z-index: 1500; }
@media (prefers-reduced-motion: reduce) {
@media (prefers-reduced-motion: reduce) {
.bcs-live-dot, .bcs-sheet, .bcs-sheet-wrap { animation: none; }
.bcs-live-dot, .bcs-sheet, .bcs-sheet-wrap { animation: none; }
}
}
+
+/* "Go live" in reach on a phone before going live (public/js/broadcast-studio.js): pinned while the slot's own Go Live
+ button is below the fold. */
+.bcs-golive {
+ position: fixed; left: 0; right: 0; bottom: 0; z-index: 1050; display: grid; gap: 4px; justify-items: center;
+ padding: 10px max(14px, env(safe-area-inset-right, 0px)) calc(10px + env(safe-area-inset-bottom, 0px)) max(14px, env(safe-area-inset-left, 0px));
+ background: linear-gradient(to bottom, transparent, var(--bg-primary, #0b0b10) 34%);
+}
+.bcs-golive[hidden] { display: none; }
+.bcs-golive-btn {
+ width: 100%; max-width: 520px; min-height: 52px; border: 0; border-radius: 14px; cursor: pointer;
+ background: var(--accent-strong, #2563eb); color: var(--on-accent-strong, #fff); font: inherit; font-size: 1.05rem; font-weight: 800;
+ box-shadow: 0 10px 30px rgba(0,0,0,0.35);
+}
+.bcs-golive-btn:disabled { opacity: 0.6; cursor: default; }
+.bcs-golive-btn:focus-visible { outline: 3px solid #93c5fd; outline-offset: 2px; }
+.bcs-golive-where { color: var(--text-secondary, #a1a1aa); font-size: 0.76rem; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+body.bc-golive-dock-on .messenger-toggle { display: none !important; }
@@ -13,7 +13,8 @@
@@ -13,7 +13,8 @@
MediaStreamTrack.enabled = false: viewers get silence or black while the stream and its recording go on. The
MediaStreamTrack.enabled = false: viewers get silence or black while the stream and its recording go on. The
choice is applied again every second, so a replaced track (a camera switch, a recovery) keeps it.
choice is applied again every second, so a replaced track (a camera switch, a recovery) keeps it.
- window.bcStudio (toggleMic, toggleCamera, flip, open) also serves the desktop control row. */
+ Before going live, a phone also keeps "Go live" in reach (a bar at the bottom while the slot's own button is off
+ screen). window.bcStudio (toggleMic, toggleCamera, flip, open) also serves the desktop control row. */
(function () {
(function () {
'use strict';
'use strict';
// A phone held upright, or turned sideways (wide but short, with a touch screen).
// A phone held upright, or turned sideways (wide but short, with a touch screen).
@@ -381,6 +382,35 @@
@@ -381,6 +382,35 @@
else if (st.sheet) closeSheet();
else if (st.sheet) closeSheet();
});
});
+ // ── Before going live on a phone: "Go live" stays in reach ──
+ // The slot workspace puts the real Go Live button below the title, method, capture mode, quality and devices,
+ // about two screens down on a phone. While it is out of view, a bar at the bottom presses it.
+ function goLiveButton() { const b = $('bc-ws-golive-btn'); return b && b.offsetParent ? b : null; }
+ function syncGoLiveDock() {
+ const real = NARROW.matches && onBroadcastPage() && !localStream() ? goLiveButton() : null;
+ let inView = false;
+ if (real) { const r = real.getBoundingClientRect(); inView = r.top < window.innerHeight - 8 && r.bottom > 0; }
+ const show = !!real && !inView;
+ let dock = $('bcs-golive');
+ if (show && !dock) {
+ dock = el('div', { class: 'bcs-golive', id: 'bcs-golive' },
+ el('button', { type: 'button', class: 'bcs-golive-btn', onclick: () => { const b = goLiveButton(); if (b && !b.disabled) b.click(); } },
+ icon('fa-tower-broadcast'), ' Go live'),
+ el('small', { class: 'bcs-golive-where' }));
+ document.body.append(dock);
+ }
+ if (dock) {
+ dock.hidden = !show;
+ if (show) {
+ const where = real.parentElement && real.parentElement.querySelector('.bc-create-reassurance');
+ dock.querySelector('.bcs-golive-where').textContent = where ? where.textContent.trim() : '';
+ dock.querySelector('.bcs-golive-btn').disabled = !!real.disabled;
+ }
+ }
+ document.body.classList.toggle('bc-golive-dock-on', show);
+ }
+ document.addEventListener('scroll', () => { if ($('bcs-golive') || goLiveButton()) syncGoLiveDock(); }, { capture: true, passive: true });
+
// ── Once a second: mount, update or leave ──
// ── Once a second: mount, update or leave ──
function tick() {
function tick() {
const has = !!localStream();
const has = !!localStream();
@@ -393,6 +423,7 @@
@@ -393,6 +423,7 @@
}
}
if (wanted()) { if (!st.root) mount(); refresh(); }
if (wanted()) { if (!st.root) mount(); refresh(); }
else { if (st.root) unmount(); applyTracks(); syncDesktopButtons(); }
else { if (st.root) unmount(); applyTracks(); syncDesktopButtons(); }
+ syncGoLiveDock();
}
}
setInterval(tick, 1000);
setInterval(tick, 1000);
if (NARROW.addEventListener) NARROW.addEventListener('change', tick);
if (NARROW.addEventListener) NARROW.addEventListener('change', tick);
@@ -6,6 +6,8 @@
@@ -6,6 +6,8 @@
* first, then ultra-wide and others), else to the next camera; one camera says so; screen sharing says to stop it.
* first, then ultra-wide and others), else to the next camera; one camera says so; screen sharing says to stop it.
* - Mute and camera off are track.enabled = false, and they hold when the track is replaced (a camera switch, a
* - Mute and camera off are track.enabled = false, and they hold when the track is replaced (a camera switch, a
* recovery) because the studio applies them again every second.
* recovery) because the studio applies them again every second.
+ * - Before going live on a phone, a "Go live" bar presses the slot's own Go Live button while that one is two screens
+ * down, under the title, method, capture mode, quality and devices.
* - The camera buttons are visible once the camera runs: the live view used to show before capture started, which
* - The camera buttons are visible once the camera runs: the live view used to show before capture started, which
* hid "Switch Cam" for the whole stream.
* hid "Switch Cam" for the whole stream.
* - The live controls say what they do: "watching" (not "total viewers"), no "WEBRTC" badge, RobotStreamer's line
* - The live controls say what they do: "watching" (not "total viewers"), no "WEBRTC" badge, RobotStreamer's line
@@ -164,6 +166,14 @@ function loadStudio({ stream, cameras = [], screenShare = false }) {
@@ -164,6 +166,14 @@ function loadStudio({ stream, cameras = [], screenShare = false }) {
assert.ok(!/passthrough`|to RS`/.test(rs) && /ms delay/.test(rs) && /reconnected once/.test(rs), 'RobotStreamer in words');
assert.ok(!/passthrough`|to RS`/.test(rs) && /ms delay/.test(rs) && /reconnected once/.test(rs), 'RobotStreamer in words');
});
});
+ await check('before going live on a phone, a "Go live" bar presses the slot\'s own button while it is off screen', async () => {
+ const dock = extract(studioSrc, 'syncGoLiveDock');
+ assert.ok(/NARROW\.matches && onBroadcastPage\(\) && !localStream\(\)/.test(dock), 'phones, on /broadcast, not live yet');
+ assert.ok(/getBoundingClientRect\(\)/.test(dock) && /const show = !!real && !inView/.test(dock), 'only while the real button is out of view');
+ assert.ok(/b && !b\.disabled\) b\.click\(\)/.test(dock), 'it clicks the real Go Live button (same checks, same settings)');
+ assert.ok(/id="bc-ws-golive-btn"/.test(read('public/js/broadcast-workspace.js')), 'the button it presses exists');
+ });
+
await check('the studio loads with the broadcast feature, after broadcast.js', async () => {
await check('the studio loads with the broadcast feature, after broadcast.js', async () => {
const js = features.broadcast.js;
const js = features.broadcast.js;
assert.ok(js.indexOf('/js/broadcast-studio.js') > js.indexOf('/js/broadcast.js'));
assert.ok(js.indexOf('/js/broadcast-studio.js') > js.indexOf('/js/broadcast.js'));