Web Speech API Specification
repository·main·Indexed 41 days ago
https://github.com/webaudio/web-speech-apiSpecification for the Web Speech API, currently in unofficial incubation by the Web Audio Community Group. Includes documentation on contextual biasing via SpeechRecognitionPhrase, on-device speech recognition using SpeechRecognitionOptions and SpeechRecognitionQuality, and acoustic boundary timestamps (speechStartTime and speechEndTime) for synchronized subtitles and click-to-seek navigation.
What's inside Web Speech API
- The Web Speech API is currently in an unofficial incubation phase and is not a web standard. It is being incubated by the Web Audio Community Group. Because it is not yet a standard, implementations and behaviors may change as the specification evolves.
Implementation details for unspoken punctuation
mainWhen using
unspokenPunctuation: true, developers should be aware of the following implementation behaviors:- Automatic Capitalization: Automatic punctuation is often coupled with automatic capitalization. The engine may automatically capitalize the first word following an inferred sentence-ending punctuation mark. Developers should not assume the text will remain strictly lowercase.
- Internationalization: Punctuation and spacing rules are implementation-dependent and should follow localized rules based on the
SpeechRecognition.langattribute (e.g., using¿in Spanish).
Improve recognition accuracy with Contextual Biasing
mainContextual biasing allows you to provide "hints" to the speech recognition engine to improve accuracy for domain-specific terminology, proper nouns, or uncommon words. By providing a list of likely phrases and associated boost values, you increase the probability that the engine correctly identifies these specific terms instead of more common, acoustically similar words.
This is particularly useful for:
- Specialized terminology: Medical, legal, or scientific terms.
- Proper nouns: Character names in games or brand names in e-commerce.
- Domain-specific commands: Specific vocabulary used within a particular application context.
How on-device speech recognition works
mainOn-device speech recognition is an enhancement to the Web Speech API that allows audio processing to happen locally on the user's device instead of relying on cloud-based services.
Key Benefits:
- Privacy: Raw audio and transcriptions stay on the device, helping with data residency and compliance (e.g., GDPR).
- Performance: Local processing reduces network latency and can support features like contextual biasing.
- Offline Functionality: Enables speech recognition in environments without internet connectivity.
Workflow:
- Define
SpeechRecognitionOptionswithprocessLocally: trueand the desiredlangs. - Check availability using
SpeechRecognition.available(options). - If status is
"downloadable", callSpeechRecognition.install(options)to fetch language models. - Initialize and start
SpeechRecognitionwith the configured options.
Check and install on-device speech models with quality constraints
mainTo implement progressive enhancement for speech recognition, use
SpeechRecognition.available(options)to check for a model that meets your required quality level, andSpeechRecognition.install(options)to attempt to download one if it is missing. Ifinstall()rejects, it typically means the device hardware (NPU/RAM) cannot support the requested quality level, and you should fall back to cloud-based recognition.const meetConfig = { langs: ['en-US'], processLocally: true, quality: 'conversation' }; async function setupSpeech() { // 1. Check if a high-quality model is already available const availability = await SpeechRecognition.available(meetConfig); if (availability == 'available') { startOnDeviceSpeech(); return; } // 2. If not, try to install one try { await SpeechRecognition.install(meetConfig); startOnDeviceSpeech(); } catch (e) { // 3. Fallback: Device hardware is too weak or user denied download. // Fallback to Cloud to ensure transcript usability. console.log("High-quality on-device model unavailable. Using Cloud."); startCloudSpeech(); } }Handle contextual biasing errors
mainWhen implementing contextual biasing, you should check for errors in the
onerrorevent handler. If the browser or speech service does not support biasing, theevent.errorwill return the string'phrases-not-supported'.recognition.onerror = (event) => { if (event.error === 'phrases-not-supported') { console.warn('Contextual biasing is not supported by this browser/service.'); } };Example: Using unspoken punctuation for natural transcription
mainThis example demonstrates how to enable
unspokenPunctuationalongside other standard settings likecontinuousandinterimResultsto capture natural, punctuated speech.When
unspokenPunctuationis enabled, thetranscriptin a final result will include inferred punctuation (e.g., "Hello there, how are you today?") instead of raw text ("hello there how are you today").const recognition = new SpeechRecognition(); // Enable unspoken punctuation recognition.unspokenPunctuation = true; // Configure other settings recognition.continuous = true; recognition.interimResults = true; recognition.lang = 'en-US'; recognition.onresult = (event) => { for (let i = event.resultIndex; i < event.results.length; ++i) { if (event.results[i].isFinal) { console.log('Final Result with Punctuation: ', event.results[i][0].transcript); // Example output: "Hello there, how are you today?" } else { console.log('Interim Result: ', event.results[i][0].transcript); } } }; recognition.start();Full example of implementing Contextual Biasing
mainThis example demonstrates how to create
SpeechRecognitionPhraseobjects, assign them to aSpeechRecognitioninstance, and handle potential errors.// A list of phrases relevant to our application's context. const phraseData = [ { phrase: 'Zoltan', boost: 3.0 }, { phrase: 'Grog', boost: 2.0 }, ]; // Create SpeechRecognitionPhrase objects. const phraseObjects = phraseData.map(p => new SpeechRecognitionPhrase(p.phrase, p.boost)); const recognition = new SpeechRecognition(); // Assign the phrase objects to the recognition instance. recognition.phrases = phraseObjects; // We can also dynamically add/remove phrases. recognition.phrases.push(new SpeechRecognitionPhrase('Xylia', 2.5)); // Some user agents (e.g. Chrome) might only support on-device contextual biasing. recognition.processLocally = true; recognition.onresult = (event) => { const transcript = event.results[0][0].transcript; console.log(`Result: ${transcript}`); }; recognition.onerror = (event) => { if (event.error === 'phrases-not-supported') { console.warn('Contextual biasing is not supported by this browser/service.'); } }; // Start recognition when the user clicks a button. document.getElementById('speak-button').onclick = () => { recognition.start(); };Create synchronized subtitles and click-to-seek navigation
mainYou can use
speechStartTimeandspeechEndTimeto generate synchronized WebVTT captions and interactive transcript elements that allow users to jump to specific parts of a media playback.- Captions: Use the timestamps to instantiate a
VTTCueand add it to a media track. - Click-to-Seek: Create DOM elements for each transcript segment. When clicked, set the
mediaElement.currentTimeto theresult.speechStartTimeto jump the playback to that exact moment.
const mediaElement = document.querySelector('video'); const track = mediaElement.addTextTrack('captions', 'English', 'en'); track.mode = 'showing'; const recognition = new SpeechRecognition(); recognition.continuous = true; recognition.interimResults = false; // Transcribe audio track from HTMLMediaElement const audioStream = mediaElement.captureStream(); recognition.start(audioStream.getAudioTracks()[0]); recognition.onresult = (event) => { for (let i = event.resultIndex; i < event.results.length; ++i) { const result = event.results[i]; if (!result.isFinal) continue; const transcriptText = result[0].transcript; // 1. Create a synchronized WebVTT caption cue const cue = new VTTCue(result.speechStartTime, result.speechEndTime, transcriptText); track.addCue(cue); // 2. Build interactive transcript element ("Click-to-Seek") const transcriptItem = document.createElement('p'); transcriptItem.textContent = `[${result.speechStartTime.toFixed(1)}s] ${transcriptText}`; transcriptItem.onclick = () => { mediaElement.currentTime = result.speechStartTime; mediaElement.play(); }; document.getElementById('transcript-container').appendChild(transcriptItem); } };- Captions: Use the timestamps to instantiate a
Implement live captions and recording timeline association
mainFor live video conferencing, you can use the timestamps to provide two distinct experiences:
- Live Display: Render interim results immediately on screen to minimize perceived lag. Use
result.isFinalto manage the visual state (e.g., styling interim vs. final text). - Recording Archive: When a result is finalized (
isFinal), archive the text along with itsspeechStartTimeandspeechEndTime. This allows you to reconstruct frame-accurate subtitle tracks for recorded media during post-meeting replay.
const recognition = new SpeechRecognition(); recognition.continuous = true; recognition.interimResults = true; let hideTimeout = null; const liveSubtitleOverlay = document.getElementById('live-captions'); const meetingTranscript = []; recognition.onresult = (event) => { const result = event.results[event.resultIndex]; const transcriptText = result[0].transcript; // Render live captions immediately on screen liveSubtitleOverlay.textContent = transcriptText; liveSubtitleOverlay.classList.toggle('interim', !result.isFinal); // Keep final captions on screen for a 3-second reading persistence window clearTimeout(hideTimeout); if (result.isFinal) { hideTimeout = setTimeout(() => { liveSubtitleOverlay.textContent = ''; }, 3000); // Archive the finalized sentence with exact acoustic timestamps meetingTranscript.push({ text: transcriptText, startTime: result.speechStartTime, endTime: result.speechEndTime }); } }; // Post-Meeting Replay: Synchronize caption cues with the recorded meeting video function initializeRecordingCaptions(recordedVideoElement) { const captionTrack = recordedVideoElement.addTextTrack('captions', 'Meeting Captions'); captionTrack.mode = 'showing'; for (const entry of meetingTranscript) { captionTrack.addCue(new VTTCue(entry.startTime, entry.endTime, entry.text)); } }- Live Display: Render interim results immediately on screen to minimize perceived lag. Use
Configure SpeechRecognition.phrases
mainThe
SpeechRecognition.phrasesattribute is anObservableArray<SpeechRecognitionPhrase>that accepts contextual hints for the recognition session. You can interact with it like a standard JavaScriptArray(e.g., using.push()) or assign a new array ofSpeechRecognitionPhraseobjects to it.Note on Browser Support: Some user agents (such as Chrome) may only support on-device contextual biasing. You can attempt to enable this by setting
recognition.processLocally = true.const recognition = new SpeechRecognition(); // Assigning an array of phrase objects recognition.phrases = [ new SpeechRecognitionPhrase('Zoltan', 3.0), new SpeechRecognitionPhrase('Grog', 2.0) ]; // Dynamically adding a phrase recognition.phrases.push(new SpeechRecognitionPhrase('Xylia', 2.5));Extend SpeechRecognitionResult with speechStartTime and speechEndTime
mainThe
SpeechRecognitionResultinterface can be extended with two new read-only attributes to provide the exact acoustic boundaries of a recognized speech segment. These timestamps are measured in seconds relative to the start of the audio stream ($t = 0.0\text{s}$), which is marked by theaudiostartevent.speechStartTime: The start timestamp of the speech segment in seconds.speechEndTime: The end timestamp of the speech segment in seconds.
Using seconds as a
doubleensures compatibility with the Web Audio API and HTML Media Elements, allowing for seamless synchronization without manual unit conversions.partial interface SpeechRecognitionResult { // Start timestamp of the speech segment in seconds relative to the start of the audio stream (0.0s). readonly attribute double speechStartTime; // End timestamp of the speech segment in seconds relative to the start of the audio stream. readonly attribute double speechEndTime; };