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
+39
-4
No files matched your search
@@ -34,6 +34,7 @@ button, select, input, textarea {
|
|||||||
button { cursor: pointer; }
|
button { cursor: pointer; }
|
||||||
button:active { filter: brightness(1.3); }
|
button:active { filter: brightness(1.3); }
|
||||||
button.wide { width: 100%; margin-top: 10px; font-size: 15px; }
|
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-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-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); }
|
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 -->
|
<!-- AUDIO -->
|
||||||
<section class="panel hidden" data-tab="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>
|
<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" />
|
<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>
|
<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('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 }); });
|
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 ----------------------------------------------------------
|
// ---- Output / Rec ----------------------------------------------------------
|
||||||
let recOn = false;
|
let recOn = false;
|
||||||
$('#fs').addEventListener('click', () => send({ type: 'outputFullscreen' }));
|
$('#fs').addEventListener('click', () => send({ type: 'outputFullscreen' }));
|
||||||
@@ -580,6 +601,7 @@ djv.onReport((m) => {
|
|||||||
case 'meters':
|
case 'meters':
|
||||||
setMeter('m-bass', m.bass); setMeter('m-mid', m.mid); setMeter('m-treble', m.treble);
|
setMeter('m-bass', m.bass); setMeter('m-mid', m.mid); setMeter('m-treble', m.treble);
|
||||||
break;
|
break;
|
||||||
|
case 'devices': inputDevices = m.list || []; renderAudioSrc(); break;
|
||||||
case 'recState': recOn = m.recording; updateRec(); break;
|
case 'recState': recOn = m.recording; updateRec(); break;
|
||||||
case 'recSaved': $('#rec-status').textContent = '✅ Salvato: ' + m.path; break;
|
case 'recSaved': $('#rec-status').textContent = '✅ Salvato: ' + m.path; break;
|
||||||
case 'recError': $('#rec-status').textContent = '⚠️ ' + m.message; break;
|
case 'recError': $('#rec-status').textContent = '⚠️ ' + m.message; break;
|
||||||
|
|||||||
+8
-4
@@ -299,12 +299,16 @@ function probeDurations(paths) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function reportDevices() {
|
async function reportDevices() {
|
||||||
try {
|
// Always answer, even with an empty list: getUserMedia can hang/reject when no
|
||||||
const inputs = await audio.listInputDevices();
|
// 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 })) });
|
djv.report({ type: 'devices', list: inputs.map(d => ({ deviceId: d.deviceId, label: d.label })) });
|
||||||
const outputs = await audio.listOutputDevices();
|
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 })) });
|
djv.report({ type: 'outputs', list: outputs.map(d => ({ deviceId: d.deviceId, label: d.label })) });
|
||||||
} catch (e) { /* ignore */ }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------- command bus
|
// ---------------------------------------------------------------- command bus
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ BIN="$(command -v chromium-browser || command -v chromium || echo chromium)"
|
|||||||
exec "$BIN" \
|
exec "$BIN" \
|
||||||
--kiosk --noerrordialogs --disable-infobars --no-first-run \
|
--kiosk --noerrordialogs --disable-infobars --no-first-run \
|
||||||
--autoplay-policy=no-user-gesture-required \
|
--autoplay-policy=no-user-gesture-required \
|
||||||
|
--use-fake-ui-for-media-stream \
|
||||||
--disable-translate --disable-features=Translate \
|
--disable-translate --disable-features=Translate \
|
||||||
--check-for-update-interval=31536000 \
|
--check-for-update-interval=31536000 \
|
||||||
--overscroll-history-navigation=0 \
|
--overscroll-history-navigation=0 \
|
||||||
|
|||||||
Reference in new issue
Block a user