fix: VAD - detector de silêncio no browser

- AudioContext + Analyser para monitorar nível de áudio em tempo real
- Threshold: avg < 10 por 60% dos frames = silêncio detectado
- Botão parar fica vermelho quando silêncio é detectado (aviso visual)
- Tooltip 'Silêncio detectado - Clique se quiser parar'
- Cleanup de AudioContext ao parar

Isso mitiga hallucinação do Whisper em áudios silenciosos/muito limpos
This commit is contained in:
2026-05-28 00:58:51 +00:00
parent d405441dda
commit 80dd6ef9aa
+59 -1
View File
@@ -2000,11 +2000,17 @@ const initApp = () => {
const btnAnalyzeTranscription = document.getElementById('btnAnalyzeTranscription'); const btnAnalyzeTranscription = document.getElementById('btnAnalyzeTranscription');
const recordProcessingText = document.getElementById('recordProcessingText'); const recordProcessingText = document.getElementById('recordProcessingText');
const recordProcessingSubtext = document.getElementById('recordProcessingSubtext'); const recordProcessingSubtext = document.getElementById('recordProcessingSubtext');
// Variáveis de gravação
let mediaRecorder = null; let mediaRecorder = null;
let audioChunks = []; let audioChunks = [];
let recordingStartTime = null; let recordingStartTime = null;
let timerInterval = null; let timerInterval = null;
let audioContext = null;
let analyser = null;
let frequencyData = null;
let silenceFrames = 0;
let totalFrames = 0;
let vadCheckInterval = null;
let generatedReport = null; let generatedReport = null;
let generatedCriancas = null; let generatedCriancas = null;
let generatedTurma = 'não informada'; let generatedTurma = 'não informada';
@@ -2021,6 +2027,9 @@ const initApp = () => {
} }
} }
clearInterval(timerInterval); clearInterval(timerInterval);
clearInterval(vadCheckInterval);
if (audioContext) { audioContext.close(); audioContext = null; }
if (analyser) { analyser = null; frequencyData = null; }
audioChunks = []; audioChunks = [];
generatedReport = null; generatedReport = null;
recordModal.style.display = 'none'; recordModal.style.display = 'none';
@@ -2074,6 +2083,52 @@ const initApp = () => {
options = { mimeType: 'audio/mp4' }; options = { mimeType: 'audio/mp4' };
} }
} }
// Configurar AudioContext para VAD
audioContext = new AudioContext();
const source = audioContext.createMediaStreamSource(stream);
analyser = audioContext.createAnalyser();
analyser.fftSize = 256;
source.connect(analyser);
frequencyData = new Uint8Array(analyser.frequencyBinCount);
// Iniciar verificação de silêncio (VAD)
silenceFrames = 0;
totalFrames = 0;
vadCheckInterval = setInterval(() => {
if (!analyser) return;
analyser.getByteFrequencyData(frequencyData);
const avg = frequencyData.reduce((a, b) => a + b, 0) / frequencyData.length;
totalFrames++;
if (avg < 10) {
silenceFrames++;
} else {
silenceFrames = Math.max(0, silenceFrames - 2); // recupera mais rápido
}
// Atualizar indicator visual
const recordWaveform = document.getElementById('recordWaveform');
if (recordWaveform && recordWaveform.children[0]) {
const bars = recordWaveform.children;
bars.forEach((bar) => {
const h = Math.max(4, (avg / 255) * 50 + Math.random() * 10);
bar.style.height = `${h}px`;
});
}
// Se mais de 60% dos últimos 30 frames for silêncio, avisar
if (totalFrames > 30 && (silenceFrames / 30) > 0.6) {
const btnStopRecord = document.getElementById('btnStopRecord');
if (btnStopRecord && !btnStopRecord.disabled) {
btnStopRecord.style.borderColor = '#ff6b6b';
btnStopRecord.title = 'Silêncio detectado - Clique se quiser parar';
}
} else {
const btnStopRecord = document.getElementById('btnStopRecord');
if (btnStopRecord) {
btnStopRecord.style.borderColor = '';
btnStopRecord.title = '';
}
}
}, 100);
mediaRecorder = new MediaRecorder(stream, options); mediaRecorder = new MediaRecorder(stream, options);
mediaRecorder.ondataavailable = (e) => { mediaRecorder.ondataavailable = (e) => {
@@ -2110,6 +2165,9 @@ const initApp = () => {
} }
} }
clearInterval(timerInterval); clearInterval(timerInterval);
clearInterval(vadCheckInterval);
if (audioContext) { audioContext.close(); audioContext = null; }
if (analyser) { analyser = null; frequencyData = null; }
// Mostra processando com texto de transcrição // Mostra processando com texto de transcrição
if (recordProcessingText) recordProcessingText.textContent = 'Transcrevendo áudio...'; if (recordProcessingText) recordProcessingText.textContent = 'Transcrevendo áudio...';