@andresaya/edge-tts 1.7.1 → 1.8.0
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 +0 -94
- package/dist/browser/edge-tts.esm.js +55 -117
- package/dist/browser/edge-tts.esm.js.map +2 -2
- package/dist/browser/edge-tts.esm.min.js +10 -10
- package/dist/browser/edge-tts.esm.min.js.map +3 -3
- package/dist/browser/edge-tts.umd.js +55 -117
- package/dist/browser/edge-tts.umd.js.map +2 -2
- package/dist/browser/edge-tts.umd.min.js +8 -8
- package/dist/browser/edge-tts.umd.min.js.map +3 -3
- package/dist/cli/edge-tts.js +1 -1
- package/dist/commands/SynthesizeCommand.js +2 -4
- package/dist/config/constants.d.ts +0 -33
- package/dist/config/constants.js +13 -48
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -1
- package/dist/services/EdgeTTS.d.ts +1 -2
- package/dist/services/EdgeTTS.js +79 -77
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
- **Word Boundaries Metadata**: Get word boundary information with precise timestamps.
|
|
17
17
|
- **Command-Line Interface**: Use a simple CLI for easy access to functionality.
|
|
18
18
|
- **Easy Integration**: Modular structure allows for easy inclusion in existing projects.
|
|
19
|
-
- **SSML Custom**: 🥳🥳 Edge TTS accepts raw SSML with all characteristics of Azure AI Speech.
|
|
20
19
|
|
|
21
20
|
## Installation
|
|
22
21
|
|
|
@@ -59,7 +58,6 @@ interface SynthesisOptions {
|
|
|
59
58
|
pitch?: string | number; // e.g., '+20Hz' or 20
|
|
60
59
|
rate?: string | number; // e.g., '50%' or 50
|
|
61
60
|
volume?: string | number; // e.g., '90%' or 90
|
|
62
|
-
inputType?: 'auto' | 'ssml' | 'text'; // Default: 'auto'
|
|
63
61
|
outputFormat?: string; // e.g., Constants.OUTPUT_FORMAT.AUDIO_24KHZ_96KBITRATE_MONO_MP3
|
|
64
62
|
}
|
|
65
63
|
|
|
@@ -107,11 +105,6 @@ To synthesize speech from text:
|
|
|
107
105
|
edge-tts synthesize -t "Hello, world!" -o hello_world_audio
|
|
108
106
|
```
|
|
109
107
|
|
|
110
|
-
From file in format SSML:
|
|
111
|
-
```bash
|
|
112
|
-
edge-tts synthesize -f ssml.txt --ssml -o salida
|
|
113
|
-
```
|
|
114
|
-
|
|
115
108
|
To list available voices:
|
|
116
109
|
```bash
|
|
117
110
|
edge-tts voice-list
|
|
@@ -156,49 +149,6 @@ const maleVoices = await tts.getVoicesByGender('Male');
|
|
|
156
149
|
|
|
157
150
|
### Text Synthesis
|
|
158
151
|
|
|
159
|
-
#### Custom SSML (Advanced)
|
|
160
|
-
Edge TTS accepts raw SSML so you can control prosody, styles, pauses, pronunciations, and more. You can pass SSML from code or the CLI. By default the library auto-detects if your input is SSML; you can also force the mode.
|
|
161
|
-
|
|
162
|
-
More information
|
|
163
|
-
[Azure AI Speech](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/speech-synthesis-markup-structure)
|
|
164
|
-
|
|
165
|
-
### SSML-builder
|
|
166
|
-
[@andresaya/ssml-builder](https://www.npmjs.com/package/@andresaya/ssml-builder) A powerful, type-safe TypeScript library for building Speech Synthesis Markup Language (SSML) documents. Create expressive text-to-speech applications with Azure Speech Service and other SSML-compliant engines.
|
|
167
|
-
|
|
168
|
-
#### What the library does for you
|
|
169
|
-
|
|
170
|
-
- Auto-detect / force mode: options.inputType may be 'auto' | 'ssml' | 'text' (default: auto).
|
|
171
|
-
- Validation: Throws helpful errors if SSML is malformed (e.g., missing <speak>, <voice>, or the synthesis namespace).
|
|
172
|
-
- Voice injection: If your SSML lacks <voice>, it injects one with the voice you passed.
|
|
173
|
-
- Text wrapping: If you pass plain text (or inputType: 'text'), it wraps it in a valid SSML envelope using your rate, pitch, and volume.
|
|
174
|
-
|
|
175
|
-
```js
|
|
176
|
-
import { EdgeTTS } from '@andresaya/edge-tts';
|
|
177
|
-
|
|
178
|
-
const tts = new EdgeTTS();
|
|
179
|
-
|
|
180
|
-
const ssml = `
|
|
181
|
-
<speak version="1.0"
|
|
182
|
-
xmlns="http://www.w3.org/2001/10/synthesis"
|
|
183
|
-
xmlns:mstts="https://www.w3.org/2001/mstts"
|
|
184
|
-
xml:lang="es-CO">
|
|
185
|
-
<voice name="es-CO-GonzaloNeural">
|
|
186
|
-
<mstts:express-as style="narration-professional">
|
|
187
|
-
<prosody rate="+5%" pitch="+10Hz" volume="+0%">
|
|
188
|
-
Hola, este es un ejemplo de <emphasis>SSML</emphasis>.
|
|
189
|
-
<break time="400ms" />
|
|
190
|
-
El número es <say-as interpret-as="cardinal">2025</say-as>.
|
|
191
|
-
La palabra se pronuncia
|
|
192
|
-
<phoneme alphabet="ipa" ph="ˈxola">hola</phoneme>.
|
|
193
|
-
</prosody>
|
|
194
|
-
</mstts:express-as>
|
|
195
|
-
</voice>
|
|
196
|
-
</speak>`.trim();
|
|
197
|
-
|
|
198
|
-
// Auto-detects SSML, or force it with inputType: 'ssml'
|
|
199
|
-
await tts.synthesize(ssml, 'es-CO-GonzaloNeural', { inputType: 'ssml' });
|
|
200
|
-
```
|
|
201
|
-
|
|
202
152
|
#### Basic Synthesis
|
|
203
153
|
```js
|
|
204
154
|
// Simple synthesis with default voice
|
|
@@ -472,45 +422,6 @@ This library can be used directly in web browsers via CDN or ES modules.
|
|
|
472
422
|
</script>
|
|
473
423
|
```
|
|
474
424
|
|
|
475
|
-
### Custom SSML Support in Browser
|
|
476
|
-
|
|
477
|
-
The browser version supports custom SSML (Speech Synthesis Markup Language) for advanced speech control:
|
|
478
|
-
|
|
479
|
-
```html
|
|
480
|
-
<script src="https://unpkg.com/@andresaya/edge-tts@latest/dist/browser/edge-tts.umd.min.js"></script>
|
|
481
|
-
|
|
482
|
-
<script>
|
|
483
|
-
const tts = new EdgeTTS();
|
|
484
|
-
|
|
485
|
-
// Custom SSML with emphasis, breaks, and expression
|
|
486
|
-
const ssml = `
|
|
487
|
-
<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis"
|
|
488
|
-
xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="en-US">
|
|
489
|
-
<voice name="en-US-AriaNeural">
|
|
490
|
-
<mstts:express-as style="cheerful">
|
|
491
|
-
<prosody rate="+10%" pitch="+5Hz">
|
|
492
|
-
Hello! This is <emphasis>custom SSML</emphasis>.
|
|
493
|
-
<break time="500ms"/>
|
|
494
|
-
You have full control over speech synthesis!
|
|
495
|
-
</prosody>
|
|
496
|
-
</mstts:express-as>
|
|
497
|
-
</voice>
|
|
498
|
-
</speak>
|
|
499
|
-
`;
|
|
500
|
-
|
|
501
|
-
// Synthesize with SSML
|
|
502
|
-
async function speakSSML() {
|
|
503
|
-
await tts.synthesize(ssml, '', { inputType: 'ssml' });
|
|
504
|
-
const audioData = tts.getAudioData();
|
|
505
|
-
|
|
506
|
-
const audioBlob = new Blob([audioData], { type: 'audio/mp3' });
|
|
507
|
-
const audioUrl = URL.createObjectURL(audioBlob);
|
|
508
|
-
const audio = new Audio(audioUrl);
|
|
509
|
-
audio.play();
|
|
510
|
-
}
|
|
511
|
-
</script>
|
|
512
|
-
```
|
|
513
|
-
|
|
514
425
|
### Streaming Support in Browser
|
|
515
426
|
|
|
516
427
|
```html
|
|
@@ -545,8 +456,6 @@ The browser version supports custom SSML (Speech Synthesis Markup Language) for
|
|
|
545
456
|
|
|
546
457
|
For a full working example with voice selection and synthesis, see [`examples/browser-standalone.html`](examples/browser-standalone.html).
|
|
547
458
|
|
|
548
|
-
For advanced SSML examples, see [`examples/browser-ssml-demo.html`](examples/browser-ssml-demo.html).
|
|
549
|
-
|
|
550
459
|
## Voice Options
|
|
551
460
|
|
|
552
461
|
### Synthesis Parameters
|
|
@@ -556,7 +465,6 @@ For advanced SSML examples, see [`examples/browser-ssml-demo.html`](examples/bro
|
|
|
556
465
|
| `pitch` | `string \| number` | `-100Hz` to `+100Hz` | Voice pitch adjustment |
|
|
557
466
|
| `rate` | `string \| number` | `-100%` to `+200%` | Speech rate adjustment |
|
|
558
467
|
| `volume` | `string \| number` | `-100%` to `+100%` | Volume adjustment |
|
|
559
|
-
| `inputType` | `string` | `ssml` or `auto` | Determines whether the input is SSML. default(auto) |
|
|
560
468
|
|
|
561
469
|
### Parameter Examples
|
|
562
470
|
```js
|
|
@@ -570,8 +478,6 @@ For advanced SSML examples, see [`examples/browser-ssml-demo.html`](examples/bro
|
|
|
570
478
|
// Mixed usage
|
|
571
479
|
{ pitch: 15, rate: '25%', volume: 85 }
|
|
572
480
|
|
|
573
|
-
// send SSML
|
|
574
|
-
{ pitch: 15, rate: '25%', volume: 85, inputType: 'ssml' }
|
|
575
481
|
```
|
|
576
482
|
|
|
577
483
|
## Error Handling
|
|
@@ -5,12 +5,12 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
5
5
|
// src/config/constants.ts
|
|
6
6
|
var Constants = {
|
|
7
7
|
TRUSTED_CLIENT_TOKEN: "6A5AA1D4EAFF4E9FB37E23D68491D6F4",
|
|
8
|
-
BASE_URL: "https://
|
|
9
|
-
WSS_URL: "wss://
|
|
10
|
-
VOICES_URL: "https://
|
|
11
|
-
CHROMIUM_FULL_VERSION: "
|
|
12
|
-
CHROMIUM_MAJOR_VERSION: "
|
|
13
|
-
VERSION_MS_GEC: "1-
|
|
8
|
+
BASE_URL: "https://speech.platform.bing.com/consumer/speech/synthesize/readaloud",
|
|
9
|
+
WSS_URL: "wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1",
|
|
10
|
+
VOICES_URL: "https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/voices/list",
|
|
11
|
+
CHROMIUM_FULL_VERSION: "143.0.3650.75",
|
|
12
|
+
CHROMIUM_MAJOR_VERSION: "143",
|
|
13
|
+
VERSION_MS_GEC: "1-143.0.3650",
|
|
14
14
|
token32() {
|
|
15
15
|
const bytes = new Uint8Array(16);
|
|
16
16
|
crypto.getRandomValues(bytes);
|
|
@@ -18,9 +18,9 @@ var Constants = {
|
|
|
18
18
|
},
|
|
19
19
|
getBaseHeaders() {
|
|
20
20
|
return {
|
|
21
|
-
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
21
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0",
|
|
22
22
|
"Accept-Encoding": "gzip, deflate, br, zstd",
|
|
23
|
-
"Accept-Language": "
|
|
23
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
24
24
|
"Cookie": "MUID=" + this.token32()
|
|
25
25
|
};
|
|
26
26
|
},
|
|
@@ -30,10 +30,10 @@ var Constants = {
|
|
|
30
30
|
"Origin": "chrome-extension://jdiccldimpdaibmpdkjnbmckianbfold",
|
|
31
31
|
"Sec-WebSocket-Protocol": "synthesize",
|
|
32
32
|
"Sec-WebSocket-Version": "13",
|
|
33
|
-
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
33
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.3650.75 Safari/537.36 Edg/143.0.3650.75"
|
|
34
34
|
},
|
|
35
35
|
VOICE_HEADERS: {
|
|
36
|
-
"Sec-CH-UA": '" Not;A Brand";v="99", "Microsoft Edge";v="
|
|
36
|
+
"Sec-CH-UA": '" Not;A Brand";v="99", "Microsoft Edge";v="143", "Chromium";v="143"',
|
|
37
37
|
"Sec-CH-UA-Mobile": "?0",
|
|
38
38
|
"Accept": "*/*",
|
|
39
39
|
"Sec-Fetch-Site": "none",
|
|
@@ -42,44 +42,9 @@ var Constants = {
|
|
|
42
42
|
},
|
|
43
43
|
// https://learn.microsoft.com/en-us/azure/ai-services/speech-service/rest-text-to-speech?tabs=nonstreaming
|
|
44
44
|
OUTPUT_FORMAT: {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
AUDIO_16KHZ_32KBITRATE_MONO_MP3: "audio-16khz-32kbitrate-mono-mp3",
|
|
49
|
-
AUDIO_16KHZ_64KBITRATE_MONO_MP3: "audio-16khz-64kbitrate-mono-mp3",
|
|
50
|
-
AUDIO_16KHZ_128KBITRATE_MONO_MP3: "audio-16khz-128kbitrate-mono-mp3",
|
|
51
|
-
AUDIO_24KHZ_16BIT_24KBPS_MONO_OPUS: "audio-24khz-16bit-24kbps-mono-opus",
|
|
52
|
-
AUDIO_24KHZ_16BIT_48KBPS_MONO_OPUS: "audio-24khz-16bit-48kbps-mono-opus",
|
|
53
|
-
AUDIO_24KHZ_48KBITRATE_MONO_MP3: "audio-24khz-48kbitrate-mono-mp3",
|
|
54
|
-
AUDIO_24KHZ_96KBITRATE_MONO_MP3: "audio-24khz-96kbitrate-mono-mp3",
|
|
55
|
-
AUDIO_24KHZ_160KBITRATE_MONO_MP3: "audio-24khz-160kbitrate-mono-mp3",
|
|
56
|
-
AUDIO_48KHZ_96KBITRATE_MONO_MP3: "audio-48khz-96kbitrate-mono-mp3",
|
|
57
|
-
AUDIO_48KHZ_192KBITRATE_MONO_MP3: "audio-48khz-192kbitrate-mono-mp3",
|
|
58
|
-
G722_16KHZ_64KBPS: "g722-16khz-64kbps",
|
|
59
|
-
OGG_16KHZ_16BIT_MONO_OPUS: "ogg-16khz-16bit-mono-opus",
|
|
60
|
-
OGG_24KHZ_16BIT_MONO_OPUS: "ogg-24khz-16bit-mono-opus",
|
|
61
|
-
OGG_48KHZ_16BIT_MONO_OPUS: "ogg-48khz-16bit-mono-opus",
|
|
62
|
-
RAW_8KHZ_8BIT_MONO_ALAW: "raw-8khz-8bit-mono-alaw",
|
|
63
|
-
RAW_8KHZ_8BIT_MONO_MULAW: "raw-8khz-8bit-mono-mulaw",
|
|
64
|
-
RAW_8KHZ_16BIT_MONO_PCM: "raw-8khz-16bit-mono-pcm",
|
|
65
|
-
RAW_16KHZ_16BIT_MONO_PCM: "raw-16khz-16bit-mono-pcm",
|
|
66
|
-
RAW_16KHZ_16BIT_MONO_TRUESILK: "raw-16khz-16bit-mono-truesilk",
|
|
67
|
-
RAW_22050HZ_16BIT_MONO_PCM: "raw-22050hz-16bit-mono-pcm",
|
|
68
|
-
RAW_24KHZ_16BIT_MONO_PCM: "raw-24khz-16bit-mono-pcm",
|
|
69
|
-
RAW_24KHZ_16BIT_MONO_TRUESILK: "raw-24khz-16bit-mono-truesilk",
|
|
70
|
-
RAW_44100HZ_16BIT_MONO_PCM: "raw-44100hz-16bit-mono-pcm",
|
|
71
|
-
RAW_48KHZ_16BIT_MONO_PCM: "raw-48khz-16bit-mono-pcm",
|
|
72
|
-
WEBM_16KHZ_16BIT_MONO_OPUS: "webm-16khz-16bit-mono-opus",
|
|
73
|
-
WEBM_24KHZ_16BIT_24KBPS_MONO_OPUS: "webm-24khz-16bit-24kbps-mono-opus",
|
|
74
|
-
WEBM_24KHZ_16BIT_MONO_OPUS: "webm-24khz-16bit-mono-opus",
|
|
75
|
-
// NonStreaming
|
|
76
|
-
RIFF_8KHZ_8BIT_MONO_ALAW: "riff-8khz-8bit-mono-alaw",
|
|
77
|
-
RIFF_8KHZ_8BIT_MONO_MULAW: "riff-8khz-8bit-mono-mulaw",
|
|
78
|
-
RIFF_8KHZ_16BIT_MONO_PCM: "riff-8khz-16bit-mono-pcm",
|
|
79
|
-
RIFF_22050HZ_16BIT_MONO_PCM: "riff-22050hz-16bit-mono-pcm",
|
|
80
|
-
RIFF_24KHZ_16BIT_MONO_PCM: "riff-24khz-16bit-mono-pcm",
|
|
81
|
-
RIFF_44100HZ_16BIT_MONO_PCM: "riff-44100hz-16bit-mono-pcm",
|
|
82
|
-
RIFF_48KHZ_16BIT_MONO_PCM: "riff-48khz-16bit-mono-pcm"
|
|
45
|
+
"AUDIO_24KHZ_48KBITRATE_MONO_MP3": "audio-24khz-48kbitrate-mono-mp3",
|
|
46
|
+
"AUDIO_24KHZ_96KBITRATE_MONO_MP3": "audio-24khz-96kbitrate-mono-mp3",
|
|
47
|
+
"WEBM_24KHZ_16BIT_MONO_OPUS": "webm-24khz-16bit-mono-opus"
|
|
83
48
|
}
|
|
84
49
|
};
|
|
85
50
|
|
|
@@ -89,19 +54,52 @@ var EdgeTTS = class {
|
|
|
89
54
|
__publicField(this, "audio_stream", []);
|
|
90
55
|
__publicField(this, "ws");
|
|
91
56
|
}
|
|
57
|
+
async normalizeVoices(data) {
|
|
58
|
+
const out = [];
|
|
59
|
+
for (const v of data || []) {
|
|
60
|
+
const short = v?.ShortName || "";
|
|
61
|
+
const locale = v?.Locale || "";
|
|
62
|
+
let base = short.replace(/^[a-z]{2}-[A-Z]{2}-/, "");
|
|
63
|
+
base = base.replace(/NeuralHD$/, "").replace(/Neural$/, "").trim();
|
|
64
|
+
const mix = `${v?.Name || ""} ${short}`;
|
|
65
|
+
const voiceType = v?.VoiceType || (/NeuralHD/i.test(mix) ? "NeuralHD" : "Neural");
|
|
66
|
+
const localeName = v?.LocaleName || (locale || null);
|
|
67
|
+
let display = v?.DisplayName || v?.FriendlyName || base || short;
|
|
68
|
+
display = display.replace(/^Microsoft\s+/i, "");
|
|
69
|
+
display = display.split(" - ")[0].trim();
|
|
70
|
+
display = display.replace(/\s*Online\s*\(Natural\)\s*/i, " ");
|
|
71
|
+
display = display.replace(/\s*Online\s*/i, " ");
|
|
72
|
+
display = display.replace(/\s+/g, " ").trim();
|
|
73
|
+
const tag = v?.VoiceTag && typeof v.VoiceTag === "object" ? v.VoiceTag : {};
|
|
74
|
+
const tailored = Array.isArray(tag.TailoredScenarios) ? tag.TailoredScenarios : Array.isArray(tag.ContentCategories) ? tag.ContentCategories : [];
|
|
75
|
+
const personalities = Array.isArray(tag.VoicePersonalities) ? tag.VoicePersonalities : [];
|
|
76
|
+
out.push({
|
|
77
|
+
Name: short || (v?.Name || ""),
|
|
78
|
+
DisplayName: display,
|
|
79
|
+
LocalName: display,
|
|
80
|
+
ShortName: short || (v?.Name || ""),
|
|
81
|
+
Gender: v?.Gender ?? null,
|
|
82
|
+
Locale: locale || null,
|
|
83
|
+
LocaleName: localeName,
|
|
84
|
+
SecondaryLocaleList: Array.isArray(v?.SecondaryLocaleList) ? v.SecondaryLocaleList : [],
|
|
85
|
+
VoiceType: voiceType,
|
|
86
|
+
VoiceTag: {
|
|
87
|
+
TailoredScenarios: tailored,
|
|
88
|
+
VoicePersonalities: personalities
|
|
89
|
+
},
|
|
90
|
+
FriendlyName: `${display} (${voiceType}) - ${localeName}`
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
92
95
|
async getVoices() {
|
|
93
96
|
const secMsGEC = await this.generateSecMsGec(Constants.TRUSTED_CLIENT_TOKEN);
|
|
94
97
|
const response = await fetch(
|
|
95
|
-
`${Constants.VOICES_URL}?
|
|
98
|
+
`${Constants.VOICES_URL}?TrustedClientToken=${Constants.TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${secMsGEC}&Sec-MS-GEC-Version=${Constants.VERSION_MS_GEC}`,
|
|
96
99
|
{ headers: Constants.getBaseHeaders() }
|
|
97
100
|
);
|
|
98
101
|
const data = await response.json();
|
|
99
|
-
return data.
|
|
100
|
-
voice.FriendlyName = voice.FriendlyName || voice.LocalName;
|
|
101
|
-
delete voice.SampleRateHertz;
|
|
102
|
-
delete voice.Status;
|
|
103
|
-
return voice;
|
|
104
|
-
});
|
|
102
|
+
return this.normalizeVoices(data.voices || []);
|
|
105
103
|
}
|
|
106
104
|
async getVoicesByLanguage(locale) {
|
|
107
105
|
const voices = await this.getVoices();
|
|
@@ -142,69 +140,10 @@ var EdgeTTS = class {
|
|
|
142
140
|
}
|
|
143
141
|
return `${volumeValue}%`;
|
|
144
142
|
}
|
|
145
|
-
detectSSML(content) {
|
|
146
|
-
const trimmedContent = content.trim();
|
|
147
|
-
const looksLikeSSML = /^<\?xml|^<speak/i.test(trimmedContent);
|
|
148
|
-
if (!looksLikeSSML) {
|
|
149
|
-
return { isValid: true, isSSML: false };
|
|
150
|
-
}
|
|
151
|
-
const errors = [];
|
|
152
|
-
const hasSpeakTag = /<speak\b[^>]*>[\s\S]*<\/speak>/i.test(trimmedContent);
|
|
153
|
-
const hasVoiceTag = /<voice\b[^>]*>[\s\S]*<\/voice>/i.test(trimmedContent);
|
|
154
|
-
if (!hasSpeakTag) {
|
|
155
|
-
throw new Error("Invalid SSML: Missing <speak> tag");
|
|
156
|
-
}
|
|
157
|
-
if (!hasVoiceTag) {
|
|
158
|
-
throw new Error("Invalid SSML: Missing <voice> tag");
|
|
159
|
-
}
|
|
160
|
-
const hasCorrectNamespace = /xmlns="http:\/\/www\.w3\.org\/2001\/10\/synthesis"/i.test(trimmedContent);
|
|
161
|
-
if (!hasCorrectNamespace && hasSpeakTag) {
|
|
162
|
-
throw new Error("Invalid SSML: Missing or incorrect namespace declaration");
|
|
163
|
-
}
|
|
164
|
-
return {
|
|
165
|
-
isValid: errors.length === 0,
|
|
166
|
-
isSSML: hasSpeakTag || hasVoiceTag,
|
|
167
|
-
errors: errors.length > 0 ? errors : void 0
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
143
|
escapeXML(text) {
|
|
171
144
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
172
145
|
}
|
|
173
146
|
getSSML(content, voice, options = {}) {
|
|
174
|
-
const inputType = options.inputType || "auto";
|
|
175
|
-
let treatAsSSML = false;
|
|
176
|
-
if (inputType === "ssml") {
|
|
177
|
-
treatAsSSML = true;
|
|
178
|
-
} else if (inputType === "text") {
|
|
179
|
-
treatAsSSML = false;
|
|
180
|
-
} else {
|
|
181
|
-
const detection = this.detectSSML(content);
|
|
182
|
-
treatAsSSML = detection.isSSML;
|
|
183
|
-
if (detection.isSSML) {
|
|
184
|
-
console.log("\u2192 Detected SSML input");
|
|
185
|
-
if (!detection.isValid) {
|
|
186
|
-
console.warn("\u26A0 SSML validation warnings:", detection.errors);
|
|
187
|
-
}
|
|
188
|
-
} else {
|
|
189
|
-
console.log("\u2192 Detected plain text input");
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
if (treatAsSSML) {
|
|
193
|
-
let ssml = content.trim();
|
|
194
|
-
if (!ssml.includes("xmlns=")) {
|
|
195
|
-
ssml = ssml.replace(
|
|
196
|
-
/<speak([^>]*)>/i,
|
|
197
|
-
'<speak$1 xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts">'
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
if (!/<voice\b[^>]*>/i.test(ssml) && voice) {
|
|
201
|
-
ssml = ssml.replace(
|
|
202
|
-
/(<speak[^>]*>)([\s\S]*?)(<\/speak>)/i,
|
|
203
|
-
`$1<voice name="${voice}">$2</voice>$3`
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
return ssml;
|
|
207
|
-
}
|
|
208
147
|
const pitch = this.validatePitch(options.pitch ?? 0);
|
|
209
148
|
const rate = this.validateRate(options.rate ?? 0);
|
|
210
149
|
const volume = this.validateVolume(options.volume ?? 0);
|
|
@@ -236,8 +175,7 @@ Path:speech.config\r
|
|
|
236
175
|
return new Promise((resolve, reject) => {
|
|
237
176
|
this.audio_stream = [];
|
|
238
177
|
const reqId = this.generateUUID();
|
|
239
|
-
const url = `${Constants.WSS_URL}?
|
|
240
|
-
console.log("WebSocket URL:", url);
|
|
178
|
+
const url = `${Constants.WSS_URL}?TrustedClientToken=${Constants.TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${secMsGEC}&Sec-MS-GEC-Version=${Constants.VERSION_MS_GEC}&ConnectionId=${reqId}`;
|
|
241
179
|
this.ws = new WebSocket(url);
|
|
242
180
|
const SSML_text = this.getSSML(text, voice, options);
|
|
243
181
|
let timedOut = false;
|
|
@@ -300,7 +238,7 @@ ${SSML_text}`;
|
|
|
300
238
|
this.audio_stream = [];
|
|
301
239
|
const reqId = this.generateUUID();
|
|
302
240
|
const secMsGEC = await this.generateSecMsGec(Constants.TRUSTED_CLIENT_TOKEN);
|
|
303
|
-
const url = `${Constants.WSS_URL}?
|
|
241
|
+
const url = `${Constants.WSS_URL}?TrustedClientToken=${Constants.TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${secMsGEC}&Sec-MS-GEC-Version=${Constants.VERSION_MS_GEC}&ConnectionId=${reqId}`;
|
|
304
242
|
this.ws = new WebSocket(url);
|
|
305
243
|
const SSML_text = this.getSSML(text, voice, options);
|
|
306
244
|
const queue = [];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/config/constants.ts", "../../src/browser/EdgeTTS.browser.ts"],
|
|
4
|
-
"sourcesContent": ["export const Constants = {\r\n TRUSTED_CLIENT_TOKEN: '6A5AA1D4EAFF4E9FB37E23D68491D6F4',\r\n BASE_URL: 'https://api.msedgeservices.com/tts/cognitiveservices',\r\n WSS_URL: 'wss://api.msedgeservices.com/tts/cognitiveservices/websocket/v1',\r\n VOICES_URL: 'https://api.msedgeservices.com/tts/cognitiveservices/voices/list',\r\n\r\n CHROMIUM_FULL_VERSION: '142.0.3595.0',\r\n CHROMIUM_MAJOR_VERSION: '142',\r\n VERSION_MS_GEC: '1-142.0.3595',\r\n\r\n token32() {\r\n const bytes = new Uint8Array(16);\r\n crypto.getRandomValues(bytes);\r\n\r\n return Array.from(bytes)\r\n .map(b => b.toString(16).padStart(2, '0'))\r\n .join('')\r\n .toUpperCase();\r\n },\r\n\r\n getBaseHeaders() {\r\n return {\r\n 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0',\r\n 'Accept-Encoding': 'gzip, deflate, br, zstd',\r\n 'Accept-Language': 'es,es-ES;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,es-CO;q=0.5,es-MX;q=0.4',\r\n 'Cookie': 'MUID=' + this.token32()\r\n };\r\n },\r\n\r\n WSS_HEADERS: {\r\n 'Pragma': 'no-cache',\r\n 'Cache-Control': 'no-cache',\r\n 'Origin': 'chrome-extension://jdiccldimpdaibmpdkjnbmckianbfold',\r\n 'Sec-WebSocket-Protocol': 'synthesize',\r\n 'Sec-WebSocket-Version': '13',\r\n 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0'\r\n },\r\n\r\n VOICE_HEADERS: {\r\n 'Sec-CH-UA': '\" Not;A Brand\";v=\"99\", \"Microsoft Edge\";v=\"140\", \"Chromium\";v=\"140\"',\r\n 'Sec-CH-UA-Mobile': '?0',\r\n 'Accept': '*/*',\r\n 'Sec-Fetch-Site': 'none',\r\n 'Sec-Fetch-Mode': 'cors',\r\n 'Sec-Fetch-Dest': 'empty'\r\n },\r\n \r\n // https://learn.microsoft.com/en-us/azure/ai-services/speech-service/rest-text-to-speech?tabs=nonstreaming\r\n OUTPUT_FORMAT: {\r\n // Streaming \r\n AMR_WB_16000HZ: 'amr-wb-16000hz',\r\n AUDIO_16KHZ_16BIT_32KBPS_MONO_OPUS: 'audio-16khz-16bit-32kbps-mono-opus',\r\n AUDIO_16KHZ_32KBITRATE_MONO_MP3: 'audio-16khz-32kbitrate-mono-mp3',\r\n AUDIO_16KHZ_64KBITRATE_MONO_MP3: 'audio-16khz-64kbitrate-mono-mp3',\r\n AUDIO_16KHZ_128KBITRATE_MONO_MP3: 'audio-16khz-128kbitrate-mono-mp3',\r\n AUDIO_24KHZ_16BIT_24KBPS_MONO_OPUS: 'audio-24khz-16bit-24kbps-mono-opus',\r\n AUDIO_24KHZ_16BIT_48KBPS_MONO_OPUS: 'audio-24khz-16bit-48kbps-mono-opus',\r\n AUDIO_24KHZ_48KBITRATE_MONO_MP3: 'audio-24khz-48kbitrate-mono-mp3',\r\n AUDIO_24KHZ_96KBITRATE_MONO_MP3: 'audio-24khz-96kbitrate-mono-mp3',\r\n AUDIO_24KHZ_160KBITRATE_MONO_MP3: 'audio-24khz-160kbitrate-mono-mp3',\r\n AUDIO_48KHZ_96KBITRATE_MONO_MP3: 'audio-48khz-96kbitrate-mono-mp3',\r\n AUDIO_48KHZ_192KBITRATE_MONO_MP3: 'audio-48khz-192kbitrate-mono-mp3',\r\n G722_16KHZ_64KBPS: 'g722-16khz-64kbps',\r\n OGG_16KHZ_16BIT_MONO_OPUS: 'ogg-16khz-16bit-mono-opus',\r\n OGG_24KHZ_16BIT_MONO_OPUS: 'ogg-24khz-16bit-mono-opus',\r\n OGG_48KHZ_16BIT_MONO_OPUS: 'ogg-48khz-16bit-mono-opus',\r\n RAW_8KHZ_8BIT_MONO_ALAW: 'raw-8khz-8bit-mono-alaw',\r\n RAW_8KHZ_8BIT_MONO_MULAW: 'raw-8khz-8bit-mono-mulaw',\r\n RAW_8KHZ_16BIT_MONO_PCM: 'raw-8khz-16bit-mono-pcm',\r\n RAW_16KHZ_16BIT_MONO_PCM: 'raw-16khz-16bit-mono-pcm',\r\n RAW_16KHZ_16BIT_MONO_TRUESILK: 'raw-16khz-16bit-mono-truesilk',\r\n RAW_22050HZ_16BIT_MONO_PCM: 'raw-22050hz-16bit-mono-pcm',\r\n RAW_24KHZ_16BIT_MONO_PCM: 'raw-24khz-16bit-mono-pcm',\r\n RAW_24KHZ_16BIT_MONO_TRUESILK: 'raw-24khz-16bit-mono-truesilk',\r\n RAW_44100HZ_16BIT_MONO_PCM: 'raw-44100hz-16bit-mono-pcm',\r\n RAW_48KHZ_16BIT_MONO_PCM: 'raw-48khz-16bit-mono-pcm',\r\n WEBM_16KHZ_16BIT_MONO_OPUS: 'webm-16khz-16bit-mono-opus',\r\n WEBM_24KHZ_16BIT_24KBPS_MONO_OPUS: 'webm-24khz-16bit-24kbps-mono-opus',\r\n WEBM_24KHZ_16BIT_MONO_OPUS: 'webm-24khz-16bit-mono-opus',\r\n // NonStreaming \r\n RIFF_8KHZ_8BIT_MONO_ALAW: 'riff-8khz-8bit-mono-alaw',\r\n RIFF_8KHZ_8BIT_MONO_MULAW: 'riff-8khz-8bit-mono-mulaw',\r\n RIFF_8KHZ_16BIT_MONO_PCM: 'riff-8khz-16bit-mono-pcm',\r\n RIFF_22050HZ_16BIT_MONO_PCM: 'riff-22050hz-16bit-mono-pcm',\r\n RIFF_24KHZ_16BIT_MONO_PCM: 'riff-24khz-16bit-mono-pcm',\r\n RIFF_44100HZ_16BIT_MONO_PCM: 'riff-44100hz-16bit-mono-pcm',\r\n RIFF_48KHZ_16BIT_MONO_PCM: 'riff-48khz-16bit-mono-pcm'\r\n }\r\n\r\n\r\n};\r\n", "import { Constants } from '../config/constants';\r\n\r\nexport interface Voice {\r\n Name: string;\r\n ShortName: string;\r\n Gender: string;\r\n Locale: string;\r\n FriendlyName: string;\r\n LocalName: string;\r\n}\r\n\r\nexport interface SynthesisOptions {\r\n pitch?: string | number;\r\n rate?: string | number;\r\n volume?: string | number;\r\n inputType?: 'auto' | 'ssml' | 'text';\r\n}\r\n\r\ninterface SSMLValidationResult {\r\n isValid: boolean;\r\n isSSML: boolean;\r\n errors?: string[];\r\n}\r\n\r\nexport class EdgeTTS {\r\n private audio_stream: Uint8Array[] = [];\r\n private ws?: WebSocket;\r\n\r\n async getVoices(): Promise<Voice[]> {\r\n const secMsGEC = await this.generateSecMsGec(Constants.TRUSTED_CLIENT_TOKEN);\r\n \r\n const response = await fetch(\r\n `${Constants.VOICES_URL}?Ocp-Apim-Subscription-Key=${Constants.TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${secMsGEC}&Sec-MS-GEC-Version=${Constants.VERSION_MS_GEC}`,\r\n { headers: Constants.getBaseHeaders() }\r\n );\r\n \r\n const data = await response.json();\r\n return data.map((voice: any) => {\r\n voice.FriendlyName = voice.FriendlyName || voice.LocalName;\r\n delete voice.SampleRateHertz;\r\n delete voice.Status;\r\n return voice;\r\n });\r\n }\r\n\r\n async getVoicesByLanguage(locale: string): Promise<Voice[]> {\r\n const voices = await this.getVoices();\r\n return voices.filter(voice => voice.Locale.startsWith(locale));\r\n }\r\n\r\n async getVoicesByGender(gender: 'Male' | 'Female'): Promise<Voice[]> {\r\n const voices = await this.getVoices();\r\n return voices.filter(voice => voice.Gender === gender);\r\n }\r\n\r\n private generateUUID(): string {\r\n return 'xxxxxxxx-xxxx-xxxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {\r\n const r = Math.random() * 16 | 0;\r\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\r\n return v.toString(16);\r\n });\r\n }\r\n\r\n private validatePitch(pitch: string | number): string {\r\n if (typeof pitch === 'number') {\r\n return (pitch >= 0 ? `+${pitch}Hz` : `${pitch}Hz`);\r\n }\r\n return pitch;\r\n }\r\n\r\n private validateRate(rate: string | number): string {\r\n let rateValue: number;\r\n if (typeof rate === 'string') {\r\n rateValue = parseFloat(rate.replace('%', ''));\r\n } else {\r\n rateValue = rate;\r\n }\r\n return rateValue >= 0 ? `+${rateValue}%` : `${rateValue}%`;\r\n }\r\n\r\n private validateVolume(volume: string | number): string {\r\n let volumeValue: number;\r\n if (typeof volume === 'string') {\r\n volumeValue = parseInt(volume.replace('%', ''), 10);\r\n } else {\r\n volumeValue = volume;\r\n }\r\n return `${volumeValue}%`;\r\n }\r\n\r\n private detectSSML(content: string): SSMLValidationResult {\r\n const trimmedContent = content.trim();\r\n const looksLikeSSML = /^<\\?xml|^<speak/i.test(trimmedContent);\r\n\r\n if (!looksLikeSSML) {\r\n return { isValid: true, isSSML: false };\r\n }\r\n\r\n const errors: string[] = [];\r\n const hasSpeakTag = /<speak\\b[^>]*>[\\s\\S]*<\\/speak>/i.test(trimmedContent);\r\n const hasVoiceTag = /<voice\\b[^>]*>[\\s\\S]*<\\/voice>/i.test(trimmedContent);\r\n\r\n if (!hasSpeakTag) {\r\n throw new Error('Invalid SSML: Missing <speak> tag');\r\n }\r\n\r\n if (!hasVoiceTag) {\r\n throw new Error('Invalid SSML: Missing <voice> tag');\r\n }\r\n\r\n const hasCorrectNamespace = /xmlns=\"http:\\/\\/www\\.w3\\.org\\/2001\\/10\\/synthesis\"/i.test(trimmedContent);\r\n if (!hasCorrectNamespace && hasSpeakTag) {\r\n throw new Error('Invalid SSML: Missing or incorrect namespace declaration');\r\n }\r\n\r\n return {\r\n isValid: errors.length === 0,\r\n isSSML: hasSpeakTag || hasVoiceTag,\r\n errors: errors.length > 0 ? errors : undefined\r\n };\r\n }\r\n\r\n private escapeXML(text: string): string {\r\n return text\r\n .replace(/&/g, '&')\r\n .replace(/</g, '<')\r\n .replace(/>/g, '>')\r\n .replace(/\"/g, '"')\r\n .replace(/'/g, ''');\r\n }\r\n\r\n private getSSML(content: string, voice: string, options: SynthesisOptions = {}): string {\r\n const inputType = options.inputType || 'auto';\r\n let treatAsSSML = false;\r\n\r\n if (inputType === 'ssml') {\r\n treatAsSSML = true;\r\n } else if (inputType === 'text') {\r\n treatAsSSML = false;\r\n } else { // 'auto'\r\n const detection = this.detectSSML(content);\r\n treatAsSSML = detection.isSSML;\r\n\r\n if (detection.isSSML) {\r\n console.log('\u2192 Detected SSML input');\r\n if (!detection.isValid) {\r\n console.warn('\u26A0 SSML validation warnings:', detection.errors);\r\n }\r\n } else {\r\n console.log('\u2192 Detected plain text input');\r\n }\r\n }\r\n\r\n if (treatAsSSML) {\r\n let ssml = content.trim();\r\n\r\n if (!ssml.includes('xmlns=')) {\r\n ssml = ssml.replace(\r\n /<speak([^>]*)>/i,\r\n '<speak$1 xmlns=\"http://www.w3.org/2001/10/synthesis\" xmlns:mstts=\"https://www.w3.org/2001/mstts\">'\r\n );\r\n }\r\n\r\n if (!/<voice\\b[^>]*>/i.test(ssml) && voice) {\r\n ssml = ssml.replace(\r\n /(<speak[^>]*>)([\\s\\S]*?)(<\\/speak>)/i,\r\n `$1<voice name=\"${voice}\">$2</voice>$3`\r\n );\r\n }\r\n\r\n return ssml;\r\n }\r\n\r\n const pitch = this.validatePitch(options.pitch ?? 0);\r\n const rate = this.validateRate(options.rate ?? 0);\r\n const volume = this.validateVolume(options.volume ?? 0);\r\n const escapedText = this.escapeXML(content);\r\n\r\n return `<speak version=\"1.0\" xmlns=\"http://www.w3.org/2001/10/synthesis\" xmlns:mstts=\"https://www.w3.org/2001/mstts\" xml:lang=\"en-US\">\r\n <voice name=\"${voice}\">\r\n <prosody pitch=\"${pitch}\" rate=\"${rate}\" volume=\"${volume}\">\r\n ${escapedText}\r\n </prosody>\r\n </voice>\r\n </speak>`;\r\n }\r\n\r\n private nowRFC1123(): string {\r\n return new Date().toUTCString();\r\n }\r\n\r\n private parseRFC1123(rfcStr: string): Date {\r\n return new Date(rfcStr);\r\n }\r\n\r\n private buildTTSConfigMessage(): string {\r\n const timestamp = this.nowRFC1123();\r\n return `X-Timestamp:${timestamp}\\r\\nContent-Type:application/json; charset=utf-8\\r\\nPath:speech.config\\r\\n\\r\\n` +\r\n `{\"context\":{\"synthesis\":{\"audio\":{\"metadataoptions\":{\"sentenceBoundaryEnabled\":false,\"wordBoundaryEnabled\":true},\"outputFormat\":\"audio-24khz-48kbitrate-mono-mp3\"}}}}`;\r\n }\r\n\r\n async synthesize(text: string, voice: string = 'en-US-JennyNeural', options: SynthesisOptions = {}): Promise<void> {\r\n const secMsGEC = await this.generateSecMsGec(Constants.TRUSTED_CLIENT_TOKEN);\r\n\r\n return new Promise((resolve, reject) => {\r\n this.audio_stream = [];\r\n const reqId = this.generateUUID();\r\n const url = `${Constants.WSS_URL}?Ocp-Apim-Subscription-Key=${Constants.TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${secMsGEC}&Sec-MS-GEC-Version=${Constants.VERSION_MS_GEC}&ConnectionId=${reqId}`;\r\n \r\n console.log('WebSocket URL:', url);\r\n this.ws = new WebSocket(url);\r\n\r\n const SSML_text = this.getSSML(text, voice, options);\r\n \r\n let timedOut = false;\r\n let inactivityTimeout: number;\r\n \r\n const resetInactivityTimeout = () => {\r\n clearTimeout(inactivityTimeout);\r\n inactivityTimeout = window.setTimeout(() => {\r\n timedOut = true;\r\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\r\n this.ws.close();\r\n }\r\n reject(new Error(\"WebSocket inactivity timeout - no response from server\"));\r\n }, 30000); // 30 seconds of inactivity\r\n };\r\n\r\n this.ws.onopen = () => {\r\n resetInactivityTimeout(); // start the inactivity timeout\r\n const message = this.buildTTSConfigMessage();\r\n this.ws!.send(message);\r\n const timestamp = this.nowRFC1123();\r\n const speechMessage = `X-RequestId:${reqId}\\r\\nContent-Type:application/ssml+xml\\r\\nX-Timestamp:${timestamp}\\r\\nPath:ssml\\r\\n\\r\\n${SSML_text}`;\r\n this.ws!.send(speechMessage);\r\n };\r\n\r\n this.ws.onmessage = (event) => {\r\n resetInactivityTimeout(); // restart inactivity timeout \r\n if (typeof event.data === 'string') {\r\n if (event.data.includes('Path:turn.end')) {\r\n this.ws?.close();\r\n }\r\n } else {\r\n const reader = new FileReader();\r\n reader.onload = () => {\r\n const arrayBuffer = reader.result as ArrayBuffer;\r\n const uint8Array = new Uint8Array(arrayBuffer);\r\n this.processAudioData(uint8Array);\r\n };\r\n reader.readAsArrayBuffer(event.data);\r\n }\r\n };\r\n\r\n this.ws.onerror = (err: any) => {\r\n clearTimeout(inactivityTimeout);\r\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\r\n this.ws.close();\r\n }\r\n reject(new Error('WebSocket error'));\r\n };\r\n\r\n this.ws.onclose = () => {\r\n clearTimeout(inactivityTimeout);\r\n if (!timedOut) {\r\n resolve();\r\n }\r\n };\r\n });\r\n }\r\n\r\n async *synthesizeStream(text: string, voice: string = 'en-US-JennyNeural', options: SynthesisOptions = {}): AsyncGenerator<Uint8Array, void, unknown> {\r\n this.audio_stream = [];\r\n\r\n const reqId = this.generateUUID();\r\n const secMsGEC = await this.generateSecMsGec(Constants.TRUSTED_CLIENT_TOKEN);\r\n\r\n const url = `${Constants.WSS_URL}?Ocp-Apim-Subscription-Key=${Constants.TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${secMsGEC}&Sec-MS-GEC-Version=${Constants.VERSION_MS_GEC}&ConnectionId=${reqId}`;\r\n\r\n this.ws = new WebSocket(url);\r\n\r\n const SSML_text = this.getSSML(text, voice, options);\r\n\r\n const queue: Uint8Array[] = [];\r\n let done = false;\r\n let error: Error | null = null;\r\n let notify: (() => void) | null = null;\r\n\r\n const push = (chunk: Uint8Array) => {\r\n queue.push(chunk);\r\n if (notify) {\r\n notify();\r\n notify = null;\r\n }\r\n };\r\n\r\n let timedOut = false;\r\n let inactivityTimeout: number;\r\n \r\n const resetInactivityTimeout = () => {\r\n clearTimeout(inactivityTimeout);\r\n inactivityTimeout = window.setTimeout(() => {\r\n timedOut = true;\r\n error = new Error(\"WebSocket inactivity timeout - no response from server\");\r\n done = true;\r\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\r\n this.ws.close();\r\n }\r\n if (notify) {\r\n notify();\r\n notify = null;\r\n }\r\n }, 30000); // 30 seconds of inactivity\r\n };\r\n\r\n this.ws.onopen = () => {\r\n resetInactivityTimeout(); // start the inactivity timeout\r\n const message = this.buildTTSConfigMessage();\r\n this.ws!.send(message);\r\n\r\n const timestamp = this.nowRFC1123();\r\n const speechMessage = `X-RequestId:${reqId}\\r\\nContent-Type:application/ssml+xml\\r\\nX-Timestamp:${timestamp}\\r\\nPath:ssml\\r\\n\\r\\n${SSML_text}`;\r\n this.ws!.send(speechMessage);\r\n };\r\n\r\n this.ws.onmessage = (event) => {\r\n resetInactivityTimeout(); // restart inactivity timeout\r\n if (typeof event.data === 'string') {\r\n if (event.data.includes('Path:turn.end')) {\r\n this.ws?.close();\r\n }\r\n } else {\r\n const reader = new FileReader();\r\n reader.onload = () => {\r\n const arrayBuffer = reader.result as ArrayBuffer;\r\n const uint8Array = new Uint8Array(arrayBuffer);\r\n \r\n const needle = new TextEncoder().encode(\"Path:audio\\r\\n\");\r\n const audioStartIndex = this.indexOf(uint8Array, needle);\r\n\r\n if (audioStartIndex !== -1) {\r\n const audioChunk = uint8Array.slice(audioStartIndex + needle.length);\r\n this.audio_stream.push(audioChunk);\r\n push(audioChunk);\r\n }\r\n };\r\n reader.readAsArrayBuffer(event.data);\r\n }\r\n };\r\n\r\n this.ws.onerror = (err: any) => {\r\n clearTimeout(inactivityTimeout);\r\n error = new Error('WebSocket error');\r\n done = true;\r\n if (notify) {\r\n notify();\r\n notify = null;\r\n }\r\n };\r\n\r\n this.ws.onclose = () => {\r\n clearTimeout(inactivityTimeout);\r\n done = true;\r\n if (notify) {\r\n notify();\r\n notify = null;\r\n }\r\n };\r\n\r\n while (!done || queue.length > 0) {\r\n if (queue.length === 0) {\r\n await new Promise<void>(resolve => (notify = resolve));\r\n continue;\r\n }\r\n const chunk = queue.shift();\r\n if (chunk) {\r\n yield chunk;\r\n }\r\n }\r\n\r\n if (error) {\r\n throw error;\r\n }\r\n }\r\n\r\n private processAudioData(buffer: Uint8Array): void {\r\n const needle = new TextEncoder().encode(\"Path:audio\\r\\n\");\r\n const audioStartIndex = this.indexOf(buffer, needle);\r\n\r\n if (audioStartIndex !== -1) {\r\n const audioChunk = buffer.slice(audioStartIndex + needle.length);\r\n this.audio_stream.push(audioChunk);\r\n }\r\n }\r\n\r\n private indexOf(arr: Uint8Array, subarr: Uint8Array): number {\r\n for (let i = 0; i <= arr.length - subarr.length; i++) {\r\n let match = true;\r\n for (let j = 0; j < subarr.length; j++) {\r\n if (arr[i + j] !== subarr[j]) {\r\n match = false;\r\n break;\r\n }\r\n }\r\n if (match) return i;\r\n }\r\n return -1;\r\n }\r\n\r\n private async generateSecMsGec(trustedClientToken: string): Promise<string> {\r\n const now = this.nowRFC1123();\r\n const fixedDate = this.parseRFC1123(now);\r\n const ticks = Math.floor(fixedDate.getTime() / 1000) + 11644473600;\r\n const rounded = ticks - (ticks % 300);\r\n const windowsTicks = rounded * 10_000_000;\r\n\r\n const encoder = new TextEncoder();\r\n const data = encoder.encode(`${windowsTicks}${trustedClientToken}`);\r\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\r\n\r\n return Array.from(new Uint8Array(hashBuffer))\r\n .map(b => b.toString(16).padStart(2, '0'))\r\n .join('')\r\n .toUpperCase();\r\n }\r\n\r\n getAudioData(): Uint8Array {\r\n if (this.audio_stream.length === 0) {\r\n throw new Error(\"No audio data available\");\r\n }\r\n const totalLength = this.audio_stream.reduce((acc, chunk) => acc + chunk.length, 0);\r\n const result = new Uint8Array(totalLength);\r\n let offset = 0;\r\n for (const chunk of this.audio_stream) {\r\n result.set(chunk, offset);\r\n offset += chunk.length;\r\n }\r\n return result;\r\n }\r\n}\r\n"],
|
|
5
|
-
"mappings": ";;;;;AAAO,IAAM,YAAY;AAAA,EACrB,sBAAsB;AAAA,EACtB,UAAU;AAAA,EACV,SAAS;AAAA,EACT,YAAY;AAAA,EAEZ,uBAAuB;AAAA,EACvB,wBAAwB;AAAA,EACxB,gBAAgB;AAAA,EAEhB,UAAU;AACN,UAAM,QAAQ,IAAI,WAAW,EAAE;AAC/B,WAAO,gBAAgB,KAAK;AAE5B,WAAO,MAAM,KAAK,KAAK,EAClB,IAAI,OAAK,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EACxC,KAAK,EAAE,EACP,YAAY;AAAA,EACrB;AAAA,EAEA,iBAAiB;AACb,WAAO;AAAA,MACH,cAAc;AAAA,MACd,mBAAmB;AAAA,MACnB,mBAAmB;AAAA,MACnB,UAAU,UAAU,KAAK,QAAQ;AAAA,IACrC;AAAA,EACJ;AAAA,EAEA,aAAa;AAAA,IACT,UAAU;AAAA,IACV,iBAAiB;AAAA,IACjB,UAAU;AAAA,IACV,0BAA0B;AAAA,IAC1B,yBAAyB;AAAA,IACzB,cAAc;AAAA,EAClB;AAAA,EAEA,eAAe;AAAA,IACX,aAAa;AAAA,IACb,oBAAoB;AAAA,IACpB,UAAU;AAAA,IACV,kBAAkB;AAAA,IAClB,kBAAkB;AAAA,IAClB,kBAAkB;AAAA,EACtB;AAAA;AAAA,EAGA,eAAe;AAAA
|
|
4
|
+
"sourcesContent": ["export const Constants = {\r\n TRUSTED_CLIENT_TOKEN: '6A5AA1D4EAFF4E9FB37E23D68491D6F4',\r\n BASE_URL: 'https://speech.platform.bing.com/consumer/speech/synthesize/readaloud',\r\n WSS_URL: 'wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1',\r\n VOICES_URL: 'https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/voices/list',\r\n\r\n CHROMIUM_FULL_VERSION: '143.0.3650.75',\r\n CHROMIUM_MAJOR_VERSION: '143',\r\n VERSION_MS_GEC: '1-143.0.3650',\r\n\r\n token32() {\r\n const bytes = new Uint8Array(16);\r\n crypto.getRandomValues(bytes);\r\n\r\n return Array.from(bytes)\r\n .map(b => b.toString(16).padStart(2, '0'))\r\n .join('')\r\n .toUpperCase();\r\n },\r\n\r\n getBaseHeaders() {\r\n return {\r\n 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0',\r\n 'Accept-Encoding': 'gzip, deflate, br, zstd',\r\n 'Accept-Language': 'en-US,en;q=0.9',\r\n 'Cookie': 'MUID=' + this.token32()\r\n };\r\n },\r\n\r\n WSS_HEADERS: {\r\n 'Pragma': 'no-cache',\r\n 'Cache-Control': 'no-cache',\r\n 'Origin': 'chrome-extension://jdiccldimpdaibmpdkjnbmckianbfold',\r\n 'Sec-WebSocket-Protocol': 'synthesize',\r\n 'Sec-WebSocket-Version': '13',\r\n 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.3650.75 Safari/537.36 Edg/143.0.3650.75'\r\n },\r\n\r\n VOICE_HEADERS: {\r\n 'Sec-CH-UA': '\" Not;A Brand\";v=\"99\", \"Microsoft Edge\";v=\"143\", \"Chromium\";v=\"143\"',\r\n 'Sec-CH-UA-Mobile': '?0',\r\n 'Accept': '*/*',\r\n 'Sec-Fetch-Site': 'none',\r\n 'Sec-Fetch-Mode': 'cors',\r\n 'Sec-Fetch-Dest': 'empty'\r\n },\r\n\r\n // https://learn.microsoft.com/en-us/azure/ai-services/speech-service/rest-text-to-speech?tabs=nonstreaming\r\n OUTPUT_FORMAT: {\r\n 'AUDIO_24KHZ_48KBITRATE_MONO_MP3': 'audio-24khz-48kbitrate-mono-mp3',\r\n 'AUDIO_24KHZ_96KBITRATE_MONO_MP3': 'audio-24khz-96kbitrate-mono-mp3',\r\n 'WEBM_24KHZ_16BIT_MONO_OPUS': 'webm-24khz-16bit-mono-opus',\r\n }\r\n\r\n\r\n};\r\n", "import { Constants } from '../config/constants';\r\n\r\nexport interface Voice {\r\n Name: string;\r\n ShortName: string;\r\n Gender: string;\r\n Locale: string;\r\n FriendlyName: string;\r\n LocalName: string;\r\n}\r\n\r\nexport interface SynthesisOptions {\r\n pitch?: string | number;\r\n rate?: string | number;\r\n volume?: string | number;\r\n}\r\n\r\nexport class EdgeTTS {\r\n private audio_stream: Uint8Array[] = [];\r\n private ws?: WebSocket;\r\n\r\n\r\n async normalizeVoices(data: any[]): Promise<Voice[]> {\r\n const out = [];\r\n for (const v of data || []) {\r\n const short = v?.ShortName || \"\";\r\n const locale = v?.Locale || \"\";\r\n\r\n // base: remove locale prefix and Neural/NeuralHD suffix\r\n let base = short.replace(/^[a-z]{2}-[A-Z]{2}-/, \"\");\r\n base = base.replace(/NeuralHD$/, \"\").replace(/Neural$/, \"\").trim();\r\n\r\n // VoiceType: if NeuralHD/Neural in Name or ShortName\r\n const mix = `${v?.Name || \"\"} ${short}`;\r\n const voiceType =\r\n v?.VoiceType || (/NeuralHD/i.test(mix) ? \"NeuralHD\" : \"Neural\");\r\n\r\n // LocaleName: prefer LocaleName -> LanguageName -> locale\r\n const localeName = v?.LocaleName || (locale || null);\r\n\r\n // DisplayName: prefer DisplayName -> FriendlyName -> base -> short\r\n let display = v?.DisplayName || v?.FriendlyName || base || short;\r\n display = display.replace(/^Microsoft\\s+/i, \"\");\r\n display = display.split(\" - \")[0].trim();\r\n\r\n display = display.replace(/\\s*Online\\s*\\(Natural\\)\\s*/i, \" \");\r\n display = display.replace(/\\s*Online\\s*/i, \" \");\r\n display = display.replace(/\\s+/g, \" \").trim();\r\n\r\n // VoiceTag parsing\r\n const tag = (v?.VoiceTag && typeof v.VoiceTag === \"object\") ? v.VoiceTag : {};\r\n const tailored = Array.isArray(tag.TailoredScenarios)\r\n ? tag.TailoredScenarios\r\n : (Array.isArray(tag.ContentCategories) ? tag.ContentCategories : []);\r\n const personalities = Array.isArray(tag.VoicePersonalities)\r\n ? tag.VoicePersonalities\r\n : [];\r\n\r\n out.push({\r\n Name: short || (v?.Name || \"\"),\r\n DisplayName: display,\r\n LocalName: display,\r\n ShortName: short || (v?.Name || \"\"),\r\n Gender: v?.Gender ?? null,\r\n Locale: locale || null,\r\n LocaleName: localeName,\r\n SecondaryLocaleList: Array.isArray(v?.SecondaryLocaleList) ? v.SecondaryLocaleList : [],\r\n VoiceType: voiceType,\r\n VoiceTag: {\r\n TailoredScenarios: tailored,\r\n VoicePersonalities: personalities,\r\n },\r\n FriendlyName: `${display} (${voiceType}) - ${localeName}`,\r\n });\r\n }\r\n\r\n return out;\r\n }\r\n\r\n async getVoices(): Promise<Voice[]> {\r\n const secMsGEC = await this.generateSecMsGec(Constants.TRUSTED_CLIENT_TOKEN);\r\n\r\n const response = await fetch(\r\n `${Constants.VOICES_URL}?TrustedClientToken=${Constants.TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${secMsGEC}&Sec-MS-GEC-Version=${Constants.VERSION_MS_GEC}`,\r\n { headers: Constants.getBaseHeaders() }\r\n );\r\n\r\n const data = await response.json();\r\n return this.normalizeVoices(data.voices || []);\r\n }\r\n\r\n async getVoicesByLanguage(locale: string): Promise<Voice[]> {\r\n const voices = await this.getVoices();\r\n return voices.filter(voice => voice.Locale.startsWith(locale));\r\n }\r\n\r\n async getVoicesByGender(gender: 'Male' | 'Female'): Promise<Voice[]> {\r\n const voices = await this.getVoices();\r\n return voices.filter(voice => voice.Gender === gender);\r\n }\r\n\r\n private generateUUID(): string {\r\n return 'xxxxxxxx-xxxx-xxxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {\r\n const r = Math.random() * 16 | 0;\r\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\r\n return v.toString(16);\r\n });\r\n }\r\n\r\n private validatePitch(pitch: string | number): string {\r\n if (typeof pitch === 'number') {\r\n return (pitch >= 0 ? `+${pitch}Hz` : `${pitch}Hz`);\r\n }\r\n return pitch;\r\n }\r\n\r\n private validateRate(rate: string | number): string {\r\n let rateValue: number;\r\n if (typeof rate === 'string') {\r\n rateValue = parseFloat(rate.replace('%', ''));\r\n } else {\r\n rateValue = rate;\r\n }\r\n return rateValue >= 0 ? `+${rateValue}%` : `${rateValue}%`;\r\n }\r\n\r\n private validateVolume(volume: string | number): string {\r\n let volumeValue: number;\r\n if (typeof volume === 'string') {\r\n volumeValue = parseInt(volume.replace('%', ''), 10);\r\n } else {\r\n volumeValue = volume;\r\n }\r\n return `${volumeValue}%`;\r\n }\r\n\r\n private escapeXML(text: string): string {\r\n return text\r\n .replace(/&/g, '&')\r\n .replace(/</g, '<')\r\n .replace(/>/g, '>')\r\n .replace(/\"/g, '"')\r\n .replace(/'/g, ''');\r\n }\r\n\r\n private getSSML(content: string, voice: string, options: SynthesisOptions = {}): string { \r\n const pitch = this.validatePitch(options.pitch ?? 0);\r\n const rate = this.validateRate(options.rate ?? 0);\r\n const volume = this.validateVolume(options.volume ?? 0);\r\n const escapedText = this.escapeXML(content);\r\n\r\n return `<speak version=\"1.0\" xmlns=\"http://www.w3.org/2001/10/synthesis\" xmlns:mstts=\"https://www.w3.org/2001/mstts\" xml:lang=\"en-US\">\r\n <voice name=\"${voice}\">\r\n <prosody pitch=\"${pitch}\" rate=\"${rate}\" volume=\"${volume}\">\r\n ${escapedText}\r\n </prosody>\r\n </voice>\r\n </speak>`;\r\n }\r\n\r\n private nowRFC1123(): string {\r\n return new Date().toUTCString();\r\n }\r\n\r\n private parseRFC1123(rfcStr: string): Date {\r\n return new Date(rfcStr);\r\n }\r\n\r\n private buildTTSConfigMessage(): string {\r\n const timestamp = this.nowRFC1123();\r\n return `X-Timestamp:${timestamp}\\r\\nContent-Type:application/json; charset=utf-8\\r\\nPath:speech.config\\r\\n\\r\\n` +\r\n `{\"context\":{\"synthesis\":{\"audio\":{\"metadataoptions\":{\"sentenceBoundaryEnabled\":false,\"wordBoundaryEnabled\":true},\"outputFormat\":\"audio-24khz-48kbitrate-mono-mp3\"}}}}`;\r\n }\r\n\r\n async synthesize(text: string, voice: string = 'en-US-JennyNeural', options: SynthesisOptions = {}): Promise<void> {\r\n const secMsGEC = await this.generateSecMsGec(Constants.TRUSTED_CLIENT_TOKEN);\r\n\r\n return new Promise((resolve, reject) => {\r\n this.audio_stream = [];\r\n const reqId = this.generateUUID();\r\n const url = `${Constants.WSS_URL}?TrustedClientToken=${Constants.TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${secMsGEC}&Sec-MS-GEC-Version=${Constants.VERSION_MS_GEC}&ConnectionId=${reqId}`;\r\n\r\n this.ws = new WebSocket(url);\r\n\r\n const SSML_text = this.getSSML(text, voice, options);\r\n\r\n let timedOut = false;\r\n let inactivityTimeout: number;\r\n\r\n const resetInactivityTimeout = () => {\r\n clearTimeout(inactivityTimeout);\r\n inactivityTimeout = window.setTimeout(() => {\r\n timedOut = true;\r\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\r\n this.ws.close();\r\n }\r\n reject(new Error(\"WebSocket inactivity timeout - no response from server\"));\r\n }, 30000); // 30 seconds of inactivity\r\n };\r\n\r\n this.ws.onopen = () => {\r\n resetInactivityTimeout(); // start the inactivity timeout\r\n const message = this.buildTTSConfigMessage();\r\n this.ws!.send(message);\r\n const timestamp = this.nowRFC1123();\r\n const speechMessage = `X-RequestId:${reqId}\\r\\nContent-Type:application/ssml+xml\\r\\nX-Timestamp:${timestamp}\\r\\nPath:ssml\\r\\n\\r\\n${SSML_text}`;\r\n this.ws!.send(speechMessage);\r\n };\r\n\r\n this.ws.onmessage = (event) => {\r\n resetInactivityTimeout(); // restart inactivity timeout \r\n if (typeof event.data === 'string') {\r\n if (event.data.includes('Path:turn.end')) {\r\n this.ws?.close();\r\n }\r\n } else {\r\n const reader = new FileReader();\r\n reader.onload = () => {\r\n const arrayBuffer = reader.result as ArrayBuffer;\r\n const uint8Array = new Uint8Array(arrayBuffer);\r\n this.processAudioData(uint8Array);\r\n };\r\n reader.readAsArrayBuffer(event.data);\r\n }\r\n };\r\n\r\n this.ws.onerror = (err: any) => {\r\n clearTimeout(inactivityTimeout);\r\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\r\n this.ws.close();\r\n }\r\n reject(new Error('WebSocket error'));\r\n };\r\n\r\n this.ws.onclose = () => {\r\n clearTimeout(inactivityTimeout);\r\n if (!timedOut) {\r\n resolve();\r\n }\r\n };\r\n });\r\n }\r\n\r\n async *synthesizeStream(text: string, voice: string = 'en-US-JennyNeural', options: SynthesisOptions = {}): AsyncGenerator<Uint8Array, void, unknown> {\r\n this.audio_stream = [];\r\n\r\n const reqId = this.generateUUID();\r\n const secMsGEC = await this.generateSecMsGec(Constants.TRUSTED_CLIENT_TOKEN);\r\n\r\n const url = `${Constants.WSS_URL}?TrustedClientToken=${Constants.TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${secMsGEC}&Sec-MS-GEC-Version=${Constants.VERSION_MS_GEC}&ConnectionId=${reqId}`;\r\n\r\n this.ws = new WebSocket(url);\r\n\r\n const SSML_text = this.getSSML(text, voice, options);\r\n\r\n const queue: Uint8Array[] = [];\r\n let done = false;\r\n let error: Error | null = null;\r\n let notify: (() => void) | null = null;\r\n\r\n const push = (chunk: Uint8Array) => {\r\n queue.push(chunk);\r\n if (notify) {\r\n notify();\r\n notify = null;\r\n }\r\n };\r\n\r\n let timedOut = false;\r\n let inactivityTimeout: number;\r\n\r\n const resetInactivityTimeout = () => {\r\n clearTimeout(inactivityTimeout);\r\n inactivityTimeout = window.setTimeout(() => {\r\n timedOut = true;\r\n error = new Error(\"WebSocket inactivity timeout - no response from server\");\r\n done = true;\r\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\r\n this.ws.close();\r\n }\r\n if (notify) {\r\n notify();\r\n notify = null;\r\n }\r\n }, 30000); // 30 seconds of inactivity\r\n };\r\n\r\n this.ws.onopen = () => {\r\n resetInactivityTimeout(); // start the inactivity timeout\r\n const message = this.buildTTSConfigMessage();\r\n this.ws!.send(message);\r\n\r\n const timestamp = this.nowRFC1123();\r\n const speechMessage = `X-RequestId:${reqId}\\r\\nContent-Type:application/ssml+xml\\r\\nX-Timestamp:${timestamp}\\r\\nPath:ssml\\r\\n\\r\\n${SSML_text}`;\r\n this.ws!.send(speechMessage);\r\n };\r\n\r\n this.ws.onmessage = (event) => {\r\n resetInactivityTimeout(); // restart inactivity timeout\r\n if (typeof event.data === 'string') {\r\n if (event.data.includes('Path:turn.end')) {\r\n this.ws?.close();\r\n }\r\n } else {\r\n const reader = new FileReader();\r\n reader.onload = () => {\r\n const arrayBuffer = reader.result as ArrayBuffer;\r\n const uint8Array = new Uint8Array(arrayBuffer);\r\n\r\n const needle = new TextEncoder().encode(\"Path:audio\\r\\n\");\r\n const audioStartIndex = this.indexOf(uint8Array, needle);\r\n\r\n if (audioStartIndex !== -1) {\r\n const audioChunk = uint8Array.slice(audioStartIndex + needle.length);\r\n this.audio_stream.push(audioChunk);\r\n push(audioChunk);\r\n }\r\n };\r\n reader.readAsArrayBuffer(event.data);\r\n }\r\n };\r\n\r\n this.ws.onerror = (err: any) => {\r\n clearTimeout(inactivityTimeout);\r\n error = new Error('WebSocket error');\r\n done = true;\r\n if (notify) {\r\n notify();\r\n notify = null;\r\n }\r\n };\r\n\r\n this.ws.onclose = () => {\r\n clearTimeout(inactivityTimeout);\r\n done = true;\r\n if (notify) {\r\n notify();\r\n notify = null;\r\n }\r\n };\r\n\r\n while (!done || queue.length > 0) {\r\n if (queue.length === 0) {\r\n await new Promise<void>(resolve => (notify = resolve));\r\n continue;\r\n }\r\n const chunk = queue.shift();\r\n if (chunk) {\r\n yield chunk;\r\n }\r\n }\r\n\r\n if (error) {\r\n throw error;\r\n }\r\n }\r\n\r\n private processAudioData(buffer: Uint8Array): void {\r\n const needle = new TextEncoder().encode(\"Path:audio\\r\\n\");\r\n const audioStartIndex = this.indexOf(buffer, needle);\r\n\r\n if (audioStartIndex !== -1) {\r\n const audioChunk = buffer.slice(audioStartIndex + needle.length);\r\n this.audio_stream.push(audioChunk);\r\n }\r\n }\r\n\r\n private indexOf(arr: Uint8Array, subarr: Uint8Array): number {\r\n for (let i = 0; i <= arr.length - subarr.length; i++) {\r\n let match = true;\r\n for (let j = 0; j < subarr.length; j++) {\r\n if (arr[i + j] !== subarr[j]) {\r\n match = false;\r\n break;\r\n }\r\n }\r\n if (match) return i;\r\n }\r\n return -1;\r\n }\r\n\r\n private async generateSecMsGec(trustedClientToken: string): Promise<string> {\r\n const now = this.nowRFC1123();\r\n const fixedDate = this.parseRFC1123(now);\r\n const ticks = Math.floor(fixedDate.getTime() / 1000) + 11644473600;\r\n const rounded = ticks - (ticks % 300);\r\n const windowsTicks = rounded * 10_000_000;\r\n\r\n const encoder = new TextEncoder();\r\n const data = encoder.encode(`${windowsTicks}${trustedClientToken}`);\r\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\r\n\r\n return Array.from(new Uint8Array(hashBuffer))\r\n .map(b => b.toString(16).padStart(2, '0'))\r\n .join('')\r\n .toUpperCase();\r\n }\r\n\r\n getAudioData(): Uint8Array {\r\n if (this.audio_stream.length === 0) {\r\n throw new Error(\"No audio data available\");\r\n }\r\n const totalLength = this.audio_stream.reduce((acc, chunk) => acc + chunk.length, 0);\r\n const result = new Uint8Array(totalLength);\r\n let offset = 0;\r\n for (const chunk of this.audio_stream) {\r\n result.set(chunk, offset);\r\n offset += chunk.length;\r\n }\r\n return result;\r\n }\r\n}\r\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|