Eine Mikrofontaste auf einer Website einzubauen, klingt einfach: Stimme aufnehmen, Wörter erkennen und in ein Textfeld einfügen. In der Praxis ist eine zuverlässige Umsetzung für Desktop- und Mobilbrowser deutlich schwieriger, besonders wenn die gesamte Spracherkennung lokal erfolgen muss, ohne Mikrofonaufnahmen an einen Server zu senden.
Dabei geht es nicht nur um die Wahl eines Spracherkennungsmodells. Eine funktionierende Implementierung muss Mikrofonberechtigungen, Audioaufnahme, Resampling, Browser-Worker, WebAssembly, die Initialisierung von ONNX-Modellen, Tokenisierung, Caching und die Anbindung an das Texteingabefeld der Anwendung korrekt handhaben.
Dieser Artikel zeigt, wie sich eine praxistaugliche, datenschutzfreundliche Spracheingabe umsetzen lässt – mit OpenAI Whisper, Transformers.js, ONNX Runtime Web, AudioWorklet und einem eigenen Web Worker. Außerdem behandelt er mehrere tatsächliche Integrationsfehler, durch die eine Mikrofontaste im Ladezustand hängen bleiben kann, obwohl die Modelldateien bereits erfolgreich heruntergeladen wurden.
Das Ziel ist klar: Jemand klickt auf das Mikrofon, spricht ganz normal und sieht den erkannten Text im vorhandenen Eingabefeld. Vor dem Absenden lässt sich das Ergebnis prüfen und bearbeiten. Die eigentliche Mikrofonaufnahme muss den Browser zu keinem Zeitpunkt verlassen.
Was vollständig browserseitige Spracherkennung bedeutet
Für die Spracheingabe auf einer Website gibt es zwei grundlegend unterschiedliche Ansätze.
Der erste nutzt einen Spracherkennungsdienst. Der Browser nimmt Audio auf, sendet es an einen externen Dienst und erhält den erkannten Text zurück. Das kann die Implementierung vereinfachen, schafft aber Netzabhängigkeiten, Datenschutzfragen, Abhängigkeit von der Verfügbarkeit des Anbieters und möglicherweise nutzungsabhängige Kosten.
Beim zweiten Ansatz läuft die Inferenz unmittelbar im Browser. Die Website lädt ein Spracherkennungsmodell und die dafür benötigte Laufzeitumgebung herunter. Der Browser nimmt das Mikrofonsignal auf, wandelt es in numerische Samples um, führt das Modell lokal aus und erstellt ein Transkript ohne Spracherkennungsserver.
Dieser Artikel konzentriert sich ausschließlich auf den zweiten Ansatz.
Herkömmliche Spracherkennung im Browser darf nicht mit garantiert lokaler Erkennung verwechselt werden. Die Web Speech API kann eine servergestützte Erkennungsengine verwenden. Manche neueren Implementierungen bieten eine processLocally-Option, deren Unterstützung je nach Browser und Betriebssystem variiert. Ist eine strikt lokale Verarbeitung erforderlich, darf die allgemeine SpeechRecognition-Schnittstelle nicht automatisch als Fallback dienen.
Stattdessen steuert die Anwendung die gesamte Inferenzpipeline selbst.
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 stateDie Website stellt weiterhin JavaScript, WebAssembly, Konfigurationsdateien und Modellgewichte bereit. Dabei handelt es sich um gewöhnliche statische Downloads. Entscheidend ist, dass Mikrofonaufnahmen lokal verarbeitet und nicht zur Erkennung hochgeladen werden. Wenn Nutzer den daraus erzeugten Text später über ein normales Formular absenden, ist das ein separater Vorgang der Anwendung.
Ein praxistaugliches Modell wählen: Whisper Tiny oder Base
Whisper ist eine Familie mehrsprachiger Spracherkennungsmodelle. Weil ein einziges Modell viele Sprachen erkennen kann, eignet es sich besonders für mehrsprachige Websites.
Für kurze Sprachnachrichten bieten zwei Modelle ein brauchbares Verhältnis zwischen Erkennungsqualität und Ressourcenverbrauch im Browser: Whisper Tiny und Whisper Base.
| Modell | Ungefähre Größe der ausgewählten Q8-ONNX-Gewichte | Empfohlener Einsatz |
|---|---|---|
| Whisper Tiny | 41 MB | Vorsichtige Wahl für Mobilgeräte und ressourcenschwache Systeme |
| Whisper Base | 77 MB | Qualitativ bessere Option für getestete, leistungsfähige Geräte |
Die Größen beziehen sich auf die ausgewählten ONNX-Dateien für Encoder und Decoder in den untersuchten Exporten. Sie umfassen nicht sämtliche Hilfsdateien und dürfen nicht mit dem maximalen Speicherverbrauch des Browsers verwechselt werden.
Whisper Tiny lädt schneller herunter, benötigt normalerweise weniger Rechenressourcen und eignet sich besser für Mobilgeräte mit unbekannter Leistungsfähigkeit. Whisper Base liefert im Allgemeinen eine höhere Erkennungsqualität, insbesondere bei manchen Sprachen, in denen kleinere Modelle mehr Fehler machen.
Die Modellauswahl sollte nicht allein vom physischen Arbeitsspeicher abhängen. Dem Browser steht unter Umständen deutlich weniger Speicher zur Verfügung, als die Gerätespezifikation vermuten lässt. Auch der Speicherbedarf der geöffneten Seite, WebAssembly-Allokationen, temporäre Tensoren und Speicherdruck durch das Betriebssystem spielen eine Rolle.
Ein vorsichtiger Ansatz besteht darin, auf Mobilgeräten mit unbekannter Leistungsfähigkeit zunächst Tiny zu verwenden, Base geeigneten Geräteklassen vorzubehalten und bei einem Initialisierungsfehler von Base auf Tiny zurückzufallen.
Eine fehlgeschlagene Base-Instanz muss freigegeben werden, bevor Tiny geladen wird. Beide Modelle gleichzeitig im Speicher zu halten, würde den Zweck des Fallbacks zunichtemachen.
Warum WebAssembly ein sinnvoller Standard für den Produktivbetrieb ist
ONNX Runtime Web ermöglicht es, neuronale Netze im Browser auszuführen. Für diese Arbeitslast bietet WebAssembly einen praktikablen CPU-basierten Inferenzpfad in den wichtigsten Desktop- und Mobilbrowsern.
WebGPU kann unterstützte Modelle auf geeigneter Hardware beschleunigen. Die Verfügbarkeit der Schnittstelle bedeutet jedoch noch nicht, dass die Spracherkennung funktioniert. Dass ein Browser navigator.gpu bereitstellt, garantiert weder die Plattformunterstützung durch ONNX Runtime noch die erfolgreiche Initialisierung des betreffenden Whisper-Graphen oder einen ressourcenschonenden Betrieb bei wiederholter Inferenz.
Eine vorsichtige Implementierung kann daher WASM als Backend für den Produktivbetrieb verwenden und WebGPU nach Kompatibilitäts-, Genauigkeits-, Speicher- und Wiederholungstests separat bewerten.
Für die erste Implementierung verhindert Single-Thread-WASM außerdem, dass die gesamte Website von Cross-Origin-Isolation und SharedArrayBuffer abhängig wird.
env.backends.onnx.wasm.numThreads = 1;
env.backends.onnx.wasm.proxy = false;Die Ausführung der Inferenz in einem eigenen Worker hält die aufwendige Berechnung vom JavaScript-Hauptthread fern. Eingabefeld, Bildschirmtastatur, Scrollen und Benutzeroberfläche bleiben während der Audioverarbeitung reaktionsfähig.
Spracherkennung vollständig vom ersten Seitenaufruf fernhalten
Eine Mikrofonfunktion sollte Besucher, die sie nie nutzen, nicht mit erheblichem Netzwerk- und Speicherverbrauch belasten.
Transformers.js darf nicht vorsorglich in das anfängliche Anwendungspaket importiert werden. ONNX Runtime sollte beim Seitenaufruf weder initialisiert werden noch sollten Modellgewichte bei Inaktivität automatisch heruntergeladen werden.
Stattdessen wird der Erkennungsstack erst geladen, nachdem der Nutzer ausdrücklich auf die Mikrofontaste geklickt hat.
Ein sinnvoller Ablauf bei der ersten Nutzung sieht so aus:
- Klick auf die Mikrofontaste entgegennehmen.
- Mikrofonberechtigung über
getUserMedia(). - Wird die Berechtigung verweigert, einen Fehler anzeigen, ohne das Modell herunterzuladen.
- Wird sie erteilt, den vorübergehend geöffneten Mikrofonstream freigeben, während das Modell vorbereitet wird.
- Den eigenen Worker dynamisch initialisieren und das ausgewählte lokale Modell laden.
- Warten, bis der Worker die Betriebsbereitschaft der Erkennungspipeline bestätigt.
- Die Aufnahme automatisch starten, sofern der Nutzer den Vorgang nicht abgebrochen hat.
- Einen verständlichen Status für die laufende Aufnahme anzeigen.
Die Berechtigungsanfrage muss an die Nutzerinteraktion gekoppelt sein. Die Anwendung sollte außerdem Abbrüche und browserspezifische Einschränkungen der Audioaufnahme berücksichtigen, statt anzunehmen, dass die Aufnahme jederzeit unbemerkt neu gestartet werden kann.
Bei der Modellvorbereitung sollte ein sichtbarer Status erscheinen, beispielsweise Spracheingabe wird vorbereitet. Ein Ladekreis ohne nachvollziehbare Statusänderung kann selbst einen erfolgreichen Modelldownload wie ein defektes Mikrofon aussehen lassen.
Nach der Initialisierung kann der Worker kurz für ein weiteres Diktat verfügbar bleiben. Ein Leerlauf-Timeout von etwa einer Minute ist ein vernünftiger Ausgangspunkt. Danach wird der Worker beendet und sein Modell aus dem Arbeitsspeicher freigegeben. Im Browsercache gespeicherte Modelldateien können bei späteren Besuchen erneut genutzt werden.
Rohaudio mit AudioWorklet erfassen
Whisper benötigt numerische Audiosamples. MediaRecorder eignet sich hervorragend zum Aufzeichnen von Audiodateien, kann bei lokaler Spracherkennung aber einen unnötigen Umweg über Codierung und Decodierung verursachen.
Der einfachere Verarbeitungsweg lautet:
getUserMedia()
|
v
AudioContext
|
v
AudioWorklet
|
v
Float32 PCM buffers
|
v
Dedicated WorkerAudioWorklet führt Code zur Audioverarbeitung außerhalb des JavaScript-Hauptthreads aus. Es kann PCM-Samples erfassen, mehrkanaliges Eingangsaudio auf Mono reduzieren, einfache Lautstärkewerte berechnen und gesammelte Puffer an den Worker übertragen.
Eine vereinfachte Mikrofoninitialisierung sieht so aus:
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.Die Mikrofoneinstellungen sind Wünsche, keine Garantien. Browser können unterschiedliche Verfahren für Echounterdrückung, Rauschunterdrückung und automatische Verstärkungsregelung anwenden.
Wichtig ist, dass die Abtastrate des Mikrofons nicht zwingend 16 kHz beträgt. Viele Audiokontexte arbeiten mit 44.1 kHz oder 48 kHz. Whisper erwartet eine Eingabe mit 16 kHz; deshalb müssen die aufgenommenen Samples korrekt umgerechnet werden.
Einfach jedes zweite oder dritte Sample wegzulassen, ist im Allgemeinen keine korrekte Lösung. Ein geeigneter Tiefpass-Resampler, etwa mit einem gefensterten Sinc-Filter, reduziert Aliasing und erhält beim Absenken der Abtastrate die für Sprache relevanten Frequenzen.
Das Resampling gehört in den eigenen Worker und nicht in den Echtzeit-Callback von AudioWorklet.
Audio effizient zwischen Browser-Threads übertragen
AudioWorklet kann viele kleine Audioblöcke erzeugen. Jeden Audio-Rendering-Block als eigene Nachricht weiterzuleiten, kann unnötigen Overhead verursachen.
Praktisch ist es, kurze PCM-Blöcke zu sammeln und jeweils als ArrayBuffer zu übertragen.
worker.postMessage(
{
type: 'audio-chunk',
buffer,
},
[buffer]
);Die Transferliste ist wichtig: Sie überträgt das Eigentum am ArrayBuffer, anstatt unnötig den gesamten Inhalt zu kopieren.
Der Worker kann diese Blöcke in einem Audiopuffer sammeln und beim Beenden der Aufnahme zusammenführen.
Zusätzlich sollten die tatsächliche Aufnahme-Abtastrate, die Anzahl der erfassten Samples und die Aufnahmedauer erfasst werden. Mit diesen Metadaten lassen sich stumme Aufnahmen diagnostizieren, ohne vertrauliche Audiodaten zu protokollieren.
Sprechpausen ohne weiteres neuronales Netz erkennen
Für das Diktieren kurzer Textnachrichten ist nicht unbedingt ein neuronaler Sprachaktivitätsdetektor erforderlich.
Ein adaptiver RMS-Detektor kann die Sprachaktivität mit wenig Aufwand näherungsweise bestimmen. RMS misst die Signalamplitude und ermöglicht so einzuschätzen, wann jemand nach dem Sprechen still geworden ist.
Die Oberfläche sollte immer das manuelle Beenden durch einen zweiten Klick auf die Mikrofontaste unterstützen. Automatische Stilleerkennung ist ein Komfortmerkmal, keine Voraussetzung für erfolgreiche Spracherkennung.
Sinnvolle Ausgangswerte für kurze Diktate sind:
| Parameter | Ausgangswert |
|---|---|
| Analyseintervall | 20 ms |
| Rauschkalibrierung | 300 ms |
| Bestätigung der Sprachaktivität | 140 ms |
| Stille bis zum automatischen Stopp | 1,4 Sekunden |
| Timeout ohne Sprache | 8 Sekunden |
| Maximale Aufnahmedauer | 30 Sekunden |
Das sind Ausgangswerte für die Konfiguration und keine universell optimalen Schwellen. Leise Sprache, Hintergrundmusik und die Rauschverarbeitung des Mikrofons können RMS-Messungen beeinflussen.
Es ist besser, die Aufnahme manuell beenden zu lassen, als die letzten Wörter eines Satzes automatisch abzuschneiden.
Für gewöhnliche kurze Texteingaben ist eine einmalige Transkription meist einfacher, als Whisper wiederholt für Zwischenergebnisse auszuführen. Äußerung aufnehmen, stoppen, das Modell einmal ausführen und das endgültige Transkript einfügen.
Für die vollständige Whisper-Pipeline reicht ein ONNX-Modell nicht aus
Das ist eines der wichtigsten Implementierungsdetails.
Whisper-Inferenz besteht nicht einfach daraus, einen ONNX-Encoder und anschließend einen ONNX-Decoder aufzurufen. Eine vollständige Pipeline für automatische Spracherkennung benötigt außerdem die Extraktion von Audiomerkmalen, Tokenisierung, Generierungskonfiguration und Decodierung der vorhergesagten Tokens in lesbaren Text.
Die vollständige Verarbeitungskette lautet:
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 textEine erfolgreich initialisierte ONNX-Session beweist nur, dass sich der Modellgraph laden lässt. Sie beweist nicht, dass die gesamte Erkennungspipeline funktioniert.
In einer getesteten Transformers.js-Integration konnten die Modellsessions initialisiert werden, aber die Transkription schlug weiterhin fehl: Der lokal aufgebauten Pipeline fehlten korrekt konfigurierte Processor- und Tokenizer-Komponenten.
Die kompatible Implementierung initialisierte diese Komponenten ausdrücklich anhand lokaler Modellmetadaten.
Das folgende verkürzte Beispiel veranschaulicht diesen versionsspezifischen Ansatz; es ist kein vollständiger, eigenständig lauffähiger Worker:
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;Die genauen Konstruktorsignaturen und die Integrationsmethode müssen zur installierten Version von Transformers.js passen. Manche Versionen und Modellexporte können diese Komponenten automatisch erzeugen; die manuelle Initialisierung ist keine allgemeine Pflicht.
Mit der vollständigen Pipeline lässt sich die Transkription kurzer Aufnahmen mit expliziter Sprache und Aufgabe aufrufen:
const result = await recognizer(audio16k, {
language: 'en',
task: 'transcribe',
return_timestamps: false,
});Wenn die Anwendung die Sprache des Nutzers bereits kennt, vermeidet ihre explizite Übergabe eine unnötige Erkennung der gesprochenen Sprache und kann kurze Aufnahmen zuverlässiger machen.
Kompatible Versionen von Transformers.js und ONNX Runtime festlegen
Eine zweite wichtige Fehlerquelle ist das Zusammenspiel zwischen Transformers.js und ONNX Runtime Web.
Transformers.js stellt die übergeordnete Erkennungspipeline bereit, während ONNX Runtime Web die Graphen des neuronalen Netzes ausführt. Diese Pakete müssen sowohl untereinander als auch mit den tatsächlich verwendeten ONNX-Modellexporten kompatibel sein.
Bei der untersuchten Implementierung konnte ein Entwicklungsbuild von ONNX Runtime Web den Whisper-Encoder initialisieren, scheiterte aber beim Erstellen einer quantisierten Decoder-Session. Der Fehler betraf einen fehlenden Quantisierungsskalierungswert in einer DequantizeLinear-Operation.
Das war weder ein Problem mit der Mikrofonberechtigung noch mit dem Download oder dem Audioformat. Der Browser konnte die Modelldateien empfangen, aber die Laufzeitumgebung konnte einen der benötigten Modellgraphen nicht initialisieren.
Durch direkte Tests von Encoder und Decoder mit einer stabilen, festgelegten Version von ONNX Runtime Web ließ sich genau diese Inkompatibilität beheben.
Der Kompatibilitätstest war besonders wertvoll, weil er zwei verschiedene Aussagen trennte:
- Die Modelldatei wurde erfolgreich heruntergeladen.
- Die Laufzeitumgebung hat aus der Modelldatei erfolgreich eine nutzbare Inferenzsession erstellt.
Das ist nicht dasselbe.
Für die getesteten Modellexporte erwies sich die Kombination aus Transformers.js 4.2.0 und ONNX Runtime Web 1.24.3 als kompatibel. Ein neuerer Entwicklungsstand der Laufzeitumgebung scheiterte am quantisierten Decoder-Graphen. Das ist eine versionsspezifische Beobachtung und kein Beleg dafür, dass ältere Laufzeitumgebungen grundsätzlich besser sind.
Wird eine indirekte Runtime-Abhängigkeit überschrieben, sollte ihre genaue Version in der Paket-Lockdatei festgelegt und die Begründung dokumentiert werden. Ein Runtime-Upgrade erfordert einen vollständigen Regressionstest der Inferenz im Browser und nicht nur eine erfolgreiche Paketinstallation.
Vor dem Debuggen von Whisper das Worker-Bundle-Format prüfen
Ein JavaScript-Web-Worker kann in unterschiedlichen Modulformaten erstellt werden.
Ein Integrationsfehler entstand, weil die Anwendung einen Worker als ES-Modul startete, das generierte Worker-Bundle jedoch den Classic-Worker-Mechanismus importScripts().
Diese Ausführungsmodelle sind nicht austauschbar. Ein Modul-Worker kann Code, der ausschließlich für klassische Worker vorgesehen ist, nicht einfach ausführen.
Entscheidend ist, den Worker-Konstruktor an die tatsächliche Ausgabe des Bundlers anzupassen, statt eine Konstruktoroption aus irgendeinem Tutorial zu übernehmen.
Die folgenden Varianten sind beispielsweise nicht automatisch gleichwertig:
// Module worker: requires a compatible module bundle.
new Worker(workerUrl, { type: 'module' });
// Classic worker: requires a compatible classic bundle.
new Worker(workerUrl);Die richtige Wahl hängt vom Bundler und dem generierten JavaScript ab.
Bei der Fehlersuche zur Worker-Initialisierung sollten die Browserkonsole, die generierte Worker-Datei und der Netzwerkbereich der Entwicklertools geprüft werden. So lässt sich feststellen, ob der Fehler bereits vor der Initialisierung von ONNX Runtime auftritt.
Manche gebündelten Browserbibliotheken berechnen URLs zudem relativ zum Speicherort ihres eigenen Moduls. Werden sie in Worker eingebunden, müssen die resultierenden Pfade zu Runtime-Dateien getestet werden, statt anzunehmen, dass die ursprünglichen paketrelativen URLs weiterhin funktionieren.
WASM- und JavaScript-Module mit korrekten MIME-Typen ausliefern
Selbst vollständig kompatible JavaScript- und ONNX-Modelldateien können scheitern, wenn der Webserver falsche Antwortheader zurückgibt.
Ein konkreter Fehler betraf ein JavaScript-Modul mit der Endung .mjs, das mit dem Typ application/octet-stream ausgeliefert wurde. Der Browser lehnte das Modul ab, weil der Antworttyp nicht für die Ausführung als JavaScript-Modul geeignet war.
Typischerweise werden folgende Antworttypen erwartet:
| Dateiendung | Content-Type |
|---|---|
| .mjs | application/javascript |
| .wasm | application/wasm |
Die tatsächliche Antwort des Produktivservers prüfen, nicht nur dessen Konfigurationsdatei.
curl -I https://example.com/runtime/speech/v2/runtime.mjs
curl -I https://example.com/runtime/speech/v2/runtime.wasmDie oben angegebenen URLs sind Beispiele. Verwendet werden müssen die tatsächlichen versionierten Dateinamen des ausgewählten Runtime-Builds.
Ein korrekter HTTP-Status allein reicht nicht aus. Der Browser kann eine Datei trotzdem wegen ihres MIME-Typs, der Content Security Policy, des Modulausführungsmodus oder einer falschen Asset-URL ablehnen.
Dieselbe Anfrage kann beim direkten Aufruf in einem Browser-Tab erfolgreich sein, beim Laden als ausführbares Modul oder WebAssembly-Abhängigkeit aber fehlschlagen.
Unveränderliche Asset-URLs verwenden, ohne Nutzer auf defekte Runtime-Dateien festzulegen
Whisper-Modelldateien sind so groß, dass Browser-Caching eine wichtige Rolle spielt.
Versionierte, unveränderliche Asset-Pfade ermöglichen es dem Browser, unveränderte Modell- und Runtime-Dateien wiederzuverwenden, ohne sie bei jeder Spracheingabe erneut herunterzuladen.
Eine typische Cache-Richtlinie lautet:
Cache-Control: public, max-age=31536000, immutableDadurch entsteht allerdings eine Anforderung für das Deployment: Sobald eine URL als unveränderlich ausgewiesen ist, darf ihr Inhalt nicht stillschweigend ersetzt werden.
Muss die Runtime-Binärdatei geändert werden, sollte sie unter einer neuen versionierten URL veröffentlicht werden.
/models/speech/v1/
/runtime/speech/v1/
/runtime/speech/v2/Den Runtime-Pfad zu ändern, ist keine Kosmetik. Wenn die Anwendung dieselbe unveränderliche URL weiterverwendet, nachdem deren Inhalt geändert wurde, kann der Browser weiterhin eine zuvor gecachte, inkompatible WebAssembly-Binärdatei nutzen.
Sinnvoll ist außerdem, JavaScript-Bindeglied und WebAssembly-Dateien aus derselben Runtime-Distribution zu verwenden. Gemischte Runtime-Versionen können schwer diagnostizierbare Initialisierungsfehler auslösen.
Ein beim Build erzeugtes Manifest mit den erwarteten Dateien, Dateigrößen und kryptografischen Prüfsummen kann fehlende oder nicht zusammenpassende Assets bereits vor dem Deployment aufdecken.
Die Erkennungsbibliothek sollte für die Nutzung selbst gehosteter Assets konfiguriert und das Laden externer Modelle deaktiviert werden. So führt ein fehlendes lokales Modell zu einem eindeutigen Fehler, statt unbeabsichtigt einen Download von einem externen Modellhost auszulösen.
Warum sich das Mikrofon endlos drehen kann, obwohl nichts funktioniert
Ein häufiges Symptom während der Entwicklung: Die Mikrofontaste fordert eine Berechtigung an, zeigt eine Ladeanimation und wechselt schließlich in einen Fehlerzustand, ohne Text einzufügen.
Der Browser kann dabei sogar anzeigen, dass das Mikrofon verwendet wird.
Das beweist nicht, dass die Spracherkennung funktioniert. Es zeigt lediglich, dass die Mikrofonaufnahme irgendwann erlaubt oder aktiv war.
Mehrere voneinander unabhängige Fehler können nahezu identische Symptome erzeugen:
- Falscher MIME-Typ des JavaScript-Moduls. Der Browser lädt ein Runtime-Modul herunter, führt es aber nicht aus.
- Nicht passendes Worker-Ausführungsformat. Das kompilierte Worker-Bundle verwendet APIs für klassische Worker, wird aber als Modul-Worker gestartet – oder umgekehrt.
- Inkompatibler ONNX-Decoder. Die Laufzeitumgebung erstellt die Encoder-Session, scheitert jedoch am quantisierten Decoder-Graphen.
- Unvollständige Erkennungspipeline. Die ONNX-Sessions existieren, aber Tokenizer oder Audioprozessor wurden nicht korrekt initialisiert.
Diese Probleme müssen unabhängig voneinander diagnostiziert werden. Wer eines behebt, kann damit erst den nächsten Fehler sichtbar machen.
Der wichtigste Grundsatz beim Debugging lautet: HTTP 200 bedeutet nicht, dass das Modell erfolgreich initialisiert wurde, und eine erfolgreiche Modellinitialisierung bedeutet nicht, dass die Transkription funktioniert.
Selbst eine erfolgreiche Transkription beweist noch nicht, dass das Ergebnis im tatsächlichen Texteingabefeld der Anwendung ankommt.
An jeder Übergangsstelle wird ein aussagekräftiges Diagnosesignal benötigt.
Diagnosemöglichkeiten ohne Aufzeichnung privater Sprache schaffen
Nützliche Diagnoseprotokolle erfordern nicht, aufzuzeichnen, was Nutzer sagen.
Die Anwendung kann beispielsweise folgende technische Zustände erfassen:
permission-granted
worker-created
runtime-loading
runtime-ready
encoder-ready
decoder-ready
recording-started
pcm-received
recording-stopped
inference-started
inference-completed
result-insertedBei einem Fehler sollten die jeweilige Phase, ein stabiler technischer Fehlercode und bereinigte Fehlermetadaten angegeben werden.
Ein Fehler bei der Modellinitialisierung muss sich von einer verweigerten Mikrofonberechtigung oder einer leeren Aufnahme unterscheiden lassen. Worker-Fehler sollten in der Entwicklung sichtbar sein und nicht von einer generischen Fehlerbehandlung verschluckt werden.
Mikrofonsamples, erkannte Sätze, Tokenfolgen und andere Sprachinhalte dürfen nicht protokolliert werden. Für die Leistungstelemetrie eignen sich Dauerklassen, die ausgewählte Modellstufe, Initialisierungszeit, Inferenzzeit und Fehlerkategorien.
Damit lassen sich Probleme mit dem Mikrofon erheblich leichter untersuchen, ohne die Datenschutzanforderungen aufzuweichen.
Das Transkript über den React-State einfügen
Spracherkennung ist nur dann nützlich, wenn das fertige Transkript im vorhandenen Formularfeld ankommt.
In einer React-Anwendung wird dieses Feld häufig durch den Komponentenstatus gesteuert. Eine direkte Zuweisung an input.value kann mit dem Renderverhalten des Frameworks kollidieren.
Stattdessen muss der erkannte Text denselben State-Update-Pfad durchlaufen wie gewöhnliche Tastatureingaben.
Bei der Integration ist auch die asynchrone Verarbeitung zu berücksichtigen. Die Erkennung kann erst fertig werden, nachdem jemand weitere Zeichen eingegeben oder den Cursor verschoben hat.
Ein veralteter Eingabewert vom Beginn der Aufnahme kann neuere Änderungen versehentlich überschreiben.
Ein vereinfachtes Integrationsmuster sieht so aus:
const insertion = insertTranscript({
currentValue: latestValue,
transcript: recognizedText,
selection: currentSelection,
maxLength: inputLimit,
});
setMessage(insertion.value);
restoreCaret(insertion.caret);Die Einfügehilfe sollte vorhandenen Text erhalten, bei Bedarf den markierten Bereich ersetzen, passende Abstände setzen und die maximale Feldlänge beachten.
Unicode erfordert besondere Aufmerksamkeit, weil JavaScript die Stringlänge in UTF-16-Codeeinheiten zählt. Wird ein String an einer beliebigen Stelle abgeschnitten, kann ein Surrogatpaar getrennt werden.
Vor allem darf die Spracherkennung das Formular niemals automatisch absenden. Nutzer müssen den Text vor dem Senden prüfen und korrigieren können.
Fehler abfangen, ohne die normale Texteingabe zu beeinträchtigen
Das Sprachsubsystem sollte optional bleiben.
Wenn die Mikrofonberechtigung verweigert wird, der Browser keinen Speicher für das Modell bereitstellen kann, ein Worker abstürzt oder die Aufnahme keine Sprache enthält, muss das normale Eingabefeld weiter funktionieren.
Der Lebenszyklus des Mikrofons sollte durch eindeutige Zustände dargestellt werden, etwa:
idle
preparing
listening
recognizing
errorVerständliche Beschriftungen und sichtbare Statusmeldungen sind nötig. Ein Mikrofon, das nach dem Laden einfach rot oder rosa wird, erklärt den Nutzern praktisch nichts.
Beim Abbrechen müssen Audiospuren, Audiokontext, ausstehende Puffer und die aktive Aufnahmesession aufgeräumt werden. Außerdem muss die Oberfläche verhindern, dass verspätete Ergebnisse einer früheren, abgebrochenen Aufnahme einen neueren Eingabestand überschreiben.
Scheitert ein großes Modell an Speicher- oder Initialisierungsbeschränkungen, muss es vor dem Versuch mit einem kleineren Modell vollständig freigegeben werden. Die Zahl der Wiederholungsversuche sollte begrenzt sein, damit der Browser nicht ständig Modelle herunterlädt oder initialisiert, die auf dem Gerät nicht funktionieren.
Wenn die lokale Erkennung fehlschlägt, darf nicht heimlich zu einem Cloud-Sprachdienst gewechselt werden. Ist rein lokale Verarbeitung eine strenge Anforderung, muss ein fehlgeschlagener lokaler Erkennungsversuch auch lokal fehlschlagen.
Die gesamte Browser-Pipeline testen
Eine erfolgreiche TypeScript-Prüfung oder ein erfolgreicher Produktiv-Build ist kein ausreichender Beweis dafür, dass die Spracheingabe funktioniert.
Ebenso beweist ein erfolgreicher Download der ONNX-Dateien nicht, dass beide Modellsessions korrekt initialisiert werden.
Der wichtigste Regressionstest sollte denselben Worker, dieselben Runtime-Dateien, dasselbe Modell, denselben Prozessor und denselben Tokenizer verwenden wie die Produktivversion.
Eine bekannte kurze PCM-Aufnahme durch den tatsächlichen Inferenzpfad im Browser schicken und prüfen, ob ein nichtleeres Transkript zurückkommt.
Anschließend die vollständige Mikrofoninteraktion in einem echten Browser testen.
- Erster Seitenaufruf: prüfen, dass vor der Mikrofonnutzung keine Sprachmodelle oder Runtime-Assets heruntergeladen werden.
- Kaltstart: Mikrofonberechtigung, Modellanfragen, MIME-Typen für JavaScript und WASM sowie eine echte Bereitschaftsbestätigung des Workers prüfen.
- ONNX-Initialisierung: prüfen, ob sich sowohl Encoder- als auch Decoder-Session erstellen lassen.
- Audioaufnahme: sicherstellen, dass AudioWorklet nichtleere PCM-Puffer erzeugt.
- Resampling: sicherstellen, dass der Worker die tatsächliche Abtastrate erhält und gültiges 16-kHz-Audio erzeugt.
- Manuelles Beenden: prüfen, ob ein zweiter Klick auf die Mikrofontaste die Aufnahme zuverlässig stoppt.
- Automatisches Beenden: prüfen, ob natürliche Pausen die Aufnahme beenden, ohne leise gesprochene Wörter abzuschneiden.
- Transkription: prüfen, ob Prozessor, Modell und Tokenizer gemeinsam erkennbaren Text zurückgeben.
- Einbindung des Eingabefelds: prüfen, ob der erkannte Text im tatsächlichen kontrollierten Feld erscheint, ohne Nutzereingaben zu überschreiben.
- Fehlerbehandlung: verweigerte Berechtigungen, fehlende Dateien, Worker-Fehler, fehlgeschlagene Modellinitialisierung, Abbrüche und den Fallback auf ein kleineres Modell testen.
- Datenschutz: Netzwerkanfragen untersuchen und sicherstellen, dass Mikrofonaufnahmen weder an einen Server noch an einen Analysedienst gesendet werden.
- Mobilgeräte-Kompatibilität: die Tests auf echten Smartphones mit Kaltstarts, Warmstarts und wiederholten Diktatsessions durchführen.
Tests mit aufgezeichnetem PCM-Material eignen sich hervorragend zur Prüfung des Inferenzstacks, beweisen aber nicht, dass die Mikrofonaufnahme funktioniert. Der abschließende Abnahmetest muss das echte Mikrofon und die tatsächliche Benutzeroberfläche einbeziehen.
Eine praxistaugliche Architektur für zuverlässige Spracheingabe im Browser
Für Websites mit kurzer, mehrsprachiger und datenschutzfreundlicher Spracheingabe ist die sinnvollste Architektur bewusst einfach gehalten:
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 inputDie Anwendung sollte versionierte Modell- und Runtime-Dateien selbst hosten, die Inferenz vom Hauptthread fernhalten, unnötige Modelldownloads vermeiden, Ressourcen nach der Nutzung freigeben und niemals Mikrofonaufnahmen zur Spracherkennung hochladen.
Komplexere Möglichkeiten wie WebGPU-Beschleunigung, neuronale Sprachaktivitätserkennung, fortlaufende Zwischentranskription oder weitere Modellstufen können später geprüft werden, wenn Messergebnisse den zusätzlichen Aufwand rechtfertigen.
Die wichtigste Erkenntnis ist weder eine bestimmte Bibliotheksversion noch eine bestimmte Modellgröße, sondern die Notwendigkeit, jede einzelne Übergangsstelle der Spracherkennungspipeline im Browser zu überprüfen.
Eine funktionierende Mikrofonberechtigungsabfrage bedeutet nicht, dass Audio transkribiert wird. Ein heruntergeladenes Modell bedeutet nicht, dass sein Decoder initialisiert wurde. Eine erfolgreiche ONNX-Session bedeutet nicht, dass der Tokenizer verfügbar ist. Und ein erfolgreiches Transkript bedeutet nicht, dass die Anwendung den Text korrekt eingefügt hat.
Die Funktion ist erst dann fertig, wenn Besucher auf das Mikrofon klicken, natürlich sprechen, die Aufnahme beenden und die erkannten Wörter im editierbaren Feld sehen können – ohne dass die Aufnahme an einen Spracherkennungsserver gesendet wird.