retiam 2.0.2 → 2.0.3
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/agent/AGENTS.md +4 -1
- package/agent/skill/SKILL.md +7 -4
- package/agent/skill/examples/NarratedMotion.ts +10 -2
- package/agent/skill/references/api.md +4 -2
- package/agent/skill/references/explainers.md +4 -2
- package/agent/skill/references/quality.md +3 -0
- package/agent/skill/references/tts.md +154 -60
- package/bin/lesson.js +39 -12
- package/dist/{UTIF-BrQ3Ng5l.min.js → UTIF-D6vx2UEX.min.js} +1 -1
- package/dist/{UTIF-CN0-pEnd.js → UTIF-mkffT70R.js} +1 -1
- package/dist/{index-yQIdejOj.js → index-CdBPrYbr.js} +3818 -3785
- package/dist/{index-3sAseSJQ.min.js → index-jgFyrc2D.min.js} +175 -175
- package/dist/libs/explainer/index.js +74 -73
- package/dist/libs/explainer/plan.js +162 -140
- package/dist/libs/explainer/types.d.ts +5 -0
- package/dist/libs/tts/animations/Speak.d.ts +13 -7
- package/dist/libs/tts/components/TTSNarrationComponent.d.ts +9 -7
- package/dist/libs/tts/core/TTSCache.d.ts +3 -3
- package/dist/libs/tts/core/TTSClient.d.ts +3 -3
- package/dist/libs/tts/core/types.d.ts +48 -6
- package/dist/libs/tts/index.d.ts +2 -1
- package/dist/libs/tts/index.js +165 -672
- package/dist/libs/tts/node/index.d.ts +10 -0
- package/dist/libs/tts/node/index.js +791 -0
- package/dist/libs/tts/providers/ElevenLabsTTSProvider.d.ts +30 -0
- package/dist/libs/tts/providers/ITTSProvider.d.ts +2 -2
- package/dist/libs/vite/index.js +1 -1
- package/dist/{main-lQwYAed8.min.js → main-Cc4QQbSK.min.js} +1 -1
- package/dist/{main-BTk_9jtl.js → main-DMu_5Qy7.js} +1 -1
- package/dist/{msEdgeV2-BLEnvPJa.js → msEdgeV2-D3w4Xsn-.js} +30 -29
- package/dist/retiam.es.js +1 -1
- package/dist/retiam.es.min.js +1 -1
- package/dist/src/engine/entities/composites/text/mathjaxInterop.d.ts +6 -0
- package/package.json +4 -2
- package/readme.md +20 -1
package/dist/libs/tts/index.js
CHANGED
|
@@ -1,543 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { TTS as p } from "./node/index.js";
|
|
2
|
+
import { ElevenLabsTTSProvider as W, MsEdgeTTSProvider as A, TTSAudio as D, TTSCache as L, TTSClient as H, encodeWAV as P } from "./node/index.js";
|
|
3
|
+
import { D as I, M as N, f as Y, a as X, g as Z, b as q, p as G, s as U } from "../../msEdgeV2-D3w4Xsn-.js";
|
|
4
|
+
import { Component as x, Color as d, hex as a, VGroup as b, SDFText as m, CENTER as S, Rect as g, Animation as C } from "retiam";
|
|
4
5
|
/*! Retiam: noncommercial use only unless separately licensed. See ../../LICENSE and ../../COMMERCIAL_LICENSE.md. Third-party notices: ../../THIRD_PARTY_NOTICES.md and ../../THIRD_PARTY_LICENSES.txt. */
|
|
5
|
-
|
|
6
|
-
const a = t * 2, r = c.length * 2, n = new ArrayBuffer(44 + r), o = new DataView(n);
|
|
7
|
-
B(o, 0, "RIFF"), o.setUint32(4, 36 + r, !0), B(o, 8, "WAVE"), B(o, 12, "fmt "), o.setUint32(16, 16, !0), o.setUint16(20, 1, !0), o.setUint16(22, 1, !0), o.setUint32(24, t, !0), o.setUint32(28, a, !0), o.setUint16(32, 2, !0), o.setUint16(34, 16, !0), B(o, 36, "data"), o.setUint32(40, r, !0);
|
|
8
|
-
let u = 44;
|
|
9
|
-
for (let h = 0; h < c.length; h++) {
|
|
10
|
-
const d = Math.max(-1, Math.min(1, c[h])), p = d < 0 ? d * 32768 : d * 32767;
|
|
11
|
-
o.setInt16(u, p, !0), u += 2;
|
|
12
|
-
}
|
|
13
|
-
return n;
|
|
14
|
-
}
|
|
15
|
-
function B(c, t, e) {
|
|
16
|
-
for (let i = 0; i < e.length; i++)
|
|
17
|
-
c.setUint8(t + i, e.charCodeAt(i));
|
|
18
|
-
}
|
|
19
|
-
class R {
|
|
20
|
-
text;
|
|
21
|
-
phonemes;
|
|
22
|
-
samples;
|
|
23
|
-
sampleRate;
|
|
24
|
-
duration;
|
|
25
|
-
durationMs;
|
|
26
|
-
voice;
|
|
27
|
-
speed;
|
|
28
|
-
wordBoundaries;
|
|
29
|
-
rawBuffer;
|
|
30
|
-
mimeType;
|
|
31
|
-
_wavBuffer = null;
|
|
32
|
-
_blob = null;
|
|
33
|
-
_objectUrl = null;
|
|
34
|
-
_dataUrl = null;
|
|
35
|
-
constructor(t) {
|
|
36
|
-
this.text = t.text, this.phonemes = t.phonemes || "", this.samples = t.samples ?? new Float32Array(0), this.sampleRate = t.sampleRate ?? 24e3, this.voice = t.voice ?? "af_heart", this.speed = t.speed ?? 1, this.wordBoundaries = t.wordBoundaries, this.rawBuffer = t.rawBuffer, this.mimeType = t.mimeType || (t.rawBuffer ? "audio/mpeg" : "audio/wav"), t.durationMs !== void 0 ? (this.durationMs = t.durationMs, this.duration = t.duration ?? t.durationMs / 1e3) : t.duration !== void 0 ? (this.duration = t.duration, this.durationMs = t.duration * 1e3) : (this.duration = this.samples.length > 0 ? this.samples.length / this.sampleRate : 0, this.durationMs = this.duration * 1e3);
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Converts raw PCM samples or decodes underlying rawBuffer into a Web Audio [[AudioBuffer]].
|
|
40
|
-
*/
|
|
41
|
-
async getOrDecodeAudioBuffer(t) {
|
|
42
|
-
if (this.samples && this.samples.length > 0)
|
|
43
|
-
return this.toAudioBuffer(t);
|
|
44
|
-
if (this.rawBuffer)
|
|
45
|
-
try {
|
|
46
|
-
const e = await t.decodeAudioData(this.rawBuffer.slice(0));
|
|
47
|
-
return this.samples = e.getChannelData(0), this.sampleRate = e.sampleRate, e;
|
|
48
|
-
} catch (e) {
|
|
49
|
-
console.warn("[TTSAudio] Failed to decode rawBuffer via context.decodeAudioData:", e);
|
|
50
|
-
}
|
|
51
|
-
return this.toAudioBuffer(t);
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Converts raw PCM samples to a Web Audio [[AudioBuffer]].
|
|
55
|
-
*/
|
|
56
|
-
toAudioBuffer(t) {
|
|
57
|
-
const e = t || (typeof window < "u" ? window.__retiam_audio_context : null);
|
|
58
|
-
if (e) {
|
|
59
|
-
const s = e.createBuffer(1, Math.max(1, this.samples.length), this.sampleRate);
|
|
60
|
-
return s.getChannelData(0).set(this.samples), s;
|
|
61
|
-
}
|
|
62
|
-
const i = typeof window < "u" ? window.AudioContext || window.webkitAudioContext : null;
|
|
63
|
-
if (i) {
|
|
64
|
-
const a = new i().createBuffer(1, Math.max(1, this.samples.length), this.sampleRate);
|
|
65
|
-
return a.getChannelData(0).set(this.samples), a;
|
|
66
|
-
}
|
|
67
|
-
throw new Error("[TTSAudio] Cannot create AudioBuffer: No AudioContext available in this environment.");
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Returns an ArrayBuffer containing encoded 16-bit PCM WAV audio.
|
|
71
|
-
*/
|
|
72
|
-
toWav() {
|
|
73
|
-
return this._wavBuffer || (this.rawBuffer && this.mimeType.includes("wav") ? this._wavBuffer = this.rawBuffer : this._wavBuffer = Y(this.samples, this.sampleRate)), this._wavBuffer;
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Returns a standard WAV or MP3 [[Blob]].
|
|
77
|
-
*/
|
|
78
|
-
toBlob() {
|
|
79
|
-
return this._blob || (this.rawBuffer ? this._blob = new Blob([this.rawBuffer], { type: this.mimeType }) : this._blob = new Blob([this.toWav()], { type: "audio/wav" })), this._blob;
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Generates a base64 Data URL ("data:audio/...;base64,...").
|
|
83
|
-
*/
|
|
84
|
-
toDataUrl() {
|
|
85
|
-
if (!this._dataUrl) {
|
|
86
|
-
const t = this.rawBuffer ? new Uint8Array(this.rawBuffer) : new Uint8Array(this.toWav());
|
|
87
|
-
let e = "";
|
|
88
|
-
const i = t.byteLength;
|
|
89
|
-
for (let a = 0; a < i; a++)
|
|
90
|
-
e += String.fromCharCode(t[a]);
|
|
91
|
-
const s = typeof btoa < "u" ? btoa(e) : Buffer.from(e, "binary").toString("base64");
|
|
92
|
-
this._dataUrl = `data:${this.mimeType};base64,${s}`;
|
|
93
|
-
}
|
|
94
|
-
return this._dataUrl;
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Creates or returns a cached Object URL ("blob:...") for this audio.
|
|
98
|
-
*/
|
|
99
|
-
toObjectUrl() {
|
|
100
|
-
if (!this._objectUrl)
|
|
101
|
-
if (typeof URL < "u" && typeof URL.createObjectURL == "function")
|
|
102
|
-
this._objectUrl = URL.createObjectURL(this.toBlob());
|
|
103
|
-
else
|
|
104
|
-
return this.toDataUrl();
|
|
105
|
-
return this._objectUrl;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Revokes any generated Object URL to release memory.
|
|
109
|
-
*/
|
|
110
|
-
revokeObjectUrl() {
|
|
111
|
-
this._objectUrl && typeof URL < "u" && typeof URL.revokeObjectURL == "function" && (URL.revokeObjectURL(this._objectUrl), this._objectUrl = null);
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Triggers a browser download or writes out the audio file.
|
|
115
|
-
*/
|
|
116
|
-
save(t) {
|
|
117
|
-
const e = this.mimeType.includes("mpeg") || this.mimeType.includes("mp3") ? ".mp3" : ".wav", i = `speech${e}`, s = t ? t.includes(".") ? t : `${t}${e}` : i;
|
|
118
|
-
if (typeof document < "u") {
|
|
119
|
-
const a = this.toObjectUrl(), r = document.createElement("a");
|
|
120
|
-
r.style.display = "none", r.href = a, r.download = s, document.body.appendChild(r), r.click(), setTimeout(() => {
|
|
121
|
-
document.body.removeChild(r);
|
|
122
|
-
}, 100);
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
try {
|
|
126
|
-
const a = globalThis.require ? globalThis.require("fs") : null;
|
|
127
|
-
if (a) {
|
|
128
|
-
const r = this.rawBuffer ? Buffer.from(this.rawBuffer) : Buffer.from(this.toWav());
|
|
129
|
-
a.writeFileSync(s, r);
|
|
130
|
-
}
|
|
131
|
-
} catch (a) {
|
|
132
|
-
console.warn(`[TTSAudio] Unable to save file ${s}:`, a);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Plays the audio through Retiam's [[AudioManager]] on the dedicated voiceover bus
|
|
137
|
-
* with automatic BGM ducking and timeline synchronization.
|
|
138
|
-
*/
|
|
139
|
-
async play(t, e = {}) {
|
|
140
|
-
let i;
|
|
141
|
-
return t && (typeof t.playVoiceover == "function" ? i = t : t.audio && typeof t.audio.playVoiceover == "function" && (i = t.audio)), i ? i.playVoiceover(this, e) : (console.warn("[TTSAudio] No active AudioManager provided to play(). Creating standalone audio playback."), this._playStandalone(e));
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Standalone playback fallback if no Retiam scene/AudioManager is provided.
|
|
145
|
-
*/
|
|
146
|
-
_playStandalone(t) {
|
|
147
|
-
return new Promise((e) => {
|
|
148
|
-
if (typeof Audio > "u") {
|
|
149
|
-
e(this.durationMs);
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
const i = new Audio(this.toObjectUrl());
|
|
153
|
-
i.volume = Math.max(0, Math.min(1, t.volume ?? 1)), i.onended = () => {
|
|
154
|
-
const s = t.padEndMs ?? 100;
|
|
155
|
-
setTimeout(() => e(this.durationMs + s), s);
|
|
156
|
-
}, i.onerror = () => {
|
|
157
|
-
e(this.durationMs);
|
|
158
|
-
}, i.play().catch(() => {
|
|
159
|
-
e(this.durationMs);
|
|
160
|
-
});
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
class C {
|
|
165
|
-
maxSize;
|
|
166
|
-
cache = /* @__PURE__ */ new Map();
|
|
167
|
-
constructor(t = 128) {
|
|
168
|
-
this.maxSize = Math.max(1, t);
|
|
169
|
-
}
|
|
170
|
-
static createKey(t, e, i) {
|
|
171
|
-
return `${e}:${i.toFixed(2)}:${t.trim().toLowerCase()}`;
|
|
172
|
-
}
|
|
173
|
-
get(t, e, i) {
|
|
174
|
-
const s = C.createKey(t, e, i), a = this.cache.get(s);
|
|
175
|
-
if (a)
|
|
176
|
-
return this.cache.delete(s), this.cache.set(s, a), a;
|
|
177
|
-
}
|
|
178
|
-
set(t, e, i, s) {
|
|
179
|
-
const a = C.createKey(t, e, i);
|
|
180
|
-
if (this.cache.has(a))
|
|
181
|
-
this.cache.delete(a);
|
|
182
|
-
else if (this.cache.size >= this.maxSize) {
|
|
183
|
-
const r = this.cache.keys().next().value;
|
|
184
|
-
r && (this.cache.get(r)?.revokeObjectUrl(), this.cache.delete(r));
|
|
185
|
-
}
|
|
186
|
-
this.cache.set(a, s);
|
|
187
|
-
}
|
|
188
|
-
has(t, e, i) {
|
|
189
|
-
return this.cache.has(C.createKey(t, e, i));
|
|
190
|
-
}
|
|
191
|
-
clear() {
|
|
192
|
-
for (const t of this.cache.values())
|
|
193
|
-
t.revokeObjectUrl();
|
|
194
|
-
this.cache.clear();
|
|
195
|
-
}
|
|
196
|
-
get size() {
|
|
197
|
-
return this.cache.size;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
class Z {
|
|
201
|
-
name = "msedge-tts";
|
|
202
|
-
_config;
|
|
203
|
-
_cache;
|
|
204
|
-
_isBrowser;
|
|
205
|
-
_nodeClients = [];
|
|
206
|
-
constructor(t = {}) {
|
|
207
|
-
this._isBrowser = typeof window < "u" && typeof window.document < "u", this._config = {
|
|
208
|
-
cacheAudio: t.cacheAudio !== !1,
|
|
209
|
-
maxCacheSize: t.maxCacheSize ?? 128,
|
|
210
|
-
msedgeEndpoint: t.msedgeEndpoint || "/api/tts/msedge"
|
|
211
|
-
}, this._cache = new C(this._config.maxCacheSize);
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Microsoft Edge TTS is cloud-hosted and always ready immediately without local weight loading.
|
|
215
|
-
*/
|
|
216
|
-
get isReady() {
|
|
217
|
-
return !0;
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* Initializes provider configuration. No weight downloading required.
|
|
221
|
-
*/
|
|
222
|
-
async init(t) {
|
|
223
|
-
t?.msedgeEndpoint && (this._config.msedgeEndpoint = t.msedgeEndpoint), t?.cacheAudio !== void 0 && (this._config.cacheAudio = t.cacheAudio);
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Synthesizes text into high-fidelity speech audio using Microsoft Edge neural voices.
|
|
227
|
-
*/
|
|
228
|
-
async synthesize(t, e = {}) {
|
|
229
|
-
const i = t.trim(), s = e.voice || M, a = Math.max(0.25, Math.min(4, e.speed ?? 1));
|
|
230
|
-
if (!i)
|
|
231
|
-
return new R({
|
|
232
|
-
text: "",
|
|
233
|
-
samples: new Float32Array(0),
|
|
234
|
-
sampleRate: 24e3,
|
|
235
|
-
voice: s,
|
|
236
|
-
speed: a
|
|
237
|
-
});
|
|
238
|
-
if (this._config.cacheAudio) {
|
|
239
|
-
const n = this._cache.get(i, s, a);
|
|
240
|
-
if (n)
|
|
241
|
-
return n;
|
|
242
|
-
}
|
|
243
|
-
const r = this._isBrowser ? await this._synthesizeBrowser(i, s, a, e) : await this._synthesizeNode(i, s, a, e);
|
|
244
|
-
return this._config.cacheAudio && this._cache.set(i, s, a, r), r;
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Node.js execution: directly calls the msedge-tts package in-process.
|
|
248
|
-
*/
|
|
249
|
-
async _synthesizeNode(t, e, i, s) {
|
|
250
|
-
const { MsEdgeTTS: a, OUTPUT_FORMAT: r } = await import("msedge-tts");
|
|
251
|
-
D(a);
|
|
252
|
-
const n = new a(), o = Math.round((i - 1) * 100), u = o >= 0 ? `+${o}%` : `${o}%`;
|
|
253
|
-
await n.setMetadata(e, r.AUDIO_24KHZ_96KBITRATE_MONO_MP3, {
|
|
254
|
-
wordBoundaryEnabled: !0,
|
|
255
|
-
sentenceBoundaryEnabled: !0
|
|
256
|
-
});
|
|
257
|
-
const h = {
|
|
258
|
-
rate: u
|
|
259
|
-
};
|
|
260
|
-
if (s.pitch !== void 0 && (h.pitch = s.pitch), s.volume !== void 0 && s.volume !== null) {
|
|
261
|
-
if (typeof s.volume == "number")
|
|
262
|
-
h.volume = s.volume <= 1 && s.volume > 0 ? Math.round(s.volume * 100) : Math.round(s.volume);
|
|
263
|
-
else if (typeof s.volume == "string") {
|
|
264
|
-
const b = parseFloat(s.volume);
|
|
265
|
-
!isNaN(b) && !s.volume.includes("%") && !s.volume.startsWith("+") && !s.volume.startsWith("-") ? h.volume = b <= 1 && b > 0 ? Math.round(b * 100) : Math.round(b) : h.volume = s.volume;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
const { audioStream: d, metadataStream: p } = n.toStream(t, h), f = [], x = [];
|
|
269
|
-
p && p.on("data", (b) => {
|
|
270
|
-
try {
|
|
271
|
-
const A = b.toString(), S = JSON.parse(A);
|
|
272
|
-
if (Array.isArray(S?.Metadata)) {
|
|
273
|
-
for (const v of S.Metadata)
|
|
274
|
-
if (v.Type === "WordBoundary" && v.Data) {
|
|
275
|
-
const U = (v.Data.Offset || 0) / 1e4, z = (v.Data.Duration || 0) / 1e4, W = v.Data.text?.Text || "";
|
|
276
|
-
x.push({
|
|
277
|
-
text: W,
|
|
278
|
-
offsetMs: U,
|
|
279
|
-
durationMs: z,
|
|
280
|
-
boundaryType: "WordBoundary"
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
} catch {
|
|
285
|
-
}
|
|
286
|
-
}), await new Promise((b, A) => {
|
|
287
|
-
d.on("data", (S) => {
|
|
288
|
-
f.push(S);
|
|
289
|
-
}), d.on("end", () => {
|
|
290
|
-
b();
|
|
291
|
-
}), d.on("error", (S) => {
|
|
292
|
-
A(S);
|
|
293
|
-
});
|
|
294
|
-
});
|
|
295
|
-
const k = Buffer.concat(f), g = k.buffer.slice(
|
|
296
|
-
k.byteOffset,
|
|
297
|
-
k.byteOffset + k.byteLength
|
|
298
|
-
), m = F(new Uint8Array(g));
|
|
299
|
-
return new R({
|
|
300
|
-
text: t,
|
|
301
|
-
rawBuffer: g,
|
|
302
|
-
mimeType: "audio/mpeg",
|
|
303
|
-
samples: new Float32Array(m.totalSamples),
|
|
304
|
-
sampleRate: m.sampleRate,
|
|
305
|
-
duration: m.durationSec,
|
|
306
|
-
durationMs: m.durationMs,
|
|
307
|
-
voice: e,
|
|
308
|
-
speed: i,
|
|
309
|
-
wordBoundaries: x.length > 0 ? x : void 0
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* Browser execution: invokes the Vite development server proxy endpoint.
|
|
314
|
-
*/
|
|
315
|
-
async _synthesizeBrowser(t, e, i, s) {
|
|
316
|
-
const a = `${this._config.msedgeEndpoint}/synthesize`, r = await fetch(a, {
|
|
317
|
-
method: "POST",
|
|
318
|
-
headers: {
|
|
319
|
-
"Content-Type": "application/json"
|
|
320
|
-
},
|
|
321
|
-
body: JSON.stringify({
|
|
322
|
-
text: t,
|
|
323
|
-
voice: e,
|
|
324
|
-
speed: i,
|
|
325
|
-
pitch: s.pitch,
|
|
326
|
-
volume: s.volume
|
|
327
|
-
})
|
|
328
|
-
});
|
|
329
|
-
if (!r.ok)
|
|
330
|
-
throw new Error(`[MsEdgeTTSProvider] Synthesis failed: HTTP ${r.status} ${r.statusText}`);
|
|
331
|
-
const n = r.headers.get("X-Audio-Duration-Ms"), o = r.headers.get("X-Word-Boundaries");
|
|
332
|
-
let u;
|
|
333
|
-
if (o)
|
|
334
|
-
try {
|
|
335
|
-
u = JSON.parse(decodeURIComponent(o));
|
|
336
|
-
} catch {
|
|
337
|
-
}
|
|
338
|
-
const h = await r.arrayBuffer();
|
|
339
|
-
let d = new Float32Array(0), p = 24e3, f = n ? parseFloat(n) : 0;
|
|
340
|
-
const x = window.AudioContext || window.webkitAudioContext;
|
|
341
|
-
if (x) {
|
|
342
|
-
const k = !!window.__retiam_audio_context, g = window.__retiam_audio_context || new x();
|
|
343
|
-
try {
|
|
344
|
-
const m = await g.decodeAudioData(h.slice(0));
|
|
345
|
-
d = m.getChannelData(0), p = m.sampleRate, f || (f = m.duration * 1e3);
|
|
346
|
-
} catch (m) {
|
|
347
|
-
console.warn("[MsEdgeTTSProvider] AudioContext decode deferred, using raw MP3 buffer:", m);
|
|
348
|
-
} finally {
|
|
349
|
-
!k && g && typeof g.close == "function" && g.close().catch(() => {
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
if (d.length === 0 || !f) {
|
|
354
|
-
const k = F(new Uint8Array(h));
|
|
355
|
-
p = k.sampleRate, f || (f = k.durationMs);
|
|
356
|
-
}
|
|
357
|
-
return new R({
|
|
358
|
-
text: t,
|
|
359
|
-
rawBuffer: h,
|
|
360
|
-
mimeType: "audio/mpeg",
|
|
361
|
-
samples: d,
|
|
362
|
-
sampleRate: p,
|
|
363
|
-
duration: f / 1e3,
|
|
364
|
-
durationMs: f,
|
|
365
|
-
voice: e,
|
|
366
|
-
speed: i,
|
|
367
|
-
wordBoundaries: u
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
/**
|
|
371
|
-
* Progressively streams synthesized speech sentence by sentence.
|
|
372
|
-
*/
|
|
373
|
-
async *stream(t, e = {}) {
|
|
374
|
-
const i = e.voice || M, s = Math.max(0.25, Math.min(4, e.speed ?? 1)), a = e.splitPattern || /([.!?;]+(?:\s+|$))/g, r = [], n = t.split(a);
|
|
375
|
-
for (let h = 0; h < n.length; h += 2) {
|
|
376
|
-
const d = (n[h] + (n[h + 1] || "")).trim();
|
|
377
|
-
d.length > 0 && r.push(d);
|
|
378
|
-
}
|
|
379
|
-
r.length === 0 && r.push(t.trim());
|
|
380
|
-
let o = 0, u = 0;
|
|
381
|
-
for (const h of r) {
|
|
382
|
-
const d = await this.synthesize(h, {
|
|
383
|
-
...e,
|
|
384
|
-
voice: i,
|
|
385
|
-
speed: s
|
|
386
|
-
});
|
|
387
|
-
yield {
|
|
388
|
-
text: h,
|
|
389
|
-
phonemes: "",
|
|
390
|
-
audio: d,
|
|
391
|
-
chunkIndex: o++,
|
|
392
|
-
startTimeMs: u
|
|
393
|
-
}, u += d.durationMs;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
/**
|
|
397
|
-
* Returns curated Microsoft Edge neural voices immediately.
|
|
398
|
-
*/
|
|
399
|
-
getVoices() {
|
|
400
|
-
return L();
|
|
401
|
-
}
|
|
402
|
-
/**
|
|
403
|
-
* Fetches the complete live catalog of all 320+ Microsoft Edge neural voices.
|
|
404
|
-
*/
|
|
405
|
-
async fetchVoices() {
|
|
406
|
-
if (this._isBrowser)
|
|
407
|
-
try {
|
|
408
|
-
const t = await fetch(`${this._config.msedgeEndpoint}/voices`);
|
|
409
|
-
if (t.ok) {
|
|
410
|
-
const e = await t.json();
|
|
411
|
-
if (Array.isArray(e))
|
|
412
|
-
return V(e), e;
|
|
413
|
-
}
|
|
414
|
-
} catch {
|
|
415
|
-
}
|
|
416
|
-
return P();
|
|
417
|
-
}
|
|
418
|
-
/**
|
|
419
|
-
* Clears cached utterances.
|
|
420
|
-
*/
|
|
421
|
-
clearCache() {
|
|
422
|
-
this._cache.clear();
|
|
423
|
-
}
|
|
424
|
-
/**
|
|
425
|
-
* Disposes resources and releases cache.
|
|
426
|
-
*/
|
|
427
|
-
async dispose() {
|
|
428
|
-
this.clearCache();
|
|
429
|
-
for (const t of this._nodeClients)
|
|
430
|
-
try {
|
|
431
|
-
t.close();
|
|
432
|
-
} catch {
|
|
433
|
-
}
|
|
434
|
-
this._nodeClients = [];
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
class O {
|
|
438
|
-
_provider;
|
|
439
|
-
_config;
|
|
440
|
-
constructor(t = {}, e) {
|
|
441
|
-
this._config = t, this._provider = e ?? new Z(t);
|
|
442
|
-
}
|
|
443
|
-
/** The active speech provider. */
|
|
444
|
-
get provider() {
|
|
445
|
-
return this._provider;
|
|
446
|
-
}
|
|
447
|
-
/** Whether the active provider is ready. */
|
|
448
|
-
get isReady() {
|
|
449
|
-
return this._provider.isReady;
|
|
450
|
-
}
|
|
451
|
-
/** Replaces the active provider instance. */
|
|
452
|
-
setProvider(t) {
|
|
453
|
-
this._provider = t;
|
|
454
|
-
}
|
|
455
|
-
/** Initializes the active provider. */
|
|
456
|
-
async init(t) {
|
|
457
|
-
t && (this._config = t), await this._provider.init(t ?? this._config);
|
|
458
|
-
}
|
|
459
|
-
/** Synthesizes text with the active provider. */
|
|
460
|
-
async synthesize(t, e = {}) {
|
|
461
|
-
return this._provider.synthesize(t, e);
|
|
462
|
-
}
|
|
463
|
-
/** Streams synthesized audio sentence by sentence. */
|
|
464
|
-
async *stream(t, e = {}) {
|
|
465
|
-
yield* this._provider.stream(t, e);
|
|
466
|
-
}
|
|
467
|
-
/** Synthesizes and plays speech through a Scene or AudioManager. */
|
|
468
|
-
async speak(t, e, i = {}) {
|
|
469
|
-
const s = await this.synthesize(e, i), a = await s.play(t, i);
|
|
470
|
-
return { audio: s, durationMs: a };
|
|
471
|
-
}
|
|
472
|
-
/** Returns voices exposed by the active provider. */
|
|
473
|
-
getVoices() {
|
|
474
|
-
return this._provider.getVoices();
|
|
475
|
-
}
|
|
476
|
-
/** Finds a voice by ID, display name, or partial match. */
|
|
477
|
-
findVoice(t) {
|
|
478
|
-
const e = t.toLowerCase();
|
|
479
|
-
return this._provider.getVoices().find(
|
|
480
|
-
(i) => i.id.toLowerCase() === e || i.name.toLowerCase() === e || i.id.toLowerCase().includes(e) || i.name.toLowerCase().includes(e)
|
|
481
|
-
) ?? N(t);
|
|
482
|
-
}
|
|
483
|
-
/** Releases provider resources and cached audio. */
|
|
484
|
-
async dispose() {
|
|
485
|
-
await this._provider.dispose();
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
let E = null;
|
|
489
|
-
function w() {
|
|
490
|
-
return E || (E = new O()), E;
|
|
491
|
-
}
|
|
492
|
-
class T {
|
|
493
|
-
/** Replaces the global client configuration and restores the built-in provider. */
|
|
494
|
-
static configure(t) {
|
|
495
|
-
E = new O(t);
|
|
496
|
-
}
|
|
497
|
-
/** Initializes the global speech provider. */
|
|
498
|
-
static async init(t) {
|
|
499
|
-
t && this.configure(t), await w().init(t);
|
|
500
|
-
}
|
|
501
|
-
/** Replaces the global provider instance. */
|
|
502
|
-
static setProvider(t) {
|
|
503
|
-
w().setProvider(t);
|
|
504
|
-
}
|
|
505
|
-
/** Active global provider. */
|
|
506
|
-
static get provider() {
|
|
507
|
-
return w().provider;
|
|
508
|
-
}
|
|
509
|
-
/** Synthesizes text to speech. */
|
|
510
|
-
static async synthesize(t, e) {
|
|
511
|
-
return w().synthesize(t, e);
|
|
512
|
-
}
|
|
513
|
-
/** Explicit alias for Microsoft Edge neural synthesis. */
|
|
514
|
-
static async msedge(t, e) {
|
|
515
|
-
return w().synthesize(t, e);
|
|
516
|
-
}
|
|
517
|
-
/** Streams synthesized speech sentence by sentence. */
|
|
518
|
-
static async *stream(t, e) {
|
|
519
|
-
yield* w().stream(t, e);
|
|
520
|
-
}
|
|
521
|
-
/** Synthesizes and plays speech through a Scene or AudioManager. */
|
|
522
|
-
static async speak(t, e, i) {
|
|
523
|
-
return w().speak(t, e, i);
|
|
524
|
-
}
|
|
525
|
-
/** Returns the active provider's voices. */
|
|
526
|
-
static getVoices() {
|
|
527
|
-
return w().getVoices();
|
|
528
|
-
}
|
|
529
|
-
/** Finds a Microsoft Edge neural voice. */
|
|
530
|
-
static findVoice(t) {
|
|
531
|
-
return w().findVoice(t);
|
|
532
|
-
}
|
|
533
|
-
/** Recommended default voice. */
|
|
534
|
-
static DEFAULT_VOICE = M;
|
|
535
|
-
/** Explicit provider-specific alias for the recommended default voice. */
|
|
536
|
-
static DEFAULT_MSEDGE_VOICE = M;
|
|
537
|
-
}
|
|
538
|
-
class Q extends j {
|
|
6
|
+
class F extends x {
|
|
539
7
|
voice;
|
|
540
8
|
speed;
|
|
9
|
+
elevenlabs;
|
|
541
10
|
animateSpeaking;
|
|
542
11
|
pulseAmplitude;
|
|
543
12
|
pulseFrequency;
|
|
@@ -548,7 +17,7 @@ class Q extends j {
|
|
|
548
17
|
_baseScaleY = 1;
|
|
549
18
|
_baseScaleZ = 1;
|
|
550
19
|
constructor(t = {}) {
|
|
551
|
-
super(), this.voice = t.voice
|
|
20
|
+
super(), this.voice = t.voice, this.speed = t.speed, this.elevenlabs = t.elevenlabs, this.animateSpeaking = t.animateSpeaking !== !1, this.pulseAmplitude = t.pulseAmplitude ?? 0.05, this.pulseFrequency = t.pulseFrequency ?? 4, this.onSpeechStart = t.onSpeechStart, this.onSpeechEnd = t.onSpeechEnd;
|
|
552
21
|
}
|
|
553
22
|
onInit() {
|
|
554
23
|
if (this.entity?.transform) {
|
|
@@ -569,20 +38,29 @@ class Q extends j {
|
|
|
569
38
|
* @param overrides Voice and playback option overrides.
|
|
570
39
|
* @returns Synthesized [[TTSAudioResult]] and total speech duration in milliseconds.
|
|
571
40
|
*/
|
|
572
|
-
async speak(t,
|
|
573
|
-
const
|
|
41
|
+
async speak(t, i = {}) {
|
|
42
|
+
const e = this.entity?.scene, s = i.voice ?? this.voice, c = i.speed ?? this.speed;
|
|
574
43
|
if (this.entity?.transform) {
|
|
575
|
-
const
|
|
576
|
-
this._baseScaleX =
|
|
44
|
+
const h = this.entity.transform.getScale();
|
|
45
|
+
this._baseScaleX = h.x, this._baseScaleY = h.y, this._baseScaleZ = h.z;
|
|
577
46
|
}
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
47
|
+
const l = await p.synthesize(t, {
|
|
48
|
+
voice: s,
|
|
49
|
+
speed: c,
|
|
50
|
+
...i,
|
|
51
|
+
elevenlabs: {
|
|
52
|
+
...this.elevenlabs,
|
|
53
|
+
...i.elevenlabs,
|
|
54
|
+
voiceSettings: { ...this.elevenlabs?.voiceSettings, ...i.elevenlabs?.voiceSettings }
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
this._isSpeaking = !0, this.onSpeechStart?.(t, l);
|
|
58
|
+
const o = await l.play(e, i);
|
|
59
|
+
return this._isSpeaking = !1, this._restoreBaseScale(), this.onSpeechEnd?.(t), { audio: l, durationMs: o };
|
|
582
60
|
}
|
|
583
|
-
update(t,
|
|
61
|
+
update(t, i) {
|
|
584
62
|
if (!(!this.enabled || !this.entity?.transform) && this._isSpeaking && this.animateSpeaking) {
|
|
585
|
-
const
|
|
63
|
+
const e = t / 1e3 * this.pulseFrequency * Math.PI * 2, s = 1 + Math.sin(e) * this.pulseAmplitude;
|
|
586
64
|
this.entity.transform.setScale([
|
|
587
65
|
this._baseScaleX * s,
|
|
588
66
|
this._baseScaleY * s,
|
|
@@ -601,14 +79,14 @@ class Q extends j {
|
|
|
601
79
|
this._isSpeaking = !1, this._restoreBaseScale();
|
|
602
80
|
}
|
|
603
81
|
}
|
|
604
|
-
const
|
|
82
|
+
const v = {
|
|
605
83
|
obsidian: {
|
|
606
84
|
fontSize: 0.38,
|
|
607
|
-
textColor:
|
|
85
|
+
textColor: a("#F8FAFC"),
|
|
608
86
|
textOpacity: 1,
|
|
609
|
-
backdropColor:
|
|
87
|
+
backdropColor: a("#060913"),
|
|
610
88
|
backdropOpacity: 0.85,
|
|
611
|
-
strokeColor: new
|
|
89
|
+
strokeColor: new d(0.22, 0.74, 0.97, 0.35),
|
|
612
90
|
strokeWidth: 0.02,
|
|
613
91
|
cornerRadius: 0.22,
|
|
614
92
|
pillShape: !1,
|
|
@@ -619,11 +97,11 @@ const q = {
|
|
|
619
97
|
},
|
|
620
98
|
pill: {
|
|
621
99
|
fontSize: 0.38,
|
|
622
|
-
textColor:
|
|
100
|
+
textColor: a("#FFFFFF"),
|
|
623
101
|
textOpacity: 1,
|
|
624
|
-
backdropColor:
|
|
102
|
+
backdropColor: a("#080D1A"),
|
|
625
103
|
backdropOpacity: 0.88,
|
|
626
|
-
strokeColor: new
|
|
104
|
+
strokeColor: new d(0.38, 0.52, 0.75, 0.4),
|
|
627
105
|
strokeWidth: 0.02,
|
|
628
106
|
pillShape: !0,
|
|
629
107
|
padding: [0.7, 0.28],
|
|
@@ -633,11 +111,11 @@ const q = {
|
|
|
633
111
|
},
|
|
634
112
|
minimal: {
|
|
635
113
|
fontSize: 0.38,
|
|
636
|
-
textColor:
|
|
114
|
+
textColor: a("#F1F5F9"),
|
|
637
115
|
textOpacity: 1,
|
|
638
|
-
backdropColor:
|
|
116
|
+
backdropColor: a("#090D16"),
|
|
639
117
|
backdropOpacity: 0.8,
|
|
640
|
-
strokeColor:
|
|
118
|
+
strokeColor: d.NONE,
|
|
641
119
|
strokeWidth: 0,
|
|
642
120
|
cornerRadius: 0.16,
|
|
643
121
|
pillShape: !1,
|
|
@@ -648,11 +126,11 @@ const q = {
|
|
|
648
126
|
},
|
|
649
127
|
broadcast: {
|
|
650
128
|
fontSize: 0.38,
|
|
651
|
-
textColor:
|
|
129
|
+
textColor: a("#FFFFFF"),
|
|
652
130
|
textOpacity: 1,
|
|
653
|
-
backdropColor:
|
|
131
|
+
backdropColor: a("#000000"),
|
|
654
132
|
backdropOpacity: 0.92,
|
|
655
|
-
strokeColor:
|
|
133
|
+
strokeColor: d.NONE,
|
|
656
134
|
strokeWidth: 0,
|
|
657
135
|
cornerRadius: 0.08,
|
|
658
136
|
pillShape: !1,
|
|
@@ -663,11 +141,11 @@ const q = {
|
|
|
663
141
|
},
|
|
664
142
|
neon: {
|
|
665
143
|
fontSize: 0.38,
|
|
666
|
-
textColor:
|
|
144
|
+
textColor: a("#E0F2FE"),
|
|
667
145
|
textOpacity: 1,
|
|
668
|
-
backdropColor:
|
|
146
|
+
backdropColor: a("#030712"),
|
|
669
147
|
backdropOpacity: 0.92,
|
|
670
|
-
strokeColor:
|
|
148
|
+
strokeColor: a("#00F0FF"),
|
|
671
149
|
strokeWidth: 0.025,
|
|
672
150
|
cornerRadius: 0.24,
|
|
673
151
|
pillShape: !1,
|
|
@@ -678,11 +156,11 @@ const q = {
|
|
|
678
156
|
},
|
|
679
157
|
glass: {
|
|
680
158
|
fontSize: 0.38,
|
|
681
|
-
textColor:
|
|
159
|
+
textColor: a("#FFFFFF"),
|
|
682
160
|
textOpacity: 1,
|
|
683
|
-
backdropColor: new
|
|
161
|
+
backdropColor: new d(0.05, 0.09, 0.15, 0.65),
|
|
684
162
|
backdropOpacity: 0.65,
|
|
685
|
-
strokeColor: new
|
|
163
|
+
strokeColor: new d(1, 1, 1, 0.25),
|
|
686
164
|
strokeWidth: 0.015,
|
|
687
165
|
cornerRadius: 0.25,
|
|
688
166
|
pillShape: !1,
|
|
@@ -693,11 +171,11 @@ const q = {
|
|
|
693
171
|
},
|
|
694
172
|
light: {
|
|
695
173
|
fontSize: 0.38,
|
|
696
|
-
textColor:
|
|
174
|
+
textColor: a("#0F172A"),
|
|
697
175
|
textOpacity: 1,
|
|
698
|
-
backdropColor:
|
|
176
|
+
backdropColor: a("#F8FAFC"),
|
|
699
177
|
backdropOpacity: 0.92,
|
|
700
|
-
strokeColor: new
|
|
178
|
+
strokeColor: new d(0.39, 0.45, 0.55, 0.25),
|
|
701
179
|
strokeWidth: 0.015,
|
|
702
180
|
cornerRadius: 0.2,
|
|
703
181
|
pillShape: !1,
|
|
@@ -707,18 +185,18 @@ const q = {
|
|
|
707
185
|
showBackdrop: !0
|
|
708
186
|
}
|
|
709
187
|
};
|
|
710
|
-
function
|
|
711
|
-
return
|
|
188
|
+
function n(r, t) {
|
|
189
|
+
return r ? typeof r == "string" ? a(r) : r : t;
|
|
712
190
|
}
|
|
713
|
-
class
|
|
191
|
+
class R extends b {
|
|
714
192
|
_textEntity;
|
|
715
193
|
_backdrop = null;
|
|
716
194
|
_fontSize = 0.38;
|
|
717
|
-
_textColor =
|
|
195
|
+
_textColor = a("#F8FAFC");
|
|
718
196
|
_textOpacity = 1;
|
|
719
|
-
_backdropColor =
|
|
197
|
+
_backdropColor = a("#060913");
|
|
720
198
|
_backdropOpacity = 0.85;
|
|
721
|
-
_strokeColor = new
|
|
199
|
+
_strokeColor = new d(0.22, 0.74, 0.97, 0.35);
|
|
722
200
|
_strokeWidth = 0.02;
|
|
723
201
|
_showBorder = !0;
|
|
724
202
|
_cornerRadius = 0.22;
|
|
@@ -731,14 +209,14 @@ class tt extends H {
|
|
|
731
209
|
_clearTimeout = null;
|
|
732
210
|
constructor(t = {}) {
|
|
733
211
|
super();
|
|
734
|
-
const
|
|
735
|
-
this._textEntity = new
|
|
212
|
+
const i = t.zIndex ?? 999;
|
|
213
|
+
this._textEntity = new m("", {
|
|
736
214
|
fontSize: this._fontSize,
|
|
737
215
|
fill: this._textColor,
|
|
738
|
-
anchor:
|
|
739
|
-
}), this._textEntity.paint.z_index =
|
|
740
|
-
const
|
|
741
|
-
this.transform.position.set([0,
|
|
216
|
+
anchor: S
|
|
217
|
+
}), this._textEntity.paint.z_index = i + 1, this._backdrop = new g(2, 1, { cornerRadius: this._cornerRadius }), this._backdrop.paint.fill = this._backdropColor, this._backdrop.paint.stroke = this._strokeColor, this._backdrop.paint.strokeWidth = this._strokeWidth, this._backdrop.paint.z_index = i, this._backdrop.paint.opacity = 0, this.add(this._backdrop), this.add(this._textEntity), this.paint.z_index = i, this.applyOptions(t);
|
|
218
|
+
const e = t.bottomOffset ?? -5;
|
|
219
|
+
this.transform.position.set([0, e, 0]);
|
|
742
220
|
}
|
|
743
221
|
/** Access the underlying SDFText entity for advanced low-level styling */
|
|
744
222
|
get textEntity() {
|
|
@@ -752,8 +230,8 @@ class tt extends H {
|
|
|
752
230
|
* Applies a complete configuration options object or partial updates.
|
|
753
231
|
*/
|
|
754
232
|
applyOptions(t = {}) {
|
|
755
|
-
const
|
|
756
|
-
return
|
|
233
|
+
const e = { ...t.preset ? v[t.preset] : {}, ...t };
|
|
234
|
+
return e.fontSize !== void 0 && (this._fontSize = e.fontSize, this._textEntity.fontSize = e.fontSize), e.textColor !== void 0 && (this._textColor = n(e.textColor, this._textColor), this._textEntity.paint.fill = this._textColor), e.textOpacity !== void 0 && (this._textOpacity = e.textOpacity), e.backdropColor !== void 0 && (this._backdropColor = n(e.backdropColor, this._backdropColor)), e.backdropOpacity !== void 0 && (this._backdropOpacity = e.backdropOpacity), e.strokeColor !== void 0 && (this._strokeColor = n(e.strokeColor, this._strokeColor)), e.strokeWidth !== void 0 && (this._strokeWidth = e.strokeWidth), e.showBorder !== void 0 && (this._showBorder = e.showBorder), e.cornerRadius !== void 0 && (this._cornerRadius = e.cornerRadius), e.pillShape !== void 0 && (this._pillShape = e.pillShape), e.showBackdrop !== void 0 && (this._showBackdrop = e.showBackdrop), e.padding !== void 0 && (typeof e.padding == "number" ? this._padding = [e.padding, e.padding] : Array.isArray(e.padding) && (this._padding = [e.padding[0], e.padding[1]])), e.maxWidth !== void 0 && (this._maxWidth = e.maxWidth), e.maxLineChars !== void 0 && (this._maxLineChars = Math.max(15, e.maxLineChars)), e.lineHeight !== void 0 && (this._lineHeight = e.lineHeight), e.bottomOffset !== void 0 && this.setBottomOffset(e.bottomOffset), this._textEntity.text && this._updateBackdropDimensions(this._textEntity.text), this.version++, this;
|
|
757
235
|
}
|
|
758
236
|
/** Set an aesthetic preset theme */
|
|
759
237
|
setPreset(t) {
|
|
@@ -768,39 +246,39 @@ class tt extends H {
|
|
|
768
246
|
return this._pillShape = t, this._textEntity.text && this._updateBackdropDimensions(this._textEntity.text), this;
|
|
769
247
|
}
|
|
770
248
|
/** Set the inner horizontal and vertical padding around the subtitle text */
|
|
771
|
-
setPadding(t,
|
|
772
|
-
return this._padding = [t,
|
|
249
|
+
setPadding(t, i = t) {
|
|
250
|
+
return this._padding = [t, i], this._textEntity.text && this._updateBackdropDimensions(this._textEntity.text), this;
|
|
773
251
|
}
|
|
774
252
|
/** Configure backdrop fill color and opacity */
|
|
775
|
-
setBackdropColor(t,
|
|
776
|
-
return this._backdropColor =
|
|
253
|
+
setBackdropColor(t, i) {
|
|
254
|
+
return this._backdropColor = n(t, this._backdropColor), i !== void 0 && (this._backdropOpacity = Math.max(0, Math.min(1, i))), this._backdrop && (this._backdrop.paint.fill = this._backdropColor, this._textEntity.text && (this._backdrop.paint.opacity = this._backdropOpacity), this._backdrop.invalidate()), this;
|
|
777
255
|
}
|
|
778
|
-
setStroke(t,
|
|
779
|
-
return typeof t == "number" ? (this._strokeWidth = t, this._showBorder = t > 0,
|
|
256
|
+
setStroke(t, i) {
|
|
257
|
+
return typeof t == "number" ? (this._strokeWidth = t, this._showBorder = t > 0, i !== void 0 && (typeof i == "string" || i instanceof d) && (this._strokeColor = n(i, this._strokeColor))) : (this._strokeColor = n(t, this._strokeColor), typeof i == "number" && (this._strokeWidth = i, this._showBorder = i > 0)), this._backdrop && (this._backdrop.paint.stroke = this._strokeColor, this._backdrop.paint.strokeWidth = this._strokeWidth, this._backdrop.invalidate()), this;
|
|
780
258
|
}
|
|
781
259
|
/** Set backdrop border stroke color */
|
|
782
260
|
setStrokeColor(t) {
|
|
783
|
-
return this._strokeColor =
|
|
261
|
+
return this._strokeColor = n(t, this._strokeColor), this._backdrop && (this._backdrop.paint.stroke = this._strokeColor, this._backdrop.invalidate()), this;
|
|
784
262
|
}
|
|
785
263
|
/** Set backdrop border stroke width */
|
|
786
264
|
setStrokeWidth(t) {
|
|
787
265
|
return this._strokeWidth = t, this._showBorder = t > 0, this._backdrop && (this._backdrop.paint.strokeWidth = this._strokeWidth, this._backdrop.invalidate()), this;
|
|
788
266
|
}
|
|
789
267
|
/** Configure subtitle typography (font color and world unit font size) */
|
|
790
|
-
setTextColor(t,
|
|
791
|
-
return this._textColor =
|
|
268
|
+
setTextColor(t, i = 1) {
|
|
269
|
+
return this._textColor = n(t, this._textColor), this._textOpacity = i, this._textEntity.paint.fill = this._textColor, this._textEntity.paint.opacity = i, this.version++, this;
|
|
792
270
|
}
|
|
793
271
|
/** Set subtitle font size in world units */
|
|
794
272
|
setFontSize(t) {
|
|
795
273
|
return t > 0 && (this._fontSize = t, this._textEntity.fontSize = t, this._textEntity.text && this._updateBackdropDimensions(this._textEntity.text)), this;
|
|
796
274
|
}
|
|
797
275
|
/** Configure backdrop visual styling (colors, stroke border, corner radius) */
|
|
798
|
-
setBackdropStyle(t,
|
|
799
|
-
return this._backdropColor =
|
|
276
|
+
setBackdropStyle(t, i, e, s) {
|
|
277
|
+
return this._backdropColor = n(t, this._backdropColor), i !== void 0 && (this._strokeColor = n(i, this._strokeColor), this._showBorder = !0), e !== void 0 && (this._strokeWidth = e, this._showBorder = e > 0), s !== void 0 && (this._cornerRadius = s, this._pillShape = !1), this._textEntity.text && this._updateBackdropDimensions(this._textEntity.text), this.version++, this;
|
|
800
278
|
}
|
|
801
279
|
/** Configure subtitle typography */
|
|
802
|
-
setTextStyle(t,
|
|
803
|
-
return this._textColor =
|
|
280
|
+
setTextStyle(t, i) {
|
|
281
|
+
return this._textColor = n(t, this._textColor), this._textEntity.paint.fill = this._textColor, i !== void 0 && i > 0 && (this._fontSize = i, this._textEntity.fontSize = i), this._textEntity.text && this._updateBackdropDimensions(this._textEntity.text), this.version++, this;
|
|
804
282
|
}
|
|
805
283
|
/** Set maximum characters per line before word-wrapping occurs */
|
|
806
284
|
setMaxLineChars(t) {
|
|
@@ -818,14 +296,14 @@ class tt extends H {
|
|
|
818
296
|
* Computes safe corner radius: clamped so circular arcs never self-intersect,
|
|
819
297
|
* or computes stadium capsule radius when pillShape is enabled.
|
|
820
298
|
*/
|
|
821
|
-
_getEffectiveCornerRadius(t,
|
|
822
|
-
const
|
|
823
|
-
return this._pillShape ?
|
|
824
|
-
Math.max(0, Math.min(this._cornerRadius[0],
|
|
825
|
-
Math.max(0, Math.min(this._cornerRadius[1],
|
|
826
|
-
Math.max(0, Math.min(this._cornerRadius[2],
|
|
827
|
-
Math.max(0, Math.min(this._cornerRadius[3],
|
|
828
|
-
] : Math.min(0.22,
|
|
299
|
+
_getEffectiveCornerRadius(t, i) {
|
|
300
|
+
const e = Math.min(t, i) / 2;
|
|
301
|
+
return this._pillShape ? e : typeof this._cornerRadius == "number" ? Math.max(0, Math.min(this._cornerRadius, e)) : Array.isArray(this._cornerRadius) ? [
|
|
302
|
+
Math.max(0, Math.min(this._cornerRadius[0], e)),
|
|
303
|
+
Math.max(0, Math.min(this._cornerRadius[1], e)),
|
|
304
|
+
Math.max(0, Math.min(this._cornerRadius[2], e)),
|
|
305
|
+
Math.max(0, Math.min(this._cornerRadius[3], e))
|
|
306
|
+
] : Math.min(0.22, e);
|
|
829
307
|
}
|
|
830
308
|
/**
|
|
831
309
|
* Internal geometry reflow: updates backdrop width & height using genuine Rect
|
|
@@ -837,35 +315,35 @@ class tt extends H {
|
|
|
837
315
|
this._backdrop.paint.opacity = 0, this._backdrop.invalidate();
|
|
838
316
|
return;
|
|
839
317
|
}
|
|
840
|
-
const
|
|
841
|
-
let
|
|
842
|
-
if (
|
|
843
|
-
const
|
|
318
|
+
const i = this._textEntity.getGroupLocalAABB(), e = i.isEmpty() ? 0 : i.size.x, s = i.isEmpty() ? 0 : i.size.y, [c, l] = this._padding;
|
|
319
|
+
let o = e, h = s;
|
|
320
|
+
if (o <= 0.1 || h <= 0.1) {
|
|
321
|
+
const f = t.split(`
|
|
844
322
|
`);
|
|
845
|
-
|
|
323
|
+
o = Math.max(...f.map((y) => y.length)) * (this._fontSize * 0.44), h = f.length * (this._fontSize * this._lineHeight);
|
|
846
324
|
}
|
|
847
|
-
const
|
|
848
|
-
this._backdrop.transform.setScale([1, 1, 1]), this._backdrop.width =
|
|
325
|
+
const _ = Math.max(2.4, Math.min(this._maxWidth, o + c * 2)), u = h + l * 2, k = this._getEffectiveCornerRadius(_, u);
|
|
326
|
+
this._backdrop.transform.setScale([1, 1, 1]), this._backdrop.width = _, this._backdrop.height = u, this._backdrop.cornerRadius = k, this._backdrop.paint.fill = this._backdropColor, this._backdrop.paint.stroke = this._showBorder ? this._strokeColor : d.NONE, this._backdrop.paint.strokeWidth = this._showBorder ? this._strokeWidth : 0, this._backdrop.paint.opacity = this._backdropOpacity, this._backdrop.invalidate();
|
|
849
327
|
}
|
|
850
328
|
/**
|
|
851
329
|
* Displays a subtitle sentence, formatting lines with word-wrapping and
|
|
852
330
|
* reflowing the backdrop pill with true circular corner arcs.
|
|
853
331
|
*/
|
|
854
|
-
showText(t,
|
|
332
|
+
showText(t, i) {
|
|
855
333
|
this._clearTimeout && (clearTimeout(this._clearTimeout), this._clearTimeout = null);
|
|
856
|
-
const
|
|
857
|
-
let s =
|
|
858
|
-
if (
|
|
859
|
-
const
|
|
860
|
-
let
|
|
861
|
-
for (const
|
|
862
|
-
(
|
|
863
|
-
|
|
334
|
+
const e = t.trim();
|
|
335
|
+
let s = e;
|
|
336
|
+
if (e.length > this._maxLineChars) {
|
|
337
|
+
const c = e.split(" "), l = [];
|
|
338
|
+
let o = "";
|
|
339
|
+
for (const h of c)
|
|
340
|
+
(o + " " + h).trim().length > this._maxLineChars ? (o && l.push(o), o = h) : o = o ? o + " " + h : h;
|
|
341
|
+
o && l.push(o), s = l.join(`
|
|
864
342
|
`);
|
|
865
343
|
}
|
|
866
|
-
return this._textEntity.text = s, this._textEntity.paint.opacity = this._textOpacity, this._updateBackdropDimensions(s), this.version++,
|
|
344
|
+
return this._textEntity.text = s, this._textEntity.paint.opacity = this._textOpacity, this._updateBackdropDimensions(s), this.version++, i && i > 0 && (this._clearTimeout = setTimeout(() => {
|
|
867
345
|
this.clear();
|
|
868
|
-
},
|
|
346
|
+
}, i)), this;
|
|
869
347
|
}
|
|
870
348
|
/**
|
|
871
349
|
* Synchronizes this subtitle banner with a [[TTSAudioResult]], displaying the spoken text
|
|
@@ -881,7 +359,7 @@ class tt extends H {
|
|
|
881
359
|
return this._clearTimeout && (clearTimeout(this._clearTimeout), this._clearTimeout = null), this._textEntity.text = "", this._textEntity.paint.opacity = 0, this._backdrop && (this._backdrop.paint.opacity = 0, this._backdrop.invalidate()), this.version++, this;
|
|
882
360
|
}
|
|
883
361
|
}
|
|
884
|
-
class
|
|
362
|
+
class T extends C {
|
|
885
363
|
text;
|
|
886
364
|
voice;
|
|
887
365
|
speed;
|
|
@@ -892,12 +370,21 @@ class et extends K {
|
|
|
892
370
|
duckingRatio;
|
|
893
371
|
volume;
|
|
894
372
|
_audioResult = null;
|
|
373
|
+
_audioTask = null;
|
|
374
|
+
_synthesisOptions;
|
|
895
375
|
_isStarted = !1;
|
|
896
376
|
_baseScale = [1, 1, 1];
|
|
897
|
-
constructor(t,
|
|
898
|
-
super(
|
|
899
|
-
|
|
900
|
-
|
|
377
|
+
constructor(t, i = {}) {
|
|
378
|
+
super(i), this.text = t, this.voice = i.voice, this.speed = i.speed ?? 1, this.pitch = i.pitch, this.subtitles = i.subtitles, this.targetEntity = i.entity, this.pulseAmplitude = i.pulseAmplitude ?? 0.05, this.duckingRatio = i.duckingRatio ?? 0.25, this.volume = i.volume ?? 1, this._synthesisOptions = {
|
|
379
|
+
voice: i.voice,
|
|
380
|
+
speed: i.speed,
|
|
381
|
+
pitch: i.pitch,
|
|
382
|
+
volume: i.volume,
|
|
383
|
+
displayText: i.displayText,
|
|
384
|
+
elevenlabs: i.elevenlabs
|
|
385
|
+
};
|
|
386
|
+
const e = t.trim().split(/\s+/).length;
|
|
387
|
+
this.duration = Math.max(1e3, e * 420 * (1 / this.speed));
|
|
901
388
|
}
|
|
902
389
|
/**
|
|
903
390
|
* Prepares animation state.
|
|
@@ -912,12 +399,15 @@ class et extends K {
|
|
|
912
399
|
* Synthesizes audio ahead of time and locks exact duration.
|
|
913
400
|
*/
|
|
914
401
|
async prepareAudio() {
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
402
|
+
if (!this._audioResult) {
|
|
403
|
+
this._audioTask ??= p.synthesize(this.text, this._synthesisOptions);
|
|
404
|
+
try {
|
|
405
|
+
this._audioResult = await this._audioTask, this.duration = this._audioResult.durationMs;
|
|
406
|
+
} finally {
|
|
407
|
+
this._audioTask = null;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return this._audioResult;
|
|
921
411
|
}
|
|
922
412
|
update(t) {
|
|
923
413
|
if (this.paused) return !1;
|
|
@@ -926,10 +416,10 @@ class et extends K {
|
|
|
926
416
|
t = -this.delay, this.delay = 0;
|
|
927
417
|
}
|
|
928
418
|
if (this._isStarted || (this._isStarted = !0, this._beginPlayback()), this.elapsed += t, this.targetEntity?.transform) {
|
|
929
|
-
const
|
|
419
|
+
const i = this.elapsed / 1e3 * 4 * Math.PI * 2, e = 1 + Math.sin(i) * this.pulseAmplitude;
|
|
930
420
|
this.targetEntity.transform.setScale([
|
|
931
|
-
this._baseScale[0] *
|
|
932
|
-
this._baseScale[1] *
|
|
421
|
+
this._baseScale[0] * e,
|
|
422
|
+
this._baseScale[1] * e,
|
|
933
423
|
this._baseScale[2]
|
|
934
424
|
]), this.targetEntity.version++;
|
|
935
425
|
}
|
|
@@ -937,13 +427,13 @@ class et extends K {
|
|
|
937
427
|
}
|
|
938
428
|
async _beginPlayback() {
|
|
939
429
|
if (this.targetEntity?.transform) {
|
|
940
|
-
const
|
|
941
|
-
this._baseScale = [
|
|
430
|
+
const e = this.targetEntity.transform.getScale();
|
|
431
|
+
this._baseScale = [e.x, e.y, e.z];
|
|
942
432
|
}
|
|
943
433
|
const t = await this.prepareAudio();
|
|
944
434
|
this.subtitles && this.subtitles.syncWithAudio(t);
|
|
945
|
-
const
|
|
946
|
-
await t.play(
|
|
435
|
+
const i = this.targetEntity?.scene;
|
|
436
|
+
await t.play(i, {
|
|
947
437
|
duckingRatio: this.duckingRatio,
|
|
948
438
|
volume: this.volume
|
|
949
439
|
});
|
|
@@ -952,38 +442,41 @@ class et extends K {
|
|
|
952
442
|
this.targetEntity?.transform && (this.targetEntity.transform.setScale(this._baseScale), this.targetEntity.version++);
|
|
953
443
|
}
|
|
954
444
|
}
|
|
955
|
-
async function
|
|
956
|
-
const
|
|
957
|
-
|
|
958
|
-
const s = await
|
|
959
|
-
voice:
|
|
960
|
-
speed:
|
|
961
|
-
pitch:
|
|
962
|
-
volume:
|
|
445
|
+
async function M(r, t, i = {}) {
|
|
446
|
+
const e = r?.audio || r;
|
|
447
|
+
e && typeof e.awaitUnlock == "function" && await e.awaitUnlock();
|
|
448
|
+
const s = await p.synthesize(t, {
|
|
449
|
+
voice: i.voice,
|
|
450
|
+
speed: i.speed,
|
|
451
|
+
pitch: i.pitch,
|
|
452
|
+
volume: i.volume,
|
|
453
|
+
displayText: i.displayText,
|
|
454
|
+
elevenlabs: i.elevenlabs
|
|
963
455
|
});
|
|
964
|
-
return
|
|
965
|
-
duckingRatio:
|
|
966
|
-
volume:
|
|
967
|
-
}),
|
|
456
|
+
return i.subtitles && i.subtitles.syncWithAudio(s), s.play(e, {
|
|
457
|
+
duckingRatio: i.duckingRatio ?? 0.25,
|
|
458
|
+
volume: i.volume ?? 1
|
|
459
|
+
}), r && typeof r.wait == "function" && await r.wait(s.durationMs), s;
|
|
968
460
|
}
|
|
969
461
|
export {
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
P as
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
462
|
+
I as DEFAULT_MSEDGE_VOICE,
|
|
463
|
+
W as ElevenLabsTTSProvider,
|
|
464
|
+
N as MSEDGE_VOICES,
|
|
465
|
+
A as MsEdgeTTSProvider,
|
|
466
|
+
v as SUBTITLE_PRESETS,
|
|
467
|
+
T as Speak,
|
|
468
|
+
R as Subtitles,
|
|
469
|
+
p as TTS,
|
|
470
|
+
D as TTSAudio,
|
|
471
|
+
L as TTSCache,
|
|
472
|
+
H as TTSClient,
|
|
473
|
+
F as TTSNarrationComponent,
|
|
474
|
+
P as encodeWAV,
|
|
475
|
+
Y as fetchRemoteMsEdgeVoices,
|
|
476
|
+
X as findMsEdgeVoice,
|
|
477
|
+
Z as getAvailableMsEdgeVoices,
|
|
478
|
+
q as getMsEdgeVoicesByLanguage,
|
|
479
|
+
G as parseMp3Metadata,
|
|
480
|
+
U as setDynamicMsEdgeVoices,
|
|
481
|
+
M as speak
|
|
989
482
|
};
|