@pie-players/pie-section-player 0.3.49 → 0.3.50

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.
@@ -1,486 +0,0 @@
1
- import { t as h } from "./defineProperty-Dt7ri80W-76aeaed7.js";
2
- function T(e, t) {
3
- if (typeof e != "string" || e.length === 0) return null;
4
- try {
5
- const r = typeof t.apiEndpoint == "string" && /^https?:\/\//i.test(t.apiEndpoint) ? t.apiEndpoint : void 0, i = r ? new URL(e, r) : new URL(e);
6
- return i.protocol !== "http:" && i.protocol !== "https:" ? null : i;
7
- } catch {
8
- return null;
9
- }
10
- }
11
- function A(e, t) {
12
- const r = Array.isArray(t.assetOrigins) ? t.assetOrigins.filter((i) => typeof i == "string" && i.length > 0).map((i) => {
13
- try {
14
- return new URL(i).origin;
15
- } catch {
16
- return i;
17
- }
18
- }) : [];
19
- if (r.length === 0) {
20
- if (typeof t.apiEndpoint == "string" && /^https?:\/\//i.test(t.apiEndpoint)) try {
21
- return new URL(t.apiEndpoint).origin === e.origin;
22
- } catch {
23
- return !1;
24
- }
25
- return typeof window < "u" && typeof window.location?.origin == "string" && window.location.origin === e.origin;
26
- }
27
- return r.includes(e.origin);
28
- }
29
- function R(e) {
30
- const t = {};
31
- for (const [r, i] of Object.entries(e)) {
32
- const o = r.toLowerCase();
33
- o === "authorization" || o === "proxy-authorization" || o === "cookie" || o.startsWith("x-auth-") || (t[r] = i);
34
- }
35
- return t;
36
- }
37
- var I = (e) => {
38
- const t = (e.providerOptions && typeof e.providerOptions == "object" ? e.providerOptions : {}).__pieTelemetry;
39
- return typeof t == "function" ? t : void 0;
40
- }, E = {
41
- pie: 3e3,
42
- custom: 3e3
43
- }, P = /* @__PURE__ */ new Set(["polly", "google"]), U = (e) => {
44
- if (!e || b(e) !== "pie") return !1;
45
- const t = (e.provider || "polly").toLowerCase();
46
- return P.has(t);
47
- }, S = (e) => e.replace(/\/+$/, ""), z = (e) => {
48
- const t = S(e.apiEndpoint), r = (e.provider || "").toLowerCase();
49
- return r === "polly" || r === "google" ? `${t}/${r}/voices` : `${t}/voices`;
50
- }, b = (e) => e.transportMode === "custom" ? "custom" : e.transportMode === "pie" ? "pie" : e.provider === "custom" ? "custom" : "pie", k = (e, t) => e.endpointMode ? e.endpointMode : t === "custom" ? "rootPost" : "synthesizePath", M = (e, t) => e.endpointValidationMode ? e.endpointValidationMode : t === "custom" ? "none" : "voices", O = (e) => {
51
- const t = e.providerOptions || {};
52
- if (typeof t.speedRate == "string") return t.speedRate;
53
- const r = Number(e.rate ?? 1);
54
- return !Number.isFinite(r) || r <= 0.95 ? "slow" : r >= 1.5 ? "fast" : "medium";
55
- }, C = (e) => {
56
- const t = [];
57
- let r = 0;
58
- const i = e.split(`
59
- `).map((o) => o.trim()).filter(Boolean);
60
- for (const o of i) try {
61
- const s = JSON.parse(o), a = typeof s.type == "string" ? s.type : "word", n = typeof s.time == "number" && Number.isFinite(s.time) ? s.time : 0, l = typeof s.value == "string" ? s.value : "", d = typeof s.start == "number" && Number.isFinite(s.start) ? s.start : null, u = typeof s.end == "number" && Number.isFinite(s.end) ? s.end : null, g = d ?? r, c = u ?? g + Math.max(1, l.length || String(s.value || "").length);
62
- r = Math.max(c + 1, r), t.push({
63
- time: n,
64
- type: a,
65
- start: g,
66
- end: c,
67
- value: l
68
- });
69
- } catch {
70
- }
71
- return t;
72
- }, L = (e) => {
73
- if (!Array.isArray(e)) return [];
74
- const t = [];
75
- let r = 0;
76
- for (const i of e) {
77
- if (!i || typeof i != "object") continue;
78
- const o = typeof i.type == "string" ? i.type : "word", s = typeof i.time == "number" && Number.isFinite(i.time) ? i.time : 0, a = typeof i.value == "string" ? i.value : "", n = typeof i.start == "number" && Number.isFinite(i.start) ? i.start : null, l = typeof i.end == "number" && Number.isFinite(i.end) ? i.end : null, d = n ?? r, u = l ?? d + Math.max(1, a.length || 1);
79
- r = Math.max(r, u + 1), t.push({
80
- time: s,
81
- type: o,
82
- start: d,
83
- end: u,
84
- value: a
85
- });
86
- }
87
- return t.sort((i, o) => i.time !== o.time ? i.time - o.time : i.start !== o.start ? i.start - o.start : i.end - o.end);
88
- }, B = {
89
- pie: {
90
- id: "pie",
91
- resolveSynthesisUrl: (e) => {
92
- const t = k(e, "pie"), r = S(e.apiEndpoint);
93
- return t === "rootPost" ? r : `${r}/synthesize`;
94
- },
95
- buildRequestBody: (e, t) => {
96
- const r = t.providerOptions || {}, i = typeof t.engine == "string" ? t.engine : typeof r.engine == "string" ? r.engine : void 0, o = typeof r.sampleRate == "number" && Number.isFinite(r.sampleRate) ? r.sampleRate : void 0, s = r.format === "mp3" || r.format === "ogg" || r.format === "pcm" ? r.format : void 0, a = Array.isArray(r.speechMarkTypes) ? r.speechMarkTypes.filter((n) => n === "word" || n === "sentence" || n === "ssml") : void 0;
97
- return {
98
- text: e,
99
- provider: t.provider || "polly",
100
- voice: t.voice,
101
- language: t.language,
102
- rate: t.rate,
103
- engine: i,
104
- sampleRate: o,
105
- format: s,
106
- speechMarkTypes: a,
107
- includeSpeechMarks: !0
108
- };
109
- },
110
- parseResponse: async (e) => {
111
- const t = await e.json();
112
- return {
113
- audio: {
114
- kind: "base64",
115
- data: t.audio,
116
- contentType: t.contentType
117
- },
118
- speechMarks: Array.isArray(t.speechMarks) ? t.speechMarks : []
119
- };
120
- }
121
- },
122
- custom: {
123
- id: "custom",
124
- resolveSynthesisUrl: (e) => {
125
- const t = k(e, "custom"), r = S(e.apiEndpoint);
126
- return t === "synthesizePath" ? `${r}/synthesize` : r;
127
- },
128
- buildRequestBody: (e, t) => {
129
- const r = t.providerOptions || {}, i = typeof r.lang_id == "string" ? r.lang_id : t.language || "en-US", o = typeof r.cache == "boolean" ? r.cache : !0;
130
- return {
131
- text: e,
132
- speedRate: O(t),
133
- lang_id: i,
134
- cache: o
135
- };
136
- },
137
- parseResponse: async (e, t, r, i) => {
138
- const o = await e.json(), s = {};
139
- if (t.includeAuthOnAssetFetch) for (const [d, u] of Object.entries(r)) d.toLowerCase() === "authorization" && (s[d] = u);
140
- let a = [];
141
- const n = L(o.speechMarks);
142
- if (n.length > 0) a = n;
143
- else if (typeof o.word == "string" && o.word.length > 0) {
144
- const d = T(o.word, t);
145
- if (d !== null) {
146
- const u = A(d, t) ? s : R(s), g = await fetch(d.toString(), {
147
- headers: u,
148
- signal: i,
149
- redirect: "error"
150
- });
151
- g.ok && (a = C(await g.text()));
152
- }
153
- }
154
- const l = T(o.audioContent, t);
155
- if (l === null) throw new Error("TTS server returned an invalid audio URL");
156
- return {
157
- audio: {
158
- kind: "url",
159
- url: l.toString()
160
- },
161
- speechMarks: a
162
- };
163
- }
164
- }
165
- }, x = class {
166
- constructor(e, t) {
167
- h(this, "config", void 0), h(this, "adapter", void 0), h(this, "currentAudio", null), h(this, "pausedState", !1), h(this, "wordTimings", []), h(this, "highlightInterval", null), h(this, "intentionallyStopped", !1), h(this, "activeSynthesisController", null), h(this, "synthesisRunId", 0), h(this, "telemetryReporter", void 0), h(this, "onWordBoundary", void 0), this.config = e, this.adapter = t, this.telemetryReporter = I(e);
168
- }
169
- async emitTelemetry(e, t) {
170
- try {
171
- await this.telemetryReporter?.(e, t);
172
- } catch (r) {
173
- console.warn("[ServerTTSProvider] telemetry callback failed:", r);
174
- }
175
- }
176
- async speak(e) {
177
- this.stop(), this.intentionallyStopped = !1;
178
- const t = ++this.synthesisRunId, r = new AbortController();
179
- this.activeSynthesisController = r;
180
- const { audioUrl: i, wordTimings: o } = await this.synthesizeSpeech(e, r.signal, t);
181
- if (t !== this.synthesisRunId) {
182
- URL.revokeObjectURL(i);
183
- return;
184
- }
185
- return this.wordTimings = o, new Promise((s, a) => {
186
- const n = new Audio(i);
187
- this.currentAudio = n, this.config.volume !== void 0 && (n.volume = Math.max(0, Math.min(1, this.config.volume))), n.onplay = () => {
188
- this.pausedState = !1, this.onWordBoundary && this.wordTimings.length > 0 && this.startWordHighlighting();
189
- }, n.onended = () => {
190
- this.stopWordHighlighting(), URL.revokeObjectURL(i), this.currentAudio = null, this.wordTimings = [], s();
191
- }, n.onerror = (l) => {
192
- this.stopWordHighlighting(), URL.revokeObjectURL(i), this.currentAudio = null, this.wordTimings = [], this.intentionallyStopped ? s() : a(/* @__PURE__ */ new Error("Failed to play audio from server"));
193
- }, n.onpause = () => {
194
- this.stopWordHighlighting(), this.pausedState = !0;
195
- }, n.play().catch(a);
196
- });
197
- }
198
- async synthesizeSpeech(e, t, r) {
199
- const i = Date.now();
200
- await this.emitTelemetry("pie-tool-backend-call-start", {
201
- toolId: "tts",
202
- backend: this.config.provider || "server",
203
- operation: "synthesize-speech"
204
- });
205
- const o = {
206
- "Content-Type": "application/json",
207
- ...this.config.headers
208
- };
209
- this.config.authToken && (o.Authorization = `Bearer ${this.config.authToken}`);
210
- const s = this.adapter.resolveSynthesisUrl(this.config), a = this.adapter.buildRequestBody(e, this.config), n = await (async () => {
211
- try {
212
- return await fetch(s, {
213
- method: "POST",
214
- headers: o,
215
- body: JSON.stringify(a),
216
- signal: t
217
- });
218
- } catch (c) {
219
- throw await this.emitTelemetry("pie-tool-backend-call-error", {
220
- toolId: "tts",
221
- backend: this.config.provider || "server",
222
- operation: "synthesize-speech",
223
- duration: Date.now() - i,
224
- errorType: "TTSBackendNetworkError",
225
- message: c instanceof Error ? c.message : String(c)
226
- }), c;
227
- }
228
- })();
229
- if (!n.ok) {
230
- const c = await n.text().catch(() => "");
231
- let p = {};
232
- if (c) try {
233
- p = JSON.parse(c);
234
- } catch (f) {
235
- console.warn("[ServerTTSProvider] non-OK response body was not JSON", {
236
- status: n.status,
237
- url: s,
238
- rawBodyPreview: c.slice(0, 500),
239
- parseError: f instanceof Error ? f.message : String(f)
240
- });
241
- }
242
- const y = typeof p.message == "string" && p.message || (typeof p.error?.message == "string" ? p.error.message : void 0) || `Server returned ${n.status}`;
243
- await this.emitTelemetry("pie-tool-backend-call-error", {
244
- toolId: "tts",
245
- backend: this.config.provider || "server",
246
- operation: "synthesize-speech",
247
- duration: Date.now() - i,
248
- statusCode: n.status,
249
- errorType: "TTSBackendRequestError",
250
- message: y
251
- });
252
- const m = e.replace(/\s+/g, " ").trim().slice(0, 120);
253
- throw console.error("[ServerTTSProvider] synthesize request failed", {
254
- status: n.status,
255
- url: s,
256
- backend: this.config.provider || "server",
257
- message: y,
258
- responseBody: p,
259
- textLength: e.length,
260
- textPreview: m.length < e.length ? `${m}…` : m
261
- }), new Error(y);
262
- }
263
- const l = await this.adapter.parseResponse(n, this.config, o, t);
264
- if (r !== this.synthesisRunId || t.aborted) throw new Error("Synthesis superseded by a newer request");
265
- let d;
266
- if (l.audio.kind === "base64") d = this.base64ToBlob(l.audio.data, l.audio.contentType);
267
- else {
268
- const c = l.audio.url, p = Date.now();
269
- await this.emitTelemetry("pie-tool-backend-call-start", {
270
- toolId: "tts",
271
- backend: this.config.provider || "server",
272
- operation: "fetch-synthesized-audio-asset"
273
- });
274
- const y = {};
275
- this.config.includeAuthOnAssetFetch && this.config.authToken && (y.Authorization = `Bearer ${this.config.authToken}`);
276
- const m = T(c, this.config);
277
- if (m === null) throw await this.emitTelemetry("pie-tool-backend-call-error", {
278
- toolId: "tts",
279
- backend: this.config.provider || "server",
280
- operation: "fetch-synthesized-audio-asset",
281
- duration: Date.now() - p,
282
- errorType: "TTSAssetInvalidUrl",
283
- message: "TTS asset URL rejected (non-http(s) or malformed)"
284
- }), /* @__PURE__ */ new Error("TTS asset URL rejected (non-http(s) or malformed)");
285
- const f = A(m, this.config) ? y : R(y), v = await (async () => {
286
- try {
287
- return await fetch(m.toString(), {
288
- headers: f,
289
- signal: t,
290
- redirect: "error"
291
- });
292
- } catch (w) {
293
- throw await this.emitTelemetry("pie-tool-backend-call-error", {
294
- toolId: "tts",
295
- backend: this.config.provider || "server",
296
- operation: "fetch-synthesized-audio-asset",
297
- duration: Date.now() - p,
298
- errorType: "TTSAssetNetworkError",
299
- message: w instanceof Error ? w.message : String(w)
300
- }), w;
301
- }
302
- })();
303
- if (!v.ok) throw await this.emitTelemetry("pie-tool-backend-call-error", {
304
- toolId: "tts",
305
- backend: this.config.provider || "server",
306
- operation: "fetch-synthesized-audio-asset",
307
- duration: Date.now() - p,
308
- statusCode: v.status,
309
- errorType: "TTSAssetFetchError",
310
- message: `Failed to download synthesized audio (${v.status})`
311
- }), /* @__PURE__ */ new Error(`Failed to download synthesized audio (${v.status})`);
312
- d = await v.blob(), await this.emitTelemetry("pie-tool-backend-call-success", {
313
- toolId: "tts",
314
- backend: this.config.provider || "server",
315
- operation: "fetch-synthesized-audio-asset",
316
- duration: Date.now() - p
317
- });
318
- }
319
- const u = URL.createObjectURL(d), g = this.parseSpeechMarks(l.speechMarks);
320
- return await this.emitTelemetry("pie-tool-backend-call-success", {
321
- toolId: "tts",
322
- backend: this.config.provider || "server",
323
- operation: "synthesize-speech",
324
- duration: Date.now() - i
325
- }), {
326
- audioUrl: u,
327
- wordTimings: g
328
- };
329
- }
330
- base64ToBlob(e, t) {
331
- const r = atob(e), i = new Array(r.length);
332
- for (let s = 0; s < r.length; s++) i[s] = r.charCodeAt(s);
333
- const o = new Uint8Array(i);
334
- return new Blob([o], { type: t });
335
- }
336
- parseSpeechMarks(e) {
337
- return e.filter((t) => t.type === "word").map((t, r) => ({
338
- time: t.time,
339
- wordIndex: r,
340
- charIndex: t.start,
341
- length: t.end - t.start,
342
- word: t.value
343
- }));
344
- }
345
- startWordHighlighting() {
346
- if (this.stopWordHighlighting(), !this.currentAudio || !this.onWordBoundary || this.wordTimings.length === 0) {
347
- console.log("[ServerTTSProvider] Cannot start highlighting:", {
348
- hasAudio: !!this.currentAudio,
349
- hasCallback: !!this.onWordBoundary,
350
- wordTimingsCount: this.wordTimings.length
351
- });
352
- return;
353
- }
354
- console.log("[ServerTTSProvider] Starting word highlighting with", this.wordTimings.length, "word timings"), console.log("[ServerTTSProvider] Playback rate:", this.currentAudio.playbackRate), console.log("[ServerTTSProvider] First 3 timings:", this.wordTimings.slice(0, 3));
355
- let e = -1;
356
- this.highlightInterval = window.setInterval(() => {
357
- if (!this.currentAudio) {
358
- this.stopWordHighlighting();
359
- return;
360
- }
361
- const t = this.currentAudio.currentTime * 1e3;
362
- for (let r = 0; r < this.wordTimings.length; r++) {
363
- const i = this.wordTimings[r];
364
- if (t >= i.time && r > e) {
365
- this.onWordBoundary && (console.log("[ServerTTSProvider] Highlighting word at charIndex:", i.charIndex, "length:", i.length, "time:", i.time, "currentTime:", t), this.onWordBoundary(i.word, i.charIndex, i.length)), e = r;
366
- break;
367
- }
368
- }
369
- }, 50);
370
- }
371
- stopWordHighlighting() {
372
- this.highlightInterval !== null && (clearInterval(this.highlightInterval), this.highlightInterval = null);
373
- }
374
- pause() {
375
- this.currentAudio && !this.pausedState && (this.currentAudio.pause(), this.stopWordHighlighting(), this.pausedState = !0);
376
- }
377
- resume() {
378
- this.currentAudio && this.pausedState && (this.currentAudio.play(), this.pausedState = !1, this.onWordBoundary && this.wordTimings.length > 0 && this.startWordHighlighting());
379
- }
380
- stop() {
381
- this.synthesisRunId += 1, this.activeSynthesisController && (this.activeSynthesisController.abort(), this.activeSynthesisController = null), this.stopWordHighlighting(), this.currentAudio && (this.intentionallyStopped = !0, this.currentAudio.pause(), this.currentAudio.src && URL.revokeObjectURL(this.currentAudio.src), this.currentAudio.src = "", this.currentAudio = null), this.pausedState = !1, this.wordTimings = [];
382
- }
383
- isPlaying() {
384
- return this.currentAudio !== null && !this.pausedState;
385
- }
386
- isPaused() {
387
- return this.pausedState;
388
- }
389
- updateSettings(e) {
390
- e.rate !== void 0 && (this.config.rate = e.rate), e.pitch !== void 0 && (this.config.pitch = e.pitch), e.voice !== void 0 && (this.config.voice = e.voice);
391
- }
392
- }, N = class {
393
- constructor() {
394
- h(this, "providerId", "server-tts"), h(this, "providerName", "Server TTS"), h(this, "version", "1.0.0"), h(this, "config", null), h(this, "adapter", null), h(this, "telemetryReporter", void 0);
395
- }
396
- async emitTelemetry(e, t) {
397
- try {
398
- await this.telemetryReporter?.(e, t);
399
- } catch (r) {
400
- console.warn("[ServerTTSProvider] telemetry callback failed:", r);
401
- }
402
- }
403
- async initialize(e) {
404
- const t = e;
405
- if (!t.apiEndpoint) throw new Error("apiEndpoint is required for ServerTTSProvider");
406
- this.config = t, this.telemetryReporter = I(t);
407
- const r = b(t);
408
- if (this.adapter = B[r], t.validateEndpoint) {
409
- const i = Date.now();
410
- if (await this.emitTelemetry("pie-tool-backend-call-start", {
411
- toolId: "tts",
412
- backend: t.provider || "server",
413
- operation: "validate-endpoint"
414
- }), !await this.testAPIAvailability()) throw await this.emitTelemetry("pie-tool-backend-call-error", {
415
- toolId: "tts",
416
- backend: t.provider || "server",
417
- operation: "validate-endpoint",
418
- duration: Date.now() - i,
419
- errorType: "TTSEndpointValidationError",
420
- message: `Server TTS API not available at ${t.apiEndpoint}`
421
- }), /* @__PURE__ */ new Error(`Server TTS API not available at ${t.apiEndpoint}`);
422
- await this.emitTelemetry("pie-tool-backend-call-success", {
423
- toolId: "tts",
424
- backend: t.provider || "server",
425
- operation: "validate-endpoint",
426
- duration: Date.now() - i
427
- });
428
- }
429
- return new x(t, this.adapter);
430
- }
431
- async testAPIAvailability() {
432
- if (!this.config || !this.adapter) return !1;
433
- try {
434
- const e = { ...this.config.headers };
435
- this.config.authToken && (e.Authorization = `Bearer ${this.config.authToken}`);
436
- const t = new AbortController(), r = setTimeout(() => t.abort(), 5e3), i = M(this.config, this.adapter.id);
437
- if (i === "none") return clearTimeout(r), !0;
438
- const o = i === "voices" ? z(this.config) : this.adapter.resolveSynthesisUrl(this.config), s = i === "voices" ? "GET" : "OPTIONS";
439
- try {
440
- const a = await fetch(o, {
441
- method: s,
442
- headers: e,
443
- signal: t.signal
444
- });
445
- return clearTimeout(r), a.ok || a.status === 405;
446
- } catch {
447
- return clearTimeout(r), !1;
448
- }
449
- } catch {
450
- return !1;
451
- }
452
- }
453
- supportsFeature(e) {
454
- switch (e) {
455
- case "pause":
456
- case "resume":
457
- case "wordBoundary":
458
- case "voiceSelection":
459
- case "rateControl":
460
- return !0;
461
- case "pitchControl":
462
- return !1;
463
- default:
464
- return !1;
465
- }
466
- }
467
- getCapabilities() {
468
- const e = this.config ? b(this.config) : "pie";
469
- return {
470
- supportsPause: !0,
471
- supportsResume: !0,
472
- supportsWordBoundary: !0,
473
- supportsVoiceSelection: !0,
474
- supportsRateControl: !0,
475
- supportsPitchControl: !1,
476
- supportsSSML: U(this.config),
477
- maxTextLength: E[e]
478
- };
479
- }
480
- destroy() {
481
- this.config = null, this.adapter = null, this.telemetryReporter = void 0;
482
- }
483
- };
484
- export {
485
- N as ServerTTSProvider
486
- };