# speech_voices

> The installed speech synthesis voices (name, language, local, default), sorted and hashed. Nothing is spoken. Kept out of both ids because some browsers fill the list late.

Source: https://whiskerjs.com/signals/speech-voices/ (last updated 2026-10-26)

**speech_voices at a glance (from the library code, signalsVersion 9)**

| Field | Value |
| --- | --- |
| Component name | `speech_voices` |
| In the visitorId | no (reported, kept out of both ids) |
| structuredId group | none |
| Hashing | always a digest, except the plain value none when nothing is found |
| Excluded under | Brave (brave_farbling) |
| Inner timeout | 1000 ms |
| Added in | [signalsVersion 1](https://whiskerjs.com/whats-new/) |

> **In plain words:**
>
> Computers and phones come with voices for reading text aloud, and which voices are installed depends on the operating system, its language packs and the browser. WhiskerJS asks the browser for that list and keeps a short hash of it. Nothing is ever spoken. Some browsers deliver the list late, so it is reported but kept out of the identifiers.

## What it reads

`speechSynthesis.getVoices()`: for each voice its `name`, `lang`, `localService` and `default` flags.

## How the technique works

1. Without `speechSynthesis.getVoices` the component is `unavailable` / `no_api`.
2. The list is read. If it is empty and the browser supports events, WhiskerJS listens for `voiceschanged` and waits up to 600 ms, whichever comes first, then reads the list again and removes the listener.
3. Each voice becomes `name|lang|local|default` (the flags as `1` or `0`); the entries are sorted and joined with `;`.
4. The value is always hashed; an empty list gives the plain value `none`.

## Why it helps

Voice lists differ a lot between operating systems, system languages and browsers (some browsers add network voices of their own, marked not local). Two devices with the same screen and fonts can still differ here.

Your server can use it as context next to the stable ids, for example to see that two results with the same visitorId also have the same voices, or to notice a minimal environment with no voices at all.

## Stability

Medium: some browsers fill the list only after a delay, so one run can see `none` and the next the full list. Installing a language pack or voice changes it too. That is why it is outside both ids.

## Browser support and what blocks it

Most current browsers, including [Chrome](https://whiskerjs.com/browsers/chrome-desktop/), [Edge](https://whiskerjs.com/browsers/edge/), [Safari](https://whiskerjs.com/browsers/safari-macos/), [iOS browsers](https://whiskerjs.com/browsers/safari-ios/) and [Firefox](https://whiskerjs.com/browsers/firefox/), have `speechSynthesis`. Headless and minimal builds often have no voices and report `none`. [Brave](https://whiskerjs.com/browsers/brave/): `excluded` / `brave_farbling` (since signalsVersion 6), because Brave farbles the voice list.

## Privacy

Installed voices can hint at the system languages a user has added. WhiskerJS reports only a digest and produces no sound. Leave it out with `collect({ exclude: ['speech_voices'] })` if you do not need it; no id changes.

## How WhiskerJS handles noise and failures

| Status | ruleId | When |
| --- | --- | --- |
| `ok` |   | a digest, or plain `none` |
| `unavailable` | `no_api` | no `speechSynthesis.getVoices` |
| `unavailable` | `no_digest` | a non-empty list without `crypto.subtle` |
| `blocked` | `threw`, `security_error` | reading the voices throws |
| `excluded` | `brave_farbling`, `caller_excluded` | exclusion table or your list |
| `timed_out` | `timeout`, `aborted` | budget or abort |

The 600 ms wait sits inside the 1000 ms inner timeout.

## Example value

In the container test, headless Chromium and Firefox reported `none` and headless WebKit reported the digest `89cde6c218596206e4119d0f5cf15c09`.

## Questions and answers

### Does WhiskerJS make the browser speak?

No. It only calls speechSynthesis.getVoices() to read the list. No utterance is created and nothing is spoken.

### Why is the list sometimes empty?

Some browsers load voices asynchronously. WhiskerJS waits up to 600 ms for the voiceschanged event; if the list is still empty it reports the plain value none.

## Related pages

- [fonts](https://whiskerjs.com/signals/fonts/): Another view of installed system resources.
- [languages](https://whiskerjs.com/signals/languages/): Language settings, in the id.
- [Timeouts and budgets](https://whiskerjs.com/docs/how-it-works/#timeouts-and-budgets): The voices wait and the other limits.
