Skip to content

Commit 484c3dd

Browse files
committed
Initial commit
0 parents  commit 484c3dd

4 files changed

Lines changed: 395 additions & 0 deletions

File tree

‎README.md‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Web Speech API SpeechSynthesis Demo
2+
3+
This [Web Speech API Text-to-Speech Demo](https://addpipe.com/web-speech-api-text-to-speech-demo/) uses the Web Speech API’s SpeechSynthesis interface to convert written text into spoken words.
4+
5+
It uses the following main `SpeechSynthesis` properties:
6+
- Voice and language selection
7+
- Real-time playback of typed text
8+
9+
## How to use
10+
1. Select a voice and language (automatically populated if supported by your browser)
11+
1. Type or paste your desired text into the textarea
12+
1. Click the “Speak” button to hear it read aloud
13+
1. (Optional) Download the spoken text as an audio file
14+
15+
## Main features
16+
- Multilingual voice and language picker
17+
- Real-time text-to-speech conversion
18+
19+
## Works on
20+
- Chrome 33+
21+
- Edge 14+
22+
- Firefox 49+
23+
- Safari 7+
24+
25+
## Known issues
26+
- Firefox support for voice selection may be limited
27+
- Not all voices are available across browsers
28+
- Browser-native SpeechSynthesis might not natively support audio file download
29+
30+
## Resources & Links
31+
- [In-depth article](https://blog.addpipe.com/a-deep-dive-into-the-web-speech-api/)
32+
- [Speech-to-text demo](https://addpipe.com/web-speech-api-demo/)
33+
- [MDN: SpeechSynthesis API](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis)

‎css/main.css‎

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
body {
2+
line-height: 1.5;
3+
font-family: sans-serif;
4+
word-wrap: break-word;
5+
overflow-wrap: break-word;
6+
color: black;
7+
margin: 2em;
8+
}
9+
10+
h1 {
11+
text-decoration: underline red;
12+
text-decoration-thickness: 3px;
13+
text-underline-offset: 6px;
14+
font-size: 220%;
15+
font-weight: bold;
16+
}
17+
18+
h2 {
19+
font-weight: bold;
20+
color: #005a9c;
21+
font-size: 140%;
22+
text-transform: uppercase;
23+
}
24+
25+
p {
26+
margin: 1em 0;
27+
}
28+
29+
pre {
30+
padding: 0px;
31+
border: 0px;
32+
border-radius: 0px;
33+
}
34+
35+
red {
36+
color: red;
37+
}
38+
39+
a {
40+
color: #4183c4;
41+
font-weight: 300;
42+
text-decoration: none;
43+
}
44+
45+
a:hover {
46+
color: #3d85c6;
47+
text-decoration: underline;
48+
}
49+
50+
a#downloadLink {
51+
display: block;
52+
margin: 0 0 1em 0;
53+
min-height: 1.2em;
54+
}
55+
56+
div#container {
57+
margin: 0 auto 0 auto;
58+
max-width: 720px;
59+
padding: 1em 1.5em 1.3em 1.5em;
60+
}
61+
62+
h3 {
63+
border-top: 1px solid #eee;
64+
color: #666;
65+
font-size: 0.9em;
66+
font-weight: 500;
67+
margin: 20px 0 10px 0;
68+
padding: 10px 0 0 0;
69+
white-space: nowrap;
70+
}
71+
72+
p#data {
73+
border-top: 1px dotted #666;
74+
font-family: Courier New, monospace;
75+
line-height: 1.3em;
76+
min-height: 6em;
77+
max-height: 1000px;
78+
overflow-y: auto;
79+
padding: 1em 0 0 0;
80+
}
81+
82+
video {
83+
background: #222;
84+
margin: 10px auto;
85+
width: 640px;
86+
height: 360px;
87+
}
88+
89+
#controls {
90+
display: flex;
91+
margin-top: 2rem;
92+
max-width: 28em;
93+
}
94+
95+
button {
96+
flex-grow: 1;
97+
height: 3rem;
98+
min-width: 10rem;
99+
border: none;
100+
border-radius: 0.15rem;
101+
background: #ed341d;
102+
margin-left: 2px;
103+
box-shadow: inset 0 -0.15rem 0 rgba(0, 0, 0, 0.2);
104+
cursor: pointer;
105+
display: flex;
106+
justify-content: center;
107+
align-items: center;
108+
color: #ffffff;
109+
font-weight: bold;
110+
font-size: 1rem;
111+
}
112+
button:hover,
113+
button:focus {
114+
outline: none;
115+
background: #c72d1c;
116+
}
117+
button::-moz-focus-inner {
118+
border: 0;
119+
}
120+
button:active {
121+
box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.2);
122+
line-height: 3rem;
123+
}
124+
button:disabled {
125+
pointer-events: none;
126+
background: lightgray;
127+
}
128+
button:first-child {
129+
margin-left: 0;
130+
}
131+
/* video {
132+
width: 1280px;
133+
height: 720px;
134+
} */
135+
136+
.transcribe-output {
137+
background-color: lightgray;
138+
width: 640px;
139+
min-height: 10px;
140+
border-radius: 7px;
141+
padding: 10px;
142+
}
143+
144+
#videoContainer {
145+
position: relative;
146+
display: inline-block;
147+
}
148+
149+
#subtitles {
150+
position: absolute;
151+
bottom: 25px;
152+
left: 0;
153+
width: 80%;
154+
color: white;
155+
font-size: 16px;
156+
text-align: center;
157+
background-color: rgba(0, 0, 0, 0.7);
158+
/* padding: 5px; */
159+
left: 50%;
160+
transform: translate(-50%);
161+
}
162+
163+
.subtitle-line {
164+
margin: 0;
165+
}

