Signal

Speech voices

Enumerates the browser's available text-to-speech voices and hashes their names, language tags, and local-service flags.

Tier 3 engine src/signals/speech-voices.ts

What it measures

The set of speech synthesis voices available to the browser, identified by name, BCP-47 language tag, and whether the voice is provided by a local on-device TTS service. Different browsers and operating systems expose different voice sets, making this an effective browser and platform discriminator.

How it is collected

Many browsers do not populate speechSynthesis.getVoices() synchronously on first call. The collector attempts a synchronous read first; if the result is empty it registers a voiceschanged event listener and waits up to VOICES_TIMEOUT_MS = 500 ms. Voices are sorted by name using localeCompare before serialisation — ensuring hash determinism regardless of the iteration order returned by different browsers. Each voice becomes the string name:lang:localService, and all voice strings are joined with the pipe delimiter before hashing via xxHash64.

Confidence rules

ConfidenceTrigger
normalAt least one voice obtained within 500 ms
absentspeechSynthesis absent, getVoices() always empty, or voiceschanged never fires within 500 ms

Why engine-bound

Voice availability is a function of both the browser engine and the operating system. Chrome on macOS exposes macOS system voices and Google TTS voices (localService: false); Firefox exposes a different subset; Safari uses AVSpeechSynthesizer voices. The same OS with different browsers can yield a different voice list because browsers selectively expose or filter the OS voice registry. The localService flag adds entropy by differentiating cloud-only voices from on-device voices.