Filter audio devices by engine type and update tutorial steps
- Add enumerate_devices_by_engine() returning per-engine device lists without cross-engine dedup so frontend can filter correctly - API /audio-devices now includes by_engine dict alongside flat list - Frontend caches per-engine data, filters device dropdown by selected template's engine_type, refreshes on template change - Reorder getting-started tutorial: add API docs and accent color steps - Fix tutorial trigger button focus outline persisting on step 2 - Use accent color variable for tutorial pulse ring animation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -60,6 +60,7 @@ export async function showAudioSourceModal(sourceType, editData) {
|
||||
|
||||
if (editData.source_type === 'multichannel') {
|
||||
_loadAudioTemplates(editData.audio_template_id);
|
||||
document.getElementById('audio-source-audio-template').onchange = _filterDevicesBySelectedTemplate;
|
||||
await _loadAudioDevices();
|
||||
_selectAudioDevice(editData.device_index, editData.is_loopback);
|
||||
} else {
|
||||
@@ -72,6 +73,7 @@ export async function showAudioSourceModal(sourceType, editData) {
|
||||
|
||||
if (sourceType === 'multichannel') {
|
||||
_loadAudioTemplates();
|
||||
document.getElementById('audio-source-audio-template').onchange = _filterDevicesBySelectedTemplate;
|
||||
await _loadAudioDevices();
|
||||
} else {
|
||||
_loadMultichannelSources();
|
||||
@@ -191,25 +193,54 @@ export async function deleteAudioSource(sourceId) {
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────
|
||||
|
||||
let _cachedDevicesByEngine = {};
|
||||
|
||||
async function _loadAudioDevices() {
|
||||
const select = document.getElementById('audio-source-device');
|
||||
if (!select) return;
|
||||
try {
|
||||
const resp = await fetchWithAuth('/audio-devices');
|
||||
if (!resp.ok) throw new Error('fetch failed');
|
||||
const data = await resp.json();
|
||||
const devices = data.devices || [];
|
||||
select.innerHTML = devices.map(d => {
|
||||
const label = d.name;
|
||||
const val = `${d.index}:${d.is_loopback ? '1' : '0'}`;
|
||||
return `<option value="${val}">${escapeHtml(label)}</option>`;
|
||||
}).join('');
|
||||
if (devices.length === 0) {
|
||||
select.innerHTML = '<option value="-1:1">Default</option>';
|
||||
}
|
||||
_cachedDevicesByEngine = data.by_engine || {};
|
||||
} catch {
|
||||
_cachedDevicesByEngine = {};
|
||||
}
|
||||
_filterDevicesBySelectedTemplate();
|
||||
}
|
||||
|
||||
function _filterDevicesBySelectedTemplate() {
|
||||
const select = document.getElementById('audio-source-device');
|
||||
if (!select) return;
|
||||
|
||||
const prevOption = select.options[select.selectedIndex];
|
||||
const prevName = prevOption ? prevOption.textContent : '';
|
||||
|
||||
const templateId = (document.getElementById('audio-source-audio-template') || {}).value;
|
||||
const templates = _cachedAudioTemplates || [];
|
||||
const template = templates.find(t => t.id === templateId);
|
||||
const engineType = template ? template.engine_type : null;
|
||||
|
||||
let devices = [];
|
||||
if (engineType && _cachedDevicesByEngine[engineType]) {
|
||||
devices = _cachedDevicesByEngine[engineType];
|
||||
} else {
|
||||
for (const devList of Object.values(_cachedDevicesByEngine)) {
|
||||
devices = devices.concat(devList);
|
||||
}
|
||||
}
|
||||
|
||||
select.innerHTML = devices.map(d => {
|
||||
const val = `${d.index}:${d.is_loopback ? '1' : '0'}`;
|
||||
return `<option value="${val}">${escapeHtml(d.name)}</option>`;
|
||||
}).join('');
|
||||
|
||||
if (devices.length === 0) {
|
||||
select.innerHTML = '<option value="-1:1">Default</option>';
|
||||
}
|
||||
|
||||
if (prevName) {
|
||||
const match = Array.from(select.options).find(o => o.textContent === prevName);
|
||||
if (match) select.value = match.value;
|
||||
}
|
||||
}
|
||||
|
||||
function _selectAudioDevice(deviceIndex, isLoopback) {
|
||||
|
||||
Reference in New Issue
Block a user