‎index.html‎

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>Web Speech API Text-to-Speech Demo | Speech Synthesis Example</title>
6+
<meta
7+
name="description"
8+
content="Try our Web Speech API Text-to-Speech (Speech Synthesis) demo. Enter text, select a voice, and convert text into natural-sounding speech in your browser."
9+
/>
10+
<meta
11+
name="viewport"
12+
content="width=device-width, initial-scale=1.0"
13+
/>
14+
<base target="_blank" />
15+
<link
16+
rel="stylesheet"
17+
href="css/main.css"
18+
/>
19+
</head>
20+
<body>
21+
<div>
22+
<h1>Web Speech API Text-to-Speech (Speech Synthesis) Demo</h1>
23+
<p>
24+
<small>Made by the <a href="https://addpipe.com">Pipe Recording Platform</a></small>
25+
</p>
26+
<p>
27+
This browser-based <strong>Text-to-Speech demo</strong> uses the
28+
<a
29+
href="https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis"
30+
rel="noopener noreferrer"
31+
>
32+
Web Speech API SpeechSynthesis
33+
</a>
34+
interface to convert written text into speech. It supports multiple languages and voices depending on your browser and OS.
35+
</p>
36+
37+
<h2>How to use this Speech Synthesis Demo:</h2>
38+
<ul>
39+
<li>Type or paste text into the box below</li>
40+
<li>Select a voice from the dropdown</li>
41+
<li>Click "Speak" to hear the text read aloud</li>
42+
<li>Optional: Download the generated audio file (if supported)</li>
43+
</ul>
44+
45+
<p>
46+
<label for="languageSelect"><strong>Select a Language:</strong></label>
47+
<select id="languageSelect"></select>
48+
</p>
49+
50+
<p>
51+
<label for="voiceSelect"><strong>Select a Voice:</strong></label>
52+
<select id="voiceSelect"></select>
53+
</p>
54+
55+
<p>
56+
<label for="textInput"><strong>Enter text to convert to speech:</strong></label>
57+
</p>
58+
<textarea
59+
id="textInput"
60+
rows="6"
61+
placeholder="Type your text here..."
62+
style="width: 50%"
63+
></textarea>
64+
65+
<div id="controls">
66+
<button id="speakButton">Speak</button>
67+
<button
68+
id="downloadButton"
69+
disabled
70+
>
71+
Download Audio
72+
</button>
73+
</div>
74+
75+
<p>This demo works using the browser's built-in <strong>SpeechSynthesis</strong> interface and the available system voices. Voice availability varies between browsers and platforms.</p>
76+
77+
<h2>Browser Support:</h2>
78+
<ul>
79+
<li>Chrome 33+</li>
80+
<li>Edge 14+</li>
81+
<li>Firefox 49+</li>
82+
<li>Safari 7+</li>
83+
</ul>
84+
85+
<p style="color: red">Firefox may support limited voices. Audio download support may vary and use experimental APIs.</p>
86+
87+
<h2>Resources & Links:</h2>
88+
<ul>
89+
<li>
90+
<a href="https://blog.addpipe.com/a-deep-dive-into-the-web-speech-api/">In-depth Article</a>
91+
</li>
92+
<li>
93+
<a href="https://addpipe.com/web-speech-api-demo/">Speech-to-text Demo</a>
94+
</li>
95+
<li>
96+
<a href="https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis">MDN: SpeechSynthesis API</a>
97+
</li>
98+
<li>
99+
Code on GitHub:
100+
<a href="https://github.com/addpipe/Web-Speech-API-TextToSpeech-Demo"> Web Speech API TTS Demo </a>
101+
</li>
102+
</ul>
103+
</div>
104+
<script src="js/main.js"></script>
105+
</body>
106+
</html>

