diff --git a/client/dev-dist/sw.js b/client/dev-dist/sw.js index 593a4ec..75e85ea 100644 --- a/client/dev-dist/sw.js +++ b/client/dev-dist/sw.js @@ -81,7 +81,7 @@ define(['./workbox-290dd570'], (function (workbox) { 'use strict'; "revision": "3ca0b8505b4bec776b69afdba2768812" }, { "url": "index.html", - "revision": "0.oebo7b1mt4g" + "revision": "0.su9rr59m8gg" }], {}); workbox.cleanupOutdatedCaches(); workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { diff --git a/client/dev-dist/sw.js.map b/client/dev-dist/sw.js.map index 46f99c8..6000e1b 100644 --- a/client/dev-dist/sw.js.map +++ b/client/dev-dist/sw.js.map @@ -1 +1 @@ -{"version":3,"file":"sw.js","sources":["../../../../../../private/var/folders/lc/87d3wzj544l4skb096m6003m0000gn/T/4b219ed9b0688aace970cf943e14dea9/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-routing/registerRoute.mjs';\nimport {ExpirationPlugin as workbox_expiration_ExpirationPlugin} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-expiration/ExpirationPlugin.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-core/clientsClaim.mjs';\nimport {precacheAndRoute as workbox_precaching_precacheAndRoute} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-precaching/precacheAndRoute.mjs';\nimport {cleanupOutdatedCaches as workbox_precaching_cleanupOutdatedCaches} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-precaching/cleanupOutdatedCaches.mjs';\nimport {NavigationRoute as workbox_routing_NavigationRoute} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-routing/NavigationRoute.mjs';\nimport {createHandlerBoundToURL as workbox_precaching_createHandlerBoundToURL} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-precaching/createHandlerBoundToURL.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\n\n\nself.skipWaiting();\nworkbox_core_clientsClaim();\n/**\n * The precacheAndRoute() method efficiently caches and responds to\n * requests for URLs in the manifest.\n * See https://goo.gl/S9QRab\n */\nworkbox_precaching_precacheAndRoute([\n {\n \"url\": \"registerSW.js\",\n \"revision\": \"3ca0b8505b4bec776b69afdba2768812\"\n },\n {\n \"url\": \"index.html\",\n \"revision\": \"0.oebo7b1mt4g\"\n }\n], {});\nworkbox_precaching_cleanupOutdatedCaches();workbox_routing_registerRoute(new workbox_routing_NavigationRoute(workbox_precaching_createHandlerBoundToURL(\"index.html\"), {\n allowlist: [/^\\/$/], }));\nworkbox_routing_registerRoute(/^https:\\/\\/.*\\.livekit\\.cloud\\/.*/i, new workbox_strategies_NetworkFirst({ \"cacheName\":\"livekit-cache\", plugins: [new workbox_expiration_ExpirationPlugin({ maxEntries: 10, maxAgeSeconds: 86400 })] }), 'GET');\n\n\n"],"names":["self","skipWaiting","workbox_core_clientsClaim","workbox_precaching_precacheAndRoute","workbox_precaching_cleanupOutdatedCaches","workbox_routing_registerRoute","workbox_routing_NavigationRoute","workbox_precaching_createHandlerBoundToURL","allowlist","workbox_strategies_NetworkFirst","plugins","workbox_expiration_ExpirationPlugin","maxEntries","maxAgeSeconds"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"sw.js","sources":["../../../../../../private/var/folders/lc/87d3wzj544l4skb096m6003m0000gn/T/3a76fc28175050c4b8053cd8cd369ac3/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-routing/registerRoute.mjs';\nimport {ExpirationPlugin as workbox_expiration_ExpirationPlugin} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-expiration/ExpirationPlugin.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-core/clientsClaim.mjs';\nimport {precacheAndRoute as workbox_precaching_precacheAndRoute} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-precaching/precacheAndRoute.mjs';\nimport {cleanupOutdatedCaches as workbox_precaching_cleanupOutdatedCaches} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-precaching/cleanupOutdatedCaches.mjs';\nimport {NavigationRoute as workbox_routing_NavigationRoute} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-routing/NavigationRoute.mjs';\nimport {createHandlerBoundToURL as workbox_precaching_createHandlerBoundToURL} from '/Users/benoit/Documents/code/PTT Live/client/node_modules/workbox-precaching/createHandlerBoundToURL.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\n\n\nself.skipWaiting();\nworkbox_core_clientsClaim();\n/**\n * The precacheAndRoute() method efficiently caches and responds to\n * requests for URLs in the manifest.\n * See https://goo.gl/S9QRab\n */\nworkbox_precaching_precacheAndRoute([\n {\n \"url\": \"registerSW.js\",\n \"revision\": \"3ca0b8505b4bec776b69afdba2768812\"\n },\n {\n \"url\": \"index.html\",\n \"revision\": \"0.su9rr59m8gg\"\n }\n], {});\nworkbox_precaching_cleanupOutdatedCaches();workbox_routing_registerRoute(new workbox_routing_NavigationRoute(workbox_precaching_createHandlerBoundToURL(\"index.html\"), {\n allowlist: [/^\\/$/], }));\nworkbox_routing_registerRoute(/^https:\\/\\/.*\\.livekit\\.cloud\\/.*/i, new workbox_strategies_NetworkFirst({ \"cacheName\":\"livekit-cache\", plugins: [new workbox_expiration_ExpirationPlugin({ maxEntries: 10, maxAgeSeconds: 86400 })] }), 'GET');\n\n\n"],"names":["self","skipWaiting","workbox_core_clientsClaim","workbox_precaching_precacheAndRoute","workbox_precaching_cleanupOutdatedCaches","workbox_routing_registerRoute","workbox_routing_NavigationRoute","workbox_precaching_createHandlerBoundToURL","allowlist","workbox_strategies_NetworkFirst","plugins","workbox_expiration_ExpirationPlugin","maxEntries","maxAgeSeconds"],"mappings":"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"} \ No newline at end of file diff --git a/client/src/Admin.jsx b/client/src/Admin.jsx index d03ea09..def1f6f 100644 --- a/client/src/Admin.jsx +++ b/client/src/Admin.jsx @@ -15,7 +15,7 @@ function Admin() { // Audio devices (Phase 2.5) const [audioDevices, setAudioDevices] = useState([]); - const [currentDevice, setCurrentDevice] = useState(null); + const [currentDevice, setCurrentDevice] = useState({ inputChannels: 8, outputChannels: 8 }); const [selectedInputDevice, setSelectedInputDevice] = useState(null); const [selectedOutputDevice, setSelectedOutputDevice] = useState(null); const [selectedSampleRate, setSelectedSampleRate] = useState(48000); @@ -30,8 +30,7 @@ function Admin() { const [editingGroup, setEditingGroup] = useState(null); const [groupForm, setGroupForm] = useState({ name: '', - audioBitrate: 96, - channels: [] + audioBitrate: 96 }); // Rafraîchissement automatique @@ -102,14 +101,16 @@ function Admin() { const channelNamesData = await channelNamesRes.json(); setAudioDevices(devicesData.devices || []); - setCurrentDevice(currentData.device || {}); + + const device = currentData.device || { inputChannels: 8, outputChannels: 8 }; + setCurrentDevice(device); setChannelNames(channelNamesData.channelNames || { inputs: {}, outputs: {} }); // Ne réinitialiser les sélections que si l'utilisateur n'est pas en train d'éditer if (!isEditingAudio) { - setSelectedInputDevice(currentData.device?.inputDeviceId ?? null); - setSelectedOutputDevice(currentData.device?.outputDeviceId ?? null); - setSelectedSampleRate(currentData.device?.sampleRate || 48000); + setSelectedInputDevice(device.inputDeviceId ?? null); + setSelectedOutputDevice(device.outputDeviceId ?? null); + setSelectedSampleRate(device.sampleRate || 48000); } }; @@ -189,8 +190,7 @@ function Admin() { setEditingGroup(group.id); setGroupForm({ name: group.name, - audioBitrate: group.audioBitrate || 96, - channels: group.channels || [] + audioBitrate: group.audioBitrate || 96 }); setShowGroupForm(true); }; @@ -198,38 +198,12 @@ function Admin() { const resetGroupForm = () => { setGroupForm({ name: '', - audioBitrate: 96, - channels: [] + audioBitrate: 96 }); setShowGroupForm(false); setEditingGroup(null); }; - const addChannel = () => { - setGroupForm({ - ...groupForm, - channels: [ - ...groupForm.channels, - { name: '', audioInput: 0, audioOutput: 0 } - ] - }); - }; - - const updateChannel = (index, field, value) => { - const newChannels = [...groupForm.channels]; - newChannels[index] = { - ...newChannels[index], - [field]: field === 'audioInput' || field === 'audioOutput' ? parseInt(value) : value - }; - setGroupForm({ ...groupForm, channels: newChannels }); - }; - - const removeChannel = (index) => { - const newChannels = [...groupForm.channels]; - newChannels.splice(index, 1); - setGroupForm({ ...groupForm, channels: newChannels }); - }; - // ========== Gestion audio devices (Phase 2.5) ========== const handleSaveChannelNames = async () => { @@ -270,8 +244,8 @@ function Admin() { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - inputDeviceId: selectedInputDevice !== null ? parseInt(selectedInputDevice) : undefined, - outputDeviceId: selectedOutputDevice !== null ? parseInt(selectedOutputDevice) : undefined, + inputDeviceId: selectedInputDevice || undefined, + outputDeviceId: selectedOutputDevice || undefined, sampleRate: parseInt(selectedSampleRate) }) }); @@ -415,43 +389,9 @@ function Admin() { -
-
-

Canaux audio

- -
- - {groupForm.channels.map((channel, index) => ( -
- updateChannel(index, 'name', e.target.value)} - required - /> - updateChannel(index, 'audioInput', e.target.value)} - min="0" - /> - updateChannel(index, 'audioOutput', e.target.value)} - min="0" - /> - -
- ))} -
+

