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:
1 parent
ec4aecaf7c
commit
6657d1afab
5 files changed
+41
-6
No files matched your search
@@ -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); }
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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 \
|
||||
|
||||
Reference in new issue
Block a user