-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Add on-device speech recognition demo #332
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
pepelsbey
merged 9 commits into
mdn:main
from
chrisdavidmills:add-on-device-speech-recognition-demo
Sep 17, 2025
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
8df754f
Add on-device speech recognition demo
chrisdavidmills 1d16823
Tweak CSS, remove SpeechGrammar
chrisdavidmills 88f1aab
Remove unusaed SpeechGrammar stuff
chrisdavidmills 093c47b
Add SpeechRecognitionPhrase example to new demo
chrisdavidmills b1283fc
Update web-speech-api/on-device-speech-color-changer/style.css
chrisdavidmills df544cb
Update web-speech-api/on-device-speech-color-changer/index.html
chrisdavidmills f115444
Fixes for pepelsbey review comments
chrisdavidmills ee881cc
Improve styling consistency between the two color changer demos
chrisdavidmills 92cb177
Update var in original color changer demo to const and let
chrisdavidmills File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="utf-8" /> | ||
<meta name="viewport" content="width=device-width" /> | ||
|
||
<title>On-device speech color changer</title> | ||
|
||
<link rel="stylesheet" href="style.css" /> | ||
<script src="script.js" defer></script> | ||
</head> | ||
|
||
<body> | ||
<h1>On-device speech color changer</h1> | ||
|
||
<p>This demo works in Chrome 142+ on desktop and equivalent Chromiums.</p> | ||
|
||
<p class="hints"></p> | ||
|
||
<button>Start recognition</button> | ||
|
||
<p class="output"><em>...diagnostic messages</em></p> | ||
</body> | ||
</html> |
131 changes: 131 additions & 0 deletions
131
web-speech-api/on-device-speech-color-changer/script.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
const colors = [ | ||
"aqua", | ||
"azure", | ||
"bisque", | ||
"blue", | ||
"brown", | ||
"chocolate", | ||
"coral", | ||
"cornflowerblue", | ||
"crimson", | ||
"cyan", | ||
"deepskyblue", | ||
"fuchsia", | ||
"ghostwhite", | ||
"gold", | ||
"goldenrod", | ||
"gray", | ||
"green", | ||
"greenyellow", | ||
"hotpink", | ||
"indigo", | ||
"ivory", | ||
"khaki", | ||
"lavender", | ||
"lightseagreen", | ||
"lime", | ||
"linen", | ||
"magenta", | ||
"maroon", | ||
"moccasin", | ||
"navy", | ||
"olive", | ||
"orange", | ||
"orchid", | ||
"peru", | ||
"pink", | ||
"plum", | ||
"purple", | ||
"rebeccapurple", | ||
"red", | ||
"salmon", | ||
"sienna", | ||
"silver", | ||
"snow", | ||
"steelblue", | ||
"tan", | ||
"teal", | ||
"thistle", | ||
"tomato", | ||
"turquoise", | ||
"violet", | ||
"yellow", | ||
]; | ||
|
||
const phraseData = [ | ||
{ phrase: "azure", boost: 10.0 }, | ||
{ phrase: "khaki", boost: 3.0 }, | ||
{ phrase: "tan", boost: 2.0 }, | ||
]; | ||
|
||
const phraseObjects = phraseData.map( | ||
(p) => new SpeechRecognitionPhrase(p.phrase, p.boost) | ||
); | ||
|
||
const recognition = new SpeechRecognition(); | ||
recognition.continuous = false; | ||
recognition.lang = "en-US"; | ||
recognition.interimResults = false; | ||
recognition.processLocally = true; | ||
recognition.phrases = phraseObjects; | ||
|
||
recognition.phrases.push(new SpeechRecognitionPhrase("thistle", 5.0)); | ||
|
||
const diagnostic = document.querySelector(".output"); | ||
const bg = document.querySelector("html"); | ||
const hints = document.querySelector(".hints"); | ||
const startBtn = document.querySelector("button"); | ||
|
||
let colorHTML = ""; | ||
colors.forEach(function (v, i, a) { | ||
console.log(v, i); | ||
colorHTML += `<span style="background-color: ${v};">${v}</span> `; | ||
}); | ||
hints.innerHTML = `Press the button then say a color to change the background color of the app. For example, you could try ${colorHTML}`; | ||
|
||
startBtn.addEventListener("click", () => { | ||
// check availability of target language | ||
SpeechRecognition.available({ langs: ["en-US"], processLocally: true }).then( | ||
(result) => { | ||
if (result === "unavailable") { | ||
diagnostic.textContent = `en-US not available to download at this time. Sorry!`; | ||
} else if (result === "available") { | ||
recognition.start(); | ||
console.log("Ready to receive a color command."); | ||
} else { | ||
diagnostic.textContent = `en-US language pack downloading`; | ||
SpeechRecognition.install({ | ||
langs: ["en-US"], | ||
processLocally: true, | ||
}).then((result) => { | ||
if (result) { | ||
diagnostic.textContent = `en-US language pack downloaded. Try again.`; | ||
} else { | ||
diagnostic.textContent = `en-US language pack failed to download. Try again later.`; | ||
} | ||
}); | ||
} | ||
} | ||
); | ||
}); | ||
|
||
recognition.addEventListener("result", (event) => { | ||
let color = event.results[0][0].transcript; | ||
// Remove whitespace from recognized speech | ||
color = color.replace(/\s+/g, ""); | ||
diagnostic.textContent = `Result received: ${color}.`; | ||
bg.style.backgroundColor = color; | ||
console.log(`Confidence: ${event.results[0][0].confidence}`); | ||
}); | ||
|
||
recognition.addEventListener("speechend", () => { | ||
recognition.stop(); | ||
}); | ||
|
||
recognition.addEventListener("nomatch", (event) => { | ||
diagnostic.textContent = "I didn't recognise that color."; | ||
}); | ||
|
||
recognition.addEventListener("error", (event) => { | ||
diagnostic.textContent = `Error occurred in recognition: ${event.error}`; | ||
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,39 @@ | ||||||||||||||||
body, | ||||||||||||||||
html { | ||||||||||||||||
margin: 0; | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
html { | ||||||||||||||||
height: 100%; | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
body { | ||||||||||||||||
height: inherit; | ||||||||||||||||
overflow: hidden; | ||||||||||||||||
max-width: 800px; | ||||||||||||||||
margin: 0 auto; | ||||||||||||||||
font-family: system-ui; | ||||||||||||||||
text-align: center; | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
.output { | ||||||||||||||||
height: 100px; | ||||||||||||||||
margin: 0; | ||||||||||||||||
overflow: auto; | ||||||||||||||||
position: absolute; | ||||||||||||||||
bottom: 0; | ||||||||||||||||
right: 0; | ||||||||||||||||
left: 0; | ||||||||||||||||
background-color: rgba(255 255 255 / 0.2); | ||||||||||||||||
display: flex; | ||||||||||||||||
justify-content: center; | ||||||||||||||||
align-items: center; | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
ul { | ||||||||||||||||
margin: 0; | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
.hints span { | ||||||||||||||||
text-shadow: 0px 0px 6px rgba(255 255 255 / 0.7); | ||||||||||||||||
} | ||||||||||||||||
Comment on lines
+37
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See above comment. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,62 +1,98 @@ | ||
var SpeechRecognition = SpeechRecognition || webkitSpeechRecognition | ||
var SpeechGrammarList = SpeechGrammarList || window.webkitSpeechGrammarList | ||
var SpeechRecognitionEvent = SpeechRecognitionEvent || webkitSpeechRecognitionEvent | ||
|
||
var colors = [ 'aqua' , 'azure' , 'beige', 'bisque', 'black', 'blue', 'brown', 'chocolate', 'coral', 'crimson', 'cyan', 'fuchsia', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'green', 'indigo', 'ivory', 'khaki', 'lavender', 'lime', 'linen', 'magenta', 'maroon', 'moccasin', 'navy', 'olive', 'orange', 'orchid', 'peru', 'pink', 'plum', 'purple', 'red', 'salmon', 'sienna', 'silver', 'snow', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'white', 'yellow']; | ||
|
||
var recognition = new SpeechRecognition(); | ||
if (SpeechGrammarList) { | ||
// SpeechGrammarList is not currently available in Safari, and does not have any effect in any other browser. | ||
// This code is provided as a demonstration of possible capability. You may choose not to use it. | ||
var speechRecognitionList = new SpeechGrammarList(); | ||
var grammar = '#JSGF V1.0; grammar colors; public <color> = ' + colors.join(' | ') + ' ;' | ||
speechRecognitionList.addFromString(grammar, 1); | ||
recognition.grammars = speechRecognitionList; | ||
} | ||
var SpeechRecognition = SpeechRecognition || webkitSpeechRecognition; | ||
var SpeechRecognitionEvent = | ||
SpeechRecognitionEvent || webkitSpeechRecognitionEvent; | ||
|
||
var colors = [ | ||
"aqua", | ||
"azure", | ||
"beige", | ||
"bisque", | ||
"black", | ||
"blue", | ||
"brown", | ||
"chocolate", | ||
"coral", | ||
"crimson", | ||
"cyan", | ||
"fuchsia", | ||
"ghostwhite", | ||
"gold", | ||
"goldenrod", | ||
"gray", | ||
"green", | ||
"indigo", | ||
"ivory", | ||
"khaki", | ||
"lavender", | ||
"lime", | ||
"linen", | ||
"magenta", | ||
"maroon", | ||
"moccasin", | ||
"navy", | ||
"olive", | ||
"orange", | ||
"orchid", | ||
"peru", | ||
"pink", | ||
"plum", | ||
"purple", | ||
"red", | ||
"salmon", | ||
"sienna", | ||
"silver", | ||
"snow", | ||
"tan", | ||
"teal", | ||
"thistle", | ||
"tomato", | ||
"turquoise", | ||
"violet", | ||
"white", | ||
"yellow", | ||
]; | ||
|
||
const recognition = new SpeechRecognition(); | ||
recognition.continuous = false; | ||
recognition.lang = 'en-US'; | ||
recognition.lang = "en-US"; | ||
recognition.interimResults = false; | ||
recognition.maxAlternatives = 1; | ||
|
||
var diagnostic = document.querySelector('.output'); | ||
var bg = document.querySelector('html'); | ||
var hints = document.querySelector('.hints'); | ||
const diagnostic = document.querySelector(".output"); | ||
const bg = document.querySelector("html"); | ||
const hints = document.querySelector(".hints"); | ||
const startBtn = document.querySelector("button"); | ||
|
||
var colorHTML= ''; | ||
colors.forEach(function(v, i, a){ | ||
let colorHTML = ""; | ||
colors.forEach(function (v, i, a) { | ||
console.log(v, i); | ||
colorHTML += '<span style="background-color:' + v + ';"> ' + v + ' </span>'; | ||
colorHTML += '<span style="background-color:' + v + ';"> ' + v + " </span>"; | ||
}); | ||
hints.innerHTML = 'Tap/click then say a color to change the background color of the app. Try ' + colorHTML + '.'; | ||
hints.innerHTML = | ||
"Press the button then say a color to change the background color of the app. Try " + | ||
colorHTML + | ||
"."; | ||
|
||
document.body.onclick = function() { | ||
startBtn.onclick = function () { | ||
recognition.start(); | ||
console.log('Ready to receive a color command.'); | ||
} | ||
|
||
recognition.onresult = function(event) { | ||
// The SpeechRecognitionEvent results property returns a SpeechRecognitionResultList object | ||
// The SpeechRecognitionResultList object contains SpeechRecognitionResult objects. | ||
// It has a getter so it can be accessed like an array | ||
// The first [0] returns the SpeechRecognitionResult at the last position. | ||
// Each SpeechRecognitionResult object contains SpeechRecognitionAlternative objects that contain individual results. | ||
// These also have getters so they can be accessed like arrays. | ||
// The second [0] returns the SpeechRecognitionAlternative at position 0. | ||
// We then return the transcript property of the SpeechRecognitionAlternative object | ||
var color = event.results[0][0].transcript; | ||
diagnostic.textContent = 'Result received: ' + color + '.'; | ||
console.log("Ready to receive a color command."); | ||
}; | ||
|
||
recognition.onresult = function (event) { | ||
const color = event.results[0][0].transcript; | ||
diagnostic.textContent = "Result received: " + color + "."; | ||
bg.style.backgroundColor = color; | ||
console.log('Confidence: ' + event.results[0][0].confidence); | ||
} | ||
console.log("Confidence: " + event.results[0][0].confidence); | ||
}; | ||
|
||
recognition.onspeechend = function() { | ||
recognition.onspeechend = function () { | ||
recognition.stop(); | ||
} | ||
}; | ||
|
||
recognition.onnomatch = function(event) { | ||
recognition.onnomatch = function (event) { | ||
diagnostic.textContent = "I didn't recognise that color."; | ||
} | ||
}; | ||
|
||
recognition.onerror = function(event) { | ||
diagnostic.textContent = 'Error occurred in recognition: ' + event.error; | ||
} | ||
recognition.onerror = function (event) { | ||
diagnostic.textContent = "Error occurred in recognition: " + event.error; | ||
}; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest using buttons to make the demo accessible for keyboard users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't want to change all of these into buttons; they are supposed to be hint text, not controls. I've fixed the keyboard accessibility issue by instead adding a single button to the DOM, and then starting the speech recognition when that is pressed. I've also updated the text description to suit.
I've done this in both versions of the color changer demo.