Añadir un botón de micrófono a un sitio web parece sencillo: captar la voz del usuario, reconocer las palabras e insertarlas en un campo de texto. En la práctica, lograr que funcione de forma fiable en navegadores de escritorio y móviles es bastante más difícil, especialmente cuando todo el reconocimiento de voz debe realizarse de forma local, sin enviar el audio del micrófono a un servidor.
El desafío no consiste simplemente en elegir un modelo de reconocimiento de voz. Una implementación funcional debe gestionar correctamente los permisos del micrófono, la captura y el remuestreo del audio, los workers del navegador, WebAssembly, la inicialización de modelos ONNX, la tokenización, la caché y la integración con el campo de entrada de texto de la aplicación.
Este artículo explica cómo implementar una función práctica de conversión de voz a texto que respete la privacidad mediante OpenAI Whisper, Transformers.js, ONNX Runtime Web, AudioWorklet y un Web Worker dedicado. También aborda varios fallos reales de integración que pueden dejar el botón del micrófono bloqueado en un estado de carga, incluso cuando los archivos del modelo se han descargado correctamente.
El objetivo es sencillo: el visitante pulsa un botón de micrófono, habla con naturalidad y ve aparecer el texto reconocido en un campo de entrada existente. Puede revisar o modificar el resultado antes de enviarlo. La grabación del micrófono no necesita salir del navegador.
Qué significa que el reconocimiento de voz se ejecute íntegramente en el navegador
Existen dos enfoques fundamentalmente distintos para añadir entrada de voz a un sitio web.
El primero utiliza un servicio de reconocimiento de voz. El navegador captura el audio, lo envía a un servicio remoto y recibe el texto reconocido. Esto puede simplificar la implementación, pero introduce dependencia de la red, consideraciones de privacidad, disponibilidad del proveedor y posibles costes según el uso.
El segundo enfoque realiza la inferencia directamente en el navegador. El sitio web descarga un modelo de reconocimiento de voz y el entorno de ejecución necesario. El navegador captura el audio del micrófono, lo convierte en muestras numéricas, ejecuta el modelo localmente y genera la transcripción sin recurrir a un servidor de reconocimiento de voz.
Este artículo se centra exclusivamente en el segundo enfoque.
No hay que confundir el reconocimiento de voz habitual del navegador con el reconocimiento garantizado en el dispositivo. La Web Speech API puede utilizar un motor de reconocimiento respaldado por un servidor. Algunas implementaciones recientes ofrecen la opción processLocally, pero la compatibilidad varía entre navegadores y sistemas operativos. Si es obligatorio procesar todo localmente, no debe utilizarse SpeechRecognition genérico como alternativa automática.
En su lugar, la aplicación controla toda la cadena de inferencia.
Microphone
|
v
AudioWorklet
|
v
Float32 PCM audio
|
v
Dedicated Web Worker
|
v
16 kHz resampling
|
v
Whisper + ONNX Runtime Web
|
v
Tokenizer and text decoding
|
v
React input stateEl sitio web sigue proporcionando JavaScript, WebAssembly, archivos de configuración y pesos del modelo. Son descargas estáticas normales. La diferencia es que el audio del micrófono se procesa localmente, en lugar de enviarse para su reconocimiento. Si después el usuario envía el texto resultante mediante un formulario normal, eso constituye una acción independiente de la aplicación.
Cómo elegir un modelo práctico: Whisper Tiny o Base
Whisper es una familia de modelos multilingües de reconocimiento de voz. Su capacidad para reconocer numerosos idiomas con un único modelo resulta especialmente útil en sitios web multilingües.
Para mensajes de voz breves, dos modelos ofrecen un equilibrio útil entre calidad del reconocimiento y consumo de recursos del navegador: Whisper Tiny y Whisper Base.
| Modelo | Tamaño aproximado de los pesos ONNX Q8 seleccionados | Uso recomendado |
|---|---|---|
| Whisper Tiny | 41 MB | Opción prudente para móviles y dispositivos con pocos recursos |
| Whisper Base | 77 MB | Opción de mayor calidad para dispositivos capaces y previamente probados |
Estos tamaños corresponden a los archivos ONNX del codificador y del decodificador seleccionados en las exportaciones examinadas. No incluyen todos los archivos auxiliares y no deben confundirse con el consumo máximo de memoria del navegador.
Whisper Tiny resulta atractivo porque se descarga más rápido, normalmente requiere menos recursos de cálculo y se adapta mejor a entornos móviles de capacidad desconocida. Whisper Base suele ofrecer una calidad de reconocimiento superior, especialmente en algunos idiomas en los que los modelos más pequeños cometen más errores.
El modelo no debe elegirse únicamente según la RAM física. El navegador puede disponer de bastante menos memoria de la que sugieren las especificaciones del dispositivo. También importan la memoria que ya consume la página, las asignaciones de WebAssembly, los tensores temporales y la presión de memoria del sistema operativo.
Un enfoque prudente consiste en empezar con Tiny en dispositivos móviles de capacidad desconocida, reservar Base para categorías de dispositivos que cumplan los requisitos y pasar de Base a Tiny si falla la inicialización del modelo.
Una instancia fallida de Base debe liberarse antes de cargar Tiny. Mantener ambos modelos en memoria anula la finalidad de esta alternativa.
Por qué WebAssembly es una opción predeterminada razonable para producción
ONNX Runtime Web permite ejecutar redes neuronales dentro del navegador. Para esta carga de trabajo, WebAssembly ofrece una vía práctica de inferencia mediante CPU en los principales navegadores de escritorio y móviles.
WebGPU puede acelerar los modelos compatibles en hardware adecuado, pero que esté disponible no equivale a disponer de una cadena funcional de reconocimiento de voz. Que un navegador exponga navigator.gpu no garantiza que ONNX Runtime admita la plataforma, que el grafo concreto de Whisper se inicialice correctamente ni que las inferencias repetidas eviten problemas de recursos.
Por tanto, una implementación prudente puede utilizar WASM como motor de ejecución en producción y evaluar WebGPU por separado, después de probar compatibilidad, precisión, memoria y sesiones repetidas.
En una primera implementación, WASM de un solo hilo también evita que todo el sitio web dependa del aislamiento entre orígenes y de SharedArrayBuffer.
env.backends.onnx.wasm.numThreads = 1;
env.backends.onnx.wasm.proxy = false;Ejecutar la inferencia en un worker dedicado mantiene el procesamiento costoso fuera del hilo principal de JavaScript. El campo de entrada, el teclado virtual, el desplazamiento y la interfaz de la aplicación siguen respondiendo mientras el modelo procesa el audio.
Mantén el reconocimiento de voz fuera de la carga inicial de la página
La función de micrófono no debería imponer un coste importante de red y memoria a quienes nunca la utilizan.
No importes Transformers.js de forma anticipada en el paquete inicial de la aplicación. No inicialices ONNX Runtime al abrir la página ni descargues automáticamente los pesos del modelo durante los períodos de inactividad.
En su lugar, carga los componentes de reconocimiento solo después de que el usuario pulse explícitamente el botón del micrófono.
Una secuencia útil para el primer uso es:
- Recibir la pulsación del botón del micrófono.
- Solicitar permiso para utilizarlo mediante
getUserMedia(). - Si se deniega el permiso, mostrar un error sin descargar el modelo.
- Si se concede, liberar el flujo temporal del micrófono mientras se prepara el modelo.
- Inicializar dinámicamente el worker dedicado y cargar el modelo local seleccionado.
- Esperar a que el worker confirme que la cadena de reconocimiento está lista.
- Iniciar automáticamente la grabación si el usuario no ha cancelado la operación.
- Mostrar un estado de escucha comprensible.
La solicitud de permiso debe estar vinculada a la interacción del usuario. La aplicación también debe gestionar las operaciones canceladas y las restricciones de captura específicas de cada navegador, en lugar de dar por hecho que siempre puede reiniciar la grabación sin solicitar otra interacción.
La preparación del modelo debe mostrar un estado visible, como Preparando la entrada de voz. Un indicador giratorio sin una transición clara puede hacer que una descarga correcta del modelo parezca un fallo del micrófono.
Una vez inicializado, el worker puede mantenerse disponible brevemente para otro dictado. Un tiempo de espera por inactividad de aproximadamente un minuto es un punto de partida razonable. Después, termina el worker y libera el modelo de la memoria. Los archivos del modelo almacenados en la caché del navegador pueden seguir disponibles para visitas posteriores.
Captura audio sin comprimir mediante AudioWorklet
Whisper necesita muestras numéricas de audio. Aunque MediaRecorder es excelente para grabar archivos de audio, utilizarlo para reconocimiento local puede introducir una conversión innecesaria de codificación y decodificación.
La vía de procesamiento más sencilla es:
getUserMedia()
|
v
AudioContext
|
v
AudioWorklet
|
v
Float32 PCM buffers
|
v
Dedicated WorkerAudioWorklet ejecuta código de procesamiento de audio fuera del hilo principal de JavaScript de la aplicación. Puede capturar muestras PCM, convertir entradas multicanal en mono, calcular mediciones sencillas de volumen y transferir al worker los búferes acumulados.
Una inicialización simplificada del micrófono tiene este aspecto:
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
channelCount: { ideal: 1 },
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
},
});
const context = new AudioContext();
await context.audioWorklet.addModule(
'/assets/speech/pcm-worklet.js'
);
const source = context.createMediaStreamSource(stream);
const processor = new AudioWorkletNode(
context,
'pcm-capture'
);
source.connect(processor);
// Connect the worklet through an inaudible output path
// if required to keep the audio processing graph active.Las restricciones del micrófono son preferencias, no garantías. Los navegadores pueden aplicar distintos algoritmos de cancelación de eco, supresión de ruido y control automático de ganancia.
Un detalle importante es que la frecuencia de muestreo del micrófono no es necesariamente de 16 kHz. Muchos contextos de audio utilizan 44.1 kHz o 48 kHz. Whisper espera una entrada de 16 kHz, por lo que las muestras capturadas deben remuestrearse correctamente.
Descartar simplemente una de cada dos o tres muestras no suele ser una solución correcta. Un remuestreador adecuado con filtro de paso bajo, como una implementación de sinc con ventana, reduce el aliasing y preserva las frecuencias útiles de la voz al disminuir la frecuencia de muestreo.
Realiza el remuestreo dentro del worker dedicado, no en la función de AudioWorklet que se ejecuta en tiempo real.
Transfiere audio eficientemente entre los hilos del navegador
AudioWorklet puede producir muchos fragmentos pequeños de audio. Enviar cada bloque de procesamiento de audio como un mensaje independiente puede generar una sobrecarga innecesaria.
Una estrategia práctica consiste en acumular lotes breves de PCM y transferir cada lote como un ArrayBuffer.
worker.postMessage(
{
type: 'audio-chunk',
buffer,
},
[buffer]
);La lista de transferencia es importante: transfiere la propiedad del ArrayBuffer en lugar de realizar una copia completa innecesaria.
El worker puede añadir estos fragmentos a un acumulador de audio y combinarlos al detener la grabación.
También conviene registrar la frecuencia de muestreo real de la captura, la cantidad de muestras recogidas y la duración de la grabación. Estos metadatos permiten diagnosticar grabaciones silenciosas sin registrar datos de audio sensibles.
Detecta pausas sin añadir otra red neuronal
El dictado de mensajes de texto breves no necesita necesariamente un detector neuronal de actividad de voz.
Un detector RMS adaptativo puede ofrecer una aproximación ligera a la actividad de voz. El RMS mide la amplitud de la señal, lo que permite estimar cuándo una persona que estaba hablando ha dejado de hacerlo.
La interfaz siempre debe permitir detener manualmente la grabación con una segunda pulsación del botón del micrófono. La detección automática de silencio es una comodidad, no un requisito para reconocer el habla correctamente.
Entre los valores iniciales razonables para dictados breves se encuentran:
| Parámetro | Valor inicial |
|---|---|
| Intervalo de análisis | 20 ms |
| Calibración del ruido | 300 ms |
| Confirmación de voz | 140 ms |
| Silencio antes de la detención automática | 1.4 segundos |
| Tiempo máximo de espera sin voz | 8 segundos |
| Duración máxima de la grabación | 30 segundos |
Estos son valores de partida para la configuración, no umbrales óptimos universales. La voz tenue, la música de fondo y el procesamiento de ruido del micrófono pueden afectar a las mediciones RMS.
Es preferible permitir que el usuario detenga manualmente la grabación antes que cortar automáticamente las últimas palabras de una frase.
Para la entrada habitual de textos breves, una transcripción única suele ser más sencilla que ejecutar Whisper repetidamente para obtener resultados provisionales. Graba la intervención, detén la grabación, ejecuta el modelo una sola vez e inserta la transcripción final.
La cadena completa de Whisper necesita más que un modelo ONNX
Este es uno de los detalles de implementación más importantes.
La inferencia de Whisper no consiste simplemente en llamar a un codificador ONNX y después a un decodificador ONNX. Una cadena completa de reconocimiento automático de voz también necesita extracción de características de audio, tokenización, configuración de generación y decodificación de los tokens predichos para obtener texto legible.
La cadena de procesamiento completa es:
Raw microphone PCM
|
v
Resampling to 16 kHz
|
v
Whisper feature extractor
|
v
ONNX encoder
|
v
ONNX decoder and token generation
|
v
Whisper tokenizer
|
v
Recognized textQue una sesión ONNX se inicialice correctamente solo demuestra que puede cargarse el grafo del modelo. No demuestra que funcione toda la cadena de reconocimiento.
En una integración probada de Transformers.js, las sesiones del modelo podían inicializarse, pero la transcripción seguía fallando porque la cadena construida localmente no tenía configurados correctamente el procesador y el tokenizador necesarios.
La implementación compatible inicializaba explícitamente estos componentes utilizando los metadatos locales del modelo.
El siguiente fragmento es una ilustración reducida de ese enfoque específico de una versión, no un worker independiente completo:
import {
WhisperTokenizer,
WhisperFeatureExtractor,
WhisperProcessor,
} from '@huggingface/transformers';
// These values come from local model configuration files.
const tokenizer = new WhisperTokenizer(
tokenizerJson,
tokenizerConfig
);
const featureExtractor = new WhisperFeatureExtractor(
preprocessorConfig
);
const processor = new WhisperProcessor(
{},
{
tokenizer,
feature_extractor: featureExtractor,
},
''
);
// In the affected version-specific integration:
recognizer.tokenizer = tokenizer;
recognizer.processor = processor;Las firmas exactas de los constructores y el método de integración deben comprobarse con la versión instalada de Transformers.js. Algunas versiones y exportaciones de modelos pueden construir estos componentes automáticamente; su creación manual no debe considerarse un requisito universal.
Con la cadena completa disponible, puede invocarse la transcripción de textos breves especificando el idioma y la tarea:
const result = await recognizer(audio16k, {
language: 'en',
task: 'transcribe',
return_timestamps: false,
});Cuando la aplicación ya conoce el idioma del usuario, indicarlo explícitamente evita una detección innecesaria del idioma y puede mejorar la previsibilidad con grabaciones breves.
Fija versiones compatibles de Transformers.js y ONNX Runtime
Un segundo tipo importante de fallo está relacionado con la interacción entre Transformers.js y ONNX Runtime Web.
Transformers.js proporciona la cadena de reconocimiento de alto nivel, mientras que ONNX Runtime Web ejecuta los grafos de la red neuronal. Estos paquetes deben ser compatibles entre sí y con las exportaciones exactas de los modelos ONNX utilizados.
En la implementación investigada, una compilación de desarrollo de ONNX Runtime Web inicializaba el codificador de Whisper, pero fallaba al crear una sesión del decodificador cuantizado. El error estaba relacionado con la ausencia de una escala de cuantización en una operación DequantizeLinear.
No se trataba de un problema de permisos del micrófono, de descarga ni de formato de audio. El navegador podía recibir los archivos del modelo, pero el entorno de ejecución no conseguía inicializar uno de los grafos necesarios.
Probar directamente tanto el codificador como el decodificador con una versión estable y fijada de ONNX Runtime Web resolvió esa incompatibilidad concreta.
La comprobación de compatibilidad fue especialmente útil porque permitió distinguir dos afirmaciones diferentes:
- El archivo del modelo se descargó correctamente.
- El entorno de ejecución creó correctamente una sesión de inferencia utilizable a partir del archivo del modelo.
No son equivalentes.
Para las exportaciones de modelos probadas, una combinación compatible fue Transformers.js 4.2.0 con ONNX Runtime Web 1.24.3. Un entorno de ejecución de desarrollo más reciente fallaba con el grafo del decodificador cuantizado. Es una observación específica de esas versiones, no una prueba de que los entornos de ejecución antiguos sean siempre mejores.
Al sustituir una dependencia transitiva del entorno de ejecución, fija la versión exacta en el archivo de bloqueo de dependencias y documenta el motivo. Actualizar el entorno debería exigir una prueba de regresión completa de la inferencia en el navegador, no solo una instalación correcta del paquete.
Comprueba el formato del paquete del worker antes de depurar Whisper
Un Web Worker de JavaScript puede crearse con distintos formatos de módulo.
Se produjo un fallo de integración porque la aplicación instanciaba un worker como módulo ES, mientras que el paquete generado para ese worker utilizaba el mecanismo importScripts() propio de los workers clásicos.
Estos modelos de ejecución no son intercambiables. Un worker de tipo módulo no puede ejecutar sin más código exclusivo de los workers clásicos.
La lección práctica es adaptar el constructor Worker al código realmente generado, en vez de copiar una opción del constructor de un tutorial genérico.
Por ejemplo, las siguientes opciones no son automáticamente equivalentes:
// Module worker: requires a compatible module bundle.
new Worker(workerUrl, { type: 'module' });
// Classic worker: requires a compatible classic bundle.
new Worker(workerUrl);La elección correcta depende de la herramienta de empaquetado y del JavaScript generado.
Al diagnosticar un error de inicialización del worker, inspecciona la consola del navegador, el archivo generado para el worker y el panel de red. Determina si el fallo se produce incluso antes de que ONNX Runtime empiece a inicializarse.
Algunas bibliotecas del navegador empaquetadas también calculan las URL con respecto a la ubicación de su propio módulo. Al incluirlas en workers, comprueba las rutas resultantes de los archivos del entorno de ejecución en lugar de suponer que las URL relativas originales seguirán resolviéndose correctamente.
Sirve los módulos WASM y JavaScript con los tipos MIME correctos
Incluso unos archivos JavaScript y ONNX perfectamente compatibles pueden fallar si el servidor web devuelve encabezados de respuesta incorrectos.
En un caso concreto, un módulo JavaScript con extensión .mjs se servía como application/octet-stream. El navegador rechazaba el módulo porque el tipo de respuesta no era apropiado para ejecutar módulos JavaScript.
Los tipos de respuesta esperados suelen ser:
| Extensión del archivo | Content-Type |
|---|---|
| .mjs | application/javascript |
| .wasm | application/wasm |
Comprueba la respuesta real en producción, no solo el archivo de configuración del servidor.
curl -I https://example.com/runtime/speech/v2/runtime.mjs
curl -I https://example.com/runtime/speech/v2/runtime.wasmLas URL anteriores son ilustrativas. Utiliza los nombres reales de los archivos versionados de la compilación del entorno de ejecución elegida.
Un estado HTTP correcto no basta. El navegador aún puede rechazar un archivo por el tipo MIME, la política de seguridad de contenidos (Content Security Policy), el modo de ejecución del módulo o una URL incorrecta del recurso.
Ten en cuenta que una misma solicitud puede funcionar al abrirse directamente en una pestaña del navegador y fallar al cargarse como módulo ejecutable o dependencia WebAssembly.
Utiliza URL inmutables para los recursos sin dejar a los usuarios atrapados con archivos defectuosos
Los archivos de los modelos Whisper son suficientemente grandes como para que la caché del navegador resulte importante.
Las rutas de recursos inmutables y versionadas permiten reutilizar los archivos del modelo y del entorno de ejecución que no han cambiado, sin descargarlos cada vez que el usuario habla.
Una política habitual de caché es:
Cache-Control: public, max-age=31536000, immutableSin embargo, esto impone un requisito de despliegue: una vez que una URL se declara inmutable, su contenido no debe sustituirse silenciosamente.
Si es necesario cambiar el binario del entorno de ejecución, publícalo bajo una nueva URL versionada.
/models/speech/v1/
/runtime/speech/v1/
/runtime/speech/v2/El cambio de ruta del entorno de ejecución no es meramente estético. Si la aplicación reutiliza una URL inmutable después de cambiar su contenido, el navegador puede seguir usando un binario de WebAssembly incompatible que ya tenía almacenado en caché.
También conviene utilizar los archivos de integración JavaScript y WebAssembly procedentes de la misma distribución del entorno de ejecución. Mezclar archivos de distintas versiones puede provocar fallos de inicialización difíciles de diagnosticar.
Un manifiesto generado durante la compilación con los archivos esperados, sus tamaños y sumas de comprobación criptográficas puede detectar recursos ausentes o que no coinciden antes del despliegue.
Configura la biblioteca de reconocimiento para que utilice recursos alojados en tu propio dominio y desactiva la carga remota de modelos. Así, la ausencia de un modelo local genera un error explícito, en lugar de provocar una descarga no prevista desde un alojamiento externo de modelos.
Por qué el micrófono puede seguir cargando sin que funcione nada
Un síntoma habitual durante el desarrollo es un botón de micrófono que solicita permiso, muestra una animación de carga y finalmente pasa a un estado de error sin insertar texto.
Incluso puede que el navegador indique que el micrófono está en uso.
Esto no demuestra que funcione el reconocedor de voz. Solo demuestra que se permitió la captura del micrófono o que estuvo activa en algún momento.
Varios fallos independientes pueden producir síntomas casi idénticos:
- Tipo MIME incorrecto del módulo JavaScript. El navegador descarga un módulo del entorno de ejecución, pero se niega a ejecutarlo.
- Incompatibilidad del formato de ejecución del worker. El worker compilado utiliza API de workers clásicos, pero se inicia como worker de tipo módulo, o viceversa.
- Incompatibilidad del decodificador ONNX. El entorno de ejecución crea la sesión del codificador, pero falla con el grafo de un decodificador cuantizado.
- Cadena de reconocimiento incompleta. Las sesiones ONNX existen, pero el tokenizador o el procesador de audio no están correctamente inicializados.
Estos problemas deben diagnosticarse de manera independiente. Corregir uno puede dejar al descubierto el siguiente.
El principio más importante de la depuración es que HTTP 200 no significa que el modelo se haya inicializado correctamente, y que el modelo se haya inicializado correctamente no significa que la transcripción funcione.
Ni siquiera una transcripción correcta demuestra que el resultado llegue al campo de texto real de la aplicación.
Cada etapa de la cadena necesita una señal de diagnóstico significativa.
Añade observabilidad sin recopilar conversaciones privadas
Los registros de diagnóstico útiles no requieren grabar lo que dice el usuario.
La aplicación puede registrar estados técnicos como estos:
permission-granted
worker-created
runtime-loading
runtime-ready
encoder-ready
decoder-ready
recording-started
pcm-received
recording-stopped
inference-started
inference-completed
result-insertedEn caso de fallo, incluye la etapa, un código de error técnico estable y metadatos del error depurados de información sensible.
Un fallo de inicialización del modelo debe poder distinguirse de una denegación del permiso del micrófono o de una grabación vacía. Durante el desarrollo, un fallo del worker debe ser visible en lugar de quedar oculto por un controlador de errores genérico.
No registres muestras del micrófono, frases reconocidas, secuencias de tokens ni ningún otro contenido de voz. La telemetría de rendimiento puede utilizar intervalos de duración, categoría del modelo elegido, tiempo de inicialización, tiempo de inferencia y categorías de errores.
Esto facilita mucho la depuración de un micrófono que falla sin debilitar los requisitos de privacidad.
Inserta la transcripción mediante el estado de React
El reconocimiento de voz solo resulta útil cuando la transcripción final llega al campo existente del formulario.
En una aplicación React, ese campo suele estar controlado por el estado del componente. Asignar directamente un valor a input.value puede entrar en conflicto con el comportamiento de renderizado del framework.
En su lugar, el texto reconocido debe pasar por la misma vía de actualización de estado que se utiliza al escribir normalmente.
La integración también debe tener en cuenta el comportamiento asíncrono. El reconocimiento puede terminar después de que el usuario haya escrito más caracteres o movido el cursor.
Utilizar un valor antiguo del campo, capturado al comenzar la grabación, puede sobrescribir accidentalmente modificaciones posteriores.
Un patrón de integración simplificado es:
const insertion = insertTranscript({
currentValue: latestValue,
transcript: recognizedText,
selection: currentSelection,
maxLength: inputLimit,
});
setMessage(insertion.value);
restoreCaret(insertion.caret);La función auxiliar de inserción debe conservar el texto actual, sustituir opcionalmente el intervalo seleccionado, aplicar los espacios adecuados y respetar la longitud máxima del campo.
Hay que prestar atención a Unicode porque JavaScript cuenta la longitud de las cadenas en unidades de código UTF-16. Cortar una cadena en una posición arbitraria puede dividir un par suplente.
Sobre todo, el reconocimiento de voz nunca debe enviar automáticamente el formulario. El usuario debe poder revisar y corregir el texto antes de enviarlo.
Gestiona los fallos sin interrumpir la entrada de texto normal
El subsistema de voz debe ser opcional.
Si se deniega el permiso del micrófono, el navegador no puede asignar memoria para el modelo, el worker falla o una grabación no contiene voz, el campo de texto habitual debe seguir funcionando.
Representa el ciclo de vida del micrófono mediante estados explícitos como:
idle
preparing
listening
recognizing
errorUtiliza etiquetas accesibles e información visible sobre el estado. Un micrófono que simplemente pasa a rojo o rosa después de cargar apenas informa al usuario.
Al cancelar, deben liberarse las pistas de audio, el contexto de audio, los búferes pendientes y la sesión de grabación activa. La interfaz también debe impedir que resultados obsoletos de una grabación cancelada sobrescriban un estado de entrada más reciente.
Si un modelo grande falla por limitaciones de memoria o de inicialización, libéralo por completo antes de intentar cargar uno más pequeño. Limita los reintentos para que el navegador no descargue o inicialice repetidamente modelos que no puede ejecutar.
No cambies silenciosamente a un servicio de voz en la nube cuando falle el reconocimiento local. Si el procesamiento exclusivamente local es un requisito estricto, un reconocedor local que falla debe seguir fallando localmente.
Prueba toda la cadena de procesamiento en el navegador
Que una comprobación de TypeScript o una compilación de producción se complete correctamente no basta para demostrar que la entrada de voz funciona.
Del mismo modo, confirmar que el navegador puede descargar los archivos ONNX no demuestra que ambas sesiones del modelo se inicialicen correctamente.
La prueba de regresión más útil debe ejercitar el mismo worker, los mismos archivos del entorno de ejecución, el modelo, el procesador y el tokenizador que se utilizan en producción.
Introduce una grabación PCM breve y conocida en la cadena de inferencia real del navegador y comprueba que devuelva una transcripción no vacía.
Después, prueba toda la interacción con el micrófono en un navegador real.
- Carga inicial de la página: comprueba que no se descarguen modelos de voz ni recursos del entorno de ejecución antes de usar el micrófono.
- Inicialización en frío: comprueba el permiso del micrófono, las solicitudes del modelo, los tipos MIME de JavaScript y WASM y una confirmación real de que el worker está listo.
- Inicialización de ONNX: comprueba que puedan crearse las sesiones tanto del codificador como del decodificador.
- Captura de audio: confirma que AudioWorklet produzca búferes PCM no vacíos.
- Remuestreo: confirma que el worker reciba la frecuencia de muestreo real y produzca audio válido a 16 kHz.
- Detención manual: comprueba que una segunda pulsación del micrófono detenga la grabación de forma fiable.
- Detención automática: comprueba que las pausas naturales terminen la grabación sin cortar la voz tenue.
- Transcripción: comprueba que el procesador, el modelo y el tokenizador completos devuelvan texto reconocible.
- Integración con el campo de entrada: comprueba que el texto reconocido aparezca en el campo controlado real sin sobrescribir las modificaciones del usuario.
- Recuperación tras fallos: prueba la denegación de permisos, los archivos ausentes, los errores del worker, los fallos de inicialización del modelo, la cancelación y el uso de un modelo más pequeño como alternativa.
- Privacidad: inspecciona las solicitudes de red y confirma que el audio del micrófono no se envía a ningún servidor ni servicio de análisis.
- Compatibilidad móvil: repite las pruebas en teléfonos reales, incluidas las inicializaciones en frío y en caliente y varias sesiones de dictado consecutivas.
Las pruebas con PCM grabado son excelentes para validar la cadena de inferencia, pero no demuestran que funcione la captura del micrófono. La prueba final de aceptación debe incluir el micrófono real y la interfaz de usuario real.
Una arquitectura práctica para dictar texto de forma fiable en el navegador
Para un sitio web que necesita entrada de voz breve, multilingüe y respetuosa con la privacidad, la arquitectura más práctica es deliberadamente sencilla:
React microphone button
|
v
Permission request
|
v
Lazy model initialization
|
v
AudioWorklet PCM capture
|
v
Adaptive RMS / manual stop
|
v
Dedicated Worker
|
v
16 kHz audio resampling
|
v
Whisper Tiny or Base Q8
|
v
Compatible ONNX Runtime WASM
|
v
Whisper processor + tokenizer
|
v
Final transcript
|
v
Controlled React inputLa aplicación debe alojar por sí misma los archivos versionados del modelo y del entorno de ejecución, mantener la inferencia fuera del hilo principal, evitar descargas innecesarias de modelos, liberar recursos después de utilizarlos y no enviar nunca el audio del micrófono para su reconocimiento.
Opciones más sofisticadas, como la aceleración mediante WebGPU, la detección neuronal de actividad de voz, la transcripción parcial continua o categorías adicionales de modelos, pueden considerarse más adelante cuando las mediciones justifiquen su complejidad.
La lección más valiosa no es una versión concreta de una biblioteca ni el tamaño específico de un modelo, sino la importancia de validar cada una de las etapas de la cadena de reconocimiento de voz del navegador.
Que funcione la solicitud de permiso del micrófono no significa que se esté transcribiendo el audio. Que se descargue un modelo no significa que se haya inicializado su decodificador. Que una sesión ONNX funcione no significa que el tokenizador esté disponible. Y que la transcripción sea correcta no significa que la aplicación haya insertado el texto adecuadamente.
La función solo está completa cuando un visitante puede pulsar el micrófono, hablar con naturalidad, detener la grabación y ver las palabras reconocidas en el campo editable, sin enviar la grabación a un servidor de reconocimiento de voz.