‎js/main.js‎

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
const synth = window.speechSynthesis;
2+
let voices = [];
3+
4+
const languageSelect = document.getElementById("languageSelect");
5+
const voiceSelect = document.getElementById("voiceSelect");
6+
const speakButton = document.getElementById("speakButton");
7+
const downloadButton = document.getElementById("downloadButton");
8+
const textInput = document.getElementById("textInput");
9+
10+
function getUniqueLanguages(voices) {
11+
const langs = voices.map((v) => v.lang);
12+
return [...new Set(langs)].sort();
13+
}
14+
15+
function populateLanguageList() {
16+
const langs = getUniqueLanguages(voices);
17+
languageSelect.innerHTML = "";
18+
langs.forEach((lang) => {
19+
const option = document.createElement("option");
20+
option.value = lang;
21+
option.textContent = lang;
22+
languageSelect.appendChild(option);
23+
});
24+
// Automatically trigger population of voices for first language
25+
populateVoiceList(langs[0]);
26+
}
27+
28+
function populateVoiceList(lang) {
29+
voiceSelect.innerHTML = "";
30+
const filtered = voices.filter((voice) => voice.lang === lang);
31+
filtered.forEach((voice, index) => {
32+
const option = document.createElement("option");
33+
option.textContent = `${voice.name} (${voice.lang})`;
34+
option.value = voices.indexOf(voice); // Use full list index
35+
voiceSelect.appendChild(option);
36+
});
37+
}
38+
39+
function initVoices() {
40+
voices = synth.getVoices();
41+
populateLanguageList();
42+
}
43+
44+
if (speechSynthesis.onvoiceschanged !== undefined) {
45+
speechSynthesis.onvoiceschanged = initVoices;
46+
}
47+
48+
languageSelect.addEventListener("change", (e) => {
49+
populateVoiceList(e.target.value);
50+
});
51+
52+
speakButton.onclick = () => {
53+
const text = textInput.value.trim();
54+
if (!text) return;
55+
56+
const utterance = new SpeechSynthesisUtterance(text);
57+
utterance.voice = voices[voiceSelect.value];
58+
utterance.lang = utterance.voice.lang;
59+
synth.speak(utterance);
60+
61+
// Setup audio recording for download
62+
// const audioStream = document.createElement("audio");
63+
const mediaStreamDestination = new MediaStreamAudioDestinationNode(new AudioContext());
64+
const mediaRecorder = new MediaRecorder(mediaStreamDestination.stream);
65+
const chunks = [];
66+
67+
mediaRecorder.ondataavailable = (e) => chunks.push(e.data);
68+
mediaRecorder.onstop = () => {
69+
const blob = new Blob(chunks, { type: "audio/webm" });
70+
const url = URL.createObjectURL(blob);
71+
downloadButton.href = url;
72+
downloadButton.download = "speech-output.webm";
73+
downloadButton.style.display = "inline-block";
74+
downloadButton.disabled = false;
75+
};
76+
77+
try {
78+
// Experimental download via Web Audio API
79+
const context = new AudioContext();
80+
const source = context.createMediaStreamSource(mediaStreamDestination.stream);
81+
source.connect(context.destination);
82+
mediaRecorder.start();
83+
84+
utterance.onend = () => {
85+
mediaRecorder.stop();
86+
context.close();
87+
};
88+
} catch (e) {
89+
console.warn("Audio recording not supported or failed", e);
90+
}
91+
};

0 commit comments

Comments
 (0)