openvibers/openvibe.live Commits
Live works like an app on a phone: Search is the navbar's magnifier (Live first), the duplicate Admin link goes, the bottom app bar comes on, the floating buttons stop covering the page, and the player's loader is the shared one over the stream's own picture (openvibe-shared 4.22.0) (#419)
Changes
@@@ -177,5 +177,5 @@ Minimum `.env`: `BASE_URL`, `JWT_SECRET`, `OV_NETWORK_URL`, `OV_NETWORK_INTERNAL<!-- versions:start -->- openvibe-contracts: v0.222.0- openvibe-sdk: v0.67.0--- openvibe-shared: v4.19.0++- openvibe-shared: v4.22.0<!-- versions:end -->@@@ -7,7 +7,7 @@"domain": "openvibe.live (www, ingest, whip)","contracts": "openvibe-contracts v0.222.0","sdk": "openvibe-sdk v0.67.0",-- "shared": "openvibe-shared v4.19.0",++ "shared": "openvibe-shared v4.22.0","capabilities": ["live.chat_context.read","live.chat_effects.write",@@@ -25,7 +25,7 @@"onnxruntime-node": "^1.20.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.222.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.67.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.19.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.22.0","pg": "^8.23.1","sanitize-html": "^2.17.7","sdp-transform": "^3.0.0",@@@ -2688,9 +2688,9 @@}},"node_modules/openvibe-shared": {-- "version": "4.19.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.19.0",-- "integrity": "sha512-OksbPienuKJ88R5HrXYh3Dgh/TWZGwUL4FUQQmGhQe6wkQpbAxaknfAouU+HaI1xwbbGhQ5f9bVIKJv9+LrNQQ==",++ "version": "4.22.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.22.0",++ "integrity": "sha512-WZw3eE1zQzasWuNQPx7cCwkKTITZmgxQkP+NoyGGvqxPp9i/P9oRA9u34Pd/Gnhz4rYQCQ3BzV9IQfT+pNBMDQ==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0"@@@ -44,7 +44,7 @@"onnxruntime-node": "^1.20.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.222.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.67.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.19.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.22.0","pg": "^8.23.1","sanitize-html": "^2.17.7","sdp-transform": "^3.0.0",@@@ -1837,6 +1837,16 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }linear-gradient(180deg, rgba(5, 8, 14, 0.95), rgba(7, 11, 18, 0.86));}++/* The shared loader (Shared 4.21 loading.js, stream-player.js _renderSharedLoader) fills the player edge to edge with++ the stream's own picture: the old card's padding, gradient and blurred ::before step aside. */++.video-placeholder.ovld-host { padding: 0; background: #000; }++.video-placeholder.ovld-host::before, .video-placeholder.ovld-host::after { display: none; }++.video-placeholder.ovld-host > .ovld { position: absolute; inset: 0; z-index: 1; }++/* An ended stream: the same overlay, still. */++.ovld.is-still .ovld-ring::before, .ovld.is-still .ovld-ring::after { animation: none; }++.ovld.is-still .ovld-ring::before { opacity: .3; }++.ovld.is-still .ovld-line { display: none; }++/* ── Themed blurred-thumbnail loading screen ─────────────────── */.video-placeholder.has-thumb::before {inset: 0;@@@ -9942,3 +9952,11 @@ body.ov-nav-overlay .openvibe-navbar.navbar.ovnav-top { background: transparent;.openvibe-navbar.navbar .openvibe-navbar-avatar { width: 36px; height: 36px; border-radius: 50%; cursor: pointer; object-fit: cover; box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 50%, transparent); }/* The site switcher stays beside the logo on a phone: the two balance chips fold into one there (Shared 4.16.0). */@media (max-width: 560px) { .openvibe-navbar.navbar .ovnav-chip { padding: 0 8px; } }++++/* Phones with the app bar (Shared 4.20, js/ov-navbar-live.js): Chat is a tab, so the global chat button steps aside; the++ messages button sits above the bar and the page keeps room for it (it covered a channel's followers and buttons). */++@media (max-width: 760px) {++ html.ov-has-appbar #floating-chat-fab { display: none !important; }++ html.ov-has-appbar .messenger-toggle { bottom: calc(var(--ovnav-appbar-h, 58px) + 14px); }++ html.ov-has-appbar body { padding-bottom: calc(var(--ovnav-appbar-h, 58px) + 64px); }++}@@@ -53,6 +53,7 @@"clipJobs"],"js": [++ "/shared/loading.js","/js/player-shape.js","/js/stream-player.js","/js/stream-pip.js"@@@ -16,20 +16,32 @@const nav = () => window.OpenVibeNavbar;let booted = false;++ // The bar's places, most used first (the owner, 2026-10-28: "move search after the Rundown and the drop-down… reanalyze++ // the nav bars… redundant options"). Search is the navbar's magnifier now (Shared 4.21: Live's results first, then all++ // of OpenVibe; config.search below); Admin is in the account menu (Admin Panel) and was here twice for staff.function links(user) {-- const staff = !!(user && ((user.capabilities && user.capabilities.admin_panel) || (typeof hasCapability === 'function' && hasCapability('can_access_staff_console'))));return [{ id: 'home', page: 'home', label: 'Home', href: '/', icon: 'fa-house' },// VODs, clips and pastes people made are one feed; what the AI made is the other.{ id: 'content', page: 'content', label: 'Content', href: '/content', icon: 'fa-photo-film' },{ id: 'moments', page: 'moments', label: 'Moments', href: '/moments', icon: 'fa-wand-magic-sparkles' },-- { id: 'search', page: 'search', label: 'Search', href: '/search', icon: 'fa-magnifying-glass' },{ id: 'chat', page: 'chat', label: 'Chat', href: '/chat', icon: 'fa-comments' },{ id: 'rundown', page: 'rundown', label: 'Rundown', href: '/rundown', icon: 'fa-newspaper' },{ id: 'broadcast', page: 'broadcast', elId: 'nav-broadcast', label: 'Go Live', href: '/broadcast', icon: 'fa-tower-broadcast', dot: false, dotId: 'nav-live-dot',onClick: (e) => call('goLiveNav', e),children: user ? [{ id: 'dashboard', label: 'Dashboard', href: '/dashboard', icon: 'fa-gauge-high', onClick: (e) => call('dashNav', e) }] : [] },-- { id: 'admin', page: 'admin', label: 'Admin', href: 'https://openvibe.network/admin', icon: 'fa-shield-halved', hidden: !staff },++ ];++ }++++ // Phones: the bottom tab bar (Shared 4.20's appBar). Home, what people made, going live in the middle, chat, and the++ // Rundown; everything else is in More (the drawer).++ function appBar() {++ return [++ { id: 'home', page: 'home', label: 'Home', href: '/', icon: 'fa-house' },++ { id: 'content', page: 'content', label: 'Videos', href: '/content', icon: 'fa-photo-film' },++ { id: 'broadcast', page: 'broadcast', label: 'Go Live', href: '/broadcast', icon: 'fa-tower-broadcast', onClick: (e) => call('goLiveNav', e) },++ { id: 'chat', page: 'chat', label: 'Chat', href: '/chat', icon: 'fa-comments' },++ { id: 'rundown', page: 'rundown', label: 'Rundown', href: '/rundown', icon: 'fa-newspaper' },];}@@@ -62,7 +74,9 @@// this sign-in (Shared 4.13.0), which returns to the page they were on.loginUrl: '/api/auth/sso/login?next={path}', networkLinks: 0, history: false, silentLogin: false, fedcm: false, recent: false,notificationsRealtime: true, // the bell hears new notifications over OpenVibe.Events (Shared 1.22.0)-- links: links(user), menu: menu(user),++ links: links(user), menu: menu(user), appBar: appBar(), adminLink: false, // Admin Panel is in the account menu++ // The magnifier (Shared 4.21): Live's streams, recordings and clips first, Live's own search page, then OpenVibe.++ search: { owner: 'live', label: 'OpenVibe.Live', page: '/search' },chips: [// On a phone the two fold into one chip (Shared 4.16.0); its panel lists them by label.{ id: 'coins', icon: 'fa-coins', tone: 'gold', value: '0', valueId: 'nav-coins-amount', label: 'OpenCoins', title: 'OpenCoins: click to open your inventory', onClick: () => call('openCosmeticsModal') },@@@ -471,6 +471,35 @@ function _ensurePlayerLoaderStructure(placeholder) {return placeholder._playerLoaderEls;}++// The network's one loading look (Shared 4.21 loading.js; the owner, 2026-10-28, of the old card: "old and outdated…++// consider the latest frames of the stream… no borders and wasted space and padding… minimalistic… a better animation").++// It covers the player edge to edge: the stream's own thumbnail blurred behind, the phase in a word with one line under++// it, a thin line that fills step by step; help (Try again, Copy details) only when it is slow or fails. Live's phases,++// timers and diagnostics are unchanged; without loading.js the card above is still drawn.++function _renderSharedLoader(placeholder, state, progress, briefCopy, showExpanded) {++ let l = placeholder._ovLoader;++ if (!l || !placeholder.contains(l.element)) {++ placeholder.textContent = '';++ placeholder._playerLoaderEls = null;++ placeholder.classList.add('ovld-host');++ l = placeholder._ovLoader = window.OpenVibeLoading.mount(placeholder, { poster: placeholder.dataset.thumb || '', steps: progress.totalSteps });++ }++ const actions = [++ { label: 'Try again', onClick: () => reloadPlayerPage() },++ { label: 'Copy details', onClick: () => copyPlayerDiagnostics() },++ ];++ if (state.severity === 'error') {++ l.fail({ title: state.title || 'Playback stopped', detail: state.detail || briefCopy, actions });++ return;++ }++ // An ended stream is not loading: the same picture and words, still (no ring turning, no line filling).++ const ended = state.phase === 'ended';++ l.element.classList.toggle('is-still', ended);++ l.update({ label: ended ? (state.title || 'Stream has ended') : _getPlayerPhaseDisplayLabel(state.phase), sub: ended ? (state.detail || briefCopy) : briefCopy,++ ...(ended ? {} : { step: progress.currentIndex, steps: progress.totalSteps }) });++ if (showExpanded && !ended) l.slow({ hint: state.hint || state.detail || 'This is taking longer than usual.', actions });++}++function _animatePlayerLoaderStatus(shell) {if (!shell) return;shell.classList.remove('is-phase-changing');@@@ -511,6 +540,10 @@ function _renderPlayerPlaceholder() {placeholder.dataset.phase = state.phase || 'signal';placeholder.dataset.severity = state.severity || 'info';++ if (window.OpenVibeLoading) {++ _renderSharedLoader(placeholder, state, progress, briefCopy, showExpanded);++ return;++ }const els = _ensurePlayerLoaderStructure(placeholder);if (placeholder.dataset.renderSignature !== renderSignature) {_animatePlayerLoaderStatus(els.shell);@@@ -861,10 +894,13 @@ function _applyLoaderThumb(stream) {const thumb = stream && stream.thumbnail_url;// Reset any previous stream's theming.placeholder.classList.remove('has-thumb');-- ['--loader-thumb', '--loader-a', '--loader-c'].forEach(v => placeholder.style.removeProperty(v));++ ['--loader-thumb', '--loader-a', '--loader-c', '--accent-light'].forEach(v => placeholder.style.removeProperty(v));++ delete placeholder.dataset.thumb;if (!thumb) return;placeholder.style.setProperty('--loader-thumb', `url("${thumb}")`);placeholder.classList.add('has-thumb');++ placeholder.dataset.thumb = thumb; // the shared loader's picture (loading.js), now or when it mounts++ if (placeholder._ovLoader && placeholder.contains(placeholder._ovLoader.element)) placeholder._ovLoader.update({ poster: thumb });try {const img = new Image();img.crossOrigin = 'anonymous';@@@ -904,6 +940,7 @@ function _applyLoaderThumb(stream) {const c = cols[1] || cols[0];const mid = { r: (a.r + c.r) / 2, g: (a.g + c.g) / 2, b: (a.b + c.b) / 2 };placeholder.style.setProperty('--loader-a', rgb(a));++ placeholder.style.setProperty('--accent-light', rgb(a)); // the shared loader's ring and line take the stream's colourplaceholder.style.setProperty('--loader-b', rgb(mid));placeholder.style.setProperty('--loader-c', rgb(c));}@@@ -23,7 +23,7 @@ const BUDGETS = {htmlBrotliKB: 30,jsFiles: 26, // measured 23 (incl. route scripts for "/")jsRawKB: 1150, // measured ~1000-- jsBrotliKB: 270, // measured 269.6 (2026-10-28: openvibe-shared 4.19.0, the account menu's gear and people-group icons inline; 268 on 2026-10-27: openvibe-shared 4.16.0, the folded balance chip, Across OpenVibe with site marks and the icon preload in the navbar); 265.3 (2026-10-27: openvibe-shared 4.15.0, the compact footer opens into the whole one, and the pulse line); 263.3 on 10-23 (openvibe-shared 4.9.0, status text twins in every theme); 262.5 on 10-17 (openvibe-shared 4.4.0, every site in the launcher)++ jsBrotliKB: 272, // measured 270.5 (2026-10-28: openvibe-shared 4.22.0, the navbar's search magnifier with / and Ctrl+K and the More menu; search.js and loading.js load only when used); 269.6 (2026-10-28: openvibe-shared 4.19.0, the account menu's gear and people-group icons inline; 268 on 2026-10-27: openvibe-shared 4.16.0, the folded balance chip, Across OpenVibe with site marks and the icon preload in the navbar); 265.3 (2026-10-27: openvibe-shared 4.15.0, the compact footer opens into the whole one, and the pulse line); 263.3 on 10-23 (openvibe-shared 4.9.0, status text twins in every theme); 262.5 on 10-17 (openvibe-shared 4.4.0, every site in the launcher)cssRawKB: 270, // measured ~243 since the first-paint subset (style.core.css) replaced style.css (~717 before)cssBrotliKB: 55, // measured ~47.5 (was ~130)};
@@ -177,5 +177,5 @@ Minimum `.env`: `BASE_URL`, `JWT_SECRET`, `OV_NETWORK_URL`, `OV_NETWORK_INTERNAL
@@ -177,5 +177,5 @@ Minimum `.env`: `BASE_URL`, `JWT_SECRET`, `OV_NETWORK_URL`, `OV_NETWORK_INTERNAL
<!-- versions:start -->
<!-- versions:start -->
- openvibe-contracts: v0.222.0
- openvibe-contracts: v0.222.0
- openvibe-sdk: v0.67.0
- openvibe-sdk: v0.67.0
-- openvibe-shared: v4.19.0
+- openvibe-shared: v4.22.0
<!-- versions:end -->
<!-- versions:end -->
@@ -7,7 +7,7 @@
@@ -7,7 +7,7 @@
"domain": "openvibe.live (www, ingest, whip)",
"domain": "openvibe.live (www, ingest, whip)",
"contracts": "openvibe-contracts v0.222.0",
"contracts": "openvibe-contracts v0.222.0",
"sdk": "openvibe-sdk v0.67.0",
"sdk": "openvibe-sdk v0.67.0",
- "shared": "openvibe-shared v4.19.0",
+ "shared": "openvibe-shared v4.22.0",
"capabilities": [
"capabilities": [
"live.chat_context.read",
"live.chat_context.read",
"live.chat_effects.write",
"live.chat_effects.write",
@@ -25,7 +25,7 @@
@@ -25,7 +25,7 @@
"onnxruntime-node": "^1.20.0",
"onnxruntime-node": "^1.20.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.222.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.222.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.67.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.67.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.19.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.22.0",
"pg": "^8.23.1",
"pg": "^8.23.1",
"sanitize-html": "^2.17.7",
"sanitize-html": "^2.17.7",
"sdp-transform": "^3.0.0",
"sdp-transform": "^3.0.0",
@@ -2688,9 +2688,9 @@
@@ -2688,9 +2688,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "4.19.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.19.0",
- "integrity": "sha512-OksbPienuKJ88R5HrXYh3Dgh/TWZGwUL4FUQQmGhQe6wkQpbAxaknfAouU+HaI1xwbbGhQ5f9bVIKJv9+LrNQQ==",
+ "version": "4.22.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.22.0",
+ "integrity": "sha512-WZw3eE1zQzasWuNQPx7cCwkKTITZmgxQkP+NoyGGvqxPp9i/P9oRA9u34Pd/Gnhz4rYQCQ3BzV9IQfT+pNBMDQ==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0"
"jsonwebtoken": "^9.0.0"
@@ -44,7 +44,7 @@
@@ -44,7 +44,7 @@
"onnxruntime-node": "^1.20.0",
"onnxruntime-node": "^1.20.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.222.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.222.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.67.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.67.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.19.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.22.0",
"pg": "^8.23.1",
"pg": "^8.23.1",
"sanitize-html": "^2.17.7",
"sanitize-html": "^2.17.7",
"sdp-transform": "^3.0.0",
"sdp-transform": "^3.0.0",
@@ -1837,6 +1837,16 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
@@ -1837,6 +1837,16 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
linear-gradient(180deg, rgba(5, 8, 14, 0.95), rgba(7, 11, 18, 0.86));
linear-gradient(180deg, rgba(5, 8, 14, 0.95), rgba(7, 11, 18, 0.86));
}
}
+/* The shared loader (Shared 4.21 loading.js, stream-player.js _renderSharedLoader) fills the player edge to edge with
+ the stream's own picture: the old card's padding, gradient and blurred ::before step aside. */
+.video-placeholder.ovld-host { padding: 0; background: #000; }
+.video-placeholder.ovld-host::before, .video-placeholder.ovld-host::after { display: none; }
+.video-placeholder.ovld-host > .ovld { position: absolute; inset: 0; z-index: 1; }
+/* An ended stream: the same overlay, still. */
+.ovld.is-still .ovld-ring::before, .ovld.is-still .ovld-ring::after { animation: none; }
+.ovld.is-still .ovld-ring::before { opacity: .3; }
+.ovld.is-still .ovld-line { display: none; }
+
/* ── Themed blurred-thumbnail loading screen ─────────────────── */
/* ── Themed blurred-thumbnail loading screen ─────────────────── */
.video-placeholder.has-thumb::before {
.video-placeholder.has-thumb::before {
inset: 0;
inset: 0;
@@ -9942,3 +9952,11 @@ body.ov-nav-overlay .openvibe-navbar.navbar.ovnav-top { background: transparent;
@@ -9942,3 +9952,11 @@ body.ov-nav-overlay .openvibe-navbar.navbar.ovnav-top { background: transparent;
.openvibe-navbar.navbar .openvibe-navbar-avatar { width: 36px; height: 36px; border-radius: 50%; cursor: pointer; object-fit: cover; box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 50%, transparent); }
.openvibe-navbar.navbar .openvibe-navbar-avatar { width: 36px; height: 36px; border-radius: 50%; cursor: pointer; object-fit: cover; box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 50%, transparent); }
/* The site switcher stays beside the logo on a phone: the two balance chips fold into one there (Shared 4.16.0). */
/* The site switcher stays beside the logo on a phone: the two balance chips fold into one there (Shared 4.16.0). */
@media (max-width: 560px) { .openvibe-navbar.navbar .ovnav-chip { padding: 0 8px; } }
@media (max-width: 560px) { .openvibe-navbar.navbar .ovnav-chip { padding: 0 8px; } }
+
+/* Phones with the app bar (Shared 4.20, js/ov-navbar-live.js): Chat is a tab, so the global chat button steps aside; the
+ messages button sits above the bar and the page keeps room for it (it covered a channel's followers and buttons). */
+@media (max-width: 760px) {
+ html.ov-has-appbar #floating-chat-fab { display: none !important; }
+ html.ov-has-appbar .messenger-toggle { bottom: calc(var(--ovnav-appbar-h, 58px) + 14px); }
+ html.ov-has-appbar body { padding-bottom: calc(var(--ovnav-appbar-h, 58px) + 64px); }
+}
@@ -53,6 +53,7 @@
@@ -53,6 +53,7 @@
"clipJobs"
"clipJobs"
],
],
"js": [
"js": [
+ "/shared/loading.js",
"/js/player-shape.js",
"/js/player-shape.js",
"/js/stream-player.js",
"/js/stream-player.js",
"/js/stream-pip.js"
"/js/stream-pip.js"
@@ -16,20 +16,32 @@
@@ -16,20 +16,32 @@
const nav = () => window.OpenVibeNavbar;
const nav = () => window.OpenVibeNavbar;
let booted = false;
let booted = false;
+ // The bar's places, most used first (the owner, 2026-10-28: "move search after the Rundown and the drop-down… reanalyze
+ // the nav bars… redundant options"). Search is the navbar's magnifier now (Shared 4.21: Live's results first, then all
+ // of OpenVibe; config.search below); Admin is in the account menu (Admin Panel) and was here twice for staff.
function links(user) {
function links(user) {
- const staff = !!(user && ((user.capabilities && user.capabilities.admin_panel) || (typeof hasCapability === 'function' && hasCapability('can_access_staff_console'))));
return [
return [
{ id: 'home', page: 'home', label: 'Home', href: '/', icon: 'fa-house' },
{ id: 'home', page: 'home', label: 'Home', href: '/', icon: 'fa-house' },
// VODs, clips and pastes people made are one feed; what the AI made is the other.
// VODs, clips and pastes people made are one feed; what the AI made is the other.
{ id: 'content', page: 'content', label: 'Content', href: '/content', icon: 'fa-photo-film' },
{ id: 'content', page: 'content', label: 'Content', href: '/content', icon: 'fa-photo-film' },
{ id: 'moments', page: 'moments', label: 'Moments', href: '/moments', icon: 'fa-wand-magic-sparkles' },
{ id: 'moments', page: 'moments', label: 'Moments', href: '/moments', icon: 'fa-wand-magic-sparkles' },
- { id: 'search', page: 'search', label: 'Search', href: '/search', icon: 'fa-magnifying-glass' },
{ id: 'chat', page: 'chat', label: 'Chat', href: '/chat', icon: 'fa-comments' },
{ id: 'chat', page: 'chat', label: 'Chat', href: '/chat', icon: 'fa-comments' },
{ id: 'rundown', page: 'rundown', label: 'Rundown', href: '/rundown', icon: 'fa-newspaper' },
{ id: 'rundown', page: 'rundown', label: 'Rundown', href: '/rundown', icon: 'fa-newspaper' },
{ id: 'broadcast', page: 'broadcast', elId: 'nav-broadcast', label: 'Go Live', href: '/broadcast', icon: 'fa-tower-broadcast', dot: false, dotId: 'nav-live-dot',
{ id: 'broadcast', page: 'broadcast', elId: 'nav-broadcast', label: 'Go Live', href: '/broadcast', icon: 'fa-tower-broadcast', dot: false, dotId: 'nav-live-dot',
onClick: (e) => call('goLiveNav', e),
onClick: (e) => call('goLiveNav', e),
children: user ? [{ id: 'dashboard', label: 'Dashboard', href: '/dashboard', icon: 'fa-gauge-high', onClick: (e) => call('dashNav', e) }] : [] },
children: user ? [{ id: 'dashboard', label: 'Dashboard', href: '/dashboard', icon: 'fa-gauge-high', onClick: (e) => call('dashNav', e) }] : [] },
- { id: 'admin', page: 'admin', label: 'Admin', href: 'https://openvibe.network/admin', icon: 'fa-shield-halved', hidden: !staff },
+ ];
+ }
+
+ // Phones: the bottom tab bar (Shared 4.20's appBar). Home, what people made, going live in the middle, chat, and the
+ // Rundown; everything else is in More (the drawer).
+ function appBar() {
+ return [
+ { id: 'home', page: 'home', label: 'Home', href: '/', icon: 'fa-house' },
+ { id: 'content', page: 'content', label: 'Videos', href: '/content', icon: 'fa-photo-film' },
+ { id: 'broadcast', page: 'broadcast', label: 'Go Live', href: '/broadcast', icon: 'fa-tower-broadcast', onClick: (e) => call('goLiveNav', e) },
+ { id: 'chat', page: 'chat', label: 'Chat', href: '/chat', icon: 'fa-comments' },
+ { id: 'rundown', page: 'rundown', label: 'Rundown', href: '/rundown', icon: 'fa-newspaper' },
];
];
}
}
@@ -62,7 +74,9 @@
@@ -62,7 +74,9 @@
// this sign-in (Shared 4.13.0), which returns to the page they were on.
// this sign-in (Shared 4.13.0), which returns to the page they were on.
loginUrl: '/api/auth/sso/login?next={path}', networkLinks: 0, history: false, silentLogin: false, fedcm: false, recent: false,
loginUrl: '/api/auth/sso/login?next={path}', networkLinks: 0, history: false, silentLogin: false, fedcm: false, recent: false,
notificationsRealtime: true, // the bell hears new notifications over OpenVibe.Events (Shared 1.22.0)
notificationsRealtime: true, // the bell hears new notifications over OpenVibe.Events (Shared 1.22.0)
- links: links(user), menu: menu(user),
+ links: links(user), menu: menu(user), appBar: appBar(), adminLink: false, // Admin Panel is in the account menu
+ // The magnifier (Shared 4.21): Live's streams, recordings and clips first, Live's own search page, then OpenVibe.
+ search: { owner: 'live', label: 'OpenVibe.Live', page: '/search' },
chips: [
chips: [
// On a phone the two fold into one chip (Shared 4.16.0); its panel lists them by label.
// On a phone the two fold into one chip (Shared 4.16.0); its panel lists them by label.
{ id: 'coins', icon: 'fa-coins', tone: 'gold', value: '0', valueId: 'nav-coins-amount', label: 'OpenCoins', title: 'OpenCoins: click to open your inventory', onClick: () => call('openCosmeticsModal') },
{ id: 'coins', icon: 'fa-coins', tone: 'gold', value: '0', valueId: 'nav-coins-amount', label: 'OpenCoins', title: 'OpenCoins: click to open your inventory', onClick: () => call('openCosmeticsModal') },
@@ -471,6 +471,35 @@ function _ensurePlayerLoaderStructure(placeholder) {
@@ -471,6 +471,35 @@ function _ensurePlayerLoaderStructure(placeholder) {
return placeholder._playerLoaderEls;
return placeholder._playerLoaderEls;
}
}
+// The network's one loading look (Shared 4.21 loading.js; the owner, 2026-10-28, of the old card: "old and outdated…
+// consider the latest frames of the stream… no borders and wasted space and padding… minimalistic… a better animation").
+// It covers the player edge to edge: the stream's own thumbnail blurred behind, the phase in a word with one line under
+// it, a thin line that fills step by step; help (Try again, Copy details) only when it is slow or fails. Live's phases,
+// timers and diagnostics are unchanged; without loading.js the card above is still drawn.
+function _renderSharedLoader(placeholder, state, progress, briefCopy, showExpanded) {
+ let l = placeholder._ovLoader;
+ if (!l || !placeholder.contains(l.element)) {
+ placeholder.textContent = '';
+ placeholder._playerLoaderEls = null;
+ placeholder.classList.add('ovld-host');
+ l = placeholder._ovLoader = window.OpenVibeLoading.mount(placeholder, { poster: placeholder.dataset.thumb || '', steps: progress.totalSteps });
+ }
+ const actions = [
+ { label: 'Try again', onClick: () => reloadPlayerPage() },
+ { label: 'Copy details', onClick: () => copyPlayerDiagnostics() },
+ ];
+ if (state.severity === 'error') {
+ l.fail({ title: state.title || 'Playback stopped', detail: state.detail || briefCopy, actions });
+ return;
+ }
+ // An ended stream is not loading: the same picture and words, still (no ring turning, no line filling).
+ const ended = state.phase === 'ended';
+ l.element.classList.toggle('is-still', ended);
+ l.update({ label: ended ? (state.title || 'Stream has ended') : _getPlayerPhaseDisplayLabel(state.phase), sub: ended ? (state.detail || briefCopy) : briefCopy,
+ ...(ended ? {} : { step: progress.currentIndex, steps: progress.totalSteps }) });
+ if (showExpanded && !ended) l.slow({ hint: state.hint || state.detail || 'This is taking longer than usual.', actions });
+}
+
function _animatePlayerLoaderStatus(shell) {
function _animatePlayerLoaderStatus(shell) {
if (!shell) return;
if (!shell) return;
shell.classList.remove('is-phase-changing');
shell.classList.remove('is-phase-changing');
@@ -511,6 +540,10 @@ function _renderPlayerPlaceholder() {
@@ -511,6 +540,10 @@ function _renderPlayerPlaceholder() {
placeholder.dataset.phase = state.phase || 'signal';
placeholder.dataset.phase = state.phase || 'signal';
placeholder.dataset.severity = state.severity || 'info';
placeholder.dataset.severity = state.severity || 'info';
+ if (window.OpenVibeLoading) {
+ _renderSharedLoader(placeholder, state, progress, briefCopy, showExpanded);
+ return;
+ }
const els = _ensurePlayerLoaderStructure(placeholder);
const els = _ensurePlayerLoaderStructure(placeholder);
if (placeholder.dataset.renderSignature !== renderSignature) {
if (placeholder.dataset.renderSignature !== renderSignature) {
_animatePlayerLoaderStatus(els.shell);
_animatePlayerLoaderStatus(els.shell);
@@ -861,10 +894,13 @@ function _applyLoaderThumb(stream) {
@@ -861,10 +894,13 @@ function _applyLoaderThumb(stream) {
const thumb = stream && stream.thumbnail_url;
const thumb = stream && stream.thumbnail_url;
// Reset any previous stream's theming.
// Reset any previous stream's theming.
placeholder.classList.remove('has-thumb');
placeholder.classList.remove('has-thumb');
- ['--loader-thumb', '--loader-a', '--loader-c'].forEach(v => placeholder.style.removeProperty(v));
+ ['--loader-thumb', '--loader-a', '--loader-c', '--accent-light'].forEach(v => placeholder.style.removeProperty(v));
+ delete placeholder.dataset.thumb;
if (!thumb) return;
if (!thumb) return;
placeholder.style.setProperty('--loader-thumb', `url("${thumb}")`);
placeholder.style.setProperty('--loader-thumb', `url("${thumb}")`);
placeholder.classList.add('has-thumb');
placeholder.classList.add('has-thumb');
+ placeholder.dataset.thumb = thumb; // the shared loader's picture (loading.js), now or when it mounts
+ if (placeholder._ovLoader && placeholder.contains(placeholder._ovLoader.element)) placeholder._ovLoader.update({ poster: thumb });
try {
try {
const img = new Image();
const img = new Image();
img.crossOrigin = 'anonymous';
img.crossOrigin = 'anonymous';
@@ -904,6 +940,7 @@ function _applyLoaderThumb(stream) {
@@ -904,6 +940,7 @@ function _applyLoaderThumb(stream) {
const c = cols[1] || cols[0];
const c = cols[1] || cols[0];
const mid = { r: (a.r + c.r) / 2, g: (a.g + c.g) / 2, b: (a.b + c.b) / 2 };
const mid = { r: (a.r + c.r) / 2, g: (a.g + c.g) / 2, b: (a.b + c.b) / 2 };
placeholder.style.setProperty('--loader-a', rgb(a));
placeholder.style.setProperty('--loader-a', rgb(a));
+ placeholder.style.setProperty('--accent-light', rgb(a)); // the shared loader's ring and line take the stream's colour
placeholder.style.setProperty('--loader-b', rgb(mid));
placeholder.style.setProperty('--loader-b', rgb(mid));
placeholder.style.setProperty('--loader-c', rgb(c));
placeholder.style.setProperty('--loader-c', rgb(c));
}
}
@@ -23,7 +23,7 @@ const BUDGETS = {
@@ -23,7 +23,7 @@ const BUDGETS = {
htmlBrotliKB: 30,
htmlBrotliKB: 30,
jsFiles: 26, // measured 23 (incl. route scripts for "/")
jsFiles: 26, // measured 23 (incl. route scripts for "/")
jsRawKB: 1150, // measured ~1000
jsRawKB: 1150, // measured ~1000
- jsBrotliKB: 270, // measured 269.6 (2026-10-28: openvibe-shared 4.19.0, the account menu's gear and people-group icons inline; 268 on 2026-10-27: openvibe-shared 4.16.0, the folded balance chip, Across OpenVibe with site marks and the icon preload in the navbar); 265.3 (2026-10-27: openvibe-shared 4.15.0, the compact footer opens into the whole one, and the pulse line); 263.3 on 10-23 (openvibe-shared 4.9.0, status text twins in every theme); 262.5 on 10-17 (openvibe-shared 4.4.0, every site in the launcher)
+ jsBrotliKB: 272, // measured 270.5 (2026-10-28: openvibe-shared 4.22.0, the navbar's search magnifier with / and Ctrl+K and the More menu; search.js and loading.js load only when used); 269.6 (2026-10-28: openvibe-shared 4.19.0, the account menu's gear and people-group icons inline; 268 on 2026-10-27: openvibe-shared 4.16.0, the folded balance chip, Across OpenVibe with site marks and the icon preload in the navbar); 265.3 (2026-10-27: openvibe-shared 4.15.0, the compact footer opens into the whole one, and the pulse line); 263.3 on 10-23 (openvibe-shared 4.9.0, status text twins in every theme); 262.5 on 10-17 (openvibe-shared 4.4.0, every site in the launcher)
cssRawKB: 270, // measured ~243 since the first-paint subset (style.core.css) replaced style.css (~717 before)
cssRawKB: 270, // measured ~243 since the first-paint subset (style.core.css) replaced style.css (~717 before)
cssBrotliKB: 55, // measured ~47.5 (was ~130)
cssBrotliKB: 55, // measured ~47.5 (was ~130)
};
};