openvibers/openvibe.live Commits

Found by the browser check on /vod/… and /clip/… (Host scripts/browser-check.js, WS-Q task 3): the VOD, clip and channel players' volume sliders are named ("Volume"; axe label, critical), and a guest's page no longer asks /api/coins/channel-balance, which answers 401 to a guest (a console error on every channel, VOD and clip page): the navbar points chip and the rewards panel ask only when signed in. test/player-a11y.test.js

OP OpenVibers committed 26 Sept 2026 · fcfc0d5c9b · 6 files, +30 −6


Changes

ddiff --git a/public/fragments/channel.html b/public/fragments/channel.html
iindex 18c5bfec..26acf116 100644
---- a/public/fragments/channel.html
++++ b/public/fragments/channel.html
@@@ -36,7 +36,7 @@
<button class="vc-btn" id="btn-volume" title="Mute/Unmute">
<i class="fa-solid fa-volume-high"></i>
</button>
-- <input type="range" id="volume-slider" min="0" max="100" value="75" class="volume-slider">
++ <input type="range" id="volume-slider" min="0" max="100" value="75" class="volume-slider" aria-label="Volume">
</div>
<span class="dvr-time-display" id="dvr-time-display" style="display:none">0:00 / 0:00</span>
<div class="vc-spacer"></div>
ddiff --git a/public/fragments/clip-player.html b/public/fragments/clip-player.html
iindex 3b4a88a0..84163922 100644
---- a/public/fragments/clip-player.html
++++ b/public/fragments/clip-player.html
@@@ -16,7 +16,7 @@
<button class="vc-btn" id="clp-btn-play" title="Play/Pause"><i class="fa-solid fa-play"></i></button>
<div class="vc-volume-group">
<button class="vc-btn" id="clp-btn-vol" title="Mute"><i class="fa-solid fa-volume-high"></i></button>
-- <input type="range" id="clp-vol-slider" min="0" max="100" value="100" class="volume-slider">
++ <input type="range" id="clp-vol-slider" min="0" max="100" value="100" class="volume-slider" aria-label="Volume">
</div>
<span class="vc-time-display" id="clp-time">0:00 / 0:00</span>
<div class="vc-spacer"></div>
ddiff --git a/public/fragments/vod-player.html b/public/fragments/vod-player.html
iindex 31e501ad..aef5b7b3 100644
---- a/public/fragments/vod-player.html
++++ b/public/fragments/vod-player.html
@@@ -25,7 +25,7 @@
<button class="vc-btn" id="vp-btn-play" title="Play/Pause"><i class="fa-solid fa-play"></i></button>
<div class="vc-volume-group">
<button class="vc-btn" id="vp-btn-vol" title="Mute"><i class="fa-solid fa-volume-high"></i></button>
-- <input type="range" id="vp-vol-slider" min="0" max="100" value="100" class="volume-slider">
++ <input type="range" id="vp-vol-slider" min="0" max="100" value="100" class="volume-slider" aria-label="Volume">
</div>
<span class="vc-time-display" id="vp-time">0:00 / 0:00</span>
<div class="vc-spacer"></div>
ddiff --git a/public/js/app.js b/public/js/app.js
iindex d5ca3e3d..c6a838e0 100644
---- a/public/js/app.js
++++ b/public/js/app.js
@@@ -791,7 +791,8 @@ async function loadBalance() {
let _navPointsStreamerId = null;
async function updateChannelPointsNav(streamerId) {
_navPointsStreamerId = (streamerId && (!currentUser || String(streamerId) !== String(currentUser.id))) ? streamerId : null;
-- if (!_navPointsStreamerId) return;
++ // A guest has no points to show, and the API answers 401 (a console error on every channel, VOD and clip page).
++ if (!_navPointsStreamerId || !currentUser) return;
try {
const d = await api(`/coins/channel-balance?streamerId=${_navPointsStreamerId}`);
const bal = (d.balance || 0).toLocaleString();
ddiff --git a/public/js/opencoins.js b/public/js/opencoins.js
iindex 01a12c6f..ff0a01ae 100644
---- a/public/js/opencoins.js
++++ b/public/js/opencoins.js
@@@ -154,8 +154,8 @@ async function loadRewardsPanel() {
grids.forEach(g => { g.innerHTML = '<p class="muted" style="padding:8px">Failed to load rewards</p>'; });
}
-- // Update this channel's points balance in all reward panels
-- try {
++ // Update this channel's points balance in all reward panels (signed in only: a guest's request answers 401).
++ if (typeof currentUser !== 'undefined' && currentUser) try {
const coinData = await api(`/coins/channel-balance?streamerId=${ownerId}`);
document.querySelectorAll('.rewards-coin-balance').forEach(el => {
el.textContent = (coinData.balance || 0).toLocaleString();
ddiff --git a/test/player-a11y.test.js b/test/player-a11y.test.js
nnew file mode 100644
iindex 00000000..87ff84d5
---- /dev/null
++++ b/test/player-a11y.test.js
@@@ -0,0 +1,23 @@
++'use strict';
++// Found by the browser check (OpenVibe.Host scripts/browser-check.js, WS-Q task 3) on /vod/… and /clip/…:
++// - the players' volume sliders had no accessible name (axe "label", critical); the channel player's too;
++// - a guest's page asked /api/coins/channel-balance, which answers 401 to a guest: a console error on every
++// channel, VOD and clip page. Only a signed-in viewer asks now.
++const assert = require('assert');
++const fs = require('fs');
++const path = require('path');
++
++const pub = (f) => fs.readFileSync(path.join(__dirname, '..', 'public', f), 'utf8');
++for (const f of ['fragments/vod-player.html', 'fragments/clip-player.html', 'fragments/channel.html']) {
++ const html = pub(f);
++ const ranges = html.match(/<input[^>]*type="range"[^>]*>/g) || [];
++ assert.ok(ranges.length, `${f} has a volume slider`);
++ for (const r of ranges) {
++ const id = (r.match(/id="([^"]+)"/) || [])[1];
++ const labelled = /aria-label="[^"]+"/.test(r) || /aria-labelledby="[^"]+"/.test(r) || (id && new RegExp(`<label[^>]*for="${id}"`).test(html));
++ assert.ok(labelled, `${f}: ${r} has an accessible name`);
++ }
++}
++assert.match(pub('js/app.js'), /if \(!_navPointsStreamerId \|\| !currentUser\) return;/, 'the navbar points chip asks only when signed in');
++assert.match(pub('js/opencoins.js'), /if \(typeof currentUser !== 'undefined' && currentUser\) try \{\n\s*const coinData = await api\(`\/coins\/channel-balance/, 'the rewards panel asks only when signed in');
++console.log('player a11y: sliders named; guests never ask for a channel balance');