mirror of
https://github.com/R0m1k3/LiveFlow.git
synced 2026-10-22 06:28:17 +02:00
fix: sécuriser startRecording, stopRecording et utiliser la contrainte de micro ideal
This commit is contained in:
1 parent
dd31090d3f
commit
e59bdd1feb
2 files changed
+84
-39
No files matched your search
+82
-39
@@ -66,36 +66,36 @@ async function populateMicList() {
|
|||||||
|
|
||||||
async function startRecording() {
|
async function startRecording() {
|
||||||
const audioConstraints = { echoCancellation: true, noiseSuppression: true, autoGainControl: true };
|
const audioConstraints = { echoCancellation: true, noiseSuppression: true, autoGainControl: true };
|
||||||
const selectedMic = $('mic-select').value;
|
const selectedMic = $('mic-select') ? $('mic-select').value : '';
|
||||||
if (selectedMic) audioConstraints.deviceId = { exact: selectedMic };
|
if (selectedMic) audioConstraints.deviceId = { ideal: selectedMic };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 1. Obtenir le flux média (microphone)
|
||||||
state.mediaStream = await navigator.mediaDevices.getUserMedia({ audio: audioConstraints });
|
state.mediaStream = await navigator.mediaDevices.getUserMedia({ audio: audioConstraints });
|
||||||
// Rafraîchir les étiquettes des micros une fois la permission accordée
|
// Rafraîchir les étiquettes des micros une fois la permission accordée
|
||||||
await populateMicList();
|
await populateMicList();
|
||||||
} catch (err) {
|
|
||||||
const causes = {
|
|
||||||
NotAllowedError: "Permission refusée. Autorisez le micro pour ce site (icône à gauche de l'adresse), et vérifiez que la page est servie en HTTPS.",
|
|
||||||
NotReadableError: "Le micro est inaccessible au niveau du système : fermez les applications qui l'utilisent (Teams, Discord...), vérifiez les paramètres de confidentialité micro de l'OS, et le périphérique d'entrée choisi par le navigateur.",
|
|
||||||
NotFoundError: "Aucun micro détecté. Branchez un micro ou choisissez le bon périphérique d'entrée.",
|
|
||||||
OverconstrainedError: "Le micro ne supporte pas les réglages demandés.",
|
|
||||||
};
|
|
||||||
alert("Impossible de démarrer le micro.\n\n" + (causes[err.name] || "") + "\n\n(" + err + ")");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 2. Établir la connexion WebSocket
|
||||||
const proto = location.protocol === 'https:' ? 'wss' : 'ws';
|
const proto = location.protocol === 'https:' ? 'wss' : 'ws';
|
||||||
state.ws = new WebSocket(`${proto}://${location.host}/ws`);
|
state.ws = new WebSocket(`${proto}://${location.host}/ws`);
|
||||||
state.ws.onopen = () => state.ws.send(JSON.stringify({
|
|
||||||
|
state.ws.onopen = () => {
|
||||||
|
const titleVal = $('title') ? $('title').value : '';
|
||||||
|
const diarizationCb = $('diarization-cb') ? $('diarization-cb').checked : false;
|
||||||
|
state.ws.send(JSON.stringify({
|
||||||
type: 'start',
|
type: 'start',
|
||||||
title: $('title').value,
|
title: titleVal,
|
||||||
diarization: $('diarization-cb').checked,
|
diarization: diarizationCb,
|
||||||
}));
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
state.ws.onmessage = onServerMessage;
|
state.ws.onmessage = onServerMessage;
|
||||||
|
|
||||||
state.ws.onerror = (err) => {
|
state.ws.onerror = (err) => {
|
||||||
console.error('Erreur WebSocket :', err);
|
console.error('Erreur WebSocket :', err);
|
||||||
setStatus('error', 'Erreur Connexion');
|
setStatus('error', 'Erreur Connexion');
|
||||||
};
|
};
|
||||||
|
|
||||||
state.ws.onclose = (e) => {
|
state.ws.onclose = (e) => {
|
||||||
console.log(`WebSocket fermé : code=${e.code}, raison=${e.reason}`);
|
console.log(`WebSocket fermé : code=${e.code}, raison=${e.reason}`);
|
||||||
if (state.recording) {
|
if (state.recording) {
|
||||||
@@ -106,6 +106,7 @@ async function startRecording() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 3. Initialiser l'audio
|
||||||
state.audioContext = new AudioContext();
|
state.audioContext = new AudioContext();
|
||||||
await state.audioContext.audioWorklet.addModule('worklet.js');
|
await state.audioContext.audioWorklet.addModule('worklet.js');
|
||||||
const source = state.audioContext.createMediaStreamSource(state.mediaStream);
|
const source = state.audioContext.createMediaStreamSource(state.mediaStream);
|
||||||
@@ -113,14 +114,79 @@ async function startRecording() {
|
|||||||
state.workletNode.port.onmessage = (e) => queuePcm(new Int16Array(e.data));
|
state.workletNode.port.onmessage = (e) => queuePcm(new Int16Array(e.data));
|
||||||
source.connect(state.workletNode);
|
source.connect(state.workletNode);
|
||||||
|
|
||||||
|
// 4. Mettre à jour l'interface
|
||||||
state.recording = true;
|
state.recording = true;
|
||||||
state.startedAt = Date.now();
|
state.startedAt = Date.now();
|
||||||
state.timerInterval = setInterval(updateTimer, 500);
|
state.timerInterval = setInterval(updateTimer, 500);
|
||||||
|
if ($('record-btn')) {
|
||||||
$('record-btn').textContent = '■ Arrêter';
|
$('record-btn').textContent = '■ Arrêter';
|
||||||
$('record-btn').classList.add('recording');
|
$('record-btn').classList.add('recording');
|
||||||
$('title').disabled = true;
|
}
|
||||||
|
if ($('title')) $('title').disabled = true;
|
||||||
setStatus('rec', 'Enregistrement…');
|
setStatus('rec', 'Enregistrement…');
|
||||||
clearTranscript();
|
clearTranscript();
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Impossible de démarrer l'enregistrement :", err);
|
||||||
|
|
||||||
|
// Nettoyage en cas d'échec
|
||||||
|
if (state.ws) {
|
||||||
|
try { state.ws.close(); } catch (e) {}
|
||||||
|
state.ws = null;
|
||||||
|
}
|
||||||
|
if (state.mediaStream) {
|
||||||
|
try { state.mediaStream.getTracks().forEach((t) => t.stop()); } catch (e) {}
|
||||||
|
state.mediaStream = null;
|
||||||
|
}
|
||||||
|
if (state.audioContext) {
|
||||||
|
try { state.audioContext.close(); } catch (e) {}
|
||||||
|
state.audioContext = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const causes = {
|
||||||
|
NotAllowedError: "Permission refusée. Autorisez le micro pour ce site (icône à gauche de l'adresse), et vérifiez que la page est servie en HTTPS.",
|
||||||
|
NotReadableError: "Le micro est inaccessible au niveau du système : fermez les applications qui l'utilisent (Teams, Discord...), vérifiez les paramètres de confidentialité micro de l'OS, et le périphérique d'entrée choisi par le navigateur.",
|
||||||
|
NotFoundError: "Aucun micro détecté. Branchez un micro ou choisissez le bon périphérique d'entrée.",
|
||||||
|
OverconstrainedError: "Le micro ne supporte pas les réglages demandés.",
|
||||||
|
};
|
||||||
|
alert("Impossible de démarrer l'enregistrement.\n\n" + (causes[err.name] || err.message || err) + "\n\n(" + err + ")");
|
||||||
|
setStatus('idle', 'Prêt');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopRecording(abrupt = false) {
|
||||||
|
state.recording = false;
|
||||||
|
clearInterval(state.timerInterval);
|
||||||
|
|
||||||
|
if (state.workletNode) {
|
||||||
|
try { state.workletNode.disconnect(); } catch (e) {}
|
||||||
|
state.workletNode = null;
|
||||||
|
}
|
||||||
|
if (state.mediaStream) {
|
||||||
|
try { state.mediaStream.getTracks().forEach((t) => t.stop()); } catch (e) {}
|
||||||
|
state.mediaStream = null;
|
||||||
|
}
|
||||||
|
if (state.audioContext) {
|
||||||
|
try { state.audioContext.close(); } catch (e) {}
|
||||||
|
state.audioContext = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!abrupt && state.ws && state.ws.readyState === WebSocket.OPEN) {
|
||||||
|
flushPcm();
|
||||||
|
try { state.ws.send(JSON.stringify({ type: 'stop' })); } catch (e) {}
|
||||||
|
setStatus('busy', 'Finalisation…');
|
||||||
|
} else {
|
||||||
|
setStatus('idle', 'Prêt');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($('record-btn')) {
|
||||||
|
$('record-btn').textContent = '● Démarrer';
|
||||||
|
$('record-btn').classList.remove('recording');
|
||||||
|
}
|
||||||
|
if ($('title')) $('title').disabled = false;
|
||||||
|
|
||||||
|
// Toujours recharger les réunions pour synchroniser la liste (cas d'arrêt abrupt ou échec)
|
||||||
|
loadMeetings();
|
||||||
}
|
}
|
||||||
|
|
||||||
function queuePcm(samples) {
|
function queuePcm(samples) {
|
||||||
@@ -140,29 +206,6 @@ function flushPcm() {
|
|||||||
if (state.ws && state.ws.readyState === WebSocket.OPEN) state.ws.send(out.buffer);
|
if (state.ws && state.ws.readyState === WebSocket.OPEN) state.ws.send(out.buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopRecording(abrupt = false) {
|
|
||||||
state.recording = false;
|
|
||||||
clearInterval(state.timerInterval);
|
|
||||||
if (state.workletNode) state.workletNode.disconnect();
|
|
||||||
if (state.mediaStream) state.mediaStream.getTracks().forEach((t) => t.stop());
|
|
||||||
if (state.audioContext) state.audioContext.close();
|
|
||||||
|
|
||||||
if (!abrupt && state.ws && state.ws.readyState === WebSocket.OPEN) {
|
|
||||||
flushPcm();
|
|
||||||
state.ws.send(JSON.stringify({ type: 'stop' }));
|
|
||||||
setStatus('busy', 'Finalisation…');
|
|
||||||
} else {
|
|
||||||
setStatus('idle', 'Prêt');
|
|
||||||
}
|
|
||||||
|
|
||||||
$('record-btn').textContent = '● Démarrer';
|
|
||||||
$('record-btn').classList.remove('recording');
|
|
||||||
$('title').disabled = false;
|
|
||||||
|
|
||||||
// Toujours recharger les réunions pour synchroniser la liste (cas d'arrêt abrupt ou échec)
|
|
||||||
loadMeetings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onServerMessage(event) {
|
function onServerMessage(event) {
|
||||||
const msg = JSON.parse(event.data);
|
const msg = JSON.parse(event.data);
|
||||||
if (msg.type === 'ready') {
|
if (msg.type === 'ready') {
|
||||||
|
|||||||
@@ -32,4 +32,6 @@ Vérification de la cohérence de tous les fichiers (backend et frontend) et tes
|
|||||||
- Correction de l'affichage de la liste des micros : demande initiale d'autorisation au chargement de la page et filtrage des périphériques sans identifiant.
|
- Correction de l'affichage de la liste des micros : demande initiale d'autorisation au chargement de la page et filtrage des périphériques sans identifiant.
|
||||||
- Ajout de la variable d'environnement DIARIZATION_MAX_SPEAKERS (défaut 8) et intégration dans l'instanciation de SpeakerDiarizer.
|
- Ajout de la variable d'environnement DIARIZATION_MAX_SPEAKERS (défaut 8) et intégration dans l'instanciation de SpeakerDiarizer.
|
||||||
- Résolution des problèmes de synchronisation de la liste des réunions (rechargement systématique dans stopRecording), détection des contextes non sécurisés (affichage d'un bandeau d'erreur si HTTP) et gestion des erreurs de connexion WebSocket.
|
- Résolution des problèmes de synchronisation de la liste des réunions (rechargement systématique dans stopRecording), détection des contextes non sécurisés (affichage d'un bandeau d'erreur si HTTP) et gestion des erreurs de connexion WebSocket.
|
||||||
|
- Sécurisation globale de startRecording() et stopRecording() (bloc try/catch global, nettoyage complet et guards pour éléments null) et changement de contrainte de micro de 'exact' à 'ideal' pour éviter de bloquer la captation.
|
||||||
|
|
||||||
|
|
||||||
Reference in new issue
Block a user