Add audio capture engine template system with multi-backend support
Introduces an engine+template abstraction for audio capture, mirroring the existing screen capture engine pattern. This enables multiple audio backends (WASAPI for Windows, sounddevice for cross-platform) with per-source engine configuration via reusable templates. Backend: - AudioCaptureEngine ABC with WasapiEngine and SounddeviceEngine implementations - AudioEngineRegistry for engine discovery and factory creation - AudioAnalyzer class decouples FFT/RMS/beat analysis from engine-specific capture - ManagedAudioStream wraps engine stream + analyzer in background thread - AudioCaptureTemplate model and AudioTemplateStore with JSON CRUD - AudioCaptureManager keyed by (engine_type, device_index, is_loopback) - Auto-migration: default template created on startup, assigned to existing sources - Full REST API: CRUD for audio templates + engine listing with availability flags - audio_template_id added to MultichannelAudioSource model and API schemas Frontend: - Audio template cards in Streams > Audio tab with engine badge and config details - Audio template editor modal with engine selector and dynamic config fields - Audio template dropdown in multichannel audio source editor - Template name crosslink badge on multichannel audio source cards - Confirm modal z-index fix (always stacks above editor modals) - i18n keys for EN and RU Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
* This module manages the editor modal and API operations.
|
||||
*/
|
||||
|
||||
import { _cachedAudioSources, set_cachedAudioSources } from '../core/state.js';
|
||||
import { _cachedAudioSources, set_cachedAudioSources, _cachedAudioTemplates } from '../core/state.js';
|
||||
import { fetchWithAuth, escapeHtml } from '../core/api.js';
|
||||
import { t } from '../core/i18n.js';
|
||||
import { showToast, showConfirm } from '../core/ui.js';
|
||||
@@ -26,6 +26,7 @@ class AudioSourceModal extends Modal {
|
||||
description: document.getElementById('audio-source-description').value,
|
||||
type: document.getElementById('audio-source-type').value,
|
||||
device: document.getElementById('audio-source-device').value,
|
||||
audioTemplate: document.getElementById('audio-source-audio-template').value,
|
||||
parent: document.getElementById('audio-source-parent').value,
|
||||
channel: document.getElementById('audio-source-channel').value,
|
||||
};
|
||||
@@ -57,6 +58,7 @@ export async function showAudioSourceModal(sourceType, editData) {
|
||||
document.getElementById('audio-source-description').value = editData.description || '';
|
||||
|
||||
if (editData.source_type === 'multichannel') {
|
||||
_loadAudioTemplates(editData.audio_template_id);
|
||||
await _loadAudioDevices();
|
||||
_selectAudioDevice(editData.device_index, editData.is_loopback);
|
||||
} else {
|
||||
@@ -68,6 +70,7 @@ export async function showAudioSourceModal(sourceType, editData) {
|
||||
document.getElementById('audio-source-description').value = '';
|
||||
|
||||
if (sourceType === 'multichannel') {
|
||||
_loadAudioTemplates();
|
||||
await _loadAudioDevices();
|
||||
} else {
|
||||
_loadMultichannelSources();
|
||||
@@ -110,6 +113,7 @@ export async function saveAudioSource() {
|
||||
const [devIdx, devLoop] = deviceVal.split(':');
|
||||
payload.device_index = parseInt(devIdx) || -1;
|
||||
payload.is_loopback = devLoop !== '0';
|
||||
payload.audio_template_id = document.getElementById('audio-source-audio-template').value || null;
|
||||
} else {
|
||||
payload.audio_source_id = document.getElementById('audio-source-parent').value;
|
||||
payload.channel = document.getElementById('audio-source-channel').value;
|
||||
@@ -223,3 +227,12 @@ function _loadMultichannelSources(selectedId) {
|
||||
`<option value="${s.id}"${s.id === selectedId ? ' selected' : ''}>${escapeHtml(s.name)}</option>`
|
||||
).join('');
|
||||
}
|
||||
|
||||
function _loadAudioTemplates(selectedId) {
|
||||
const select = document.getElementById('audio-source-audio-template');
|
||||
if (!select) return;
|
||||
const templates = _cachedAudioTemplates || [];
|
||||
select.innerHTML = templates.map(t =>
|
||||
`<option value="${t.id}"${t.id === selectedId ? ' selected' : ''}>${escapeHtml(t.name)} (${t.engine_type.toUpperCase()})</option>`
|
||||
).join('');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user