solo el indicador del microfono- b-Web Speech API








 <!DOCTYPE html>

<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Grabador con Transcripción en Tiempo Real</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            background-color: #f5f5f5;
            color: #333;
        }
        h1 {
            color: #2c3e50;
            text-align: center;
            margin-bottom: 30px;
        }
        #controls {
            background-color: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            margin-bottom: 20px;
            display: flex;
            flex-direction: column;
            gap: 15px;
        }
        .button-group {
            display: flex;
            gap: 10px;
            justify-content: center;
        }
        button {
            padding: 12px 24px;
            font-size: 16px;
            cursor: pointer;
            border: none;
            border-radius: 6px;
            transition: all 0.3s;
            font-weight: 600;
        }
        #recordButton {
            background-color: #e74c3c;
            color: white;
        }
        #recordButton:hover {
            background-color: #c0392b;
        }
        #pauseButton {
            background-color: #f39c12;
            color: white;
        }
        #pauseButton:hover {
            background-color: #d35400;
        }
        #stopButton {
            background-color: #2c3e50;
            color: white;
        }
        #stopButton:hover {
            background-color: #1a252f;
        }
        button:disabled {
            background-color: #95a5a6 !important;
            cursor: not-allowed;
        }
        .status {
            padding: 12px;
            border-radius: 6px;
            text-align: center;
            font-weight: 500;
        }
        .recording {
            background-color: #ffebee;
            color: #c62828;
        }
        .paused {
            background-color: #fff8e1;
            color: #ff8f00;
        }
        .transcribing {
            background-color: #e8f5e9;
            color: #2e7d32;
        }
        .error {
            background-color: #ffebee;
            color: #c62828;
        }
        #transcriptionContainer {
            background-color: white;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            padding: 20px;
            height: 400px;
            overflow-y: auto;
        }
        .segment {
            margin-bottom: 15px;
            padding: 12px;
            background-color: #f8f9fa;
            border-radius: 6px;
            border-left: 4px solid #3498db;
        }
        .timestamp {
            font-size: 0.85em;
            color: #7f8c8d;
            margin-bottom: 5px;
            font-weight: 500;
        }
        .transcript {
            line-height: 1.6;
        }
        .audio-visualizer {
            height: 60px;
            width: 100%;
            margin: 15px 0;
            background-color: #ecf0f1;
            border-radius: 4px;
            overflow: hidden;
        }
        #audioIndicator {
            height: 100%;
            width: 0%;
            background-color: #e74c3c;
            transition: width 0.1s;
        }
        .settings {
            background-color: white;
            padding: 15px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            margin-bottom: 20px;
        }
        .settings label {
            display: block;
            margin-bottom: 8px;
            font-weight: 500;
        }
        .settings select, .settings input {
            width: 100%;
            padding: 8px;
            margin-bottom: 15px;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        .queue-status {
            font-size: 0.9em;
            color: #7f8c8d;
            text-align: center;
            margin-top: 10px;
        }
    </style>
</head>
<body>
    <h1>Grabador con Transcripción en Tiempo Real</h1>
   
    <div class="settings">
        <h2>Configuración</h2>
        <label for="language">Idioma:</label>
        <select id="language">
            <option value="spanish">Español</option>
            <option value="english">Inglés</option>
        </select>
       
        <label for="segmentDuration">Duración de segmentos (ms):</label>
        <input type="number" id="segmentDuration" value="3000" min="1000" max="10000">
    </div>
   
    <div id="controls">
        <div class="audio-visualizer">
            <div id="audioIndicator"></div>
        </div>
       
        <div class="button-group">
            <button id="recordButton">Iniciar Grabación</button>
            <button id="pauseButton" disabled>Pausar</button>
            <button id="stopButton" disabled>Detener</button>
        </div>
       
        <div id="recordingStatus" class="status">Preparado para grabar</div>
        <div id="queueStatus" class="queue-status">Segmentos en cola: 0</div>
    </div>
   
    <h2>Transcripción en Tiempo Real</h2>
    <div id="transcriptionStatus" class="status">Esperando para transcribir...</div>
    <div id="transcriptionContainer">
        <div id="transcriptionText"></div>
    </div>
   
    <script>
        // Elementos del DOM
        const recordButton = document.getElementById('recordButton');
        const pauseButton = document.getElementById('pauseButton');
        const stopButton = document.getElementById('stopButton');
        const recordingStatus = document.getElementById('recordingStatus');
        const transcriptionStatus = document.getElementById('transcriptionStatus');
        const transcriptionText = document.getElementById('transcriptionText');
        const audioIndicator = document.getElementById('audioIndicator');
        const languageSelect = document.getElementById('language');
        const segmentDurationInput = document.getElementById('segmentDuration');
        const queueStatus = document.getElementById('queueStatus');
        const transcriptionContainer = document.getElementById('transcriptionContainer');

        // Variables de estado optimizadas
        let mediaRecorder;
        let audioContext;
        let analyser;
        let microphone;
        let isRecording = false;
        let isPaused = false;
        let transcriptionSocket;
        let currentSegmentId = 0;
        let audioChunks = [];
        let audioQueue = [];
        let stream;
        let animationId;
        let mimeType;
        let lastSegmentTime = 0;
        let reconnectAttempts = 0;
        const MAX_RECONNECT_ATTEMPTS = 5;
        const RECONNECT_DELAY = 3000;
        const MIN_SEGMENT_DURATION = 1500;
        const MAX_QUEUE_SIZE = 5;
       
        // Configuración
        let WHISPER_SERVER_URL = 'ws://localhost:9000';
        let SEGMENT_DURATION = 3000;

        // Inicializar análisis de audio para el visualizador
        function initAudioAnalyzer(audioStream) {
            audioContext = new (window.AudioContext || window.webkitAudioContext)();
            analyser = audioContext.createAnalyser();
            microphone = audioContext.createMediaStreamSource(audioStream);
            microphone.connect(analyser);
            analyser.fftSize = 256;
           
            updateAudioIndicator();
        }

        // Actualizar visualizador de audio
        function updateAudioIndicator() {
            if (!isRecording || isPaused) {
                audioIndicator.style.width = '0%';
                animationId = requestAnimationFrame(updateAudioIndicator);
                return;
            }
           
            const bufferLength = analyser.frequencyBinCount;
            const dataArray = new Uint8Array(bufferLength);
            analyser.getByteFrequencyData(dataArray);
           
            let sum = 0;
            for (let i = 0; i < bufferLength; i++) {
                sum += dataArray[i];
            }
            const average = sum / bufferLength;
           
            const level = Math.min(100, Math.max(0, average / 2));
            audioIndicator.style.width = `${level}%`;
           
            animationId = requestAnimationFrame(updateAudioIndicator);
        }

        // Obtener códec compatible
        function getSupportedMimeType() {
            const types = [
                'audio/webm;codecs=opus',
                'audio/ogg;codecs=opus',
                'audio/mp4;codecs=mp4a',
                'audio/webm',
                'audio/ogg',
                'audio/mp4'
            ];
           
            for (let type of types) {
                if (MediaRecorder.isTypeSupported(type)) {
                    console.log("Usando códec:", type);
                    return type;
                }
            }
            return null;
        }

        // Conectar al servidor de transcripción con reconexión automática
        function connectToTranscriptionServer() {
            transcriptionSocket = new WebSocket(WHISPER_SERVER_URL);
           
            transcriptionSocket.onopen = () => {
                reconnectAttempts = 0;
                transcriptionStatus.textContent = "Conectado al servidor de transcripción";
                transcriptionStatus.className = "status transcribing";
               
                // Procesar cualquier segmento en cola
                processQueue();
            };
           
            transcriptionSocket.onmessage = (event) => {
                try {
                    const data = JSON.parse(event.data);
                    const now = new Date();
                   
                    const segmentDiv = document.createElement('div');
                    segmentDiv.className = 'segment';
                   
                    const timestampDiv = document.createElement('div');
                    timestampDiv.className = 'timestamp';
                    timestampDiv.textContent = `[${now.toLocaleTimeString()}] Segmento ${data.segmentId}`;
                   
                    const transcriptDiv = document.createElement('div');
                    transcriptDiv.className = 'transcript';
                    transcriptDiv.textContent = data.text;
                   
                    segmentDiv.appendChild(timestampDiv);
                    segmentDiv.appendChild(transcriptDiv);
                    transcriptionText.appendChild(segmentDiv);
                   
                    // Auto-scroll
                    transcriptionContainer.scrollTop = transcriptionContainer.scrollHeight;
                } catch (error) {
                    console.error("Error procesando mensaje:", error);
                }
            };
           
            transcriptionSocket.onerror = (error) => {
                console.error("Error en conexión de transcripción:", error);
                transcriptionStatus.textContent = "Error en conexión de transcripción";
                transcriptionStatus.className = "status error";
            };
           
            transcriptionSocket.onclose = () => {
                transcriptionStatus.textContent = "Conexión de transcripción cerrada";
                transcriptionStatus.className = "status";
               
                if (isRecording && reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
                    reconnectAttempts++;
                    transcriptionStatus.textContent = `Reconectando (intento ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})...`;
                    setTimeout(connectToTranscriptionServer, RECONNECT_DELAY);
                }
            };
        }

        // Procesar segmento de audio optimizado
        async function processAudioSegment(blob) {
            const segmentId = ++currentSegmentId;
            const now = new Date();
           
            try {
                const reader = new FileReader();
                reader.onload = () => {
                    const base64Data = reader.result.split(',')[1];
                   
                    if (transcriptionSocket && transcriptionSocket.readyState === WebSocket.OPEN) {
                        transcriptionSocket.send(JSON.stringify({
                            segmentId: segmentId,
                            timestamp: now.toISOString(),
                            audioData: base64Data,
                            language: languageSelect.value,
                            sampleRate: 16000,
                            sampleSize: 16,
                            channels: 1
                        }));
                       
                        addStatusMessage(`Enviando segmento ${segmentId} (${Math.round(blob.size/1024)} KB)`);
                        lastSegmentTime = Date.now();
                        updateQueueStatus();
                    } else {
                        if (audioQueue.length < MAX_QUEUE_SIZE) {
                            audioQueue.push({
                                blob,
                                segmentId,
                                timestamp: now.toISOString(),
                                language: languageSelect.value
                            });
                            updateQueueStatus();
                        } else {
                            addStatusMessage("Cola llena - descartando segmento");
                        }
                    }
                };
                reader.readAsDataURL(blob);
               
            } catch (error) {
                console.error("Error procesando segmento:", error);
                addStatusMessage(`Error al procesar segmento ${segmentId}`);
            }
        }

        // Procesar cola de segmentos
        function processQueue() {
            if (audioQueue.length > 0 && transcriptionSocket && transcriptionSocket.readyState === WebSocket.OPEN) {
                const queuedItem = audioQueue.shift();
                const reader = new FileReader();
               
                reader.onload = () => {
                    const base64Data = reader.result.split(',')[1];
                   
                    transcriptionSocket.send(JSON.stringify({
                        segmentId: queuedItem.segmentId,
                        timestamp: queuedItem.timestamp,
                        audioData: base64Data,
                        language: queuedItem.language,
                        sampleRate: 16000,
                        sampleSize: 16,
                        channels: 1
                    }));
                   
                    updateQueueStatus();
                };
               
                reader.readAsDataURL(queuedItem.blob);
            }
        }

        // Actualizar estado de la cola
        function updateQueueStatus() {
            queueStatus.textContent = `Segmentos en cola: ${audioQueue.length}`;
        }

        // Agregar mensaje de estado
        function addStatusMessage(message) {
            const now = new Date();
            const statusDiv = document.createElement('div');
            statusDiv.className = 'segment';
           
            const timestampDiv = document.createElement('div');
            timestampDiv.className = 'timestamp';
            timestampDiv.textContent = `[${now.toLocaleTimeString()}] ${message}`;
           
            statusDiv.appendChild(timestampDiv);
            transcriptionText.appendChild(statusDiv);
            transcriptionContainer.scrollTop = transcriptionContainer.scrollHeight;
        }

        // Iniciar grabación optimizada
        async function startRecording() {
            try {
                SEGMENT_DURATION = parseInt(segmentDurationInput.value) || 3000;
               
                // Obtener stream de audio optimizado
                stream = await navigator.mediaDevices.getUserMedia({
                    audio: {
                        channelCount: 1,
                        sampleRate: 16000,
                        sampleSize: 16,
                        echoCancellation: true,
                        noiseSuppression: true,
                        autoGainControl: true
                    },
                    video: false
                });
               
                // Configurar visualizador de audio
                initAudioAnalyzer(stream);
               
                // Obtener códec compatible
                mimeType = getSupportedMimeType();
                if (!mimeType) {
                    throw new Error("No se encontró un códec de audio soportado");
                }
               
                // Configurar MediaRecorder optimizado
                mediaRecorder = new MediaRecorder(stream, {
                    mimeType: mimeType,
                    audioBitsPerSecond: 64000 // Calidad suficiente para voz
                });
               
                // Manejar eventos optimizados
                mediaRecorder.ondataavailable = (event) => {
                    if (event.data.size > 0) {
                        audioChunks.push(event.data);
                       
                        // Solo procesar si ha pasado el tiempo mínimo entre segmentos
                        const now = Date.now();
                        if (now - lastSegmentTime >= MIN_SEGMENT_DURATION || !isRecording) {
                            const audioBlob = new Blob(audioChunks, { type: mimeType });
                            processAudioSegment(audioBlob);
                            audioChunks = [];
                        }
                    }
                };
               
                mediaRecorder.onstart = () => {
                    recordingStatus.textContent = "Grabando...";
                    recordingStatus.className = "status recording";
                    recordButton.textContent = "Grabando...";
                    pauseButton.disabled = false;
                    stopButton.disabled = false;
                    isRecording = true;
                    audioQueue = [];
                    updateQueueStatus();
                   
                    connectToTranscriptionServer();
                    addStatusMessage("Iniciando grabación...");
                };
               
                mediaRecorder.onpause = () => {
                    recordingStatus.textContent = "Grabación pausada";
                    recordingStatus.className = "status paused";
                    pauseButton.textContent = "Reanudar";
                    isPaused = true;
                    addStatusMessage("Grabación pausada");
                };
               
                mediaRecorder.onresume = () => {
                    recordingStatus.textContent = "Grabando...";
                    recordingStatus.className = "status recording";
                    pauseButton.textContent = "Pausar";
                    isPaused = false;
                    addStatusMessage("Grabación reanudada");
                };
               
                mediaRecorder.onstop = () => {
                    // Procesar cualquier audio restante
                    if (audioChunks.length > 0) {
                        const audioBlob = new Blob(audioChunks, { type: mimeType });
                        processAudioSegment(audioBlob);
                        audioChunks = [];
                    }
                   
                    recordingStatus.textContent = "Grabación finalizada";
                    recordingStatus.className = "status";
                    recordButton.textContent = "Iniciar Grabación";
                    pauseButton.disabled = true;
                    stopButton.disabled = true;
                    isRecording = false;
                    isPaused = false;
                   
                    if (transcriptionSocket) {
                        transcriptionSocket.close();
                    }
                   
                    stream.getTracks().forEach(track => track.stop());
                    cancelAnimationFrame(animationId);
                   
                    addStatusMessage("Grabación finalizada");
                };
               
                mediaRecorder.onerror = (event) => {
                    console.error("Error en MediaRecorder:", event.error);
                    recordingStatus.textContent = `Error: ${event.error.name}`;
                    recordingStatus.className = "status error";
                    stopRecording();
                };
               
                // Iniciar grabación con segmentación
                mediaRecorder.start(SEGMENT_DURATION);
               
            } catch (error) {
                console.error("Error al iniciar grabación:", error);
                recordingStatus.textContent = `Error: ${error.message}`;
                recordingStatus.className = "status error";
               
                if (stream) {
                    stream.getTracks().forEach(track => track.stop());
                }
            }
        }

        // Detener grabación
        function stopRecording() {
            if (mediaRecorder && mediaRecorder.state !== 'inactive') {
                mediaRecorder.stop();
            }
        }

        // Event listeners
        recordButton.addEventListener('click', () => {
            if (!isRecording) {
                startRecording();
            } else {
                stopRecording();
            }
        });
       
        pauseButton.addEventListener('click', () => {
            if (!isPaused) {
                mediaRecorder.pause();
            } else {
                mediaRecorder.resume();
            }
        });
       
        stopButton.addEventListener('click', stopRecording);

        // Verificar compatibilidad al cargar la página
        window.addEventListener('load', () => {
            if (!navigator.mediaDevices || !window.MediaRecorder) {
                recordingStatus.textContent = "Error: Tu navegador no soporta las APIs necesarias";
                recordingStatus.className = "status error";
                recordButton.disabled = true;
            }
        });
    </script>
</body>
</html>

Comentarios

Entradas más populares de este blog

b-Web Speech API

captura video con audio del sistema (como música o sonidos del navegador) pero sin usar el micrófono

EL audio lo envia el navegador-Transcripción de Voz con Whisper