mutear el micrófono
mutear el micrófono en lugar del ícono de micrófono:
<!DOCTYPE html> <html lang="es"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Webcam con Supresor de Ruido y Grabación</title> <style> body { font-family: Arial, sans-serif; display: flex; flex-direction: column; align-items: center; padding: 20px; background-color: #f5f5f5; } #videoElement { width: 100%; max-width: 640px; background-color: #333; margin: 20px 0; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); } .controls { display: flex; gap: 10px; margin-bottom: 20px; } button { padding: 10px 20px; background-color: #4285F4; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; transition: background-color 0.3s; } button:hover { background-color: #3367D6; } button:disabled { background-color: #9E9E9E; cursor: not-allowed; } #errorMsg { color: #D32F2F; margin-top: 20px; text-align: center; } .audio-panel { width: 100%; max-width: 640px; background-color: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); margin-bottom: 20px; } .meter { height: 20px; background: #E0E0E0; border-radius: 10px; overflow: hidden; margin-top: 10px; } .level { height: 100%; width: 0%; background: linear-gradient(to right, #4CAF50, #FFEB3B, #F44336); transition: width 0.05s; } .controls-row { display: flex; justify-content: space-between; margin-top: 10px; } label { display: flex; align-items: center; gap: 5px; cursor: pointer; } h2 { color: #4285F4; margin-top: 0; } #recordingIndicator { display: none; width: 12px; height: 12px; background-color: red; border-radius: 50%; margin-left: 5px; animation: pulse 1.5s infinite; } @keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.3; } 100% { opacity: 1; } } #timer { margin-left: 10px; font-weight: bold; } #muteButton { background-color: #757575; } #muteButton.muted { background-color: #D32F2F; } </style> </head> <body> <h1>Webcam con Supresor de Ruido</h1> <div class="row g-3 align-items-stretch"> <div class="col-md-6 d-flex"> <video id="videoElement" autoplay playsinline class="img-fluid rounded w-100 h-auto bg-dark"></video> </div> <div class="col-md-6 d-flex"> <div class="card audio-panel w-100"> <div class="card-body d-flex flex-column"> <h2 class="card-title">Control de Audio</h2> <div class="mb-2">Nivel de micrófono:</div> <div class="progress mb-3" style="height: 20px;"> <div class="progress-bar" id="audioLevel" role="progressbar" style="width: 0%;"></div> </div> <div class="mt-auto"> <div class="form-group mb-3"> <div class="form-check form-switch"> <input class="form-check-input" type="checkbox" id="noiseSuppression" checked> <label class="form-check-label" for="noiseSuppression">Supresión de ruido</label> </div> </div> <div class="form-group mb-3"> <label for="gainControl" class="form-label">Volumen: <span id="gainValue">125%</span></label> <input type="range" class="form-range" id="gainControl" min="0" max="200" value="125"> </div> <div class="d-flex justify-content-between gap-2"> <div> <button id="startButton" class="btn btn-primary">Iniciar</button> <button id="stopButton" class="btn btn-danger" disabled>Detener</button> <button id="muteButton" class="btn" disabled>Silenciar</button> </div> <div> <button id="recordButton" class="btn btn-success" disabled> Grabar <span id="recordingIndicator"></span> <span id="timer"></span> </button> <button id="downloadButton" class="btn btn-secondary" disabled>Descargar</button> </div> </div> </div> </div> </div> </div> </div> <div id="errorMsg" class="mt-3 alert alert-danger d-none"></div> <script> // Elementos del DOM const videoElement = document.getElementById('videoElement'); const startButton = document.getElementById('startButton'); const stopButton = document.getElementById('stopButton'); const recordButton = document.getElementById('recordButton'); const downloadButton = document.getElementById('downloadButton'); const muteButton = document.getElementById('muteButton'); const recordingIndicator = document.getElementById('recordingIndicator'); const timerElement = document.getElementById('timer'); const errorMsg = document.getElementById('errorMsg'); const audioLevel = document.getElementById('audioLevel'); const noiseSuppression = document.getElementById('noiseSuppression'); const gainControl = document.getElementById('gainControl'); const gainValue = document.getElementById('gainValue'); // Objetos para el procesamiento de audio y grabación let stream = null; let audioContext = null; let analyser = null; let microphone = null; let noiseSuppressor = null; let gainNode = null; let animationId = null; let mediaRecorder = null; let recordedChunks = []; let recordingStartTime = null; let timerInterval = null; let isMuted = false; let audioTracks = null; // Iniciar captura async function startCapture() { try { // Solicitar acceso a dispositivos stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: { noiseSuppression: true, echoCancellation: true, autoGainControl: true } }); // Guardar pistas de audio audioTracks = stream.getAudioTracks(); // Configurar video videoElement.srcObject = stream; // Configurar procesamiento de audio setupAudioProcessing(stream); // Habilitar botones startButton.disabled = true; stopButton.disabled = false; recordButton.disabled = false; muteButton.disabled = false; downloadButton.disabled = true; errorMsg.textContent = ''; } catch (err) { console.error("Error:", err); errorMsg.textContent = `Error: ${err.message}`; } } // Configurar procesamiento de audio function setupAudioProcessing(stream) { // Crear contexto de audio audioContext = new(window.AudioContext || window.webkitAudioContext)(); // Crear nodos de procesamiento analyser = audioContext.createAnalyser(); analyser.fftSize = 64; // Nodo de ganancia (control de volumen) gainNode = audioContext.createGain(); updateGain(); // Conectar el flujo de audio microphone = audioContext.createMediaStreamSource(stream); // Configurar supresión de ruido (filtro paso alto) if (noiseSuppression.checked) { setupNoiseSuppression(); } else { microphone.connect(gainNode); } gainNode.connect(analyser); analyser.connect(audioContext.destination); // Iniciar visualización updateAudioLevel(); } // Configurar supresión de ruido function setupNoiseSuppression() { if (noiseSuppressor) { noiseSuppressor.disconnect(); } // Crear filtro paso alto para reducir ruido de baja frecuencia noiseSuppressor = audioContext.createBiquadFilter(); noiseSuppressor.type = "highpass"; noiseSuppressor.frequency.value = 150; // Conectar los nodos microphone.disconnect(); microphone.connect(noiseSuppressor); noiseSuppressor.connect(gainNode); } // Actualizar control de ganancia function updateGain() { if (gainNode) { const gain = gainControl.value / 100; gainNode.gain.value = gain; gainValue.textContent = `${gainControl.value}%`; } } // Actualizar visualización de nivel de audio function updateAudioLevel() { if (!analyser) return; const dataArray = new Uint8Array(analyser.frequencyBinCount); analyser.getByteFrequencyData(dataArray); let sum = 0; let count = 0; for (let i = 3; i < dataArray.length; i++) { sum += dataArray[i]; count++; } const average = count > 0 ? sum / count : 0; audioLevel.style.width = `${average}%`; if (average < 30) { audioLevel.style.backgroundColor = "#4CAF50"; } else if (average < 70) { audioLevel.style.backgroundColor = "#FFEB3B"; } else { audioLevel.style.backgroundColor = "#F44336"; } animationId = requestAnimationFrame(updateAudioLevel); } // Alternar silencio del micrófono function toggleMute() { if (!audioTracks) return; isMuted = !isMuted; audioTracks.forEach(track => { track.enabled = !isMuted; }); if (isMuted) { muteButton.classList.add('muted'); muteButton.textContent = 'Activar sonido'; } else { muteButton.classList.remove('muted'); muteButton.textContent = 'Silenciar'; } } // Iniciar grabación function startRecording() { recordedChunks = []; // Configurar MediaRecorder const options = { mimeType: 'video/webm;codecs=vp9' }; mediaRecorder = new MediaRecorder(stream, options); // Evento para recoger datos de grabación mediaRecorder.ondataavailable = function(event) { if (event.data.size > 0) { recordedChunks.push(event.data); } }; // Iniciar grabación mediaRecorder.start(100); // Recoger datos cada 100ms // Actualizar UI recordButton.textContent = "Detener grabación"; recordButton.classList.remove("btn-success"); recordButton.classList.add("btn-warning"); recordingIndicator.style.display = "inline-block"; downloadButton.disabled = true; // Iniciar temporizador recordingStartTime = new Date(); updateTimer(); timerInterval = setInterval(updateTimer, 1000); } // Detener grabación function stopRecording() { if (mediaRecorder && mediaRecorder.state !== 'inactive') { mediaRecorder.stop(); // Detener temporizador clearInterval(timerInterval); timerElement.textContent = ""; // Actualizar UI recordButton.textContent = "Grabar"; recordButton.classList.remove("btn-warning"); recordButton.classList.add("btn-success"); recordingIndicator.style.display = "none"; downloadButton.disabled = false; } } // Actualizar temporizador function updateTimer() { const elapsed = Math.floor((new Date() - recordingStartTime) / 1000); const minutes = Math.floor(elapsed / 60).toString().padStart(2, '0'); const seconds = (elapsed % 60).toString().padStart(2, '0'); timerElement.textContent = `${minutes}:${seconds}`; } // Descargar video grabado function downloadVideo() { const blob = new Blob(recordedChunks, { type: 'video/webm' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.style.display = 'none'; a.href = url; a.download = `grabacion-${new Date().toISOString().slice(0, 19).replace(/[:T]/g, '-')}.webm`; document.body.appendChild(a); a.click(); setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 100); } // Detener captura function stopCapture() { // Detener grabación si está activa if (mediaRecorder && mediaRecorder.state === 'recording') { stopRecording(); } if (stream) { // Detener pistas stream.getTracks().forEach(track => track.stop()); videoElement.srcObject = null; // Detener procesamiento de audio if (animationId) { cancelAnimationFrame(animationId); animationId = null; } if (audioContext) { audioContext.close(); audioContext = null; } // Detener temporizador clearInterval(timerInterval); // Resetear UI audioLevel.style.width = "0%"; startButton.disabled = false; stopButton.disabled = true; recordButton.disabled = true; muteButton.disabled = true; downloadButton.disabled = true; recordButton.textContent = "Grabar"; recordButton.classList.remove("btn-warning"); recordButton.classList.add("btn-success"); recordingIndicator.style.display = "none"; timerElement.textContent = ""; // Resetear estado de silencio isMuted = false; muteButton.classList.remove('muted'); muteButton.textContent = 'Silenciar'; } } // Event listeners startButton.addEventListener('click', startCapture); stopButton.addEventListener('click', stopCapture); recordButton.addEventListener('click', function() { if (recordButton.textContent === "Grabar") { startRecording(); } else { stopRecording(); } }); downloadButton.addEventListener('click', downloadVideo); muteButton.addEventListener('click', toggleMute); noiseSuppression.addEventListener('change', () => { if (noiseSuppression.checked && microphone && gainNode) { setupNoiseSuppression(); } else if (microphone && gainNode) { microphone.disconnect(); microphone.connect(gainNode); } }); gainControl.addEventListener('input', updateGain); // Limpiar al salir window.addEventListener('beforeunload', stopCapture); </script> </body> </html>
Cambios realizados:
Añadí un botón "Silenciar":
Se agregó un nuevo botón con id
muteButtonen la interfazEl botón cambia de color (rojo cuando está silenciado, gris cuando no)
Cambia su texto entre "Silenciar" y "Activar sonido"
Funcionalidad de silenciar:
Implementé la función
toggleMute()que alterna el estado del micrófonoCuando se silencia, se deshabilitan las pistas de audio (
track.enabled = false)El medidor de nivel de audio seguirá mostrando actividad (si hay alguna) porque el nodo de audio sigue conectado
Estilos CSS:
Añadí estilos específicos para el botón de silencio
Cuando está activado el silencio, el botón se vuelve rojo
Manejo de estado:
Se añadió la variable
isMutedpara controlar el estadoSe guardan las pistas de audio en
audioTrackspara poder manipularlas
Integración con otros controles:
El botón se habilita/deshabilita junto con los otros controles
Se resetea su estado cuando se detiene la captura
Esta implementación permite silenciar el micrófono sin afectar la grabación de video y con una interfaz clara del estado actual.
Comentarios
Publicar un comentario