@pickleball/expo-sdk 0.1.1 → 0.2.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/CHANGELOG.md +24 -0
- package/README.md +69 -0
- package/dist/advanced-BXDrDl1h.d.ts +316 -0
- package/dist/advanced-BdAa2Erv.d.cts +316 -0
- package/dist/advanced.cjs +35 -0
- package/dist/advanced.d.cts +3 -0
- package/dist/advanced.d.ts +3 -0
- package/dist/advanced.js +35 -0
- package/dist/chunk-2JHULJZ7.js +265 -0
- package/dist/chunk-6JOMESNT.js +9 -0
- package/dist/chunk-7OX2DSKI.cjs +33 -0
- package/dist/chunk-DHDX3ZOO.js +800 -0
- package/dist/chunk-FODE2W2H.js +1953 -0
- package/dist/chunk-KFQGP6VL.js +33 -0
- package/dist/chunk-LCWGNGEN.cjs +265 -0
- package/dist/chunk-TBPR2QND.cjs +1953 -0
- package/dist/chunk-TRLASNPQ.cjs +9 -0
- package/dist/chunk-YGSQ25UA.cjs +800 -0
- package/dist/device-agent/index.cjs +498 -0
- package/dist/device-agent/index.d.cts +157 -0
- package/dist/device-agent/index.d.ts +157 -0
- package/dist/device-agent/index.js +498 -0
- package/dist/engine-CCSSqPbZ.d.cts +516 -0
- package/dist/engine-CCSSqPbZ.d.ts +516 -0
- package/dist/index.cjs +339 -5569
- package/dist/index.d.cts +22 -1406
- package/dist/index.d.ts +22 -1406
- package/dist/index.js +269 -5401
- package/dist/match/index.cjs +2456 -0
- package/dist/match/index.d.cts +253 -0
- package/dist/match/index.d.ts +253 -0
- package/dist/match/index.js +2456 -0
- package/dist/match/remote/index.cjs +21 -0
- package/dist/match/remote/index.d.cts +67 -0
- package/dist/match/remote/index.d.ts +67 -0
- package/dist/match/remote/index.js +21 -0
- package/dist/match/voice/index.cjs +23 -0
- package/dist/match/voice/index.d.cts +114 -0
- package/dist/match/voice/index.d.ts +114 -0
- package/dist/match/voice/index.js +23 -0
- package/dist/plugin.cjs +53 -70
- package/dist/plugin.d.cts +13 -0
- package/dist/plugin.d.ts +13 -0
- package/dist/plugin.js +20 -15
- package/dist/types-CrmcX408.d.cts +176 -0
- package/dist/types-CrmcX408.d.ts +176 -0
- package/dist/types-DiKWjwvN.d.cts +96 -0
- package/dist/types-DiKWjwvN.d.ts +96 -0
- package/dist/version.cjs +4 -32
- package/dist/version.js +2 -1
- package/package.json +68 -4
- package/src/advanced.ts +47 -0
- package/src/device-agent/agent.ts +443 -0
- package/src/device-agent/component.tsx +49 -0
- package/src/device-agent/convex-control-channel.ts +82 -0
- package/src/device-agent/http-transport.ts +143 -0
- package/src/device-agent/index.ts +4 -0
- package/src/engine.ts +21 -5
- package/src/errors.ts +16 -0
- package/src/http-session-provider.ts +19 -13
- package/src/index.ts +38 -14
- package/src/livekit-room-adapter.ts +39 -2
- package/src/livestream-screen.tsx +26 -4
- package/src/match/index.ts +29 -0
- package/src/match/provider.tsx +4 -2
- package/src/match/remote/hid-gesture.ts +10 -2
- package/src/match/remote/types.ts +6 -4
- package/src/match/voice/scheduler.ts +6 -1
- package/src/match/voice/service.ts +16 -4
- package/src/native-runtime.ts +54 -4
- package/src/plugin.ts +35 -14
- package/src/provider.tsx +35 -4
- package/src/rtmp-room-adapter.ts +196 -0
- package/dist/chunk-VHLUJQG5.js +0 -17
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2;
|
|
2
|
+
|
|
3
|
+
var _chunk7OX2DSKIcjs = require('./chunk-7OX2DSKI.cjs');
|
|
4
|
+
|
|
5
|
+
// src/match/voice/types.ts
|
|
6
|
+
var VoiceError = (_class = class extends Error {
|
|
7
|
+
constructor(code, message, options) {
|
|
8
|
+
super(message, options);_class.prototype.__init.call(this);;
|
|
9
|
+
this.code = code;
|
|
10
|
+
}
|
|
11
|
+
__init() {this.name = "VoiceError"}
|
|
12
|
+
}, _class);
|
|
13
|
+
|
|
14
|
+
// src/match/voice/audio.ts
|
|
15
|
+
function createExpoAudioPlayback(options = {}) {
|
|
16
|
+
const load = _nullishCoalesce(options.load, () => ( (() => _chunk7OX2DSKIcjs.__require.call(void 0, "expo-audio"))));
|
|
17
|
+
const loadNativeAudio = _nullishCoalesce(options.loadNativeAudio, () => ( (() => {
|
|
18
|
+
const imported = _chunk7OX2DSKIcjs.__require.call(void 0, "@pickleball/voice-native");
|
|
19
|
+
return _nullishCoalesce(imported.default, () => ( imported));
|
|
20
|
+
})));
|
|
21
|
+
let player = null;
|
|
22
|
+
let settleCurrent = null;
|
|
23
|
+
let nativeAudio;
|
|
24
|
+
let nativeSubscription = null;
|
|
25
|
+
let disposed = false;
|
|
26
|
+
let operationGeneration = 0;
|
|
27
|
+
let configureTail = Promise.resolve();
|
|
28
|
+
let disposePromise = null;
|
|
29
|
+
let routeState = {
|
|
30
|
+
route: "system",
|
|
31
|
+
interrupted: false,
|
|
32
|
+
disconnectedReason: null
|
|
33
|
+
};
|
|
34
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
35
|
+
const updateRouteState = (state) => {
|
|
36
|
+
if (disposed) return;
|
|
37
|
+
routeState = state;
|
|
38
|
+
for (const listener of listeners) listener(routeState);
|
|
39
|
+
};
|
|
40
|
+
const getNativeAudio = () => {
|
|
41
|
+
if (nativeAudio !== void 0) return nativeAudio;
|
|
42
|
+
try {
|
|
43
|
+
nativeAudio = loadNativeAudio();
|
|
44
|
+
} catch (e) {
|
|
45
|
+
nativeAudio = null;
|
|
46
|
+
}
|
|
47
|
+
return nativeAudio;
|
|
48
|
+
};
|
|
49
|
+
const observeNativeAudio = () => {
|
|
50
|
+
const native = getNativeAudio();
|
|
51
|
+
if (native && !nativeSubscription) {
|
|
52
|
+
nativeSubscription = native.addListener("onAudioRouteChanged", updateRouteState);
|
|
53
|
+
}
|
|
54
|
+
return native;
|
|
55
|
+
};
|
|
56
|
+
const stop = async () => {
|
|
57
|
+
if (!player) return;
|
|
58
|
+
const current = player;
|
|
59
|
+
player = null;
|
|
60
|
+
const settle = settleCurrent;
|
|
61
|
+
settleCurrent = null;
|
|
62
|
+
try {
|
|
63
|
+
current.pause();
|
|
64
|
+
} catch (e2) {
|
|
65
|
+
}
|
|
66
|
+
if (settle) settle();
|
|
67
|
+
else try {
|
|
68
|
+
current.remove();
|
|
69
|
+
} catch (e3) {
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
const isCurrentAudioOperation = (generation) => !disposed && generation === operationGeneration;
|
|
73
|
+
const releaseNativeAudioSession = async (native) => {
|
|
74
|
+
_optionalChain([nativeSubscription, 'optionalAccess', _ => _.remove, 'call', _2 => _2()]);
|
|
75
|
+
nativeSubscription = null;
|
|
76
|
+
await native.disposeAudioSession();
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
configure(mode) {
|
|
80
|
+
if (disposed) return Promise.resolve();
|
|
81
|
+
const generation = operationGeneration;
|
|
82
|
+
const operation = configureTail.then(async () => {
|
|
83
|
+
if (!isCurrentAudioOperation(generation)) return;
|
|
84
|
+
const native = observeNativeAudio();
|
|
85
|
+
try {
|
|
86
|
+
await _optionalChain([native, 'optionalAccess', _3 => _3.captureAudioSessionState, 'call', _4 => _4()]);
|
|
87
|
+
} catch (e4) {
|
|
88
|
+
}
|
|
89
|
+
if (!isCurrentAudioOperation(generation)) return;
|
|
90
|
+
try {
|
|
91
|
+
await load().setAudioModeAsync({
|
|
92
|
+
allowsRecording: mode === "live",
|
|
93
|
+
playsInSilentMode: true
|
|
94
|
+
});
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (native) {
|
|
97
|
+
try {
|
|
98
|
+
await releaseNativeAudioSession(native);
|
|
99
|
+
} catch (e5) {
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
if (native) {
|
|
105
|
+
try {
|
|
106
|
+
const nextRouteState = await native.configureAudioSession(mode);
|
|
107
|
+
if (isCurrentAudioOperation(generation)) updateRouteState(nextRouteState);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
try {
|
|
110
|
+
await releaseNativeAudioSession(native);
|
|
111
|
+
} catch (e6) {
|
|
112
|
+
}
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
configureTail = operation.catch(() => void 0);
|
|
118
|
+
return operation;
|
|
119
|
+
},
|
|
120
|
+
async play(source) {
|
|
121
|
+
if (disposed) {
|
|
122
|
+
throw new VoiceError("playback_failed", "Audio playback is disposed");
|
|
123
|
+
}
|
|
124
|
+
await stop();
|
|
125
|
+
if (disposed) {
|
|
126
|
+
throw new VoiceError("playback_failed", "Audio playback is disposed");
|
|
127
|
+
}
|
|
128
|
+
let current;
|
|
129
|
+
try {
|
|
130
|
+
current = load().createAudioPlayer(
|
|
131
|
+
options.resolveSource ? options.resolveSource(source) : source
|
|
132
|
+
);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
throw new VoiceError("playback_failed", "Unable to create audio player", { cause: error });
|
|
135
|
+
}
|
|
136
|
+
player = current;
|
|
137
|
+
await new Promise((resolve, reject) => {
|
|
138
|
+
let settled = false;
|
|
139
|
+
let subscription = null;
|
|
140
|
+
const timeout = setTimeout(() => {
|
|
141
|
+
settle(new VoiceError("playback_failed", "Audio playback timed out"));
|
|
142
|
+
}, _nullishCoalesce(options.playbackTimeoutMs, () => ( 3e4)));
|
|
143
|
+
const settle = (error) => {
|
|
144
|
+
if (settled) return;
|
|
145
|
+
settled = true;
|
|
146
|
+
clearTimeout(timeout);
|
|
147
|
+
try {
|
|
148
|
+
_optionalChain([subscription, 'optionalAccess', _5 => _5.remove, 'call', _6 => _6()]);
|
|
149
|
+
} catch (e7) {
|
|
150
|
+
}
|
|
151
|
+
if (player === current) player = null;
|
|
152
|
+
if (settleCurrent === settle) settleCurrent = null;
|
|
153
|
+
try {
|
|
154
|
+
current.remove();
|
|
155
|
+
} catch (e8) {
|
|
156
|
+
}
|
|
157
|
+
if (error) {
|
|
158
|
+
reject(error instanceof VoiceError ? error : new VoiceError("playback_failed", "Audio playback failed", { cause: error }));
|
|
159
|
+
} else resolve();
|
|
160
|
+
};
|
|
161
|
+
settleCurrent = settle;
|
|
162
|
+
try {
|
|
163
|
+
subscription = current.addListener("playbackStatusUpdate", (status) => {
|
|
164
|
+
if (status.didJustFinish) settle();
|
|
165
|
+
else if (status.error != null || status.playbackState === "error") {
|
|
166
|
+
settle(_nullishCoalesce(status.error, () => ( new Error("Audio player entered an error state"))));
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
current.play();
|
|
170
|
+
} catch (error) {
|
|
171
|
+
settle(error);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
},
|
|
175
|
+
stop,
|
|
176
|
+
dispose() {
|
|
177
|
+
if (disposePromise) return disposePromise;
|
|
178
|
+
disposed = true;
|
|
179
|
+
operationGeneration += 1;
|
|
180
|
+
disposePromise = (async () => {
|
|
181
|
+
await stop();
|
|
182
|
+
await configureTail;
|
|
183
|
+
const native = getNativeAudio();
|
|
184
|
+
if (!native) {
|
|
185
|
+
_optionalChain([nativeSubscription, 'optionalAccess', _7 => _7.remove, 'call', _8 => _8()]);
|
|
186
|
+
nativeSubscription = null;
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
if (native) await releaseNativeAudioSession(native);
|
|
190
|
+
} catch (e9) {
|
|
191
|
+
}
|
|
192
|
+
listeners.clear();
|
|
193
|
+
})();
|
|
194
|
+
return disposePromise;
|
|
195
|
+
},
|
|
196
|
+
getRouteState() {
|
|
197
|
+
return routeState;
|
|
198
|
+
},
|
|
199
|
+
subscribeToRouteChanges(listener) {
|
|
200
|
+
listeners.add(listener);
|
|
201
|
+
observeNativeAudio();
|
|
202
|
+
return () => listeners.delete(listener);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/match/voice/bundled-assets.ts
|
|
208
|
+
function resolveBundledVoiceAsset(source) {
|
|
209
|
+
return source;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/match/voice/gemini-engine.ts
|
|
213
|
+
function createExpoGeneratedAudioFileSystem() {
|
|
214
|
+
return {
|
|
215
|
+
async writeFile(path, bytes) {
|
|
216
|
+
const fs = _chunk7OX2DSKIcjs.__require.call(void 0, "expo-file-system");
|
|
217
|
+
const file = new fs.File(path);
|
|
218
|
+
if (!file.exists) file.create({ intermediates: true, overwrite: true });
|
|
219
|
+
const handle = file.open(fs.FileMode.ReadWrite);
|
|
220
|
+
try {
|
|
221
|
+
handle.writeBytes(bytes);
|
|
222
|
+
} finally {
|
|
223
|
+
handle.close();
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
async removeFile(path) {
|
|
227
|
+
const fs = _chunk7OX2DSKIcjs.__require.call(void 0, "expo-file-system");
|
|
228
|
+
const file = new fs.File(path);
|
|
229
|
+
if (file.exists) file.delete();
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function createGeminiVoiceEngine(options) {
|
|
234
|
+
const fetcher = _nullishCoalesce(options.fetch, () => ( globalThis.fetch));
|
|
235
|
+
const fileSystem = _nullishCoalesce(options.fileSystem, () => ( createExpoGeneratedAudioFileSystem()));
|
|
236
|
+
const timeoutMs = _nullishCoalesce(options.requestTimeoutMs, () => ( 8e3));
|
|
237
|
+
const controllers = /* @__PURE__ */ new Set();
|
|
238
|
+
let sequence = 0;
|
|
239
|
+
let disposed = false;
|
|
240
|
+
const abortAll = () => {
|
|
241
|
+
for (const controller of controllers) controller.abort();
|
|
242
|
+
controllers.clear();
|
|
243
|
+
};
|
|
244
|
+
return {
|
|
245
|
+
async availability() {
|
|
246
|
+
return !disposed && options.url ? "available" : "unavailable";
|
|
247
|
+
},
|
|
248
|
+
async prewarm() {
|
|
249
|
+
},
|
|
250
|
+
async synthesize(text, signal) {
|
|
251
|
+
if (disposed) {
|
|
252
|
+
throw new VoiceError("engine_unavailable", "Gemini voice engine is disposed");
|
|
253
|
+
}
|
|
254
|
+
const controller = new AbortController();
|
|
255
|
+
controllers.add(controller);
|
|
256
|
+
const onAbort = () => controller.abort();
|
|
257
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
258
|
+
if (signal.aborted) controller.abort();
|
|
259
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
260
|
+
try {
|
|
261
|
+
let response;
|
|
262
|
+
try {
|
|
263
|
+
response = await fetcher(options.url, {
|
|
264
|
+
method: "POST",
|
|
265
|
+
headers: {
|
|
266
|
+
"content-type": "application/json",
|
|
267
|
+
...options.headers ? await options.headers() : void 0
|
|
268
|
+
},
|
|
269
|
+
body: JSON.stringify({
|
|
270
|
+
text,
|
|
271
|
+
voiceName: options.voiceName,
|
|
272
|
+
languageCode: options.languageCode
|
|
273
|
+
}),
|
|
274
|
+
signal: controller.signal
|
|
275
|
+
});
|
|
276
|
+
} catch (caught) {
|
|
277
|
+
throw new VoiceError(
|
|
278
|
+
controller.signal.aborted ? "synthesis_failed" : "network_unavailable",
|
|
279
|
+
controller.signal.aborted ? "Voice synthesis was cancelled" : "Voice synthesis request could not reach the TTS endpoint",
|
|
280
|
+
{ cause: caught }
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
if (!response.ok) {
|
|
284
|
+
throw new VoiceError(
|
|
285
|
+
"synthesis_failed",
|
|
286
|
+
`TTS endpoint responded with status ${response.status}`
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
290
|
+
if (bytes.length === 0) {
|
|
291
|
+
throw new VoiceError("synthesis_failed", "TTS endpoint returned an empty body");
|
|
292
|
+
}
|
|
293
|
+
if (controller.signal.aborted || disposed) {
|
|
294
|
+
throw new VoiceError("synthesis_failed", "Voice synthesis was cancelled");
|
|
295
|
+
}
|
|
296
|
+
const path = `${options.generatedRoot}/tts-${++sequence}-${Date.now()}.wav`;
|
|
297
|
+
try {
|
|
298
|
+
await fileSystem.writeFile(path, bytes);
|
|
299
|
+
} catch (caught) {
|
|
300
|
+
throw new VoiceError("storage_failure", "Unable to persist generated audio", {
|
|
301
|
+
cause: caught
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
if (controller.signal.aborted || disposed) {
|
|
305
|
+
await fileSystem.removeFile(path).catch(() => void 0);
|
|
306
|
+
throw new VoiceError("synthesis_failed", "Voice synthesis was cancelled");
|
|
307
|
+
}
|
|
308
|
+
return path;
|
|
309
|
+
} finally {
|
|
310
|
+
clearTimeout(timeout);
|
|
311
|
+
signal.removeEventListener("abort", onAbort);
|
|
312
|
+
controllers.delete(controller);
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
async cancel() {
|
|
316
|
+
abortAll();
|
|
317
|
+
},
|
|
318
|
+
async release(filePath) {
|
|
319
|
+
try {
|
|
320
|
+
await fileSystem.removeFile(filePath);
|
|
321
|
+
} catch (e10) {
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
async dispose() {
|
|
325
|
+
disposed = true;
|
|
326
|
+
abortAll();
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function createUnavailableVoiceEngine() {
|
|
331
|
+
return {
|
|
332
|
+
async availability() {
|
|
333
|
+
return "unavailable";
|
|
334
|
+
},
|
|
335
|
+
async prewarm() {
|
|
336
|
+
},
|
|
337
|
+
async synthesize() {
|
|
338
|
+
throw new VoiceError("engine_unavailable", "No TTS endpoint is configured");
|
|
339
|
+
},
|
|
340
|
+
async cancel() {
|
|
341
|
+
},
|
|
342
|
+
async dispose() {
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// src/match/voice/phrases.ts
|
|
348
|
+
var VIETNAMESE_NUMBERS = [
|
|
349
|
+
"kh\xF4ng",
|
|
350
|
+
"m\u1ED9t",
|
|
351
|
+
"hai",
|
|
352
|
+
"ba",
|
|
353
|
+
"b\u1ED1n",
|
|
354
|
+
"n\u0103m",
|
|
355
|
+
"s\xE1u",
|
|
356
|
+
"b\u1EA3y",
|
|
357
|
+
"t\xE1m",
|
|
358
|
+
"ch\xEDn"
|
|
359
|
+
];
|
|
360
|
+
function spokenNumber(value) {
|
|
361
|
+
if (!Number.isInteger(value) || value < 0 || value > 99) {
|
|
362
|
+
throw new RangeError("Announcement scores must be integers from 0 to 99");
|
|
363
|
+
}
|
|
364
|
+
if (value < 10) return VIETNAMESE_NUMBERS[value];
|
|
365
|
+
if (value === 10) return "m\u01B0\u1EDDi";
|
|
366
|
+
const tens = Math.floor(value / 10);
|
|
367
|
+
const units = value % 10;
|
|
368
|
+
const tensWord = tens === 1 ? "m\u01B0\u1EDDi" : `${VIETNAMESE_NUMBERS[tens]} m\u01B0\u01A1i`;
|
|
369
|
+
if (units === 0) return tensWord;
|
|
370
|
+
if (units === 1) return `${tensWord} m\u1ED1t`;
|
|
371
|
+
if (units === 5) return `${tensWord} l\u0103m`;
|
|
372
|
+
return `${tensWord} ${VIETNAMESE_NUMBERS[units]}`;
|
|
373
|
+
}
|
|
374
|
+
function teamName(snapshot, teamId) {
|
|
375
|
+
const team = snapshot.teams[teamId];
|
|
376
|
+
return _nullishCoalesce(team.name, () => ( team.players.map((player) => player.name).join(" v\xE0 ")));
|
|
377
|
+
}
|
|
378
|
+
function numberTokens(value) {
|
|
379
|
+
if (value < 20 || value % 10 === 0) return [`number-${value}`];
|
|
380
|
+
const units = value % 10;
|
|
381
|
+
const unitToken = units === 1 ? "number-mot" : units === 5 ? "number-lam" : `number-${units}`;
|
|
382
|
+
return [`number-${Math.floor(value / 10) * 10}`, unitToken];
|
|
383
|
+
}
|
|
384
|
+
function scoreDeuTokens(value) {
|
|
385
|
+
if (value < 20 || value % 10 === 0) return [`score-deu-${value}`];
|
|
386
|
+
const units = value % 10;
|
|
387
|
+
const unitToken = units === 1 ? "score-deu-mot" : units === 5 ? "score-deu-lam" : `score-deu-${units}`;
|
|
388
|
+
return [`number-${Math.floor(value / 10) * 10}`, unitToken];
|
|
389
|
+
}
|
|
390
|
+
function doublesServerNumber(snapshot) {
|
|
391
|
+
if (snapshot.scoringMode === "sideOut") {
|
|
392
|
+
return snapshot.serve.serverNumber;
|
|
393
|
+
}
|
|
394
|
+
const servingTeam = snapshot.teams[snapshot.serve.teamId];
|
|
395
|
+
const playerIndex = servingTeam.players.findIndex(
|
|
396
|
+
(player) => player.id === snapshot.serve.playerId
|
|
397
|
+
);
|
|
398
|
+
return playerIndex === 1 ? 2 : 1;
|
|
399
|
+
}
|
|
400
|
+
function scorePlan(snapshot, prefix = "") {
|
|
401
|
+
const serving = snapshot.serve.teamId;
|
|
402
|
+
const receiving = serving === "A" ? "B" : "A";
|
|
403
|
+
const game = snapshot.games[snapshot.currentGame - 1];
|
|
404
|
+
if (!game) throw new TypeError("Current game is missing");
|
|
405
|
+
const servingScore = game.score[serving];
|
|
406
|
+
const receivingScore = game.score[receiving];
|
|
407
|
+
const serverNum = snapshot.format === "doubles" ? doublesServerNumber(snapshot) : 1;
|
|
408
|
+
const server = snapshot.format === "doubles" ? ` ${spokenNumber(serverNum)}` : "";
|
|
409
|
+
const fallbackTokens = [];
|
|
410
|
+
if (prefix === "\u0110\u1ED5i giao b\xF3ng") {
|
|
411
|
+
fallbackTokens.push("service-change");
|
|
412
|
+
} else if (prefix === "\u0110i\u1EC3m th\u1EAFng v\xE1n") {
|
|
413
|
+
fallbackTokens.push("game-point");
|
|
414
|
+
}
|
|
415
|
+
if (servingScore === receivingScore) {
|
|
416
|
+
fallbackTokens.push(...scoreDeuTokens(servingScore));
|
|
417
|
+
} else {
|
|
418
|
+
fallbackTokens.push(...numberTokens(servingScore));
|
|
419
|
+
fallbackTokens.push(...numberTokens(receivingScore));
|
|
420
|
+
}
|
|
421
|
+
if (snapshot.format === "doubles") {
|
|
422
|
+
fallbackTokens.push(`server-${serverNum}`);
|
|
423
|
+
}
|
|
424
|
+
const secondPart = servingScore === receivingScore ? "\u0111\u1EC1u" : spokenNumber(receivingScore);
|
|
425
|
+
const scoreBody = `${spokenNumber(servingScore).replace(/^./u, (letter) => letter.toLocaleUpperCase("vi"))} ${secondPart}${server}.`;
|
|
426
|
+
return {
|
|
427
|
+
kind: "score",
|
|
428
|
+
text: prefix ? `${prefix}, ${scoreBody.replace(/^./u, (letter) => letter.toLocaleLowerCase("vi"))}` : scoreBody,
|
|
429
|
+
score: [servingScore, receivingScore],
|
|
430
|
+
fallbackTokens
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
function statusPlan(kind, text, token) {
|
|
434
|
+
return { kind, text, fallbackTokens: [token] };
|
|
435
|
+
}
|
|
436
|
+
function buildVietnameseAnnouncement(previous, current, event) {
|
|
437
|
+
if (current.status === "completed" && previous.status !== "completed" && current.winner) {
|
|
438
|
+
return statusPlan(
|
|
439
|
+
"matchWon",
|
|
440
|
+
`\u0110\u1ED9i ${teamName(current, current.winner)} th\u1EAFng tr\u1EADn.`,
|
|
441
|
+
"match-won"
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
const previousGame = previous.games[previous.currentGame - 1];
|
|
445
|
+
const currentGame = current.games[current.currentGame - 1];
|
|
446
|
+
if (_optionalChain([currentGame, 'optionalAccess', _9 => _9.winner]) && !_optionalChain([previousGame, 'optionalAccess', _10 => _10.winner])) {
|
|
447
|
+
return statusPlan(
|
|
448
|
+
"gameWon",
|
|
449
|
+
`\u0110\u1ED9i ${teamName(current, currentGame.winner)} th\u1EAFng v\xE1n.`,
|
|
450
|
+
"game-won"
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
if (event.type === "courtSwitchConfirmed") {
|
|
454
|
+
return statusPlan("courtSwitch", "\u0110\u1ED5i s\xE2n.", "court-switch");
|
|
455
|
+
}
|
|
456
|
+
const serviceChanged = previous.serve.teamId !== current.serve.teamId;
|
|
457
|
+
if (serviceChanged) {
|
|
458
|
+
const plan = scorePlan(current, "\u0110\u1ED5i giao b\xF3ng");
|
|
459
|
+
return { ...plan, kind: "serviceChange" };
|
|
460
|
+
}
|
|
461
|
+
if (currentGame && !currentGame.winner && Math.max(currentGame.score.A, currentGame.score.B) === current.targetScore - 1) {
|
|
462
|
+
const plan = scorePlan(current, "\u0110i\u1EC3m th\u1EAFng v\xE1n");
|
|
463
|
+
return { ...plan, kind: "gamePoint" };
|
|
464
|
+
}
|
|
465
|
+
return scorePlan(current);
|
|
466
|
+
}
|
|
467
|
+
function planBundledFallback(plan) {
|
|
468
|
+
let tokens = plan.fallbackTokens;
|
|
469
|
+
if (plan.score) {
|
|
470
|
+
const prefixTokens = plan.fallbackTokens.filter(
|
|
471
|
+
(token) => token === "service-change" || token === "game-point"
|
|
472
|
+
);
|
|
473
|
+
const suffixTokens = plan.fallbackTokens.filter(
|
|
474
|
+
(token) => token.startsWith("server-") || token.startsWith("court-")
|
|
475
|
+
);
|
|
476
|
+
const scoreTokens = plan.score[0] === plan.score[1] ? [...scoreDeuTokens(plan.score[0])] : [...numberTokens(plan.score[0]), ...numberTokens(plan.score[1])];
|
|
477
|
+
tokens = [
|
|
478
|
+
...prefixTokens,
|
|
479
|
+
...scoreTokens,
|
|
480
|
+
...suffixTokens
|
|
481
|
+
];
|
|
482
|
+
}
|
|
483
|
+
return tokens.map((token) => `asset://voice/${token}.wav`);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// src/match/voice/scheduler.ts
|
|
487
|
+
var LatestAnnouncementScheduler = (_class2 = class _LatestAnnouncementScheduler {
|
|
488
|
+
constructor(options) {;_class2.prototype.__init2.call(this);_class2.prototype.__init3.call(this);_class2.prototype.__init4.call(this);_class2.prototype.__init5.call(this);_class2.prototype.__init6.call(this);_class2.prototype.__init7.call(this);_class2.prototype.__init8.call(this);_class2.prototype.__init9.call(this);
|
|
489
|
+
this.options = options;
|
|
490
|
+
}
|
|
491
|
+
static __initStatic() {this.MAX_CACHE_ENTRIES = 32}
|
|
492
|
+
__init2() {this.generation = 0}
|
|
493
|
+
__init3() {this.activeController = null}
|
|
494
|
+
__init4() {this.lastPlan = null}
|
|
495
|
+
__init5() {this.neuralCache = /* @__PURE__ */ new Map()}
|
|
496
|
+
__init6() {this.operations = /* @__PURE__ */ new Set()}
|
|
497
|
+
__init7() {this.prewarmControllers = /* @__PURE__ */ new Set()}
|
|
498
|
+
__init8() {this.lifecycle = "active"}
|
|
499
|
+
__init9() {this.disposePromise = null}
|
|
500
|
+
prewarm(commonPhrases = []) {
|
|
501
|
+
if (this.lifecycle !== "active") return Promise.resolve();
|
|
502
|
+
const generation = this.generation;
|
|
503
|
+
return this.track((async () => {
|
|
504
|
+
try {
|
|
505
|
+
await this.options.engine.prewarm(commonPhrases);
|
|
506
|
+
if (this.lifecycle !== "active" || generation !== this.generation) return;
|
|
507
|
+
const availability = await this.options.engine.availability();
|
|
508
|
+
if (this.lifecycle !== "active" || generation !== this.generation) return;
|
|
509
|
+
if (availability === "available") {
|
|
510
|
+
for (const phrase of commonPhrases) {
|
|
511
|
+
if (this.lifecycle !== "active" || generation !== this.generation) return;
|
|
512
|
+
if (this.neuralCache.has(phrase)) continue;
|
|
513
|
+
const controller = new AbortController();
|
|
514
|
+
this.prewarmControllers.add(controller);
|
|
515
|
+
let file;
|
|
516
|
+
try {
|
|
517
|
+
file = await this.options.engine.synthesize(phrase, controller.signal);
|
|
518
|
+
} finally {
|
|
519
|
+
this.prewarmControllers.delete(controller);
|
|
520
|
+
}
|
|
521
|
+
if (this.lifecycle !== "active" || generation !== this.generation || controller.signal.aborted) {
|
|
522
|
+
await _optionalChain([this, 'access', _11 => _11.options, 'access', _12 => _12.engine, 'access', _13 => _13.release, 'optionalCall', _14 => _14(file)]);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
await this.cacheFile(phrase, file);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
} catch (error) {
|
|
529
|
+
this.report(error);
|
|
530
|
+
}
|
|
531
|
+
})());
|
|
532
|
+
}
|
|
533
|
+
enqueue(plan) {
|
|
534
|
+
if (this.lifecycle !== "active") return Promise.resolve();
|
|
535
|
+
return this.track(this.performEnqueue(plan));
|
|
536
|
+
}
|
|
537
|
+
async performEnqueue(plan) {
|
|
538
|
+
this.lastPlan = plan;
|
|
539
|
+
const generation = ++this.generation;
|
|
540
|
+
_optionalChain([this, 'access', _15 => _15.activeController, 'optionalAccess', _16 => _16.abort, 'call', _17 => _17()]);
|
|
541
|
+
const controller = new AbortController();
|
|
542
|
+
this.activeController = controller;
|
|
543
|
+
await Promise.allSettled([
|
|
544
|
+
this.options.engine.cancel(),
|
|
545
|
+
this.options.audio.stop()
|
|
546
|
+
]);
|
|
547
|
+
if (generation !== this.generation) return;
|
|
548
|
+
let neuralFailure = null;
|
|
549
|
+
try {
|
|
550
|
+
const avail = await this.options.engine.availability();
|
|
551
|
+
if (avail === "available") {
|
|
552
|
+
let file = this.touchCachedFile(plan.text);
|
|
553
|
+
if (!file) {
|
|
554
|
+
file = await this.options.engine.synthesize(plan.text, controller.signal);
|
|
555
|
+
await this.cacheFile(plan.text, file);
|
|
556
|
+
}
|
|
557
|
+
if (this.lifecycle !== "active" || generation !== this.generation || controller.signal.aborted) return;
|
|
558
|
+
await this.options.audio.play(file);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
} catch (error) {
|
|
562
|
+
if (controller.signal.aborted || generation !== this.generation) return;
|
|
563
|
+
neuralFailure = error;
|
|
564
|
+
}
|
|
565
|
+
let fallbackPlayed = false;
|
|
566
|
+
for (const source of planBundledFallback(plan)) {
|
|
567
|
+
if (generation !== this.generation || controller.signal.aborted) return;
|
|
568
|
+
try {
|
|
569
|
+
await this.options.audio.play(source);
|
|
570
|
+
fallbackPlayed = true;
|
|
571
|
+
} catch (error) {
|
|
572
|
+
this.report(error);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
if (neuralFailure && !fallbackPlayed) this.report(neuralFailure);
|
|
576
|
+
}
|
|
577
|
+
async replayLast() {
|
|
578
|
+
if (this.lifecycle !== "active") return;
|
|
579
|
+
if (this.lastPlan) await this.enqueue(this.lastPlan);
|
|
580
|
+
}
|
|
581
|
+
testSpeaker() {
|
|
582
|
+
if (this.lifecycle !== "active") return Promise.resolve();
|
|
583
|
+
return this.track(this.performSpeakerTest());
|
|
584
|
+
}
|
|
585
|
+
async performSpeakerTest() {
|
|
586
|
+
const generation = ++this.generation;
|
|
587
|
+
_optionalChain([this, 'access', _18 => _18.activeController, 'optionalAccess', _19 => _19.abort, 'call', _20 => _20()]);
|
|
588
|
+
await Promise.allSettled([
|
|
589
|
+
this.options.engine.cancel(),
|
|
590
|
+
this.options.audio.stop()
|
|
591
|
+
]);
|
|
592
|
+
if (generation === this.generation) {
|
|
593
|
+
try {
|
|
594
|
+
await this.options.audio.play("asset://voice/test-speaker.wav");
|
|
595
|
+
} catch (error) {
|
|
596
|
+
this.report(error);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
async dispose() {
|
|
601
|
+
if (this.disposePromise) return this.disposePromise;
|
|
602
|
+
this.lifecycle = "disposing";
|
|
603
|
+
this.generation += 1;
|
|
604
|
+
_optionalChain([this, 'access', _21 => _21.activeController, 'optionalAccess', _22 => _22.abort, 'call', _23 => _23()]);
|
|
605
|
+
this.activeController = null;
|
|
606
|
+
for (const controller of this.prewarmControllers) controller.abort();
|
|
607
|
+
this.prewarmControllers.clear();
|
|
608
|
+
this.disposePromise = (async () => {
|
|
609
|
+
await Promise.allSettled([
|
|
610
|
+
this.options.engine.cancel(),
|
|
611
|
+
this.options.audio.stop()
|
|
612
|
+
]);
|
|
613
|
+
await Promise.allSettled([...this.operations]);
|
|
614
|
+
await this.clearCache();
|
|
615
|
+
await Promise.allSettled([
|
|
616
|
+
this.options.engine.dispose(),
|
|
617
|
+
this.options.audio.dispose()
|
|
618
|
+
]);
|
|
619
|
+
this.lifecycle = "disposed";
|
|
620
|
+
})();
|
|
621
|
+
return this.disposePromise;
|
|
622
|
+
}
|
|
623
|
+
async clearCache() {
|
|
624
|
+
const files = [...new Set(this.neuralCache.values())];
|
|
625
|
+
this.neuralCache.clear();
|
|
626
|
+
if (this.options.engine.release) {
|
|
627
|
+
await Promise.allSettled(files.map((file) => this.options.engine.release(file)));
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
async cacheFile(text, file) {
|
|
631
|
+
if (this.lifecycle !== "active") {
|
|
632
|
+
await _optionalChain([this, 'access', _24 => _24.options, 'access', _25 => _25.engine, 'access', _26 => _26.release, 'optionalCall', _27 => _27(file)]);
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
const replaced = this.neuralCache.get(text);
|
|
636
|
+
if (replaced && replaced !== file) await _optionalChain([this, 'access', _28 => _28.options, 'access', _29 => _29.engine, 'access', _30 => _30.release, 'optionalCall', _31 => _31(replaced)]);
|
|
637
|
+
this.neuralCache.delete(text);
|
|
638
|
+
this.neuralCache.set(text, file);
|
|
639
|
+
while (this.neuralCache.size > _LatestAnnouncementScheduler.MAX_CACHE_ENTRIES) {
|
|
640
|
+
const oldest = this.neuralCache.entries().next().value;
|
|
641
|
+
if (!oldest) break;
|
|
642
|
+
this.neuralCache.delete(oldest[0]);
|
|
643
|
+
await _optionalChain([this, 'access', _32 => _32.options, 'access', _33 => _33.engine, 'access', _34 => _34.release, 'optionalCall', _35 => _35(oldest[1])]);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
touchCachedFile(text) {
|
|
647
|
+
const file = this.neuralCache.get(text);
|
|
648
|
+
if (!file) return void 0;
|
|
649
|
+
this.neuralCache.delete(text);
|
|
650
|
+
this.neuralCache.set(text, file);
|
|
651
|
+
return file;
|
|
652
|
+
}
|
|
653
|
+
track(operation) {
|
|
654
|
+
this.operations.add(operation);
|
|
655
|
+
operation.then(
|
|
656
|
+
() => this.operations.delete(operation),
|
|
657
|
+
() => this.operations.delete(operation)
|
|
658
|
+
);
|
|
659
|
+
return operation;
|
|
660
|
+
}
|
|
661
|
+
report(caught) {
|
|
662
|
+
const error = caught instanceof Error ? caught : new Error("Voice operation failed");
|
|
663
|
+
try {
|
|
664
|
+
_optionalChain([this, 'access', _36 => _36.options, 'access', _37 => _37.onError, 'optionalCall', _38 => _38(error)]);
|
|
665
|
+
} catch (e11) {
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}, _class2.__initStatic(), _class2);
|
|
669
|
+
|
|
670
|
+
// src/match/voice/service.ts
|
|
671
|
+
function removeDirectory(path) {
|
|
672
|
+
const fs = _chunk7OX2DSKIcjs.__require.call(void 0, "expo-file-system");
|
|
673
|
+
const info = fs.Paths.info(path);
|
|
674
|
+
if (!info.exists) return;
|
|
675
|
+
if (info.isDirectory) new fs.Directory(path).delete();
|
|
676
|
+
else new fs.File(path).delete();
|
|
677
|
+
}
|
|
678
|
+
function createDefaultMatchVoiceService(options = {}) {
|
|
679
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
680
|
+
let lastError = null;
|
|
681
|
+
let disposed = false;
|
|
682
|
+
let lifecycle = "active";
|
|
683
|
+
let initialized = false;
|
|
684
|
+
let operationTail = Promise.resolve();
|
|
685
|
+
let disposePromise = null;
|
|
686
|
+
let notificationQueue = Promise.resolve();
|
|
687
|
+
const audio = _nullishCoalesce(options.audio, () => ( createExpoAudioPlayback({ resolveSource: resolveBundledVoiceAsset })));
|
|
688
|
+
const generatedDirectory = () => _nullishCoalesce(options.generatedRoot, () => ( `${_chunk7OX2DSKIcjs.__require.call(void 0, "expo-file-system").Paths.cache.uri}/pickleball-voice-generated/gemini`));
|
|
689
|
+
const engine = _nullishCoalesce(options.engine, () => ( (options.tts ? createGeminiVoiceEngine({ ...options.tts, generatedRoot: generatedDirectory() }) : createUnavailableVoiceEngine())));
|
|
690
|
+
let errorEpoch = 0;
|
|
691
|
+
const scheduler = new LatestAnnouncementScheduler({
|
|
692
|
+
engine,
|
|
693
|
+
audio,
|
|
694
|
+
onError(error) {
|
|
695
|
+
lastError = error;
|
|
696
|
+
errorEpoch += 1;
|
|
697
|
+
enqueueNotify();
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
function notify() {
|
|
701
|
+
for (const listener of listeners) listener();
|
|
702
|
+
}
|
|
703
|
+
function enqueueNotify() {
|
|
704
|
+
notificationQueue = notificationQueue.then(() => {
|
|
705
|
+
if (!disposed) notify();
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
const unsubscribeAudio = _optionalChain([audio, 'access', _39 => _39.subscribeToRouteChanges, 'optionalCall', _40 => _40(() => enqueueNotify())]);
|
|
709
|
+
async function sweepGeneratedAudio() {
|
|
710
|
+
await scheduler.clearCache();
|
|
711
|
+
try {
|
|
712
|
+
if (options.removeGeneratedDirectory) {
|
|
713
|
+
await options.removeGeneratedDirectory(generatedDirectory());
|
|
714
|
+
} else {
|
|
715
|
+
removeDirectory(generatedDirectory());
|
|
716
|
+
}
|
|
717
|
+
} catch (e12) {
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
function runActive(operation) {
|
|
721
|
+
if (lifecycle !== "active") return Promise.resolve();
|
|
722
|
+
const result = operationTail.then(async () => {
|
|
723
|
+
if (lifecycle === "active") await operation();
|
|
724
|
+
});
|
|
725
|
+
operationTail = result.catch(() => void 0);
|
|
726
|
+
return result;
|
|
727
|
+
}
|
|
728
|
+
const stillActive = () => lifecycle === "active";
|
|
729
|
+
return {
|
|
730
|
+
get audioState() {
|
|
731
|
+
return audio.getRouteState();
|
|
732
|
+
},
|
|
733
|
+
get lastError() {
|
|
734
|
+
return lastError;
|
|
735
|
+
},
|
|
736
|
+
initialize: () => runActive(async () => {
|
|
737
|
+
if (initialized) return;
|
|
738
|
+
await audio.configure(_nullishCoalesce(options.audioMode, () => ( "offline")));
|
|
739
|
+
if (!stillActive()) return;
|
|
740
|
+
await sweepGeneratedAudio();
|
|
741
|
+
if (!stillActive()) return;
|
|
742
|
+
await scheduler.prewarm(["\u0110\u1ED5i giao b\xF3ng.", "\u0110i\u1EC3m th\u1EAFng v\xE1n.", "\u0110\u1ED5i s\xE2n."]);
|
|
743
|
+
if (!stillActive()) return;
|
|
744
|
+
initialized = true;
|
|
745
|
+
notify();
|
|
746
|
+
}),
|
|
747
|
+
subscribe(listener) {
|
|
748
|
+
listeners.add(listener);
|
|
749
|
+
return () => listeners.delete(listener);
|
|
750
|
+
},
|
|
751
|
+
announce(previous, current, event) {
|
|
752
|
+
if (!stillActive()) return;
|
|
753
|
+
const epochBefore = errorEpoch;
|
|
754
|
+
void scheduler.enqueue(buildVietnameseAnnouncement(previous, current, event)).then(
|
|
755
|
+
() => {
|
|
756
|
+
if (lastError && errorEpoch === epochBefore && stillActive()) {
|
|
757
|
+
lastError = null;
|
|
758
|
+
notify();
|
|
759
|
+
}
|
|
760
|
+
},
|
|
761
|
+
(error) => {
|
|
762
|
+
lastError = error instanceof Error ? error : new Error("Announcement failed");
|
|
763
|
+
notify();
|
|
764
|
+
}
|
|
765
|
+
);
|
|
766
|
+
},
|
|
767
|
+
replayAnnouncement: () => stillActive() ? scheduler.replayLast() : Promise.resolve(),
|
|
768
|
+
testSpeaker: () => stillActive() ? scheduler.testSpeaker() : Promise.resolve(),
|
|
769
|
+
configureAudioMode: (mode) => runActive(async () => {
|
|
770
|
+
await audio.configure(mode);
|
|
771
|
+
if (stillActive()) enqueueNotify();
|
|
772
|
+
}),
|
|
773
|
+
dispose() {
|
|
774
|
+
if (disposePromise) return disposePromise;
|
|
775
|
+
lifecycle = "disposing";
|
|
776
|
+
disposed = true;
|
|
777
|
+
_optionalChain([unsubscribeAudio, 'optionalCall', _41 => _41()]);
|
|
778
|
+
disposePromise = (async () => {
|
|
779
|
+
await operationTail;
|
|
780
|
+
await scheduler.dispose();
|
|
781
|
+
await notificationQueue;
|
|
782
|
+
listeners.clear();
|
|
783
|
+
lifecycle = "disposed";
|
|
784
|
+
})();
|
|
785
|
+
return disposePromise;
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
exports.VoiceError = VoiceError; exports.createExpoAudioPlayback = createExpoAudioPlayback; exports.resolveBundledVoiceAsset = resolveBundledVoiceAsset; exports.createGeminiVoiceEngine = createGeminiVoiceEngine; exports.createUnavailableVoiceEngine = createUnavailableVoiceEngine; exports.buildVietnameseAnnouncement = buildVietnameseAnnouncement; exports.planBundledFallback = planBundledFallback; exports.LatestAnnouncementScheduler = LatestAnnouncementScheduler; exports.createDefaultMatchVoiceService = createDefaultMatchVoiceService;
|