openvibers/openvibe.mediahub Commits

openvibe-shared v4.192.0, and the video comes along (the small player) (#375)

OP OpenVibers committed 3 days ago · 22b163b158 · 9 files, +26 −14

* openvibe-shared v4.192.0 (was v4.186.0)

Opened by scripts/pin-bump.js in OpenVibers/OpenVibe.Shared after the v4.192.0 release.

* The video comes along: openvibe.video takes the small player (openvibe-shared 4.192.0)

Every Video page loads mini.js; a recording's and a clip's video says it may come along, with its title, who made it and its page. The player's picture-in-picture button takes the person to the front page and the video goes on in the small player from the same second. The home page's size budget follows the release (dated note).

* The watch page test reads the video tag with its small-player attributes

Changes

ddiff --git a/README.md b/README.md
iindex 1d5dbec..2d2cfe0 100644
---- a/README.md
++++ b/README.md
@@@ -553,7 +553,7 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [
<!-- versions:start -->
- openvibe-contracts: v0.384.0
- openvibe-sdk: v0.125.0
--- openvibe-shared: v4.186.0
++- openvibe-shared: v4.192.0
<!-- versions:end -->
### Public Pics gallery
ddiff --git a/STATUS.json b/STATUS.json
iindex ddeb3ac..77440d4 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -10,7 +10,7 @@
"packages": [
"openvibe-contracts v0.384.0",
"openvibe-sdk v0.125.0",
-- "openvibe-shared v4.186.0"
++ "openvibe-shared v4.192.0"
],
"summary": "OpenVibe.MediaHub: openvibe.download as a private drive. Account-only uploads stream into OpenVibe.Media (a plain form up to 256 MB, resumable parts beyond). Files are private. Share links expire in 1 hour to 7 days, need a signed-in downloader and can be limited to named accounts (matched by subject). Reports suspend a share for staff. Downloads are always sandboxed attachments. Per-person quotas, and account export and deletion (ADR-033). openvibe.video serves public watch pages and playlists for Live's recordings and clips (no uploads); openvibe.pics is parked and redirects to openvibe.network.",
"works": [
@@@ -44,7 +44,7 @@
],
"contracts": "openvibe-contracts v0.384.0",
"sdk": "openvibe-sdk v0.125.0",
-- "shared": "openvibe-shared v4.186.0",
++ "shared": "openvibe-shared v4.192.0",
"deployment": "openvibe-ovh: unit openvibe-media-hub.service on 127.0.0.1:4990 (git checkout /opt/openvibe.download, env /etc/openvibe/media-hub.env, database ov_media_hub), deployed with `sudo ovhost deploy media-hub`; public at https://openvibe.download since 2026-10-08, https://openvibe.video since 2026-10-12 and https://openvibe.pics since 2026-10-15.",
"updated": "2026-11-10"
}
ddiff --git a/package-lock.json b/package-lock.json
iindex c8bbbde..08ac2f9 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -17,7 +17,7 @@
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.384.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.125.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.186.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.192.0",
"pg": "^8.23.0",
"sharp": "^0.33.5"
},
@@@ -1654,9 +1654,9 @@
}
},
"node_modules/openvibe-shared": {
-- "version": "4.186.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.186.0",
-- "integrity": "sha512-wD4net+UjO0a6OG73W+RR1z5MIWtPgj407xLEWphiVWgLTQ4V1r76+DtLOfSf6RfEda/ALizRu1zmxbvuGGZOw==",
++ "version": "4.192.0",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.192.0",
++ "integrity": "sha512-Wecot1Ivp9LXtv9Mss5f69SkqyrlfSwboahZe7rlTa5S0Hqz2YtQWnhPA7GgWrPXu/krYj+BoSTEPJYjHAL9jA==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0",
ddiff --git a/package.json b/package.json
iindex 0910c61..1a5f2a3 100644
---- a/package.json
++++ b/package.json
@@@ -24,7 +24,7 @@
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.384.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.125.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.186.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.192.0",
"pg": "^8.23.0",
"sharp": "^0.33.5"
},
ddiff --git a/server/video/layout.js b/server/video/layout.js
iindex 971ceda..20eccf0 100644
---- a/server/video/layout.js
++++ b/server/video/layout.js
@@@ -72,7 +72,8 @@ ${appIcon.headTags({ site: 'media-hub' })}
<link rel="stylesheet" href="${asset('css/video.css')}">
${(o.styles || []).map((name) => `<link rel="stylesheet" href="${ovServe.url(name)}">`).join('\n')}
${(o.sharedScripts || []).map((name) => `<script src="${ovServe.url(name)}" defer></script>`).join('\n')}
--${(o.localStyles || []).map((rel) => `<link rel="stylesheet" href="${asset(rel)}">\n`).join('')}${o.player ? `<link rel="stylesheet" href="${ovServe.url('player.css')}">\n<script src="${ovServe.url('player.js')}" defer></script>\n` : ''}${o.path === '/record' ? `<script src="${CAM_KIT_URL}" integrity="${CAM_KIT_SRI}" crossorigin="anonymous" defer></script>\n` : ''}${(o.scripts || []).map((rel) => `<script src="${asset(rel)}" defer></script>`).join('\n')}`;
++${(o.localStyles || []).map((rel) => `<link rel="stylesheet" href="${asset(rel)}">\n`).join('')}<script src="${ovServe.url('mini.js')}" defer></script>
++${o.player ? `<link rel="stylesheet" href="${ovServe.url('player.css')}">\n<script src="${ovServe.url('player.js')}" defer></script>\n` : ''}${o.path === '/record' ? `<script src="${CAM_KIT_URL}" integrity="${CAM_KIT_SRI}" crossorigin="anonymous" defer></script>\n` : ''}${(o.scripts || []).map((rel) => `<script src="${asset(rel)}" defer></script>`).join('\n')}`;
const account = signedIn
? html`Signed in as <strong>${viewer.displayName || viewer.username || 'you'}</strong> · <a href="/auth/logout?next=${encodeURIComponent(pathNow)}">Sign out</a>`
: html`<a href="/auth/login?next=${encodeURIComponent(pathNow)}">Sign in with OpenVibe</a>`;
ddiff --git a/server/video/pages.js b/server/video/pages.js
iindex e09b6a0..8c58fdc 100644
---- a/server/video/pages.js
++++ b/server/video/pages.js
@@@ -110,6 +110,14 @@ function posterAttr(url) {
* broadcast with no camera, plan R4) shows it in place of a black box. Network's /avatar/:username answers the image
* (or a neutral placeholder), so no lookup is needed here.
*/
++/**
++ * A watch page's video may come along (openvibe-shared 4.192 mini.js): the player's picture-in-picture button takes the
++ * person to the front page and the video goes on in the small player, on every page, from the same second.
++ */
++function miniAttr(title, by, page) {
++ return html` data-ov-mini data-ov-mini-title="${String(title || 'Video')}" data-ov-mini-by="${String(by || '')}" data-ov-mini-page="${String(page || '')}"`;
++}
++
function avatarAttr(username, networkUrl) {
const name = String(username || '');
return /^[A-Za-z0-9_]{1,32}$/.test(name) && /^https?:\/\//.test(String(networkUrl || '')) ? html` data-ov-avatar="${String(networkUrl).replace(/\/+$/, '')}/avatar/${name}"` : '';
@@@ -1494,7 +1502,7 @@ ${found ? sections.map((x) => (x.cards.length || kind ? html`<section class="ovv
<div class="ovv-col ovv-col--main">
<div class="ovv-stage">
${savedNotice(req)}${ownOnly ? notice('Private: only you (and OpenVibe staff) can see this recording.', 'info') : ''}
--<div class="ovv-player-wrap"${labelsUi.attrs(labelsUi.inForce(vod), { owner: vod.username, opened: true })}><video class="ovv-player" data-ov-player controls preload="metadata" playsinline${posterAttr(httpsUrl(vod.thumbnail_url))}${avatarAttr(vod.username, config.networkUrl)}${shapeAttr} src="${fileOf(vod) || ''}" data-ovv-video${nextAttr}${viewAttr}${hlsAttr}>${segs.length ? html`<track kind="captions" label="Transcript (AI)" src="/v/${id}/captions.vtt" default>` : ''}${mems.length ? html`<track kind="chapters" src="/v/${id}/chapters.vtt">` : ''}</video>${pipsTag(pips)}</div>
++<div class="ovv-player-wrap"${labelsUi.attrs(labelsUi.inForce(vod), { owner: vod.username, opened: true })}><video class="ovv-player" data-ov-player${miniAttr(vod.title || 'Stream recording', channelName || vod.username, watchPath)} controls preload="metadata" playsinline${posterAttr(httpsUrl(vod.thumbnail_url))}${avatarAttr(vod.username, config.networkUrl)}${shapeAttr} src="${fileOf(vod) || ''}" data-ovv-video${nextAttr}${viewAttr}${hlsAttr}>${segs.length ? html`<track kind="captions" label="Transcript (AI)" src="/v/${id}/captions.vtt" default>` : ''}${mems.length ? html`<track kind="chapters" src="/v/${id}/chapters.vtt">` : ''}</video>${pipsTag(pips)}</div>
<div class="ovv-head">
${titleLine('Stream recording', vod.title || 'Stream recording')}
${labelsUi.chips(labelsUi.inForce(vod))}
@@@ -1620,7 +1628,7 @@ ${more || ''}
<div class="ovv-stage">
${savedNotice(req)}${req.query.made === '1' ? notice('Your clip was made. It is credited to you on OpenVibe.Live and here.', 'ok') : ''}${ownOnly ? notice('Private: only you (and OpenVibe staff) can see this clip.', 'info') : ''}
${cutting ? html`<div class="ovv-player-wrap ovv-cutting" role="status" data-ovv-reload="8"><p><i class="fa-solid fa-scissors" aria-hidden="true"></i> This clip is being cut. It plays here in a moment; the page checks again by itself.</p></div>`
-- : html`<div class="ovv-player-wrap"${labelsUi.attrs(labelsUi.inForce(clip), { owner: clip.username, opened: true })}><video class="ovv-player" data-ov-player controls preload="metadata" playsinline${avatarAttr(clip.username, config.networkUrl)}${posterAttr(httpsUrl(clip.thumbnail_url))} src="${fileOf(clip) || ''}" data-ovv-video${nextAttr}${viewAttr}${hlsAttr}></video></div>`}
++ : html`<div class="ovv-player-wrap"${labelsUi.attrs(labelsUi.inForce(clip), { owner: clip.username, opened: true })}><video class="ovv-player" data-ov-player${miniAttr(clip.title || 'Clip', clip.source_streamer_display_name || clip.display_name || clip.username, watchPath)} controls preload="metadata" playsinline${avatarAttr(clip.username, config.networkUrl)}${posterAttr(httpsUrl(clip.thumbnail_url))} src="${fileOf(clip) || ''}" data-ovv-video${nextAttr}${viewAttr}${hlsAttr}></video></div>`}
<div class="ovv-head">
${titleLine(aiClip ? 'AI clip' : 'Clip', clip.title || 'Clip')}
${labelsUi.chips(labelsUi.inForce(clip))}
ddiff --git a/test/labels-ui.test.js b/test/labels-ui.test.js
iindex 1786ec5..69f878a 100644
---- a/test/labels-ui.test.js
++++ b/test/labels-ui.test.js
@@@ -144,6 +144,9 @@ function hreq(base, host, path, { method = 'GET', cookie, form, body, type, same
await check('beside the player: more from the channel, never the one playing', async () => {
const v = await video('/v/301');
const more = (v.text.split('<section class="ovv-panel ovv-also"')[1] || '').split('</section>')[0];
++ // The video may come along (the small player): it carries its title, who made it and its own page.
++ assert.match(v.text, /<video class="ovv-player" data-ov-player data-ov-mini data-ov-mini-title="[^"]+" data-ov-mini-by="[^"]*" data-ov-mini-page="\/(?:watch\?v=[^"]+|v\/301)"/, 'a recording says it may come along');
++ assert.match(v.text, /<script src="[^"]*mini[^"]*\.js[^"]*" defer><\/script>/, 'every Video page loads the small player');
assert.ok(!/Kim labelled/.test(more), 'never the recording playing');
assert.ok(!/Kim clip/.test(more), 'its own clips are in Clips from this stream, not again here');
const c = await video('/c/401');
ddiff --git a/test/perf-budget.test.js b/test/perf-budget.test.js
iindex a2975a8..e8d2375 100644
---- a/test/perf-budget.test.js
++++ b/test/perf-budget.test.js
@@@ -14,9 +14,9 @@ const { measure, check, format } = require('openvibe-shared/perf-budget');
// Budgets are set from the numbers measured on 2026-10-08 (the real ones, fresh database), rounded up with about 10%
// headroom; file counts are the measured counts and external files stay at 0.
const BUDGETS = {
-- htmlRawKB: 60.4, // 60.4: measured 58.4 with openvibe-shared 4.161.0, plus room for 4.162.0–4.163.0, 2026-11-09
++ htmlRawKB: 62.5, // 62.5 (2026-11-10: measured 61.8 with openvibe-shared 4.192.0: the small player's script tag, the footer's online strip, the site name's dropdown); 60.4: measured 58.4 with openvibe-shared 4.161.0, plus room for 4.162.0–4.163.0, 2026-11-09
// FAQPage JSON-LD) and next steps on the network; was 29.7 at 27.5
-- htmlBrotliKB: 12.6, // 12.3 (2026-11-07: nine features on the home); 10.6 openvibe-shared 4.22.0: the navbar's magnifier search button and More dropdown (search.js and loading.js load only when used); 9.4 on 10-22 (the same sections); was 7.7 at 7.1
++ htmlBrotliKB: 13.5, // 13.5 (2026-11-10: measured 13.1 with openvibe-shared 4.192.0, as above); 12.6 before; 12.3 (2026-11-07: nine features on the home); 10.6 openvibe-shared 4.22.0: the navbar's magnifier search button and More dropdown (search.js and loading.js load only when used); 9.4 on 10-22 (the same sections); was 7.7 at 7.1
jsFiles: 6, // 6: /shared/ov-glyphs.js in place of Font Awesome's stylesheet (2026-11-01, plan L54 K4); was: 5 (theme-loader, web-runtime, navbar, footer, boost: openvibe-shared/shell)
jsRawKB: 401, // 400.5 with openvibe-shared 4.80.0: 33 more solid glyphs in ov-glyphs.js (2026-11-01); 393.9 with openvibe-shared 4.77.0: the navbar loads the glyphs when a page first shows an icon (2026-11-01); 393: 393: /shared/ov-glyphs.js in place of Font Awesome (2026-11-01, plan L54 K4; Font Awesome's CSS and font were external, uncounted, and heavier); 328.2 openvibe-shared 4.70.0 (2026-11-01: the app bar, busy(), skeletons, view transitions); 324.8 with openvibe-shared 4.66.0: the mark with depth and motion, ov-fx loaded on request (2026-10-31); 323.2 with openvibe-shared 4.59.0: the Pair mark beside the name, set in the brand face (2026-10-31); 321.1 with openvibe-shared 4.53.0: the footer's columns fold on phones (2026-10-30); 318.1 with openvibe-shared 4.52.0: the Dock's loader and its Chats tab (2026-10-30); 315.6 with openvibe-shared 4.48.0 (the site switcher on the mark, the menu's Start tab and quick tools; Kit's touch hit areas and the phone navbar fix, 4.44-4.47.3); 307.8 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 300.5 openvibe-shared 4.25.0: the menu's orb beside the brand, both balances on phones, messages, the account sheet on phones and the bar stepping away on phones; 293 openvibe-shared 4.22.0: the navbar's magnifier search button and More dropdown (search.js and loading.js load only when used); 287.2 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 273.2 with Shared 4.15.0 (2026-10-27: the compact footer opens into the whole one, the pulse line); 264.3 with Shared 4.13.0; 239.8
jsBrotliKB: 98, // 97.6 with openvibe-shared 4.80.0 (2026-11-01); 96.1 with openvibe-shared 4.77.0 (2026-11-01); 96: 96: /shared/ov-glyphs.js in place of Font Awesome (2026-11-01, plan L54 K4); 80.2 openvibe-shared 4.70.0 (2026-11-01: the app bar, busy(), skeletons, view transitions); 79.5 with openvibe-shared 4.59.0: the Pair mark beside the name, set in the brand face (2026-10-31); 78.2 with openvibe-shared 4.52.0: the Dock's loader and its Chats tab (2026-10-30); 77.6 with openvibe-shared 4.48.0 (the site switcher on the mark, the menu's Start tab and quick tools; Kit's touch hit areas and the phone navbar fix, 4.44-4.47.3); 76.1 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 74.2 openvibe-shared 4.25.0: the menu's orb beside the brand, both balances on phones, messages, the account sheet on phones and the bar stepping away on phones; 70.5 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 67.9 Shared 4.15.0; 65.3 Shared 4.12.0: the light/dark switch, the seasonal mark and the theme pairs; 56.5
ddiff --git a/test/video-pages.test.js b/test/video-pages.test.js
iindex c142f8d..931a045 100644
---- a/test/video-pages.test.js
++++ b/test/video-pages.test.js
@@@ -348,7 +348,7 @@ function countOccurrences(haystack, needle) {
assert.ok(r.text.includes('playsinline'));
assert.ok(r.text.includes('data-ovv-video'));
// The network's player (openvibe-shared player.js/css): marked, and both files load on the page.
-- assert.ok(/<video class="ovv-player" data-ov-player controls /.test(r.text), 'the video takes the network player');
++ assert.ok(/<video class="ovv-player" data-ov-player data-ov-mini data-ov-mini-title="[^"]+" data-ov-mini-by="[^"]*" data-ov-mini-page="[^"]+" controls /.test(r.text), 'the video takes the network player, and may come along in the small player');
// The streamer's face for an audio-only recording (openvibe-shared 4.90 player.js, plan R4): Network's avatar URL.
assert.match(r.text, /<video class="ovv-player"[^>]* data-ov-avatar="[^"]+\/avatar\/streamer"/, 'the player knows the streamer\'s face');
assert.ok(/<link rel="stylesheet" href="\/shared\/player\.css\?v=[0-9a-f]+">/.test(r.text) && /<script src="\/shared\/player\.js\?v=[0-9a-f]+" defer><\/script>/.test(r.text), 'player.css and player.js load');