Mobile: live external audio input (USB) source selector

Expose the engine's live-input mode on the remote: an "Sorgente audio"
selector in the Audio tab lists the input devices the output reports and
switches between File/Playlist and a live USB capture (useInput); picking
File again stops the input. Grant the kiosk getUserMedia access with
--use-fake-ui-for-media-stream so device enumeration/labels work without a
prompt. Make reportDevices resilient (per-probe timeout, always report even
an empty list) so the UI gets feedback when no input is connected yet.

The Pi 5 has no audio input — a USB (line) sound card is required; visuals
react to the source while the audio stays on the mixer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
lucianoandClaude Opus 4.8 committed 2026-06-30 12:27:41 +02:00
1 parent ec4aecaf7c
commit 6657d1afab
5 files changed
+41 -6

No files matched your search

+1
View File
@@ -34,6 +34,7 @@ button, select, input, textarea {
button { cursor: pointer; }
button:active { filter: brightness(1.3); }
button.wide { width: 100%; margin-top: 10px; font-size: 15px; }
#audio-src { width: 100%; }
input[type=range] { -webkit-appearance: none; appearance: none; background: transparent; padding: 0; height: 34px; border: none; }
input[type=range]::-webkit-slider-runnable-track { height: 6px; border-radius: 4px; background: rgba(255,255,255,.14); }
input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 24px; height: 24px; border-radius: 50%; background: var(--accent); margin-top: -9px; box-shadow: 0 1px 4px rgba(0,0,0,.5); }
+7
View File
@@ -90,6 +90,13 @@
<!-- AUDIO -->
<section class="panel hidden" data-tab="audio">
<label class="lbl">Sorgente audio
<button id="audio-src-refresh" class="mini" title="Rileva dispositivi di ingresso">↻ Rileva</button>
</label>
<select id="audio-src">
<option value="">🎵 File / Playlist</option>
</select>
<div id="audio-src-hint" class="hint">Per un ingresso live serve una scheda audio USB collegata al Pi. I visual reagiscono alla sorgente (l'audio resta sul mixer).</div>
<label class="lbl">Reattività <span id="gain-v">1.0×</span></label>
<input id="gain" type="range" min="0" max="3" step="0.05" value="1" />
<label class="lbl">Velocità <span id="speed-v">1.0×</span></label>
+22
View File
@@ -543,6 +543,27 @@ slider('bass', (v) => { $('#bass-v').textContent = v.toFixed(2) + '×'; send({ t
slider('mid', (v) => { $('#mid-v').textContent = v.toFixed(2) + '×'; send({ type: 'bandGain', band: 'mid', value: v }); });
slider('treble', (v) => { $('#treble-v').textContent = v.toFixed(2) + '×'; send({ type: 'bandGain', band: 'treble', value: v }); });
// ---- Sorgente audio (file vs ingresso live USB) ----------------------------
let inputDevices = [];
function renderAudioSrc(selId) {
const sel = $('#audio-src'); if (!sel) return;
const cur = selId != null ? selId : sel.value;
sel.innerHTML = '<option value="">🎵 File / Playlist</option>' +
inputDevices.map((d) => '<option value="' + d.deviceId + '">🎚 ' + (d.label || 'Ingresso ' + d.deviceId.slice(0, 6)) + '</option>').join('');
sel.value = cur;
const hint = $('#audio-src-hint');
if (hint) hint.textContent = inputDevices.length
? inputDevices.length + ' ingresso/i rilevato/i. Scegli la scheda USB: i visual reagiranno alla sorgente (l\'audio resta sul mixer).'
: 'Nessun ingresso rilevato. Collega una scheda audio USB al Pi, poi premi ↻ Rileva.';
}
$('#audio-src').addEventListener('change', (e) => {
const id = e.target.value;
if (id) { send({ type: 'useInput', deviceId: id }); $('#now').textContent = 'Ingresso live'; }
else { send({ type: 'playSilence' }); } // back to file: stops the live input
});
$('#audio-src-refresh').addEventListener('click', () => send({ type: 'refreshDevices' }));
send({ type: 'refreshDevices' }); // ask the output for the current input device list
// ---- Output / Rec ----------------------------------------------------------
let recOn = false;
$('#fs').addEventListener('click', () => send({ type: 'outputFullscreen' }));
@@ -580,6 +601,7 @@ djv.onReport((m) => {
case 'meters':
setMeter('m-bass', m.bass); setMeter('m-mid', m.mid); setMeter('m-treble', m.treble);
break;
case 'devices': inputDevices = m.list || []; renderAudioSrc(); break;
case 'recState': recOn = m.recording; updateRec(); break;
case 'recSaved': $('#rec-status').textContent = '✅ Salvato: ' + m.path; break;
case 'recError': $('#rec-status').textContent = '⚠️ ' + m.message; break;
+10 -6
View File
@@ -299,12 +299,16 @@ function probeDurations(paths) {
}
async function reportDevices() {
try {
const inputs = await audio.listInputDevices();
djv.report({ type: 'devices', list: inputs.map(d => ({ deviceId: d.deviceId, label: d.label })) });
const outputs = await audio.listOutputDevices();
djv.report({ type: 'outputs', list: outputs.map(d => ({ deviceId: d.deviceId, label: d.label })) });
} catch (e) { /* ignore */ }
// Always answer, even with an empty list: getUserMedia can hang/reject when no
// input device exists (e.g. before a USB sound card is plugged in), so guard
// each probe with a timeout and still report so the control UI isn't left hanging.
const withTimeout = (p, ms) => Promise.race([p, new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), ms))]);
let inputs = [];
try { inputs = await withTimeout(audio.listInputDevices(), 3000); } catch (e) { /* none/blocked */ }
djv.report({ type: 'devices', list: inputs.map(d => ({ deviceId: d.deviceId, label: d.label })) });
let outputs = [];
try { outputs = await withTimeout(audio.listOutputDevices(), 3000); } catch (e) { /* none/blocked */ }
djv.report({ type: 'outputs', list: outputs.map(d => ({ deviceId: d.deviceId, label: d.label })) });
}
// ---------------------------------------------------------------- command bus
+1
View File
@@ -5,6 +5,7 @@ BIN="$(command -v chromium-browser || command -v chromium || echo chromium)"
exec "$BIN" \
--kiosk --noerrordialogs --disable-infobars --no-first-run \
--autoplay-policy=no-user-gesture-required \
--use-fake-ui-for-media-stream \
--disable-translate --disable-features=Translate \
--check-for-update-interval=31536000 \
--overscroll-history-navigation=0 \