@creativeorange/azure-text-to-speech 2.2.3 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +455 -0
- package/dist/SpeechToText.d.ts +27 -0
- package/dist/TextToSpeech.d.ts +79 -0
- package/dist/authentication.d.ts +44 -0
- package/dist/co-azure-tts.es.js +672 -209
- package/dist/co-azure-tts.umd.js +8 -8
- package/dist/main.d.ts +4 -0
- package/package.json +27 -5
- package/.eslintrc.js +0 -30
- package/index.html +0 -578
- package/nuxt/plugins/azure-speech-to-text.client.js +0 -27
- package/nuxt/plugins/azure-text-to-speech.client.js +0 -28
- package/src/SpeechToText.ts +0 -106
- package/src/TextToSpeech.ts +0 -745
- package/src/main.ts +0 -2
- package/tsconfig.json +0 -21
- package/vite.config.ts +0 -26
package/README.md
CHANGED
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
# Azure Text to Speech / Speech to Text
|
|
2
|
+
|
|
3
|
+
Browser package for Azure Cognitive Services Speech with **temporary authorization tokens**. Permanent Azure subscription keys never enter frontend JavaScript.
|
|
4
|
+
|
|
5
|
+
Package: `@creativeorange/azure-text-to-speech`
|
|
6
|
+
Current major version: **3.0.0**
|
|
7
|
+
|
|
8
|
+
## Why version 3 exists
|
|
9
|
+
|
|
10
|
+
In version 2, constructors accepted a permanent Azure subscription key and used `fromSubscription`. That forced the real API key into browser bundles, DevTools, and memory.
|
|
11
|
+
|
|
12
|
+
Version 3 removes that pattern completely. The permanent key stays on your server. The browser only receives short-lived Azure authorization tokens via your own backend.
|
|
13
|
+
|
|
14
|
+
## Architecture
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
Browser
|
|
18
|
+
→ your backend token endpoint
|
|
19
|
+
→ Azure issueToken endpoint
|
|
20
|
+
← temporary Azure token + region
|
|
21
|
+
→ Azure Speech SDK (fromAuthorizationToken)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Supported authentication options:
|
|
25
|
+
|
|
26
|
+
1. `tokenEndpoint` — simple GET endpoint (Craft, classic sites)
|
|
27
|
+
2. `getAuthorizationToken` — custom async provider
|
|
28
|
+
|
|
29
|
+
There is **no** option to pass a permanent subscription key.
|
|
30
|
+
|
|
31
|
+
The Azure Speech SDK is **bundled** into the published `dist` files, so consumers do not need to install `microsoft-cognitiveservices-speech-sdk` separately. That dependency remains a `devDependency` of this package.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install @creativeorange/azure-text-to-speech
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
TypeScript declarations are published at `dist/main.d.ts`.
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import {
|
|
43
|
+
TextToSpeech,
|
|
44
|
+
SpeechToText,
|
|
45
|
+
type TextToSpeechOptions,
|
|
46
|
+
type SpeechToTextOptions,
|
|
47
|
+
type SpeechAuthorization,
|
|
48
|
+
} from '@creativeorange/azure-text-to-speech';
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Always import from the package root. Do not import from `/dist/...` paths.
|
|
52
|
+
|
|
53
|
+
## Public API
|
|
54
|
+
|
|
55
|
+
### Text to Speech with `tokenEndpoint`
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
const textToSpeech = new TextToSpeech({
|
|
59
|
+
tokenEndpoint: '/actions/azure-speech/token',
|
|
60
|
+
voice: 'nl-NL-FennaNeural',
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await textToSpeech.start();
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The Speech region comes from the token endpoint response, not from frontend configuration. That prevents mismatches between frontend, backend and Azure resource region.
|
|
67
|
+
|
|
68
|
+
### Text to Speech with a custom provider
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
const textToSpeech = new TextToSpeech({
|
|
72
|
+
voice: 'nl-NL-FennaNeural',
|
|
73
|
+
getAuthorizationToken: async () => {
|
|
74
|
+
const response = await fetch('/api/azure-speech/token', {
|
|
75
|
+
credentials: 'same-origin',
|
|
76
|
+
cache: 'no-store',
|
|
77
|
+
headers: {
|
|
78
|
+
Accept: 'application/json',
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (!response.ok) {
|
|
83
|
+
throw new Error('Could not obtain Azure Speech token');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return response.json();
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
await textToSpeech.start();
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Speech to Text
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
const speechToText = new SpeechToText({
|
|
97
|
+
tokenEndpoint: '/actions/azure-speech/token',
|
|
98
|
+
sourceLanguage: 'nl-NL',
|
|
99
|
+
targetLanguage: 'nl',
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
await speechToText.start();
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Continuous recognition can outlive a single Azure token. After recognition starts successfully, Speech-to-Text refreshes the active recognizer token about every eight minutes (`tokenLifetimeMs`) by setting `recognizer.authorizationToken`.
|
|
106
|
+
|
|
107
|
+
### Token endpoint response
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"token": "temporary-azure-token",
|
|
112
|
+
"region": "westeurope"
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Options
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
type SpeechAuthorization = {
|
|
120
|
+
token: string;
|
|
121
|
+
region: string;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
type SpeechAuthenticationOptions = {
|
|
125
|
+
tokenEndpoint?: string;
|
|
126
|
+
getAuthorizationToken?: () => Promise<SpeechAuthorization>;
|
|
127
|
+
tokenRequestOptions?: {
|
|
128
|
+
headers?: Record<string, string>;
|
|
129
|
+
credentials?: RequestCredentials;
|
|
130
|
+
cache?: RequestCache; // default: 'no-store'
|
|
131
|
+
};
|
|
132
|
+
tokenLifetimeMs?: number; // default: 8 minutes
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
type TextToSpeechOptions = SpeechAuthenticationOptions & {
|
|
136
|
+
voice: string;
|
|
137
|
+
rate?: number;
|
|
138
|
+
pitch?: number;
|
|
139
|
+
url?: string; // lexicon URL
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
type SpeechToTextOptions = SpeechAuthenticationOptions & {
|
|
143
|
+
sourceLanguage: string;
|
|
144
|
+
targetLanguage?: string;
|
|
145
|
+
};
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Provide **exactly one** of `tokenEndpoint` or `getAuthorizationToken`.
|
|
149
|
+
|
|
150
|
+
### Token caching and transport
|
|
151
|
+
|
|
152
|
+
- Tokens are cached in memory and reused while valid.
|
|
153
|
+
- Tokens refresh after about **8 minutes** by default.
|
|
154
|
+
- Parallel callers share one in-flight request.
|
|
155
|
+
- `tokenEndpoint` requests use `credentials: 'same-origin'` and `cache: 'no-store'` by default.
|
|
156
|
+
- Tokens are never logged and never included in error events.
|
|
157
|
+
|
|
158
|
+
### Browser events
|
|
159
|
+
|
|
160
|
+
TTS:
|
|
161
|
+
|
|
162
|
+
- `COAzureTTSStartedPlaying`
|
|
163
|
+
- `COAzureTTSFinishedPlaying`
|
|
164
|
+
- `COAzureTTSStoppedPlaying`
|
|
165
|
+
- `COAzureTTSPausedPlaying`
|
|
166
|
+
- `COAzureTTSResumedPlaying`
|
|
167
|
+
- `COAzureTTSError` → `{ detail: { error: { message, code? } } }`
|
|
168
|
+
|
|
169
|
+
STT:
|
|
170
|
+
|
|
171
|
+
- `COAzureSTTStartedRecording`
|
|
172
|
+
- `COAzureSTTStoppedRecording`
|
|
173
|
+
- `COAzureSTTError` → `{ detail: { error: { message, code? } } }`
|
|
174
|
+
|
|
175
|
+
## Migrating from v2 to v3
|
|
176
|
+
|
|
177
|
+
### Old unsafe usage
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
const textToSpeech = new TextToSpeech(
|
|
181
|
+
window.azureSpeechKey,
|
|
182
|
+
'westeurope',
|
|
183
|
+
'nl-NL-FennaNeural',
|
|
184
|
+
0,
|
|
185
|
+
0
|
|
186
|
+
);
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Remove Twig/Vite/window exports of the subscription key.
|
|
190
|
+
|
|
191
|
+
### New safe usage
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
const textToSpeech = new TextToSpeech({
|
|
195
|
+
tokenEndpoint: '/actions/azure-speech/token',
|
|
196
|
+
voice: 'nl-NL-FennaNeural',
|
|
197
|
+
rate: 0,
|
|
198
|
+
pitch: 0,
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Migration checklist
|
|
203
|
+
|
|
204
|
+
1. Update the package to `3.x`.
|
|
205
|
+
2. Store `AZURE_SPEECH_KEY` and `AZURE_SPEECH_REGION` server-side only.
|
|
206
|
+
3. Add a backend token endpoint.
|
|
207
|
+
4. Replace the old constructor with the options object.
|
|
208
|
+
5. Remove every frontend variable that contained the subscription key.
|
|
209
|
+
6. Rebuild the frontend.
|
|
210
|
+
7. Confirm in DevTools that the permanent key no longer appears.
|
|
211
|
+
8. Test TTS, STT, highlighting, pause/resume, chained playback and prefetching.
|
|
212
|
+
9. **Rotate the previously exposed Azure subscription key.**
|
|
213
|
+
|
|
214
|
+
## Craft CMS integration
|
|
215
|
+
|
|
216
|
+
### `.env`
|
|
217
|
+
|
|
218
|
+
```dotenv
|
|
219
|
+
AZURE_SPEECH_KEY="replace-with-secret-key"
|
|
220
|
+
AZURE_SPEECH_REGION="westeurope"
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Never export these values to Twig globals, Vite `define`, or frontend env files.
|
|
224
|
+
|
|
225
|
+
### Module registration (`config/app.php`)
|
|
226
|
+
|
|
227
|
+
```php
|
|
228
|
+
<?php
|
|
229
|
+
|
|
230
|
+
return [
|
|
231
|
+
'modules' => [
|
|
232
|
+
'azure-speech' => [
|
|
233
|
+
'class' => \modules\azurespeech\Module::class,
|
|
234
|
+
],
|
|
235
|
+
],
|
|
236
|
+
'bootstrap' => [
|
|
237
|
+
'azure-speech',
|
|
238
|
+
],
|
|
239
|
+
];
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Craft action routing uses:
|
|
243
|
+
|
|
244
|
+
- module ID: `azure-speech`
|
|
245
|
+
- controller ID: `token` (`TokenController`)
|
|
246
|
+
- action: `actionIndex`
|
|
247
|
+
|
|
248
|
+
Resulting URL:
|
|
249
|
+
|
|
250
|
+
```text
|
|
251
|
+
/actions/azure-speech/token
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Ensure the module registers its controller namespace, for example in `modules/azurespeech/Module.php`:
|
|
255
|
+
|
|
256
|
+
```php
|
|
257
|
+
public function init(): void
|
|
258
|
+
{
|
|
259
|
+
parent::init();
|
|
260
|
+
// Controllers live in modules\azurespeech\controllers
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Token controller for Craft 4 / Craft 5
|
|
265
|
+
|
|
266
|
+
```php
|
|
267
|
+
<?php
|
|
268
|
+
|
|
269
|
+
namespace modules\azurespeech\controllers;
|
|
270
|
+
|
|
271
|
+
use Craft;
|
|
272
|
+
use craft\web\Controller;
|
|
273
|
+
use yii\web\Response;
|
|
274
|
+
|
|
275
|
+
class TokenController extends Controller
|
|
276
|
+
{
|
|
277
|
+
// Craft 4 and Craft 5
|
|
278
|
+
protected array|bool|int $allowAnonymous = true;
|
|
279
|
+
|
|
280
|
+
public function actionIndex(): Response
|
|
281
|
+
{
|
|
282
|
+
$key = Craft::parseEnv('$AZURE_SPEECH_KEY');
|
|
283
|
+
$region = Craft::parseEnv('$AZURE_SPEECH_REGION');
|
|
284
|
+
|
|
285
|
+
if (!$key || !$region) {
|
|
286
|
+
return $this->asFailure(
|
|
287
|
+
'Azure Speech configuration is missing.'
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
$client = Craft::createGuzzleClient();
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
$azureResponse = $client->post(
|
|
295
|
+
"https://{$region}.api.cognitive.microsoft.com/sts/v1.0/issueToken",
|
|
296
|
+
[
|
|
297
|
+
'headers' => [
|
|
298
|
+
'Ocp-Apim-Subscription-Key' => $key,
|
|
299
|
+
],
|
|
300
|
+
'timeout' => 10,
|
|
301
|
+
]
|
|
302
|
+
);
|
|
303
|
+
} catch (\Throwable $exception) {
|
|
304
|
+
Craft::error(
|
|
305
|
+
'Could not retrieve an Azure Speech token.',
|
|
306
|
+
__METHOD__
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
return $this->asFailure(
|
|
310
|
+
'Speech authorization is temporarily unavailable.'
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
$craftResponse = $this->asJson([
|
|
315
|
+
'token' => (string) $azureResponse->getBody(),
|
|
316
|
+
'region' => $region,
|
|
317
|
+
]);
|
|
318
|
+
|
|
319
|
+
$craftResponse->getHeaders()->set(
|
|
320
|
+
'Cache-Control',
|
|
321
|
+
'private, no-store, max-age=0'
|
|
322
|
+
);
|
|
323
|
+
$craftResponse->getHeaders()->set(
|
|
324
|
+
'Pragma',
|
|
325
|
+
'no-cache'
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
return $craftResponse;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Token controller property for Craft 3 / older PHP
|
|
334
|
+
|
|
335
|
+
```php
|
|
336
|
+
// Craft 3 or older PHP versions without union property types
|
|
337
|
+
protected $allowAnonymous = true;
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Use the property form that matches your Craft and PHP version. Do not log tokens or exception details to the browser.
|
|
341
|
+
|
|
342
|
+
### Frontend initialization
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
import {TextToSpeech, SpeechToText} from '@creativeorange/azure-text-to-speech';
|
|
346
|
+
|
|
347
|
+
const textToSpeech = new TextToSpeech({
|
|
348
|
+
tokenEndpoint: '/actions/azure-speech/token',
|
|
349
|
+
voice: 'nl-NL-FennaNeural',
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
textToSpeech.start().catch((error) => {
|
|
353
|
+
console.error('Could not initialize text to speech.', error);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
const speechToText = new SpeechToText({
|
|
357
|
+
tokenEndpoint: '/actions/azure-speech/token',
|
|
358
|
+
sourceLanguage: 'nl-NL',
|
|
359
|
+
targetLanguage: 'nl',
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
speechToText.start().catch((error) => {
|
|
363
|
+
console.error('Could not initialize speech to text.', error);
|
|
364
|
+
});
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Protecting the Craft token endpoint
|
|
368
|
+
|
|
369
|
+
A temporary token may appear in the browser. That does **not** mean the endpoint can be public and unlimited.
|
|
370
|
+
|
|
371
|
+
Recommended controls:
|
|
372
|
+
|
|
373
|
+
- Craft rate limiting and/or webserver/proxy rate limiting
|
|
374
|
+
- Cloudflare rate limiting when available
|
|
375
|
+
- Authentication when TTS/STT is not meant for anonymous visitors
|
|
376
|
+
- HTTPS only
|
|
377
|
+
- Short server-side timeouts
|
|
378
|
+
- Azure budget and usage alerts
|
|
379
|
+
- Azure resource networking restrictions when infrastructure allows it
|
|
380
|
+
- Never put the permanent key in responses or logs
|
|
381
|
+
- Return `Cache-Control: private, no-store, max-age=0`
|
|
382
|
+
|
|
383
|
+
**CORS alone is not security.** Endpoints can be called outside a browser.
|
|
384
|
+
|
|
385
|
+
## Laravel token endpoint
|
|
386
|
+
|
|
387
|
+
```php
|
|
388
|
+
use Illuminate\Support\Facades\Http;
|
|
389
|
+
use Illuminate\Support\Facades\Route;
|
|
390
|
+
|
|
391
|
+
Route::get('/api/azure-speech/token', function () {
|
|
392
|
+
$region = config('services.azure_speech.region');
|
|
393
|
+
|
|
394
|
+
$azureResponse = Http::timeout(10)
|
|
395
|
+
->withHeaders([
|
|
396
|
+
'Ocp-Apim-Subscription-Key' =>
|
|
397
|
+
config('services.azure_speech.key'),
|
|
398
|
+
])
|
|
399
|
+
->post(
|
|
400
|
+
"https://{$region}.api.cognitive.microsoft.com/sts/v1.0/issueToken"
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
abort_unless($azureResponse->successful(), 502);
|
|
404
|
+
|
|
405
|
+
return response()
|
|
406
|
+
->json([
|
|
407
|
+
'token' => $azureResponse->body(),
|
|
408
|
+
'region' => $region,
|
|
409
|
+
])
|
|
410
|
+
->header(
|
|
411
|
+
'Cache-Control',
|
|
412
|
+
'private, no-store, max-age=0'
|
|
413
|
+
)
|
|
414
|
+
->header('Pragma', 'no-cache');
|
|
415
|
+
})->middleware('throttle:20,1');
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
Put this route behind authentication whenever the feature is not public.
|
|
419
|
+
|
|
420
|
+
## Existing browser features
|
|
421
|
+
|
|
422
|
+
Version 3 keeps the existing client behaviour from the development branch:
|
|
423
|
+
|
|
424
|
+
- play / pause / resume / stop
|
|
425
|
+
- word-boundary highlighting
|
|
426
|
+
- chained playback (`co-tts.next`)
|
|
427
|
+
- audio prefetching
|
|
428
|
+
- lexicons via `url`
|
|
429
|
+
- voice / rate / pitch setters
|
|
430
|
+
- existing custom browser events
|
|
431
|
+
|
|
432
|
+
## Development
|
|
433
|
+
|
|
434
|
+
```bash
|
|
435
|
+
npm ci
|
|
436
|
+
npm run lint
|
|
437
|
+
npm test
|
|
438
|
+
npm run build
|
|
439
|
+
npm run typecheck
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Security sanity checks:
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
grep -R "fromSubscription" src
|
|
446
|
+
grep -R "subscriptionKey" src
|
|
447
|
+
grep -R "Ocp-Apim-Subscription-Key" src
|
|
448
|
+
grep -R "@creativeorange/azure-text-to-speech/dist" .
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
These should return no matches in package source or active examples.
|
|
452
|
+
|
|
453
|
+
## License
|
|
454
|
+
|
|
455
|
+
MIT
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { TranslationRecognizer } from 'microsoft-cognitiveservices-speech-sdk';
|
|
2
|
+
import { SpeechAuthenticationOptions } from './authentication';
|
|
3
|
+
export type SpeechToTextOptions = SpeechAuthenticationOptions & {
|
|
4
|
+
sourceLanguage: string;
|
|
5
|
+
targetLanguage?: string;
|
|
6
|
+
};
|
|
7
|
+
export declare class SpeechToText {
|
|
8
|
+
region: string;
|
|
9
|
+
sourceLanguage: string;
|
|
10
|
+
targetLanguage: string;
|
|
11
|
+
recognizer: TranslationRecognizer | undefined;
|
|
12
|
+
private readonly authorizationManager;
|
|
13
|
+
private readonly authorizationRefreshIntervalMs;
|
|
14
|
+
private authorizationRefreshTimer?;
|
|
15
|
+
private stopPromise?;
|
|
16
|
+
constructor(options: SpeechToTextOptions);
|
|
17
|
+
start(): Promise<void>;
|
|
18
|
+
registerBindings(node: any): Promise<void>;
|
|
19
|
+
private createSpeechTranslationConfig;
|
|
20
|
+
handleStartModifier(node: any, attr: Attr): Promise<void>;
|
|
21
|
+
handleStopModifier(node: any, _attr: Attr): Promise<void>;
|
|
22
|
+
stop(): Promise<void>;
|
|
23
|
+
private scheduleAuthorizationRefresh;
|
|
24
|
+
private clearAuthorizationRefreshTimer;
|
|
25
|
+
private stopRecognizer;
|
|
26
|
+
private dispatchError;
|
|
27
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { SpeechAuthenticationOptions } from './authentication';
|
|
2
|
+
export type TextToSpeechOptions = SpeechAuthenticationOptions & {
|
|
3
|
+
voice: string;
|
|
4
|
+
rate?: number;
|
|
5
|
+
pitch?: number;
|
|
6
|
+
url?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare class TextToSpeech {
|
|
9
|
+
region: string;
|
|
10
|
+
voice: string;
|
|
11
|
+
rate: number;
|
|
12
|
+
pitch: number;
|
|
13
|
+
textToRead: string;
|
|
14
|
+
wordBoundryList: any[];
|
|
15
|
+
clickedNode: any;
|
|
16
|
+
highlightDiv: any;
|
|
17
|
+
speechConfig: any;
|
|
18
|
+
audioConfig: any;
|
|
19
|
+
player: any;
|
|
20
|
+
synthesizer: any;
|
|
21
|
+
previousWordBoundary: any;
|
|
22
|
+
interval: any;
|
|
23
|
+
wordEncounters: number[];
|
|
24
|
+
originalHighlightDivInnerHTML: string;
|
|
25
|
+
currentWord: string;
|
|
26
|
+
currentOffset: number;
|
|
27
|
+
wordBoundaryOffset: number;
|
|
28
|
+
playbackTextOffsetBase: number | undefined;
|
|
29
|
+
prevTextOffset: number;
|
|
30
|
+
url: string;
|
|
31
|
+
prefetchedAudio: Map<string, any>;
|
|
32
|
+
prefetchPromises: Map<string, Promise<any>>;
|
|
33
|
+
activePrefetchedAudioUrl: string;
|
|
34
|
+
playbackSegments: any[];
|
|
35
|
+
private readonly authorizationManager;
|
|
36
|
+
private stopPromise?;
|
|
37
|
+
constructor(options: TextToSpeechOptions);
|
|
38
|
+
start(): Promise<void>;
|
|
39
|
+
setVoice(voice: string): this;
|
|
40
|
+
setRate(rate: number): this;
|
|
41
|
+
setPitch(pitch: number): this;
|
|
42
|
+
registerBindings(node: any): Promise<void>;
|
|
43
|
+
handleIdModifier(node: any, attr: Attr): Promise<void>;
|
|
44
|
+
handleAjaxModifier(node: any, attr: Attr): Promise<void>;
|
|
45
|
+
handleDefault(node: any, attr: Attr): Promise<void>;
|
|
46
|
+
handleWithoutClick(node: any, attr: Attr): Promise<void>;
|
|
47
|
+
handleStopModifier(node: any, _attr: Attr): Promise<void>;
|
|
48
|
+
handlePauseModifier(node: any, _attr: Attr): Promise<void>;
|
|
49
|
+
handleResumeModifier(node: any, _attr: Attr): Promise<void>;
|
|
50
|
+
stopPlayer(): Promise<void>;
|
|
51
|
+
private performStopPlayer;
|
|
52
|
+
private createSpeechConfig;
|
|
53
|
+
startSynthesizer(_node: any, _attr: Attr): Promise<void>;
|
|
54
|
+
collectPlaybackChain(node: any): {
|
|
55
|
+
node: any;
|
|
56
|
+
text: any;
|
|
57
|
+
start: number;
|
|
58
|
+
end: any;
|
|
59
|
+
highlightDiv: any;
|
|
60
|
+
originalHighlightDivInnerHTML: any;
|
|
61
|
+
}[];
|
|
62
|
+
preparePlaybackChain(chain: any[]): void;
|
|
63
|
+
getHighlightDivForNode(node: any): any;
|
|
64
|
+
resetPlaybackSegments(): void;
|
|
65
|
+
updateChainedHighlight(wordBoundary: any): void;
|
|
66
|
+
getNodeText(node: any, attr?: Attr | null): any;
|
|
67
|
+
getPrefetchKey(node: any, text: string): string;
|
|
68
|
+
clearPrefetchedAudio(): void;
|
|
69
|
+
prefetchNextNode(node: any): Promise<void>;
|
|
70
|
+
playPrefetchedNode(node: any): Promise<boolean>;
|
|
71
|
+
clearInterval(): Promise<void>;
|
|
72
|
+
createInterval(): Promise<void>;
|
|
73
|
+
getPosition(string: string, subString: string, textOffset: number): number;
|
|
74
|
+
buildSSML(text: string): string;
|
|
75
|
+
convertHtmlEntities(input: string): string;
|
|
76
|
+
private closeSynthesizer;
|
|
77
|
+
private closeResource;
|
|
78
|
+
private dispatchError;
|
|
79
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type SpeechAuthorization = {
|
|
2
|
+
token: string;
|
|
3
|
+
region: string;
|
|
4
|
+
};
|
|
5
|
+
export type SpeechAuthorizationProvider = () => Promise<SpeechAuthorization>;
|
|
6
|
+
export type SpeechTokenRequestOptions = {
|
|
7
|
+
headers?: Record<string, string>;
|
|
8
|
+
credentials?: RequestCredentials;
|
|
9
|
+
cache?: RequestCache;
|
|
10
|
+
};
|
|
11
|
+
export type SpeechAuthenticationOptions = {
|
|
12
|
+
tokenEndpoint?: string;
|
|
13
|
+
getAuthorizationToken?: SpeechAuthorizationProvider;
|
|
14
|
+
tokenRequestOptions?: SpeechTokenRequestOptions;
|
|
15
|
+
tokenLifetimeMs?: number;
|
|
16
|
+
};
|
|
17
|
+
export type SpeechSafeError = {
|
|
18
|
+
message: string;
|
|
19
|
+
code?: string;
|
|
20
|
+
};
|
|
21
|
+
export declare const DEFAULT_TOKEN_LIFETIME_MS: number;
|
|
22
|
+
export declare function createSpeechAuthorizationProvider(options: SpeechAuthenticationOptions): SpeechAuthorizationProvider;
|
|
23
|
+
export declare function validateSpeechAuthorization(value: unknown): SpeechAuthorization;
|
|
24
|
+
export declare function createSafeError(message: string, code?: string): Error & SpeechSafeError;
|
|
25
|
+
export declare function toSafeErrorDetail(error: unknown): SpeechSafeError;
|
|
26
|
+
export declare function sanitizeErrorText(message: string): string;
|
|
27
|
+
export declare function isLikelyAuthorizationError(error: unknown): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Fetches, validates, caches and refreshes temporary Azure Speech tokens.
|
|
30
|
+
* Never logs authorization tokens.
|
|
31
|
+
*/
|
|
32
|
+
export declare class SpeechAuthorizationManager {
|
|
33
|
+
private authorization?;
|
|
34
|
+
private authorizationExpiresAt;
|
|
35
|
+
private authorizationRequest?;
|
|
36
|
+
private readonly provider;
|
|
37
|
+
private readonly tokenLifetimeMs;
|
|
38
|
+
constructor(options: SpeechAuthenticationOptions);
|
|
39
|
+
getTokenLifetimeMs(): number;
|
|
40
|
+
getAuthorization(): Promise<SpeechAuthorization>;
|
|
41
|
+
refreshAuthorization(): Promise<SpeechAuthorization>;
|
|
42
|
+
clearAuthorization(): void;
|
|
43
|
+
private fetchAuthorization;
|
|
44
|
+
}
|