+ Le routing audio se configure dans l'onglet "Audio" via la matrice de routing. +

))} @@ -514,22 +443,22 @@ function Admin() { value={selectedInputDevice ?? ''} onChange={(e) => { setIsEditingAudio(true); - setSelectedInputDevice(e.target.value === '' ? null : parseInt(e.target.value)); + setSelectedInputDevice(e.target.value === '' ? null : e.target.value); }} className="device-select" > {audioDevices .filter(d => d.maxInputChannels > 0) - .map(device => ( - ))} - {selectedInputDevice !== null && ( -

- Device ID {selectedInputDevice} sélectionné + {selectedInputDevice !== null && selectedInputDevice !== '' && ( +

+ Device ID: {selectedInputDevice}

)} @@ -540,22 +469,22 @@ function Admin() { value={selectedOutputDevice ?? ''} onChange={(e) => { setIsEditingAudio(true); - setSelectedOutputDevice(e.target.value === '' ? null : parseInt(e.target.value)); + setSelectedOutputDevice(e.target.value === '' ? null : e.target.value); }} className="device-select" > {audioDevices .filter(d => d.maxOutputChannels > 0) - .map(device => ( - ))} - {selectedOutputDevice !== null && ( -

- Device ID {selectedOutputDevice} sélectionné + {selectedOutputDevice !== null && selectedOutputDevice !== '' && ( +

+ Device ID: {selectedOutputDevice}

)} @@ -603,9 +532,11 @@ function Admin() {
-

Entrées (Inputs)

+

+ Entrées (Inputs) - {currentDevice.inputChannels || 0} canaux disponibles +

- {Array.from({length: 8}, (_, i) => ( + {Array.from({length: currentDevice.inputChannels || 8}, (_, i) => (
{i}
-

Sorties (Outputs)

+

+ Sorties (Outputs) - {currentDevice.outputChannels || 0} canaux disponibles +

- {Array.from({length: 8}, (_, i) => ( + {Array.from({length: currentDevice.outputChannels || 8}, (_, i) => (
{i} - {currentDevice && Object.keys(currentDevice).length > 0 && ( + {currentDevice && currentDevice.inputDeviceId && (

Configuration actuelle

-

Input Device ID: {currentDevice.inputDeviceId ?? 'Non configuré'}

-

Output Device ID: {currentDevice.outputDeviceId ?? 'Non configuré'}

+

Input Device: {currentDevice.inputDeviceName || currentDevice.inputDeviceId}

+

Output Device: {currentDevice.outputDeviceName || currentDevice.outputDeviceId}

Sample Rate: {currentDevice.sampleRate ?? 48000} Hz

+

Canaux: {currentDevice.inputChannels} entrées / {currentDevice.outputChannels} sorties

)} @@ -683,9 +617,9 @@ function Admin() { - {audioDevices.map(device => ( - - {device.id} + {audioDevices.map((device, index) => ( + + {device.id} {device.name} {device.maxInputChannels} {device.maxOutputChannels} diff --git a/client/src/components/AudioRoutingMatrix.jsx b/client/src/components/AudioRoutingMatrix.jsx index 0c70749..57dea72 100644 --- a/client/src/components/AudioRoutingMatrix.jsx +++ b/client/src/components/AudioRoutingMatrix.jsx @@ -10,9 +10,11 @@ function AudioRoutingMatrix({ groups, channelNames }) { const [routing, setRouting] = useState({ inputToGroup: {}, groupToOutput: {}, gains: {} }); const [loading, setLoading] = useState(true); const [showOnlyNamedChannels, setShowOnlyNamedChannels] = useState(false); + const [audioDevice, setAudioDevice] = useState({ inputChannels: 8, outputChannels: 8 }); useEffect(() => { loadRouting(); + loadAudioDevice(); }, []); const loadRouting = async () => { @@ -30,6 +32,21 @@ function AudioRoutingMatrix({ groups, channelNames }) { } }; + const loadAudioDevice = async () => { + try { + const res = await fetch(`${API_URL}/admin/audio/device`); + if (res.ok) { + const data = await res.json(); + setAudioDevice({ + inputChannels: data.device?.inputChannels || 8, + outputChannels: data.device?.outputChannels || 8 + }); + } + } catch (error) { + console.error('Erreur chargement audio device:', error); + } + }; + const saveRouting = async () => { try { const res = await fetch(`${API_URL}/admin/audio/routing`, { @@ -146,7 +163,7 @@ function AudioRoutingMatrix({ groups, channelNames }) { }; const getVisibleInputChannels = () => { - const allInputs = Array.from({length: 8}, (_, i) => i); + const allInputs = Array.from({length: audioDevice.inputChannels}, (_, i) => i); if (showOnlyNamedChannels) { return allInputs.filter(i => hasCustomName('inputs', i)); } @@ -154,7 +171,7 @@ function AudioRoutingMatrix({ groups, channelNames }) { }; const getVisibleOutputChannels = () => { - const allOutputs = Array.from({length: 8}, (_, i) => i); + const allOutputs = Array.from({length: audioDevice.outputChannels}, (_, i) => i); if (showOnlyNamedChannels) { return allOutputs.filter(i => hasCustomName('outputs', i)); } diff --git a/client/vite.config.js b/client/vite.config.js index b4c7e9a..903a966 100644 --- a/client/vite.config.js +++ b/client/vite.config.js @@ -68,7 +68,7 @@ export default defineConfig({ }, proxy: { '/api': { - target: 'http://localhost:3000', + target: 'http://192.168.0.146:3000', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') }, diff --git a/server/api/admin.js b/server/api/admin.js index eff8113..06833a9 100644 --- a/server/api/admin.js +++ b/server/api/admin.js @@ -179,7 +179,7 @@ router.get('/groups', (req, res) => { /** * POST /admin/groups * Crée un nouveau groupe - * Body: { name, audioBitrate?, channels } + * Body: { name, audioBitrate? } * L'ID est généré automatiquement à partir du nom */ router.post('/groups', (req, res) => { @@ -204,7 +204,7 @@ router.post('/groups', (req, res) => { }); } - // Créer le nouveau groupe (sans channels) + // Créer le nouveau groupe const newGroup = { name, ...(audioBitrate && { audioBitrate }) @@ -482,8 +482,28 @@ router.get('/audio/device', (req, res) => { const config = configManager.get(); const audioDevice = config.audio?.device || {}; + // Enrichir avec les infos réelles de la carte si configurée + const devices = CoreAudioBackend.getDevices(); + let deviceInfo = { ...audioDevice }; + + if (audioDevice.inputDeviceId) { + const inputDev = devices.find(d => d.id === audioDevice.inputDeviceId); + if (inputDev) { + deviceInfo.inputChannels = inputDev.maxInputChannels; + deviceInfo.inputDeviceName = inputDev.name; + } + } + + if (audioDevice.outputDeviceId) { + const outputDev = devices.find(d => d.id === audioDevice.outputDeviceId); + if (outputDev) { + deviceInfo.outputChannels = outputDev.maxOutputChannels; + deviceInfo.outputDeviceName = outputDev.name; + } + } + res.json({ - device: audioDevice + device: deviceInfo }); } catch (error) { console.error('Erreur GET /admin/audio/device:', error); diff --git a/server/bridge/AudioBridge.js b/server/bridge/AudioBridge.js index e703395..0e1e32e 100644 --- a/server/bridge/AudioBridge.js +++ b/server/bridge/AudioBridge.js @@ -196,13 +196,31 @@ export class AudioBridge extends EventEmitter { throw new Error(`Plateforme non supportée : ${os}`); } + // Résoudre les device IDs vers les noms pour CoreAudio/sox + let inputDeviceName = null; + let outputDeviceName = null; + + if (this.options.inputDeviceId) { + const inputDevice = BackendClass.getDevices().find(d => d.id === this.options.inputDeviceId); + inputDeviceName = inputDevice ? inputDevice.name : this.options.inputDeviceId; + console.log(`📥 Input device: "${inputDeviceName}" (ID: ${this.options.inputDeviceId})`); + } + + if (this.options.outputDeviceId) { + const outputDevice = BackendClass.getDevices().find(d => d.id === this.options.outputDeviceId); + outputDeviceName = outputDevice ? outputDevice.name : this.options.outputDeviceId; + console.log(`📤 Output device: "${outputDeviceName}" (ID: ${this.options.outputDeviceId})`); + } + // Initialisation du backend sélectionné this.audioBackend = new BackendClass({ sampleRate: this.options.sampleRate, channels: this.options.channels, framesPerBuffer: this.options.frameSize, inputDeviceId: this.options.inputDeviceId, + inputDeviceName: inputDeviceName, outputDeviceId: this.options.outputDeviceId, + outputDeviceName: outputDeviceName, // Options spécifiques PipeWire latency: this.options.latency || 20 }); @@ -366,6 +384,10 @@ export class AudioBridge extends EventEmitter { this.inputChannelBuffers ); + if (this.stats.framesCapture % 100 === 0) { + console.log(`[AudioBridge] Frame ${this.stats.framesCapture}: ${this.inputChannelBuffers.size} inputs → ${groupBuffers.size} groupes`); + } + // ÉTAPE 2 : Pour chaque groupe, envoyer vers LiveKit groupBuffers.forEach((groupBuffer, groupName) => { // Convertir Float32Array → PCM Buffer @@ -375,13 +397,19 @@ export class AudioBridge extends EventEmitter { const opusData = this.opusEncoder.encode(pcmBuffer); if (opusData) { - this.stats.framesCapture++; this.stats.bytesEncoded += opusData.length; // Envoi vers LiveKit via sendAudioData (prend du PCM, pas de l'Opus) // Note: LiveKit gère lui-même l'encodage Opus en interne - if (this.liveKitClient && this.liveKitClient.connected) { + if (this.liveKitClient && this.liveKitClient.isConnected) { this.liveKitClient.sendAudioData(pcmBuffer); + if (this.stats.framesCapture % 100 === 0) { + console.log(`[AudioBridge] → LiveKit groupe "${groupName}": ${pcmBuffer.length} bytes`); + } + } else { + if (this.stats.framesCapture % 100 === 0) { + console.log(`[AudioBridge] ⚠️ LiveKit non connecté, audio non envoyé`); + } } // Émettre aussi pour monitoring/debug @@ -389,7 +417,27 @@ export class AudioBridge extends EventEmitter { } }); + // ÉTAPE 3 : Loopback local - Groupes → Outputs physiques (sans passer par LiveKit) + const outputBuffers = this.groupAudioRouter.processGroupsToOutputs(groupBuffers); + + if (this.stats.framesCapture % 100 === 0) { + console.log(`[AudioBridge] Loopback local: ${groupBuffers.size} groupes → ${outputBuffers.size} outputs`); + } + + // ÉTAPE 4 : Envoyer chaque output à la carte son + outputBuffers.forEach((outputBuffer, channelId) => { + const pcmBuffer = this._float32ToBuffer(outputBuffer); + + // Envoyer à la carte son + this.audioBackend.queueAudio(pcmBuffer); + + if (this.stats.framesCapture % 100 === 0) { + console.log(`[AudioBridge] → Output ${channelId}: ${pcmBuffer.length} bytes`); + } + }); + this.stats.framesCapture++; + this.stats.framesPlayback++; } catch (error) { console.error('Erreur routing capture:', error); this.stats.errors.capture++; diff --git a/server/bridge/AudioBridgeManager.js b/server/bridge/AudioBridgeManager.js index c2985ed..79f9002 100644 --- a/server/bridge/AudioBridgeManager.js +++ b/server/bridge/AudioBridgeManager.js @@ -79,6 +79,10 @@ class AudioBridgeManager extends EventEmitter { if (audioConfig.defaultBitrate) audioConfig.defaultBitrate = parseInt(audioConfig.defaultBitrate, 10); if (audioConfig.customOpusBitrate) audioConfig.customOpusBitrate = parseInt(audioConfig.customOpusBitrate, 10); + // Extraire les device IDs depuis le sous-objet device + const inputDeviceId = audioConfig.device?.inputDeviceId || null; + const outputDeviceId = audioConfig.device?.outputDeviceId || null; + // Créer l'instance avec la config this.bridge = new AudioBridge({ ...audioConfig, @@ -90,7 +94,10 @@ class AudioBridgeManager extends EventEmitter { routing: config.audio?.routing || {}, groups: config.groups || [], maxInputChannels: 32, - maxOutputChannels: 32 + maxOutputChannels: 32, + // Device IDs extraits + inputDeviceId, + outputDeviceId }); // Démarrer le bridge diff --git a/server/bridge/GroupAudioRouter.js b/server/bridge/GroupAudioRouter.js index d320281..5eb4a0f 100644 --- a/server/bridge/GroupAudioRouter.js +++ b/server/bridge/GroupAudioRouter.js @@ -10,6 +10,9 @@ */ import { EventEmitter } from 'events'; +import { getLogger } from '../utils/Logger.js'; + +const logger = getLogger('Routing'); /** * Représente une route audio avec gain @@ -76,7 +79,10 @@ export class GroupAudioRouter extends EventEmitter { * Configure le routing depuis la config YAML */ configure(routingConfig) { - console.log('Configuration du routing audio...'); + logger.info('Configuration du routing audio...'); + logger.debug(' Groupes disponibles:', this.config.groups.map(g => `${g.name || g} (id: ${g.id || g})`).join(', ')); + logger.debug(' inputToGroup:', JSON.stringify(routingConfig.inputToGroup || {})); + logger.debug(' groupToOutput:', JSON.stringify(routingConfig.groupToOutput || {})); // Réinitialise les routes this.inputToGroupRoutes.clear(); @@ -104,7 +110,7 @@ export class GroupAudioRouter extends EventEmitter { } this._updateStatsActiveRoutes(); - console.log(`Routing configuré : ${this.stats.routesActive} routes actives`); + logger.success(`Routing configuré : ${this.stats.routesActive} routes actives`); this.emit('configured', this.stats); } @@ -128,7 +134,7 @@ export class GroupAudioRouter extends EventEmitter { const route = new AudioRoute(inputChannel, groupName, gainDb); this.inputToGroupRoutes.get(key).push(route); - console.log(`Route ajoutée : Input ${inputChannel} -> Group "${groupName}" (${gainDb}dB)`); + logger.info(`Input ${inputChannel} → Group "${groupName}" (${gainDb}dB)`); this._updateStatsActiveRoutes(); } @@ -145,7 +151,7 @@ export class GroupAudioRouter extends EventEmitter { const route = new AudioRoute(groupName, outputChannel, gainDb); this.groupToOutputRoutes.get(key).push(route); - console.log(`Route ajoutée : Group "${groupName}" -> Output ${outputChannel} (${gainDb}dB)`); + logger.info(`Group "${groupName}" → Output ${outputChannel} (${gainDb}dB)`); this._updateStatsActiveRoutes(); } @@ -205,7 +211,9 @@ export class GroupAudioRouter extends EventEmitter { // Réinitialise les buffers de groupe this.groupBuffers.clear(); this.config.groups.forEach(group => { - this.groupBuffers.set(group.name, new Float32Array(this.config.frameSize)); + // Utiliser l'ID (slugifié) plutôt que le nom pour correspondre au routing + const groupId = group.id || group.name || group; + this.groupBuffers.set(groupId, new Float32Array(this.config.frameSize)); }); // Pour chaque canal d'entrée @@ -221,7 +229,10 @@ export class GroupAudioRouter extends EventEmitter { // Applique chaque route (mixage additif vers les groupes) routes.forEach(route => { const groupBuffer = this.groupBuffers.get(route.destination); - if (!groupBuffer) return; + if (!groupBuffer) { + logger.warn(`Buffer groupe "${route.destination}" introuvable pour routing depuis Input ${channelId}`); + return; + } // Mixage avec gain for (let i = 0; i < pcmData.length && i < groupBuffer.length; i++) { @@ -235,6 +246,9 @@ export class GroupAudioRouter extends EventEmitter { for (let i = 0; i < buffer.length; i++) { if (Math.abs(buffer[i]) > 1.0) { this.stats.clippingEvents++; + if (this.stats.clippingEvents % 1000 === 1) { + logger.warn(`Clipping détecté sur groupe "${groupName}" (${this.stats.clippingEvents} événements)`); + } buffer[i] = Math.sign(buffer[i]) * 1.0; // Hard clipping } } @@ -376,7 +390,7 @@ export class GroupAudioRouter extends EventEmitter { this.groupBuffers.clear(); this.outputBuffers.clear(); this.removeAllListeners(); - console.log('GroupAudioRouter détruit'); + logger.info('GroupAudioRouter détruit'); } } diff --git a/server/bridge/LiveKitClient.js b/server/bridge/LiveKitClient.js index e03ea0c..4029be2 100644 --- a/server/bridge/LiveKitClient.js +++ b/server/bridge/LiveKitClient.js @@ -223,6 +223,11 @@ export class LiveKitClient extends EventEmitter { return; } + if (!this.isConnected || !this.localAudioTrack) { + // Silently drop frames si pas encore connecté + return; + } + try { // Création d'un AudioFrame (conversion en int32 explicite) const samplesPerChannel = Math.floor(pcmData.length / 2 / this.options.channels); @@ -238,7 +243,10 @@ export class LiveKitClient extends EventEmitter { await this.audioSource.captureFrame(frame); } catch (error) { - console.error('Erreur envoi audio:', error); + // Ne logger que les erreurs non-InvalidState pour éviter le spam + if (!error.message.includes('InvalidState')) { + console.error('Erreur envoi audio:', error); + } } } diff --git a/server/bridge/backends/CoreAudioBackend.js b/server/bridge/backends/CoreAudioBackend.js index 0ce6947..5983895 100644 --- a/server/bridge/backends/CoreAudioBackend.js +++ b/server/bridge/backends/CoreAudioBackend.js @@ -48,7 +48,6 @@ export class CoreAudioBackend extends EventEmitter { const data = JSON.parse(output); const devices = []; - let id = 0; // Parse audio devices if (data.SPAudioDataType) { @@ -62,13 +61,16 @@ export class CoreAudioBackend extends EventEmitter { const outputChannels = parseInt(device.coreaudio_device_output) || 0; const sampleRate = parseInt(device.coreaudio_device_srate) || 48000; + // Utiliser le UID CoreAudio comme ID (unique et stable) + const deviceUID = device._uniqueID || device.coreaudio_device_uid || name; + // Ignorer les devices sans input ni output if (inputChannels === 0 && outputChannels === 0) { return; } devices.push({ - id: id++, + id: deviceUID, name: name, maxInputChannels: inputChannels, maxOutputChannels: outputChannels, @@ -90,7 +92,7 @@ export class CoreAudioBackend extends EventEmitter { if (devices.length === 0) { devices.push( { - id: 0, + id: 'builtin-mic', name: 'Built-in Microphone', maxInputChannels: 1, maxOutputChannels: 0, @@ -98,7 +100,7 @@ export class CoreAudioBackend extends EventEmitter { hostAPIName: 'Core Audio' }, { - id: 1, + id: 'builtin-output', name: 'Built-in Output', maxInputChannels: 0, maxOutputChannels: 2, @@ -116,7 +118,7 @@ export class CoreAudioBackend extends EventEmitter { // Fallback : devices par défaut return [ { - id: 0, + id: 'builtin-mic', name: 'Built-in Microphone', maxInputChannels: 1, maxOutputChannels: 0, @@ -124,7 +126,7 @@ export class CoreAudioBackend extends EventEmitter { hostAPIName: 'Core Audio' }, { - id: 1, + id: 'builtin-output', name: 'Built-in Output', maxInputChannels: 0, maxOutputChannels: 2, @@ -203,7 +205,7 @@ export class CoreAudioBackend extends EventEmitter { // Si device spécifié if (this.options.inputDeviceName) { - args[1] = this.options.inputDeviceName; + args[2] = this.options.inputDeviceName; // Index 2 = device name } this.captureProcess = spawn('sox', args); @@ -255,8 +257,10 @@ export class CoreAudioBackend extends EventEmitter { * @returns {Promise} */ async startPlayback() { + console.log('🔊 Démarrage playback sox...'); + if (this.isPlaying) { - console.warn('Lecture déjà active'); + console.warn('⚠️ Lecture déjà active'); return; } @@ -264,7 +268,9 @@ export class CoreAudioBackend extends EventEmitter { // Commande sox pour lecture audio // play : lire vers output par défaut // -t raw : format raw PCM depuis stdin + // --buffer : taille du buffer interne sox (en bytes) const args = [ + '--buffer', '8192', // Buffer interne sox '-t', 'raw', '-b', '16', '-e', 'signed-integer', @@ -280,7 +286,9 @@ export class CoreAudioBackend extends EventEmitter { args[args.length - 1] = this.options.outputDeviceName; } - this.playbackProcess = spawn('sox', args); + this.playbackProcess = spawn('sox', args, { + stdio: ['pipe', 'ignore', 'pipe'] // stdin=pipe, stdout=ignore, stderr=pipe + }); // Gérer l'erreur EPIPE sur stdin (si processus se ferme) this.playbackProcess.stdin.on('error', (error) => { @@ -305,13 +313,28 @@ export class CoreAudioBackend extends EventEmitter { }); this.playbackProcess.on('close', (code) => { - console.log(`Sox playback fermé (code ${code})`); + console.log(`⚠️ Sox playback fermé (code ${code}) après ${((Date.now() - this.playbackStartTime) / 1000).toFixed(1)}s`); this.isPlaying = false; + + // Tenter de redémarrer si c'était inattendu + if (code !== 0) { + console.log('🔄 Tentative de redémarrage du playback...'); + setTimeout(() => this.startPlayback(), 1000); + } }); + this.playbackStartTime = Date.now(); this.isPlaying = true; this._startPlaybackLoop(); + // Envoyer immédiatement du silence pour démarrer sox + const silenceBuffer = Buffer.alloc(this.options.framesPerBuffer * 2 * this.options.channels); + for (let i = 0; i < 10; i++) { + if (this.playbackProcess.stdin.writable) { + this.playbackProcess.stdin.write(silenceBuffer); + } + } + console.log(`✓ Lecture audio démarrée : ${this.options.sampleRate}Hz, ${this.options.channels}ch`); } catch (error) { console.error('Erreur démarrage lecture:', error); @@ -323,6 +346,11 @@ export class CoreAudioBackend extends EventEmitter { * Arrête la lecture audio */ stopPlayback() { + if (this.playbackInterval) { + clearInterval(this.playbackInterval); + this.playbackInterval = null; + } + if (this.playbackProcess && this.isPlaying) { this.playbackProcess.kill('SIGTERM'); this.playbackProcess = null; @@ -338,10 +366,16 @@ export class CoreAudioBackend extends EventEmitter { */ queueAudio(audioData) { if (!this.isPlaying) { - console.warn('Tentative ajout audio alors que lecture inactive'); + // Ne logger qu'une fois pour éviter le spam + if (!this.playbackInactiveWarned) { + console.warn('⚠️ Tentative ajout audio alors que lecture inactive (message unique)'); + this.playbackInactiveWarned = true; + } return; } + this.playbackInactiveWarned = false; + // Limite la taille du buffer pour éviter la latence excessive if (this.playbackBuffer.length < this.maxBufferSize) { this.playbackBuffer.push(audioData); @@ -356,42 +390,48 @@ export class CoreAudioBackend extends EventEmitter { * @private */ _startPlaybackLoop() { - const playNextChunk = () => { + // Calculer l'intervalle en ms (ex: 960 frames à 48kHz = 20ms) + const intervalMs = (this.options.framesPerBuffer / this.options.sampleRate) * 1000; + + console.log(`🔁 Boucle playback démarrée (intervalle: ${intervalMs}ms)`); + + // Utiliser setInterval pour garantir un flux continu + this.playbackInterval = setInterval(() => { if (!this.isPlaying || !this.playbackProcess || !this.playbackProcess.stdin) { + if (this.playbackInterval) { + clearInterval(this.playbackInterval); + this.playbackInterval = null; + } return; } + let chunk; if (this.playbackBuffer.length > 0) { - const chunk = this.playbackBuffer.shift(); - try { - if (this.playbackProcess.stdin.writable) { - this.playbackProcess.stdin.write(chunk); - } - } catch (error) { - console.error('Erreur écriture stdin sox:', error); - this.isPlaying = false; - return; - } + chunk = this.playbackBuffer.shift(); } else { - // Buffer vide : underrun (silence) - const silenceBuffer = Buffer.alloc(this.options.framesPerBuffer * 2 * this.options.channels); - try { - if (this.playbackProcess.stdin.writable) { - this.playbackProcess.stdin.write(silenceBuffer); - } - } catch (error) { - // Ignore si process fermé - this.isPlaying = false; - return; - } - this.emit('bufferUnderrun'); + // Buffer vide : underrun (envoyer du silence) + chunk = Buffer.alloc(this.options.framesPerBuffer * 2 * this.options.channels); } - const intervalMs = (this.options.framesPerBuffer / this.options.sampleRate) * 1000; - setTimeout(playNextChunk, intervalMs); - }; - - playNextChunk(); + // Toujours écrire quelque chose pour garder sox actif + try { + if (this.playbackProcess.stdin.writable) { + this.playbackProcess.stdin.write(chunk); + } else { + console.warn('⚠️ Sox stdin non writable, arrêt boucle'); + this.isPlaying = false; + clearInterval(this.playbackInterval); + this.playbackInterval = null; + } + } catch (error) { + if (error.code !== 'EPIPE') { + console.error('Erreur écriture stdin sox:', error); + } + this.isPlaying = false; + clearInterval(this.playbackInterval); + this.playbackInterval = null; + } + }, intervalMs); } /** diff --git a/server/config/config.yaml b/server/config/config.yaml index fdef42d..05a651f 100644 --- a/server/config/config.yaml +++ b/server/config/config.yaml @@ -4,30 +4,29 @@ audio: defaultBitrate: 96 jitterBufferMs: 40 device: - # inputDeviceId et outputDeviceId : laisser vide pour auto-détection du device par défaut - # ou spécifier un ID numérique pour forcer un device spécifique - inputDeviceId: null - outputDeviceId: null + inputDeviceId: Microphone MacBook Pro + outputDeviceId: Haut-parleurs MacBook Pro sampleRate: 48000 routing: inputToGroup: "0": - - technique - "1": - - technique - "2": - - technique + - production + "1": [] + "2": [] "4": - technique "5": - technique groupToOutput: technique: + - "1" + production: - "0" + - "1" gains: {} channelNames: inputs: - "0": Micro Régisseur + "0": iphone "1": Talkback FOH "2": Retour Console "3": Liaison Scène @@ -42,6 +41,7 @@ groups: audioBitrate: 96 channels: [] - name: Technique + audioBitrate: 96 channels: [] - name: Sonorisation audioBitrate: 128 @@ -50,8 +50,8 @@ server: host: 0.0.0.0 port: 3000 livekit: - url: ws://localhost:7880 + url: ws://192.168.0.146:7880 logging: - level: debug - logLatency: true - logAudioStats: true + level: info # Changez à 'debug' pour voir plus de détails + logLatency: false + logAudioStats: false diff --git a/server/index.js b/server/index.js index d1d2c37..b528f74 100644 --- a/server/index.js +++ b/server/index.js @@ -13,12 +13,18 @@ import adminRouter, { registerUser, addLog } from './api/admin.js'; import configManager from './config/ConfigManager.js'; import audioBridgeManager from './bridge/AudioBridgeManager.js'; import AudioLevelsServer from './websocket/AudioLevelsServer.js'; +import { setGlobalLogLevel } from './utils/Logger.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); // Chargement configuration via ConfigManager const config = configManager.get(); +// Configure le niveau de log +const logLevel = config.logging?.level?.toUpperCase() || 'INFO'; +setGlobalLogLevel(logLevel); +console.log(`📊 Niveau de log: ${logLevel}`); + // Note: Les IDs sont maintenant générés automatiquement par le ConfigManager /** diff --git a/server/utils/Logger.js b/server/utils/Logger.js new file mode 100644 index 0000000..68d44a9 --- /dev/null +++ b/server/utils/Logger.js @@ -0,0 +1,78 @@ +/** + * Logger.js + * Système de logging centralisé avec niveaux configurables + */ + +const LOG_LEVELS = { + ERROR: 0, + WARN: 1, + INFO: 2, + DEBUG: 3, + TRACE: 4 +}; + +class Logger { + constructor(category = 'default', level = 'INFO') { + this.category = category; + this.level = LOG_LEVELS[level] ?? LOG_LEVELS.INFO; + } + + setLevel(level) { + this.level = LOG_LEVELS[level] ?? LOG_LEVELS.INFO; + } + + error(message, ...args) { + if (this.level >= LOG_LEVELS.ERROR) { + console.error(`[${this.category}] ❌`, message, ...args); + } + } + + warn(message, ...args) { + if (this.level >= LOG_LEVELS.WARN) { + console.warn(`[${this.category}] ⚠️ `, message, ...args); + } + } + + info(message, ...args) { + if (this.level >= LOG_LEVELS.INFO) { + console.log(`[${this.category}] ℹ️ `, message, ...args); + } + } + + success(message, ...args) { + if (this.level >= LOG_LEVELS.INFO) { + console.log(`[${this.category}] ✓`, message, ...args); + } + } + + debug(message, ...args) { + if (this.level >= LOG_LEVELS.DEBUG) { + console.log(`[${this.category}] 🔍`, message, ...args); + } + } + + trace(message, ...args) { + if (this.level >= LOG_LEVELS.TRACE) { + console.log(`[${this.category}] 🔬`, message, ...args); + } + } +} + +// Configuration globale depuis env ou config +const globalLevel = process.env.LOG_LEVEL || 'INFO'; + +// Loggers par catégorie +const loggers = new Map(); + +export function getLogger(category) { + if (!loggers.has(category)) { + loggers.set(category, new Logger(category, globalLevel)); + } + return loggers.get(category); +} + +export function setGlobalLogLevel(level) { + loggers.forEach(logger => logger.setLevel(level)); +} + +export default { getLogger, setGlobalLogLevel };