DEV Community

Cover image for Ready to Make Your Web App Speak? No External TTS API Required
Elif Nur Turk
Elif Nur Turk

Posted on Originally published at elifnurturk.com

Ready to Make Your Web App Speak? No External TTS API Required

Your browser does more than run your app. It can give it a voice, too.

Adding TTS (text-to-speech) capability to your web app doesn't necessarily require a separately hosted text-to-speech solution, audio file generation, or the addition of any external dependencies to your application.

I initially thought I'd need an external TTS service to make sentences playable in my app. But while looking into it, I found out that my browser could already do the job with just a few lines of JavaScript. No extra service, no audio files. I hadn't expected it to be that simple.

How It Works

The Web Speech API is, indeed, an interface providing speech synthesis.

It exposes methods and properties which provide access to the speech synthesis capabilities of the browser.

It turns out that creating a SpeechSynthesisUtterance instance and calling speak on it simply queues up the speech request for synthesis by the implementation which powers the API.

The quality of the synthesized speech, the voices available for selection, and even the specific speaker voice used will vary depending on the browser, the operating system, and the speech synthesis implementation in use.

That also means your application does not need to generate an audio file or send any text to some server in order to use this API.

The general flow looks like this:

application → Web Speech API → speech synthesis implementation → audio output.

How do you get started?

In other words, your application doesn't necessarily have direct access to the speaker voice. That will always be a part of whatever system the application is running on. The same code, therefore, can produce somewhat different results on different devices.

The Web Speech API provides the JavaScript interfaces for working with speech: recognition and synthesis. We only need the latter (synthesis) for the purposes of this article.

Here's a minimal, safe helper:

function speak(text) {
  if (
    !("speechSynthesis" in window) ||
    !("SpeechSynthesisUtterance" in window)
  ) {
    console.warn("Speech synthesis is not supported.");
    return;
  }

  window.speechSynthesis.cancel();

  const utterance = new SpeechSynthesisUtterance(text);
  utterance.lang = "en-US";

  window.speechSynthesis.speak(utterance);
}
Enter fullscreen mode Exit fullscreen mode

Calling cancel() stops the current speech and clears queued utterances. This is useful when a new message should replace the current one, but not when you want queued messages to play in order.

That's enough to queue a speech request. You can also tweak values like speaking rate or pitch and select from available voices.

But how does this work behind the scenes? What are the limits to this approach? And what should you know before adding this to your application?

What the Native Speech Synthesis Does Not Offer

It's worth noting that the Web Speech API is generally convenient to use, but is still limited in some ways.

These limitations become more apparent when you go to production.

1. Voices and Their Qualities Can Vary

It can be challenging to select the right voice for the right purpose if you can't audition voices or test how they sound on target devices.

Setting the language with utterance.lang = "en-US"; only tells the speech synthesizer which language to use. It doesn't specify an accent, a speaker, or any particular quality of the voice.

For language learning applications (or other uses), voice quality and accent can be critically important.

There is also a common gotcha: in some browsers, speechSynthesis.getVoices() returns an empty list on the first call. The voices load asynchronously, and the browser fires a voiceschanged event when they are ready. So it's safer to wait for them:

function loadVoices() {
  return new Promise((resolve) => {
    const voices = window.speechSynthesis.getVoices();
    if (voices.length) return resolve(voices);

    window.speechSynthesis.addEventListener(
      "voiceschanged",
      () => resolve(window.speechSynthesis.getVoices()),
      { once: true }
    );
  });
}
Enter fullscreen mode Exit fullscreen mode

Once the voices are available, you can pick the closest match and fall back gracefully:

async function pickVoice(lang = "en-US") {
  const voices = await loadVoices();

  return (
    voices.find((v) => v.lang === lang) ??  // exact match
    voices.find((v) => v.lang.startsWith(lang.split("-")[0])) ?? // same language
    null   // browser default
  );
}

const utterance = new SpeechSynthesisUtterance("Hello! How are you today?");
utterance.lang = "en-US";

const voice = await pickVoice("en-US");
if (voice) utterance.voice = voice;

window.speechSynthesis.speak(utterance);
Enter fullscreen mode Exit fullscreen mode

If no suitable voice is found, leaving utterance.voice unset lets the browser use its default voice for the given language.

What to do:

Wait for the voice list before selecting a voice. Pick the closest match for your target language and provide a fallback. Make sure the voices you rely on exist on your target devices and operating systems. Test with text, numbers, and punctuation on those devices.

2. Playback Is Asynchronous and Platform-Dependent

SpeechSynthesisUtterance simply queues up speech for playback.

It has no effect on whether or when the speech actually plays.

There may be other queued utterances which cause yours to start later than expected.

You can listen for events, such as 'start', 'end', or 'error'.

The Web Speech API is generally consistent across browsers, but variations between operating systems still affect how speech is handled.

Mobile is generally a bigger concern for differences in voices and overall speech behavior.

What to do:

Use and manage the utterance queue explicitly.

Make sure to call speechSynthesis.cancel() when removing queued items. Check that the necessary interfaces are available at run time rather than assuming they will always be there. Pay attention to the behaviors and quirks of the particular browsers and mobile platforms you support.

3. It Isn't Always Totally Offline

Your application may not need to handle a TTS API hosted elsewhere, but that doesn't mean that the speech synthesis implementation doesn't require a network connection to operate.

This is especially true on mobile, where network connections are generally less reliable.

A hosted TTS service may offer more consistent performance than some native implementations.

What to do:

If your application makes essential use of text-to-speech and requires offline or low-network support, test with the actual implementation to make sure that it satisfies your requirements.

When Should You Use Native Speech Synthesis?

Use native speech synthesis for quick, simple implementations where voice consistency isn't essential. Choose a hosted TTS service when you need greater control over voice quality and output.

Neither is universally better. It depends on your application's needs.

Speech synthesis can support accessibility, language learning, and reading apps. The key is knowing what the browser can do and where its limits lie.

And Speech Is Just the Beginning

Browsers already come with a growing collection of APIs that let us do more with less infrastructure.

For example:

  • Clipboard API: Copy text to the clipboard without relying on an extra library.
  • Web Share API: Open the device's native sharing interface to share links, text, or files.
  • Web Audio API: Generate sounds, build audio effects, and process audio directly in the browser.
  • Geolocation API: Access a user's location with their permission.
  • Vibration API: Trigger vibration patterns on supported devices.
  • Web Crypto API: Perform cryptographic operations using built-in browser capabilities.

They're worth knowing about before adding another dependency to a project.

I used to think about the files and services I needed to add. Now, I find it just as useful to ask a different question first:

What can the browser already do?

And that might be the beginning of another article.

Sometimes, we're so busy looking for something new that we forget to look at what we already have.

See you in the next article!

Top comments (0)