Hardware & Capabilities · Browser Tests

Speech Voices Test

List every text-to-speech voice your browser exposes, a small but distinctive fingerprint signal.

Loading speech voices…
Reading speechSynthesis.getVoices()…

Voices load asynchronously, so the list may arrive after the first voiceschanged event rather than on the first call.

On this page
  1. What the API exposes
  2. Where the voices come from
  3. Why the list arrives late
  4. Reading a voice entry
  5. Why the list is a fingerprint
  6. What this page does not do

What the API exposes

The Web Speech API gives a page two capabilities: it can speak text, and it can ask which voices are installed. This tool only uses the second one. A single call to window.speechSynthesis.getVoices() returns an array of SpeechSynthesisVoice objects describing everything the browser can speak with.

The count, the languages and the mix of local and network voices are the interesting parts. A stock Windows laptop reports a handful of system voices, a macOS machine reports a longer set, and a phone may add several depending on which language packs the owner installed. Android varies widely in particular, because the Google speech engine downloads voices on demand rather than shipping them all up front.

No audio is involved

Reading the list does not play anything and does not need the microphone. It is a metadata read, the same kind of lookup as asking how many fonts are installed.

Where the voices come from

The list is less a browser feature than an inventory of your operating system. On Windows the voices come from the SAPI and OneCore engines, which is why they are named Microsoft David, Zira or Mark, and why extra ones appear when you add a language pack. macOS uses a different stack and ships a larger set with friendlier names such as Samantha, Alex or Daniel. Android routes through Google Speech Services, and a Linux desktop usually relies on eSpeak NG or the Speech Dispatcher stack, so its list can be short or empty.

On top of the local engines, Chromium browsers add network voices, the ones with localService set to false. These are generated on Google’s servers and streamed back as audio, which is why they need a connection and why they appear on Windows, macOS and Android alike. Firefox generally keeps to the system voices and exposes no network set of its own.

Why the list arrives late

A common trap with this API is trusting the first call. On several browsers, getVoices() returns an empty array the first time it is run, and only later does the engine finish enumerating what is installed. The signal that the list is ready is the voiceschanged event on the speechSynthesis object.

The panel handles this three ways. It reads once on load, it subscribes to voiceschanged both as an event listener and as a plain handler property, because browsers have differed over which they honour, and it reads again after 1.5 seconds as a safety net. If all three come back empty, the tool reports that rather than showing a blank list, since no voices is itself a real answer and usually means no engine is present for that browser.

Reading a voice entry

Each entry in the list carries a small set of properties worth knowing.

  • name is the display name of the voice, such as Microsoft David or Google UK English Male.
  • lang is a BCP-47 language tag like en-GB or de-DE, naming the language and often the region.
  • localService is true when the voice runs on your device and false when the audio is generated over the network.
  • default marks the voice the browser would pick for that language if a page asked for one without naming it.
  • voiceURI is the identifier the page would pass to select the voice, and it is also the string most likely to differ between machines.

The distinct language chips above collapse the list down to the languages you actually have, which is easier to read than forty near-identical rows, and the list hash below them condenses the whole set into a single short value for comparison.

Why the list is a fingerprint

The point of browser fingerprinting is finding signals that are stable for one machine and rare across machines. The voice list fits both conditions uncomfortably well. It changes only when the operating system, the browser or the installed language packs change, so it stays put for months, and the exact set is not the same for any two people.

For most machines the list contributes only a few bits, because many people run a stock English install. The signal grows when the mix is unusual, for example a user with four languages, or a Linux desktop with a particular eSpeak build. It is one column, not an identity, and it matters because it joins the other columns. The font detection test measures a similar kind of list, and the fingerprint test combines both with the rest of your browser surface.

What this page does not do

The tool reads the voice list and stops there. It does not call speak(), so nothing is synthesised, no audio is played and no connection is opened for a network voice. It does not request microphone access, it does not store the list and it does not send it anywhere. The list never leaves this page.

It is worth knowing that a page can also infer voices by attempting to speak and watching for errors, so a site that wanted this signal would not need permission. Blocking speech synthesis entirely is possible in some browsers and extensions, but it will also break legitimate accessibility features such as screen readers that rely on the same API. Treat the list as one more ordinary browser property rather than a lever on its own.

Frequently Asked Questions

What are speech synthesis voices?
They are the text-to-speech engines your browser and operating system make available to web pages through window.speechSynthesis.getVoices(). Each entry carries a name, a language tag, a flag for whether it runs on your device or over the network, and a flag marking the default voice for its language. The panel above lists every one your browser reports, along with a hash of the whole set.
Why was the list empty at first?
The Web Speech API loads voices asynchronously. On the first call getVoices() often returns an empty array, and the real list arrives shortly after, signalled by a voiceschanged event. This page reads the list immediately, again on that event, and once more after a short delay, so it catches whichever route your browser uses. If all three come back empty, there is genuinely no engine installed for that browser.
What does "on-device" versus "network" mean?
A voice with localService set to true is synthesised by an engine installed on your machine, such as the Windows, macOS or Android system voices. One set to false is generated remotely and streamed back. On-device voices work offline; network voices need a connection and may send the text you ask them to say to a server. Reading the list never triggers either kind.
Why does my voice list differ from someone else's?
Because it depends on your operating system, its version and which language packs are installed. A machine with several languages installed has a longer, more distinctive list, and a Linux desktop running eSpeak can look very different from a stock Windows laptop. That variation is exactly what makes the list useful for fingerprinting, and you can see the rest of that surface in the browser fingerprint test.
Does this page play any sound or record anything?
No. It only reads the list of available voices and their properties. It never calls speak(), never opens the microphone or any audio output, and never uploads the list. The microphone is a separate matter entirely, covered by the camera and microphone test, which does require your permission before it reads anything.