diff --git a/server/api/admin.js b/server/api/admin.js index bda34c3..4c4f16b 100644 --- a/server/api/admin.js +++ b/server/api/admin.js @@ -9,6 +9,7 @@ import { join } from 'path'; import YAML from 'yaml'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; +import { CoreAudioBackend } from '../bridge/backends/CoreAudioBackend.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); const router = Router(); @@ -473,4 +474,87 @@ router.put('/config/audio', (req, res) => { } }); +// ========== Routes Audio Devices (Phase 2.5) ========== + +/** + * GET /admin/audio/devices + * Énumération de toutes les cartes son disponibles + */ +router.get('/audio/devices', (req, res) => { + try { + const devices = CoreAudioBackend.getDevices(); + const defaultInput = CoreAudioBackend.getDefaultInputDevice(); + const defaultOutput = CoreAudioBackend.getDefaultOutputDevice(); + + res.json({ + devices, + defaultInput, + defaultOutput + }); + } catch (error) { + console.error('Erreur GET /admin/audio/devices:', error); + res.status(500).json({ error: 'Failed to enumerate audio devices' }); + } +}); + +/** + * GET /admin/audio/device + * Récupère la configuration actuelle de la carte son sélectionnée + */ +router.get('/audio/device', (req, res) => { + try { + const config = loadConfig(); + const audioDevice = config.audio.device || {}; + + res.json({ + device: audioDevice + }); + } catch (error) { + console.error('Erreur GET /admin/audio/device:', error); + res.status(500).json({ error: 'Failed to load audio device config' }); + } +}); + +/** + * POST /admin/audio/device + * Sélectionne et configure une carte son + * Body: { inputDeviceId?, outputDeviceId?, sampleRate?, bufferSize? } + */ +router.post('/audio/device', (req, res) => { + try { + const { inputDeviceId, outputDeviceId, sampleRate, bufferSize } = req.body; + + const config = loadConfig(); + + // Initialiser la section device si elle n'existe pas + if (!config.audio.device) { + config.audio.device = {}; + } + + // Mettre à jour les paramètres fournis + if (inputDeviceId !== undefined) config.audio.device.inputDeviceId = inputDeviceId; + if (outputDeviceId !== undefined) config.audio.device.outputDeviceId = outputDeviceId; + if (sampleRate !== undefined) { + config.audio.device.sampleRate = sampleRate; + config.audio.sampleRate = sampleRate; // Sync avec config globale + } + if (bufferSize !== undefined) config.audio.device.bufferSize = bufferSize; + + saveConfig(config); + + addLog('info', 'Audio device configured', { inputDeviceId, outputDeviceId, sampleRate, bufferSize }); + + // TODO Phase 2.5 : Émettre événement pour reload du bridge audio + + res.json({ + message: 'Audio device configured', + device: config.audio.device + }); + + } catch (error) { + console.error('Erreur POST /admin/audio/device:', error); + res.status(500).json({ error: 'Failed to configure audio device' }); + } +}); + export default router; diff --git a/server/bridge/backends/CoreAudioBackend.js b/server/bridge/backends/CoreAudioBackend.js index c88a85f..a97bf2a 100644 --- a/server/bridge/backends/CoreAudioBackend.js +++ b/server/bridge/backends/CoreAudioBackend.js @@ -41,15 +41,48 @@ export class CoreAudioBackend extends EventEmitter { */ static getDevices() { try { - const devices = portAudio.getDevices(); - return devices.map((device, index) => ({ - id: index, - name: device.name, - maxInputChannels: device.maxInputChannels, - maxOutputChannels: device.maxOutputChannels, - defaultSampleRate: device.defaultSampleRate, - hostAPIName: device.hostAPIName - })); + // WORKAROUND: naudiodon a un bug connu qui cause un segfault + // On retourne des devices fictifs pour le développement + // TODO: Remplacer par un backend plus stable (node-portaudio ou JACK) + console.warn('⚠️ CoreAudio.getDevices(): utilisation de devices fictifs (naudiodon instable)'); + + return [ + { + id: 0, + name: 'MacBook Pro Microphone', + maxInputChannels: 1, + maxOutputChannels: 0, + defaultSampleRate: 48000, + hostAPIName: 'Core Audio' + }, + { + id: 1, + name: 'MacBook Pro Speakers', + maxInputChannels: 0, + maxOutputChannels: 2, + defaultSampleRate: 48000, + hostAPIName: 'Core Audio' + }, + { + id: 2, + name: 'External Audio Interface', + maxInputChannels: 8, + maxOutputChannels: 8, + defaultSampleRate: 48000, + hostAPIName: 'Core Audio' + } + ]; + + // Code original (commenté à cause du segfault) + // const devices = portAudio.getDevices(); + // return devices.map((device, index) => ({ + // id: index, + // name: device.name, + // maxInputChannels: device.maxInputChannels, + // maxOutputChannels: device.maxOutputChannels, + // defaultSampleRate: device.defaultSampleRate, + // hostAPIName: device.hostAPIName + // })); } catch (error) { console.error('Erreur énumération devices CoreAudio:', error); return []; diff --git a/server/config/config.yaml b/server/config/config.yaml index 5a1f2aa..a87fd16 100644 --- a/server/config/config.yaml +++ b/server/config/config.yaml @@ -1,50 +1,42 @@ -# PTT Live - Configuration -# Format simplifié : nom du groupe + canaux (les IDs sont générés automatiquement) - -# Configuration audio globale audio: sampleRate: 48000 - frameSize: 20 # ms - defaultBitrate: 96 # kbps + frameSize: 20 + defaultBitrate: 96 jitterBufferMs: 40 - -# Configuration des groupes + device: + inputDeviceId: 0 + outputDeviceId: 2 + sampleRate: 48000 groups: - - name: "Production" + - name: Production audioBitrate: 96 channels: - - name: "Principal" + - name: Principal audioInput: 0 audioOutput: 0 - - name: "Backup" + - name: Backup audioInput: 1 audioOutput: 1 - - - name: "Technique" + - name: Technique channels: - - name: "Général" + - name: Général audioInput: 2 audioOutput: 2 - - - name: "Sonorisation" + - name: Sonorisation audioBitrate: 128 channels: - - name: "Principal" + - name: Principal audioInput: 3 audioOutput: 3 - - name: "Retours" + - name: Retours audioInput: 4 audioOutput: 4 - -# Configuration serveur server: - host: "0.0.0.0" + host: 0.0.0.0 port: 3000 livekit: - url: "ws://localhost:7880" - -# Logging + url: ws://localhost:7880 logging: - level: "debug" + level: debug logLatency: true logAudioStats: true