Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions web-speech-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,22 @@ Code for demos illustrating features of the Web Speech API. See [Web_Speech_API]

[Run recognition demo live](https://mdn.github.io/dom-examples/web-speech-api/speech-color-changer/)

Tap the screen then say a colour — the grammar string contains a large number of HTML keywords to choose from, although we've removed most of the multiple word colors to remove ambiguity. We did keep goldenrod, cos, well.
Tap the screen then say a color — the grammar string contains a large number of HTML named color keywords to choose from.

This current works only on Chrome/Chrome Mobile. To get this code running successfully, you'll need to run the code through a web server (localhost will work.)
## On-device speech color changer demo

[Run on-device recognition demo live](https://mdn.github.io/dom-examples/web-speech-api/on-device-speech-color-changer/)

This version of the color changer demo uses the new on-device color changer functionality, which at the time of writing works in Chrome 139+ desktop only.

## Phrase matcher demo

Speak the phrase and then see if the recognition engine successfully recognises it — this is another demo that relies on speech recognition, written for a research team at the University of Nebraska at Kearney.

This current works only on Chrome/Chrome Mobile. To get this code running successfully, you'll need to run the code through a web server (localhost will work.)

[Run phrase matcher demo live](https://mdn.github.io/dom-examples/web-speech-api/phrase-matcher/)

## Speak easy synthesis demo

[Run synthesis demo live](https://mdn.github.io/dom-examples/web-speech-api/speak-easy-synthesis/)

Type words in the input then submit the form to hear it spoken. You can also select the different voices available on the system, and alter the rate and pitch.

This currently works in Chrome, Firefox and Safari.
5 changes: 5 additions & 0 deletions web-speech-api/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ <h1>Pick your test</h1>
<li>
<a href="speech-color-changer/index.html">Speech color changer</a>
</li>
<li>
<a href="on-device-speech-color-changer/index.html"
>On-device-speech color changer</a
>
</li>
</ul>
<h2>More information</h2>
<ul>
Expand Down
21 changes: 21 additions & 0 deletions web-speech-api/on-device-speech-color-changer/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!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 139+ on desktop and equivalent Chromiums.</p>
Copy link
Member

@pepelsbey pepelsbey Sep 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<p>This demo works in Chrome 139+ on desktop and equivalent Chromiums.</p>
<p>This demo works in Chrome 142+ on desktop and equivalent Chromiums.</p>

The demo gave me “Uncaught ReferenceError: SpeechRecognitionPhrase is not defined” error in Chrome 140 but it worked fine in Canary 142. So I guess it would be fair to update it to 142, as the demo now requires additional APIs to work.


<p class="hints"></p>
<p class="output"><em>...diagnostic messages</em></p>
</body>
</html>
138 changes: 138 additions & 0 deletions web-speech-api/on-device-speech-color-changer/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
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");

let colorHTML = "";
colors.forEach(function (v, i, a) {
console.log(v, i);
colorHTML += `<span style="background-color: ${v};">${v}</span> `;
Copy link
Member

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.

Suggested change
colorHTML += `<span style="background-color: ${v};">${v}</span> `;
colorHTML += `<button style="background-color: ${v};">${v}</button> `;

});
hints.innerHTML = `Tap/click then say a color to change the background color of the app. For example, you could try ${colorHTML}`;

document.body.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) => {
// 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
Comment on lines +112 to +119
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if that prose would be easier to read in a guide/reference where this demo is linked from? And if it’s already there, then we don’t need it here.

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}`;
});
43 changes: 43 additions & 0 deletions web-speech-api/on-device-speech-color-changer/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
body,
html {
margin: 0;
}

html {
height: 100%;
}

body {
height: inherit;
overflow: hidden;
max-width: 800px;
margin: 0 auto;
}

h1,
p {
font-family: sans-serif;
text-align: center;
}
Comment on lines +10 to +21
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
body {
height: inherit;
overflow: hidden;
max-width: 800px;
margin: 0 auto;
}
h1,
p {
font-family: sans-serif;
text-align: center;
}
body {
height: inherit;
overflow: hidden;
max-width: 800px;
margin: 0 auto;
font-family: sans-serif;
text-align: center;
}

They will be inherited from the body anyway. I would suggest setting them like that :)


.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 +41 to +43
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.hints span {
text-shadow: 0px 0px 6px rgba(255 255 255 / 0.7);
}
.hints button {
border: none;
text-shadow: 0px 0px 6px rgba(255 255 255 / 0.7);
}

111 changes: 77 additions & 34 deletions web-speech-api/speech-color-changer/script.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,83 @@
var SpeechRecognition = SpeechRecognition || webkitSpeechRecognition
var SpeechGrammarList = SpeechGrammarList || window.webkitSpeechGrammarList
var SpeechRecognitionEvent = SpeechRecognitionEvent || webkitSpeechRecognitionEvent
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'];
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;
}
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');
var diagnostic = document.querySelector(".output");
var bg = document.querySelector("html");
var hints = document.querySelector(".hints");

var colorHTML= '';
colors.forEach(function(v, i, a){
var 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 =
"Tap/click then say a color to change the background color of the app. Try " +
colorHTML +
".";

document.body.onclick = function() {
document.body.onclick = function () {
recognition.start();
console.log('Ready to receive a color command.');
}
console.log("Ready to receive a color command.");
};

recognition.onresult = function(event) {
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
Expand All @@ -44,19 +87,19 @@ recognition.onresult = function(event) {
// 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 + '.';
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;
};