@@ -5,3 +5,127 @@ To use care_token_display in a project:
55``` python
66import token_display
77```
8+
9+ ## Audio announcements
10+
11+ The display calls out new tokens with a chime followed by a pre-recorded voice
12+ prompt that spells the token code one character at a time. Audio is assembled
13+ ** in the browser** using the Web Audio API from a flat folder of small WAV
14+ fragments shipped with the plugin — no server-side TTS, no extra dependencies,
15+ and no ` SpeechSynthesis ` (which is unreliable on signage hardware such as
16+ Samsung Tizen / MagicINFO).
17+
18+ A typical announcement plays as:
19+
20+ > * (chime)* "Now serving token, G, zero, zero, one"
21+
22+ The "Now serving token" prefix can be played in multiple languages back to
23+ back for the same token — see [ Multi-language announcements] ( #multi-language-announcements ) .
24+ Tokens are deduplicated client-side via ` localStorage ` , so a given token is
25+ announced at most once per display across page refreshes.
26+
27+ ### Query parameters
28+
29+ | Param | Default | Description |
30+ | --------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
31+ | ` va_lang ` | ` VA_DEFAULT_LANG ` setting | Comma-separated language codes for the prefix announcement (e.g. ` en_IN ` , ` ml_IN,en_IN ` ). Each code must have a matching ` prefix-<code>.wav ` . Pass ` ?va_lang= ` (empty) to mute. |
32+
33+ ### Muting the voice announcer
34+
35+ The voice announcer is muted by resolving to an empty list of languages. Two
36+ ways to do this:
37+
38+ - Per-request: pass ` ?va_lang= ` with no value (or only invalid codes).
39+ - Globally: configure ` VA_DEFAULT_LANG = [] ` in your plugin settings.
40+
41+ When muted, the page is rendered without the announcer payload or script —
42+ no ` localStorage ` writes, no fragment fetches — and a plain
43+ ` <meta http-equiv="refresh"> ` drives the periodic reload.
44+
45+ ### Multi-language announcements
46+
47+ The chime is language-neutral and reused as-is. Everything else — the
48+ * "Now serving token"* prefix and the per-character utterances — is recorded
49+ per language under a ` <lang>/ ` subdirectory (for example ` en_IN/prefix.wav ` ,
50+ ` en_IN/A.wav ` , ` ml_IN/prefix.wav ` , ` ml_IN/A.wav ` ).
51+
52+ The playback order is controlled by:
53+
54+ 1 . The ` ?va_lang= ` query parameter, if present (comma-separated, e.g.
55+ ` ?va_lang=ml_IN,en_IN ` ).
56+ 2 . Otherwise, the ` VA_DEFAULT_LANG ` plugin setting (default: ` ["en_IN"] ` ).
57+
58+ For each token, the announcer schedules every configured language pass
59+ back-to-back with a 1-second pause between languages:
60+
61+ ```
62+ [chime] [ml_IN/prefix] [ml_IN/G] [ml_IN/0] [ml_IN/0] [ml_IN/1]
63+ ... 1 second pause ...
64+ [chime] [en_IN/prefix] [en_IN/G] [en_IN/0] [en_IN/0] [en_IN/1]
65+ ```
66+
67+ See [ Muting the voice announcer] ( #muting-the-voice-announcer ) for how to
68+ disable playback.
69+
70+ ### Audio fragments
71+
72+ The fragments live under
73+ ` src/token_display/static/token_display/sounds/ ` :
74+
75+ | File | Contents |
76+ | ----------------------------------- | --------------------------------------------------------------- |
77+ | ` chime.wav ` | Two-tone leading chime (language-neutral). |
78+ | ` <lang>/prefix.wav ` | "Now serving token" recorded in ` <lang> ` (e.g. ` en_IN/prefix.wav ` ). |
79+ | ` <lang>/A.wav ` … ` <lang>/Z.wav ` | Each English letter pronounced in ` <lang> ` . |
80+ | ` <lang>/0.wav ` … ` <lang>/9.wav ` | Each digit pronounced in ` <lang> ` . |
81+
82+ So a configuration with ` VA_DEFAULT_LANG = ["en_IN", "ml_IN"] ` requires the
83+ ` en_IN/ ` and ` ml_IN/ ` subdirectories to each contain ` prefix.wav ` , ` A.wav ` …
84+ ` Z.wav ` and ` 0.wav ` … ` 9.wav ` . ` chime.wav ` is shared.
85+
86+ All files must share the same sample format (the bundled placeholders are
87+ 22.05 kHz mono 16-bit PCM, but the Web Audio API will resample anything it
88+ can decode). Each fragment should bake in a short trailing pause (~ 120 ms) so
89+ that consecutive characters do not slur together when concatenated.
90+
91+ ### Replacing the placeholder voice
92+
93+ The shipped fragments are auto-generated placeholders using macOS's ` say `
94+ utility (or stdlib tones on other platforms). To swap in real recordings:
95+
96+ 1 . Record one WAV per fragment using the same filenames as above.
97+ 2 . Use a single voice talent and a consistent peak level (~ -3 dBFS).
98+ 3 . Trim leading silence aggressively; leave ~ 120 ms of trailing silence.
99+ 4 . Drop the new files into
100+ ` src/token_display/static/token_display/sounds/ ` — no code changes needed.
101+
102+ To regenerate the placeholders at any time, run:
103+
104+ ``` sh
105+ python scripts/generate_placeholder_fragments.py
106+ ```
107+
108+ ### Kiosk setup
109+
110+ Most browsers block autoplay until the user interacts with the page. For an
111+ unattended kiosk, launch Chromium with:
112+
113+ ``` shell
114+ chromium --kiosk \
115+ --autoplay-policy=no-user-gesture-required \
116+ " https://<your-care-host>/token_display/sub_queues/<sub_queue_external_ids>/?token=<api_token>"
117+ ```
118+
119+ If the display is opened in a normal browser tab, click anywhere on the page
120+ once after loading to unlock audio.
121+
122+ ### Graceful degradation
123+
124+ - If the Web Audio API is unavailable, the page renders silently and the
125+ currently shown tokens are recorded as "announced" so they do not loop on
126+ every refresh.
127+ - If a fragment fails to load or decode, the announcement is aborted, the
128+ affected tokens are still marked as announced, and the page reloads on
129+ schedule.
130+ - The ` <meta http-equiv="refresh"> ` fallback still works with JavaScript
131+ disabled — the page refreshes on schedule but plays no audio.
0 commit comments