@creativeorange/azure-text-to-speech 3.0.0 → 3.0.2
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 +99 -140
- package/dist/SpeechToText.d.ts +27 -0
- package/dist/TextToSpeech.d.ts +87 -0
- package/dist/authentication.d.ts +44 -0
- package/dist/co-azure-tts.es.js +671 -401
- package/dist/co-azure-tts.umd.js +7 -11
- package/dist/main.d.ts +4 -0
- package/package.json +11 -7
- package/src/SpeechToText.ts +0 -172
- package/src/TextToSpeech.ts +0 -846
- package/src/authentication.ts +0 -228
- package/src/main.ts +0 -9
package/README.md
CHANGED
|
@@ -7,16 +7,9 @@ Current major version: **3.0.0**
|
|
|
7
7
|
|
|
8
8
|
## Why version 3 exists
|
|
9
9
|
|
|
10
|
-
In version 2, constructors accepted a permanent Azure subscription key and used
|
|
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
11
|
|
|
12
|
-
|
|
13
|
-
SpeechConfig.fromSubscription(key, region);
|
|
14
|
-
SpeechTranslationConfig.fromSubscription(key, region);
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
That forced the real API key into browser bundles, DevTools, and memory. Anyone who can open the page can extract the key and consume your Azure Speech quota.
|
|
18
|
-
|
|
19
|
-
Version 3 removes that pattern completely. The permanent key stays on your server. The browser only receives short-lived Azure authorization tokens.
|
|
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.
|
|
20
13
|
|
|
21
14
|
## Architecture
|
|
22
15
|
|
|
@@ -24,50 +17,63 @@ Version 3 removes that pattern completely. The permanent key stays on your serve
|
|
|
24
17
|
Browser
|
|
25
18
|
→ your backend token endpoint
|
|
26
19
|
→ Azure issueToken endpoint
|
|
27
|
-
← temporary Azure token
|
|
20
|
+
← temporary Azure token + region
|
|
28
21
|
→ Azure Speech SDK (fromAuthorizationToken)
|
|
29
22
|
```
|
|
30
23
|
|
|
31
24
|
Supported authentication options:
|
|
32
25
|
|
|
33
26
|
1. `tokenEndpoint` — simple GET endpoint (Craft, classic sites)
|
|
34
|
-
2. `getAuthorizationToken` — custom async provider
|
|
27
|
+
2. `getAuthorizationToken` — custom async provider
|
|
35
28
|
|
|
36
29
|
There is **no** option to pass a permanent subscription key.
|
|
37
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
|
+
|
|
38
33
|
## Install
|
|
39
34
|
|
|
40
35
|
```bash
|
|
41
36
|
npm install @creativeorange/azure-text-to-speech
|
|
42
37
|
```
|
|
43
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
|
+
|
|
44
53
|
## Public API
|
|
45
54
|
|
|
46
55
|
### Text to Speech with `tokenEndpoint`
|
|
47
56
|
|
|
48
57
|
```ts
|
|
49
|
-
import {TextToSpeech} from '@creativeorange/azure-text-to-speech';
|
|
50
|
-
|
|
51
58
|
const textToSpeech = new TextToSpeech({
|
|
52
59
|
tokenEndpoint: '/actions/azure-speech/token',
|
|
53
60
|
voice: 'nl-NL-FennaNeural',
|
|
54
|
-
region: 'westeurope',
|
|
55
61
|
});
|
|
56
62
|
|
|
57
63
|
await textToSpeech.start();
|
|
58
64
|
```
|
|
59
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
|
+
|
|
60
68
|
### Text to Speech with a custom provider
|
|
61
69
|
|
|
62
70
|
```ts
|
|
63
|
-
import {TextToSpeech} from '@creativeorange/azure-text-to-speech';
|
|
64
|
-
|
|
65
71
|
const textToSpeech = new TextToSpeech({
|
|
66
72
|
voice: 'nl-NL-FennaNeural',
|
|
67
|
-
region: 'westeurope',
|
|
68
73
|
getAuthorizationToken: async () => {
|
|
69
74
|
const response = await fetch('/api/azure-speech/token', {
|
|
70
75
|
credentials: 'same-origin',
|
|
76
|
+
cache: 'no-store',
|
|
71
77
|
headers: {
|
|
72
78
|
Accept: 'application/json',
|
|
73
79
|
},
|
|
@@ -87,11 +93,8 @@ await textToSpeech.start();
|
|
|
87
93
|
### Speech to Text
|
|
88
94
|
|
|
89
95
|
```ts
|
|
90
|
-
import {SpeechToText} from '@creativeorange/azure-text-to-speech';
|
|
91
|
-
|
|
92
96
|
const speechToText = new SpeechToText({
|
|
93
97
|
tokenEndpoint: '/actions/azure-speech/token',
|
|
94
|
-
region: 'westeurope',
|
|
95
98
|
sourceLanguage: 'nl-NL',
|
|
96
99
|
targetLanguage: 'nl',
|
|
97
100
|
});
|
|
@@ -99,6 +102,8 @@ const speechToText = new SpeechToText({
|
|
|
99
102
|
await speechToText.start();
|
|
100
103
|
```
|
|
101
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
|
+
|
|
102
107
|
### Token endpoint response
|
|
103
108
|
|
|
104
109
|
```json
|
|
@@ -122,12 +127,12 @@ type SpeechAuthenticationOptions = {
|
|
|
122
127
|
tokenRequestOptions?: {
|
|
123
128
|
headers?: Record<string, string>;
|
|
124
129
|
credentials?: RequestCredentials;
|
|
130
|
+
cache?: RequestCache; // default: 'no-store'
|
|
125
131
|
};
|
|
126
132
|
tokenLifetimeMs?: number; // default: 8 minutes
|
|
127
133
|
};
|
|
128
134
|
|
|
129
135
|
type TextToSpeechOptions = SpeechAuthenticationOptions & {
|
|
130
|
-
region?: string;
|
|
131
136
|
voice: string;
|
|
132
137
|
rate?: number;
|
|
133
138
|
pitch?: number;
|
|
@@ -135,27 +140,20 @@ type TextToSpeechOptions = SpeechAuthenticationOptions & {
|
|
|
135
140
|
};
|
|
136
141
|
|
|
137
142
|
type SpeechToTextOptions = SpeechAuthenticationOptions & {
|
|
138
|
-
region?: string;
|
|
139
143
|
sourceLanguage: string;
|
|
140
144
|
targetLanguage?: string;
|
|
141
145
|
};
|
|
142
146
|
```
|
|
143
147
|
|
|
144
|
-
Provide **exactly one** of `tokenEndpoint` or `getAuthorizationToken
|
|
145
|
-
|
|
146
|
-
- both → `Provide either tokenEndpoint or getAuthorizationToken, not both.`
|
|
147
|
-
- neither → `A tokenEndpoint or getAuthorizationToken provider is required.`
|
|
148
|
+
Provide **exactly one** of `tokenEndpoint` or `getAuthorizationToken`.
|
|
148
149
|
|
|
149
|
-
### Token caching
|
|
150
|
+
### Token caching and transport
|
|
150
151
|
|
|
151
|
-
Tokens are cached in memory
|
|
152
|
-
|
|
153
|
-
-
|
|
154
|
-
-
|
|
155
|
-
-
|
|
156
|
-
- parallel callers share one in-flight request
|
|
157
|
-
- invalid/empty token or region values throw clear errors
|
|
158
|
-
- tokens are never logged and never included in error events
|
|
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.
|
|
159
157
|
|
|
160
158
|
### Browser events
|
|
161
159
|
|
|
@@ -174,12 +172,8 @@ STT:
|
|
|
174
172
|
- `COAzureSTTStoppedRecording`
|
|
175
173
|
- `COAzureSTTError` → `{ detail: { error: { message, code? } } }`
|
|
176
174
|
|
|
177
|
-
Error payloads never contain tokens or authorization headers.
|
|
178
|
-
|
|
179
175
|
## Migrating from v2 to v3
|
|
180
176
|
|
|
181
|
-
This is a **breaking** change. The old positional constructor is removed on purpose.
|
|
182
|
-
|
|
183
177
|
### Old unsafe usage
|
|
184
178
|
|
|
185
179
|
```ts
|
|
@@ -192,22 +186,13 @@ const textToSpeech = new TextToSpeech(
|
|
|
192
186
|
);
|
|
193
187
|
```
|
|
194
188
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
```twig
|
|
198
|
-
<script>
|
|
199
|
-
window.azureSpeechKey = '{{ craft.app.config.general.azureSpeechKey }}';
|
|
200
|
-
</script>
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
Remove these patterns.
|
|
189
|
+
Remove Twig/Vite/window exports of the subscription key.
|
|
204
190
|
|
|
205
191
|
### New safe usage
|
|
206
192
|
|
|
207
193
|
```ts
|
|
208
194
|
const textToSpeech = new TextToSpeech({
|
|
209
195
|
tokenEndpoint: '/actions/azure-speech/token',
|
|
210
|
-
region: 'westeurope',
|
|
211
196
|
voice: 'nl-NL-FennaNeural',
|
|
212
197
|
rate: 0,
|
|
213
198
|
pitch: 0,
|
|
@@ -218,13 +203,13 @@ const textToSpeech = new TextToSpeech({
|
|
|
218
203
|
|
|
219
204
|
1. Update the package to `3.x`.
|
|
220
205
|
2. Store `AZURE_SPEECH_KEY` and `AZURE_SPEECH_REGION` server-side only.
|
|
221
|
-
3. Add a backend token endpoint
|
|
206
|
+
3. Add a backend token endpoint.
|
|
222
207
|
4. Replace the old constructor with the options object.
|
|
223
|
-
5. Remove every
|
|
208
|
+
5. Remove every frontend variable that contained the subscription key.
|
|
224
209
|
6. Rebuild the frontend.
|
|
225
|
-
7. Confirm in DevTools that the permanent key no longer appears
|
|
226
|
-
8. Test TTS, STT, highlighting, pause/resume, chained playback
|
|
227
|
-
9. **Rotate the previously exposed Azure subscription key.**
|
|
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.**
|
|
228
213
|
|
|
229
214
|
## Craft CMS integration
|
|
230
215
|
|
|
@@ -254,13 +239,29 @@ return [
|
|
|
254
239
|
];
|
|
255
240
|
```
|
|
256
241
|
|
|
257
|
-
|
|
242
|
+
Craft action routing uses:
|
|
243
|
+
|
|
244
|
+
- module ID: `azure-speech`
|
|
245
|
+
- controller ID: `token` (`TokenController`)
|
|
246
|
+
- action: `actionIndex`
|
|
247
|
+
|
|
248
|
+
Resulting URL:
|
|
258
249
|
|
|
259
250
|
```text
|
|
260
251
|
/actions/azure-speech/token
|
|
261
252
|
```
|
|
262
253
|
|
|
263
|
-
|
|
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
|
|
264
265
|
|
|
265
266
|
```php
|
|
266
267
|
<?php
|
|
@@ -273,7 +274,8 @@ use yii\web\Response;
|
|
|
273
274
|
|
|
274
275
|
class TokenController extends Controller
|
|
275
276
|
{
|
|
276
|
-
|
|
277
|
+
// Craft 4 and Craft 5
|
|
278
|
+
protected array|bool|int $allowAnonymous = true;
|
|
277
279
|
|
|
278
280
|
public function actionIndex(): Response
|
|
279
281
|
{
|
|
@@ -289,7 +291,7 @@ class TokenController extends Controller
|
|
|
289
291
|
$client = Craft::createGuzzleClient();
|
|
290
292
|
|
|
291
293
|
try {
|
|
292
|
-
$
|
|
294
|
+
$azureResponse = $client->post(
|
|
293
295
|
"https://{$region}.api.cognitive.microsoft.com/sts/v1.0/issueToken",
|
|
294
296
|
[
|
|
295
297
|
'headers' => [
|
|
@@ -309,44 +311,50 @@ class TokenController extends Controller
|
|
|
309
311
|
);
|
|
310
312
|
}
|
|
311
313
|
|
|
312
|
-
|
|
313
|
-
'token' => (string) $
|
|
314
|
+
$craftResponse = $this->asJson([
|
|
315
|
+
'token' => (string) $azureResponse->getBody(),
|
|
314
316
|
'region' => $region,
|
|
315
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;
|
|
316
329
|
}
|
|
317
330
|
}
|
|
318
331
|
```
|
|
319
332
|
|
|
320
|
-
|
|
333
|
+
### Token controller property for Craft 3 / older PHP
|
|
321
334
|
|
|
322
335
|
```php
|
|
336
|
+
// Craft 3 or older PHP versions without union property types
|
|
323
337
|
protected $allowAnonymous = true;
|
|
324
338
|
```
|
|
325
339
|
|
|
326
|
-
Use the property form that matches your Craft and PHP version.
|
|
340
|
+
Use the property form that matches your Craft and PHP version. Do not log tokens or exception details to the browser.
|
|
327
341
|
|
|
328
342
|
### Frontend initialization
|
|
329
343
|
|
|
330
344
|
```ts
|
|
331
|
-
import {TextToSpeech} from '@creativeorange/azure-text-to-speech';
|
|
345
|
+
import {TextToSpeech, SpeechToText} from '@creativeorange/azure-text-to-speech';
|
|
332
346
|
|
|
333
347
|
const textToSpeech = new TextToSpeech({
|
|
334
348
|
tokenEndpoint: '/actions/azure-speech/token',
|
|
335
|
-
region: 'westeurope',
|
|
336
349
|
voice: 'nl-NL-FennaNeural',
|
|
337
350
|
});
|
|
338
351
|
|
|
339
352
|
textToSpeech.start().catch((error) => {
|
|
340
353
|
console.error('Could not initialize text to speech.', error);
|
|
341
354
|
});
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
```ts
|
|
345
|
-
import {SpeechToText} from '@creativeorange/azure-text-to-speech';
|
|
346
355
|
|
|
347
356
|
const speechToText = new SpeechToText({
|
|
348
357
|
tokenEndpoint: '/actions/azure-speech/token',
|
|
349
|
-
region: 'westeurope',
|
|
350
358
|
sourceLanguage: 'nl-NL',
|
|
351
359
|
targetLanguage: 'nl',
|
|
352
360
|
});
|
|
@@ -366,12 +374,13 @@ Recommended controls:
|
|
|
366
374
|
- Cloudflare rate limiting when available
|
|
367
375
|
- Authentication when TTS/STT is not meant for anonymous visitors
|
|
368
376
|
- HTTPS only
|
|
369
|
-
- Short server-side timeouts
|
|
377
|
+
- Short server-side timeouts
|
|
370
378
|
- Azure budget and usage alerts
|
|
371
379
|
- Azure resource networking restrictions when infrastructure allows it
|
|
372
380
|
- Never put the permanent key in responses or logs
|
|
381
|
+
- Return `Cache-Control: private, no-store, max-age=0`
|
|
373
382
|
|
|
374
|
-
**CORS alone is not security.** Endpoints can be called outside a browser
|
|
383
|
+
**CORS alone is not security.** Endpoints can be called outside a browser.
|
|
375
384
|
|
|
376
385
|
## Laravel token endpoint
|
|
377
386
|
|
|
@@ -382,7 +391,7 @@ use Illuminate\Support\Facades\Route;
|
|
|
382
391
|
Route::get('/api/azure-speech/token', function () {
|
|
383
392
|
$region = config('services.azure_speech.region');
|
|
384
393
|
|
|
385
|
-
$
|
|
394
|
+
$azureResponse = Http::timeout(10)
|
|
386
395
|
->withHeaders([
|
|
387
396
|
'Ocp-Apim-Subscription-Key' =>
|
|
388
397
|
config('services.azure_speech.key'),
|
|
@@ -391,53 +400,23 @@ Route::get('/api/azure-speech/token', function () {
|
|
|
391
400
|
"https://{$region}.api.cognitive.microsoft.com/sts/v1.0/issueToken"
|
|
392
401
|
);
|
|
393
402
|
|
|
394
|
-
abort_unless($
|
|
403
|
+
abort_unless($azureResponse->successful(), 502);
|
|
395
404
|
|
|
396
|
-
return response()
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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');
|
|
400
415
|
})->middleware('throttle:20,1');
|
|
401
416
|
```
|
|
402
417
|
|
|
403
418
|
Put this route behind authentication whenever the feature is not public.
|
|
404
419
|
|
|
405
|
-
Example `config/services.php` entries:
|
|
406
|
-
|
|
407
|
-
```php
|
|
408
|
-
'azure_speech' => [
|
|
409
|
-
'key' => env('AZURE_SPEECH_KEY'),
|
|
410
|
-
'region' => env('AZURE_SPEECH_REGION', 'westeurope'),
|
|
411
|
-
],
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
Frontend:
|
|
415
|
-
|
|
416
|
-
```ts
|
|
417
|
-
const textToSpeech = new TextToSpeech({
|
|
418
|
-
tokenEndpoint: '/api/azure-speech/token',
|
|
419
|
-
region: 'westeurope',
|
|
420
|
-
voice: 'nl-NL-FennaNeural',
|
|
421
|
-
});
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
Or with CSRF / custom headers:
|
|
425
|
-
|
|
426
|
-
```ts
|
|
427
|
-
const textToSpeech = new TextToSpeech({
|
|
428
|
-
voice: 'nl-NL-FennaNeural',
|
|
429
|
-
region: 'westeurope',
|
|
430
|
-
tokenEndpoint: '/api/azure-speech/token',
|
|
431
|
-
tokenRequestOptions: {
|
|
432
|
-
headers: {
|
|
433
|
-
'X-CSRF-TOKEN': document
|
|
434
|
-
.querySelector('meta[name="csrf-token"]')
|
|
435
|
-
?.getAttribute('content') ?? '',
|
|
436
|
-
},
|
|
437
|
-
},
|
|
438
|
-
});
|
|
439
|
-
```
|
|
440
|
-
|
|
441
420
|
## Existing browser features
|
|
442
421
|
|
|
443
422
|
Version 3 keeps the existing client behaviour from the development branch:
|
|
@@ -450,36 +429,14 @@ Version 3 keeps the existing client behaviour from the development branch:
|
|
|
450
429
|
- voice / rate / pitch setters
|
|
451
430
|
- existing custom browser events
|
|
452
431
|
|
|
453
|
-
## HTML attributes (unchanged)
|
|
454
|
-
|
|
455
|
-
TTS examples:
|
|
456
|
-
|
|
457
|
-
```html
|
|
458
|
-
<button co-tts="Hello world">Play</button>
|
|
459
|
-
<button co-tts.pause>Pause</button>
|
|
460
|
-
<button co-tts.resume>Resume</button>
|
|
461
|
-
<button co-tts.stop>Stop</button>
|
|
462
|
-
|
|
463
|
-
<div id="article" co-tts.highlight co-tts.text="Read this text" co-tts.next="next-block">
|
|
464
|
-
Read this text
|
|
465
|
-
</div>
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
STT examples:
|
|
469
|
-
|
|
470
|
-
```html
|
|
471
|
-
<button co-stt.start="transcript">Start</button>
|
|
472
|
-
<button co-stt.stop>Stop</button>
|
|
473
|
-
<div id="transcript"></div>
|
|
474
|
-
```
|
|
475
|
-
|
|
476
432
|
## Development
|
|
477
433
|
|
|
478
434
|
```bash
|
|
479
|
-
npm
|
|
480
|
-
npm run build
|
|
481
|
-
npm test
|
|
435
|
+
npm ci
|
|
482
436
|
npm run lint
|
|
437
|
+
npm test
|
|
438
|
+
npm run build
|
|
439
|
+
npm run typecheck
|
|
483
440
|
```
|
|
484
441
|
|
|
485
442
|
Security sanity checks:
|
|
@@ -487,9 +444,11 @@ Security sanity checks:
|
|
|
487
444
|
```bash
|
|
488
445
|
grep -R "fromSubscription" src
|
|
489
446
|
grep -R "subscriptionKey" src
|
|
447
|
+
grep -R "Ocp-Apim-Subscription-Key" src
|
|
448
|
+
grep -R "@creativeorange/azure-text-to-speech/dist" .
|
|
490
449
|
```
|
|
491
450
|
|
|
492
|
-
|
|
451
|
+
These should return no matches in package source or active examples.
|
|
493
452
|
|
|
494
453
|
## License
|
|
495
454
|
|
|
@@ -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,87 @@
|
|
|
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
|
+
originalHighlightDivInnerHTML: string;
|
|
23
|
+
currentWord: string;
|
|
24
|
+
currentOffset: number;
|
|
25
|
+
wordBoundaryOffset: number;
|
|
26
|
+
playbackTextOffsetBase: number | undefined;
|
|
27
|
+
prevTextOffset: number;
|
|
28
|
+
url: string;
|
|
29
|
+
prefetchedAudio: Map<string, any>;
|
|
30
|
+
prefetchPromises: Map<string, Promise<any>>;
|
|
31
|
+
activePrefetchedAudioUrl: string;
|
|
32
|
+
playbackSegments: any[];
|
|
33
|
+
private readonly authorizationManager;
|
|
34
|
+
private stopPromise?;
|
|
35
|
+
private playbackSessionId;
|
|
36
|
+
private highlightInterval?;
|
|
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: HTMLElement | undefined;
|
|
60
|
+
originalHighlightDivInnerHTML: string;
|
|
61
|
+
}[];
|
|
62
|
+
preparePlaybackChain(chain: any[]): void;
|
|
63
|
+
getHighlightDivForNode(node: any): HTMLElement | undefined;
|
|
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
|
+
private createPlaybackSession;
|
|
72
|
+
private isActivePlaybackSession;
|
|
73
|
+
private invalidatePlaybackSession;
|
|
74
|
+
private stopHighlightingInterval;
|
|
75
|
+
private startHighlightingInterval;
|
|
76
|
+
private updateHighlightForCurrentTime;
|
|
77
|
+
private highlightTextRange;
|
|
78
|
+
private setHighlightTargetFromNode;
|
|
79
|
+
private shouldHighlight;
|
|
80
|
+
private resetHighlightState;
|
|
81
|
+
private ensurePlaybackTextOffsetBase;
|
|
82
|
+
private resetCurrentHighlight;
|
|
83
|
+
buildSSML(text: string): string;
|
|
84
|
+
convertHtmlEntities(input: string): string;
|
|
85
|
+
private closeResource;
|
|
86
|
+
private dispatchError;
|
|
87
|
+
}
|
|
@@ -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
|
+
}
|