openvibers/openvibe.mediahub Commits

The player knows the streamer's face: an audio-only recording shows it (#247)

OP OpenVibers committed 11 days ago · a4b1c8490e · 2 files, +14 −2

Changes

ddiff --git a/server/video/pages.js b/server/video/pages.js
iindex 47c7b87..1bc5c4e 100644
---- a/server/video/pages.js
++++ b/server/video/pages.js
@@@ -103,6 +103,16 @@ function posterAttr(url) {
return url ? html` poster="${url}" data-ov-ambient` : '';
}
++/**
++ * The streamer's face for the player (openvibe-shared 4.90 player.js): a recording with no picture track (a browser
++ * 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.
++ */
++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}"` : '';
++}
++
/**
* An item's playable file. Live's `file_url` is always the file; its `playback_url` is the HLS playlist when Media has
* one (Live T4 step 5), so it is only the fallback for answers from before `file_url` existed.
@@@ -1240,7 +1250,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"><video class="ovv-player" data-ov-player controls preload="metadata" playsinline${posterAttr(httpsUrl(vod.thumbnail_url))}${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"><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-head">
<h1 class="ovv-title">${vod.title || 'Stream recording'}</h1>
${what}
@@@ -1363,7 +1373,7 @@ ${clips.length ? html`<section class="ovv-panel ovv-clips" aria-labelledby="h-cl
<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"><video class="ovv-player" data-ov-player controls preload="metadata" playsinline${posterAttr(httpsUrl(clip.thumbnail_url))} src="${fileOf(clip) || ''}" data-ovv-video${nextAttr}${viewAttr}${hlsAttr}></video></div>`}
++ : html`<div class="ovv-player-wrap"><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>`}
<div class="ovv-head">
<h1 class="ovv-title">${clip.title || 'Clip'}</h1>
${what}
ddiff --git a/test/video-pages.test.js b/test/video-pages.test.js
iindex 26ada4f..bfd781a 100644
---- a/test/video-pages.test.js
++++ b/test/video-pages.test.js
@@@ -342,6 +342,8 @@ function countOccurrences(haystack, needle) {
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');
++ // 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');
assert.ok(r.text.includes('data-ovv-view="https://openvibe.live/api/views/vod/101"'), 'playback counts a view on Live');
const ld = graphOf(r.text).find((x) => x['@type'] === 'VideoObject');