RUIDO DEL MICROFONO-API MediaRecorder
funcionalidad de grabación de video y descarga, necesitarás usar la API MediaRecorder. Aquí está el código modificado con esta funcionalidad:
<!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;
}
</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>
</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 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;
// Iniciar captura
async function startCapture() {
try {
// Solicitar acceso a dispositivos
stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: {
noiseSuppression: true,
echoCancellation: true,
autoGainControl: true
}
});
// Configurar video
videoElement.srcObject = stream;
// Configurar procesamiento de audio
setupAudioProcessing(stream);
// Habilitar botones
startButton.disabled = true;
stopButton.disabled = false;
recordButton.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);
}
// 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;
downloadButton.disabled = true;
recordButton.textContent = "Grabar";
recordButton.classList.remove("btn-warning");
recordButton.classList.add("btn-success");
recordingIndicator.style.display = "none";
timerElement.textContent = "";
}
}
// 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);
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:
Nuevos botones añadidos:
Botón "Grabar/Detener grabación" (alterna entre ambos estados)
Botón "Descargar" (para guardar el video)
Indicador visual de grabación:
Punto rojo parpadeante cuando está grabando
Temporizador que muestra la duración de la grabación
Funcionalidad de grabación:
Usa la API MediaRecorder para capturar el stream de video/audio
Almacena los datos en
recordedChunksPermite detener la grabación y descargar el archivo
Formato del video:
El video se guarda en formato WEBM (VP9)
El nombre del archivo incluye la fecha y hora actual
Limitaciones importantes:
Guardar en el servidor: Este código solo permite descargar el video al dispositivo del usuario. Para guardarlo directamente en una carpeta del servidor necesitarías:
Un backend (Node.js, PHP, etc.)
Enviar el video mediante AJAX/Fetch
Procesar y guardar el archivo en el servidor
Compatibilidad: El formato WEBM es ampliamente compatible, pero algunos navegadores antiguos podrían requerir formatos diferentes.
Si necesitas que el video se guarde automáticamente en una carpeta del servidor, necesitarás implementar una solución de backend adicional. ¿Te gustaría que también te muestre cómo hacer esa parte
Comentarios
Publicar un comentario