interview-widget 3.3.1 → 3.3.3-beta.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.
Files changed (74) hide show
  1. package/README.md +15 -4
  2. package/dist/StreamingAudioPlayer-DmOtqI_a-BX7ptCkK.js +430 -0
  3. package/dist/_headers +2 -0
  4. package/dist/assets/avatar_core_wasm-36bf5449.wasm +0 -0
  5. package/dist/assets/avatar_core_wasm.js +2697 -0
  6. package/dist/assets/index.d.ts +5 -12
  7. package/dist/assets/net-cross-icon.d.ts +1 -0
  8. package/dist/assets/style-8JJ3h3Ol.css +1 -0
  9. package/dist/assets/wifi-icon.d.ts +1 -0
  10. package/dist/assets/wifi-low-icon.d.ts +1 -0
  11. package/dist/assets/wifi-off-icon.d.ts +1 -0
  12. package/dist/avatar_core_wasm-BIbE25D3-D94zNSYX.js +1651 -0
  13. package/dist/components/interview/interview-content.d.ts +13 -0
  14. package/dist/components/interview/interview-controller.d.ts +8 -1
  15. package/dist/components/interview/interview-header.d.ts +1 -1
  16. package/dist/components/interview/proctoring/gaze-detection/utils/types.d.ts +1 -10
  17. package/dist/components/interview/proctoring/interview-proctoring.d.ts +1 -0
  18. package/dist/components/interview/question-text.d.ts +4 -0
  19. package/dist/components/interview/transcript/interview-transcript.d.ts +1 -0
  20. package/dist/components/media/spatius-avatar-feed.d.ts +11 -0
  21. package/dist/components/media/video-feed.d.ts +5 -0
  22. package/dist/components/modals/onboarding-modal.d.ts +4 -1
  23. package/dist/components/modals/retry-confirmation-modal.d.ts +9 -0
  24. package/dist/components/network/network-quality-bar.d.ts +6 -0
  25. package/dist/components/network/network-quality-config.d.ts +21 -0
  26. package/dist/components/network/network-warning-modal.d.ts +12 -0
  27. package/dist/components/ui/extended/chat-ui.d.ts +1 -4
  28. package/dist/context/interview-widget-context.d.ts +3 -8
  29. package/dist/context/proctoring-context.d.ts +1 -22
  30. package/dist/hooks/index.d.ts +3 -1
  31. package/dist/hooks/use-network-quality.d.ts +11 -0
  32. package/dist/hooks/use-next-question-stream.d.ts +11 -0
  33. package/dist/hooks/use-question-stream.d.ts +20 -0
  34. package/dist/hooks/use-spatius-question-stream.d.ts +22 -0
  35. package/dist/hooks/use-timer.d.ts +3 -2
  36. package/dist/hooks/use-tts.d.ts +1 -1
  37. package/dist/hooks/use-video-recording.d.ts +1 -1
  38. package/dist/index-DXUDYyS8.js +17107 -0
  39. package/dist/services/api/endpoints.d.ts +2 -4
  40. package/dist/services/stt/stt-service.d.ts +1 -0
  41. package/dist/services/timer/index.d.ts +0 -1
  42. package/dist/services/tts/tts-service.d.ts +3 -2
  43. package/dist/types.d.ts +7 -0
  44. package/dist/utils/constants.d.ts +0 -5
  45. package/dist/utils/crypto.d.ts +0 -7
  46. package/dist/utils/helper.d.ts +0 -4
  47. package/dist/utils/safari-audio-utils.d.ts +49 -0
  48. package/dist/widget.es.3.3.3-beta.0.js +10532 -0
  49. package/dist/widget.umd.3.3.3-beta.0.js +783 -0
  50. package/package.json +10 -11
  51. package/dist/assets/clock-icon.d.ts +0 -1
  52. package/dist/assets/focus-icon.d.ts +0 -1
  53. package/dist/assets/info-icon.d.ts +0 -1
  54. package/dist/assets/keyboard-icon.d.ts +0 -1
  55. package/dist/assets/mic-off-icon.d.ts +0 -1
  56. package/dist/assets/monitor-icon.d.ts +0 -1
  57. package/dist/assets/mouse-pointer-click-icon.d.ts +0 -1
  58. package/dist/assets/scan-face-icon.d.ts +0 -1
  59. package/dist/assets/shield-icon.d.ts +0 -2
  60. package/dist/assets/speak-icon.d.ts +0 -1
  61. package/dist/assets/video-off-icon.d.ts +0 -1
  62. package/dist/assets/volumn-icon.d.ts +0 -1
  63. package/dist/components/interview/answer-area.d.ts +0 -12
  64. package/dist/components/interview/phases/interview-completion-modal.d.ts +0 -7
  65. package/dist/components/interview/proctoring/face-data-permission-modal.d.ts +0 -7
  66. package/dist/components/ui/dialog.d.ts +0 -13
  67. package/dist/components/ui/input.d.ts +0 -8
  68. package/dist/components/ui/media-control-item.d.ts +0 -8
  69. package/dist/hooks/use-dialog.d.ts +0 -6
  70. package/dist/styles.d.ts +0 -0
  71. package/dist/utils/audio-storage.d.ts +0 -24
  72. package/dist/widget.3.3.1.css +0 -1
  73. package/dist/widget.es.3.3.1.js +0 -9366
  74. package/dist/widget.umd.3.3.1.js +0 -179
package/README.md CHANGED
@@ -72,6 +72,7 @@ export default function App() {
72
72
  language: "en",
73
73
  },
74
74
  tts: { provider: "piper" },
75
+ avatar: { provider: "none" },
75
76
  timers: {
76
77
  thinkingDuration: 30,
77
78
  answeringDuration: 120,
@@ -201,10 +202,13 @@ Pass this object to `InterviewWidgetProvider` via the `config` prop. Tables list
201
202
 
202
203
  ### UI
203
204
 
204
- | Prop | Type | Default | Description |
205
- | ------------ | -------------- | --------- | -------------------------------------- |
206
- | baseColor | `string (hex)` | "#3B82F6" | Primary brand color used by the widget |
207
- | borderRadius | `string (CSS)` | "8px" | Global corner radius for components |
205
+ | Prop | Type | Default | Description |
206
+ | ----------------------- | -------------- | --------------------------------------------------------------- | ------------------------------------------------------------- |
207
+ | baseColor | `string (hex)` | "#3B82F6" | Primary brand color used by the widget |
208
+ | borderRadius | `string (CSS)` | "8px" | Global corner radius for components |
209
+ | retryProgressSavedText | `string` | `"Your progress is saved — tap Retry or exit the interview..."` | Text to show in next-question retry modal |
210
+ | retryMaxAttempts | `number` | 3 | Maximum number of attempts allowed for retrying next question |
211
+ | retryNoAttemptsLeftText | `string` | `"Retry limit reached. Please exit and resume later."` | Error message shown when max attempts are reached |
208
212
 
209
213
  ### Interview
210
214
 
@@ -216,6 +220,7 @@ Pass this object to `InterviewWidgetProvider` via the `config` prop. Tables list
216
220
  | timers | `object` | [see below](#timers) | Per-phase and global timing configuration |
217
221
  | stt | `object` | [see below](#stt-speech-to-text) | Speech-to-Text provider settings |
218
222
  | tts | `object` | [see below](#tts-text-to-speech) | Text-to-Speech provider settings |
223
+ | avatar | `object` | [see below](#avatar) | Optional avatar question-delivery provider |
219
224
  | proctoring | `object` | [see below](#proctoring) | Proctoring settings and cheating detection |
220
225
 
221
226
  #### Timers
@@ -236,6 +241,12 @@ Pass this object to `InterviewWidgetProvider` via the `config` prop. Tables list
236
241
  | model | `"whisper-large-v3-turbo" \| string` | "whisper-large-v3-turbo" | STT model identifier |
237
242
  | language | `string` | "en" | Language code passed to the STT engine |
238
243
 
244
+ #### Avatar
245
+
246
+ | Prop | Type | Default | Description |
247
+ | -------- | ------------------------- | -------- | ------------------------------------------------------------------------------------------- |
248
+ | provider | `"none" \| "spatius"` | `"none"` | Keep the standard SSE/audio flow or render streamed questions through Spatius Direct Mode. |
249
+
239
250
  #### TTS (Text-To-Speech)
240
251
 
241
252
  | Prop | Type | Default | Description |
@@ -0,0 +1,430 @@
1
+ import { A as C, l as a, e as g, a as c } from "./index-DXUDYyS8.js";
2
+ var m = Object.defineProperty, f = (r, t, e) => t in r ? m(r, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : r[t] = e, s = (r, t, e) => f(r, typeof t != "symbol" ? t + "" : t, e);
3
+ class p {
4
+ // Mark if AudioContext is being resumed, avoid concurrent resume requests
5
+ constructor(t) {
6
+ s(this, "audioContext", null), s(this, "sampleRate"), s(this, "channelCount"), s(this, "debug"), s(this, "sessionId"), s(this, "sessionStartTime", 0), s(this, "pausedTimeOffset", 0), s(this, "pausedAt", 0), s(this, "pausedAudioContextTime", 0), s(this, "scheduledTime", 0), s(this, "isPlaying", !1), s(this, "isPaused", !1), s(this, "autoStartEnabled", !0), s(this, "autoContinue", !1), s(this, "audioChunks", []), s(this, "scheduledChunks", 0), s(this, "activeSources", /* @__PURE__ */ new Set()), s(this, "lastScheduledChunkEndTime", 0), s(this, "lastGetCurrentTimeLog", 0), s(this, "scheduledChunkInfo", []), s(this, "gainNode", null), s(this, "volume", 1), s(this, "onEndedCallback"), s(this, "stateChangeHandler"), s(this, "isResuming", !1), this.sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, this.sampleRate = (t == null ? void 0 : t.sampleRate) ?? C.audio.sampleRate, this.channelCount = (t == null ? void 0 : t.channelCount) ?? 1, this.debug = (t == null ? void 0 : t.debug) ?? !1;
7
+ }
8
+ /**
9
+ * Initialize audio context (create and ensure it's ready)
10
+ */
11
+ async initialize() {
12
+ if (!this.audioContext)
13
+ try {
14
+ this.audioContext = new AudioContext({
15
+ sampleRate: this.sampleRate
16
+ }), this.gainNode = this.audioContext.createGain(), this.gainNode.gain.value = this.volume, this.gainNode.connect(this.audioContext.destination), this.audioContext.state === "suspended" && await this.audioContext.resume(), this.stateChangeHandler = (t) => {
17
+ t.target.state === "suspended" && this.isPlaying && !this.isPaused && this.ensureAudioContextRunning().catch((i) => {
18
+ a.error("[StreamingAudioPlayer] Failed to auto-resume AudioContext after external suspend:", i);
19
+ });
20
+ }, this.audioContext.addEventListener("statechange", this.stateChangeHandler), this.log("AudioContext initialized", {
21
+ sessionId: this.sessionId,
22
+ sampleRate: this.audioContext.sampleRate,
23
+ state: this.audioContext.state
24
+ });
25
+ } catch (t) {
26
+ const e = g(t);
27
+ throw c("audio_session_init_failed", "warning", {
28
+ session_id: this.sessionId,
29
+ reason: e
30
+ }), a.error("Failed to initialize AudioContext:", e), t instanceof Error ? t : new Error(e);
31
+ }
32
+ }
33
+ /**
34
+ * Ensure AudioContext is running (auto-resume if suspended)
35
+ * Only auto-resume when playing and not paused, avoid interfering with normal pause/resume logic
36
+ *
37
+ * Optimizations:
38
+ * - Fast path: if already in running state, return directly
39
+ * - Avoid concurrent resume: use isResuming flag to prevent duplicate resume requests
40
+ * - Handle closed state: if AudioContext is closed, cannot resume
41
+ * @internal
42
+ */
43
+ async ensureAudioContextRunning() {
44
+ if (!this.audioContext)
45
+ return;
46
+ const t = this.audioContext.state;
47
+ if (t !== "running") {
48
+ if (t === "closed") {
49
+ this.log("AudioContext is closed, cannot resume", {
50
+ sessionId: this.sessionId,
51
+ state: t
52
+ });
53
+ return;
54
+ }
55
+ if (t === "suspended" && this.isPlaying && !this.isPaused) {
56
+ if (this.isResuming) {
57
+ this.log("AudioContext resume already in progress, skipping duplicate request", {
58
+ sessionId: this.sessionId,
59
+ state: t
60
+ });
61
+ return;
62
+ }
63
+ this.isResuming = !0;
64
+ try {
65
+ this.log("AudioContext is suspended during playback, resuming...", {
66
+ sessionId: this.sessionId,
67
+ state: t,
68
+ isPlaying: this.isPlaying,
69
+ isPaused: this.isPaused
70
+ }), await this.audioContext.resume(), this.log("AudioContext resumed successfully", {
71
+ sessionId: this.sessionId,
72
+ state: this.audioContext.state
73
+ });
74
+ } catch (e) {
75
+ a.error("[StreamingAudioPlayer] Failed to resume AudioContext:", e), c("audio_context_resume_failed", "error", {
76
+ session_id: this.sessionId,
77
+ reason: e instanceof Error ? e.message : String(e)
78
+ });
79
+ } finally {
80
+ this.isResuming = !1;
81
+ }
82
+ }
83
+ }
84
+ }
85
+ /**
86
+ * Add audio chunk (16-bit PCM)
87
+ */
88
+ addChunk(t, e = !1) {
89
+ if (!this.audioContext) {
90
+ a.error("AudioContext not initialized");
91
+ return;
92
+ }
93
+ this.isPlaying && !this.isPaused && this.audioContext.state === "suspended" && this.ensureAudioContextRunning().catch((i) => {
94
+ a.error("[StreamingAudioPlayer] Failed to ensure AudioContext running in addChunk:", i);
95
+ }), this.audioChunks.push({ data: t, isLast: e }), this.log(`Added chunk ${this.audioChunks.length}`, {
96
+ size: t.length,
97
+ totalChunks: this.audioChunks.length,
98
+ isLast: e,
99
+ isPlaying: this.isPlaying,
100
+ scheduledChunks: this.scheduledChunks
101
+ }), this.autoContinue && this.isPaused && (this.log("[StreamingAudioPlayer] autoContinue=true, auto-resuming playback"), this.autoContinue = !1, this.resume().catch((i) => {
102
+ a.error("Failed to auto-resume playback:", i);
103
+ })), !this.isPlaying && this.autoStartEnabled && this.audioChunks.length > 0 ? (this.log("[StreamingAudioPlayer] Auto-starting playback from addChunk"), this.startPlayback().catch((i) => {
104
+ a.error("[StreamingAudioPlayer] Failed to start playback from addChunk:", i);
105
+ })) : this.isPlaying && !this.isPaused ? (this.log("[StreamingAudioPlayer] Already playing, scheduling next chunk"), this.scheduleNextChunk()) : this.log("[StreamingAudioPlayer] Not playing and no chunks, waiting for more chunks");
106
+ }
107
+ /**
108
+ * Start new session (stop current and start fresh)
109
+ */
110
+ async startNewSession(t, e = 0) {
111
+ if (this.stop(), this.sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, this.audioChunks = [], this.scheduledChunks = 0, this.pausedTimeOffset = 0, this.pausedAt = 0, this.pausedAudioContextTime = 0, this.autoContinue = !1, this.log("Starting new session", {
112
+ chunks: t.length,
113
+ leadingSilenceMs: e
114
+ }), e > 0) {
115
+ const i = Math.floor(e / 1e3 * this.sampleRate) * this.channelCount, u = new Uint8Array(i * 2);
116
+ this.addChunk(u, !1);
117
+ }
118
+ for (const i of t)
119
+ this.addChunk(i.data, i.isLast);
120
+ }
121
+ /**
122
+ * Start playback
123
+ */
124
+ async startPlayback() {
125
+ if (!this.audioContext) {
126
+ this.log("[StreamingAudioPlayer] Cannot start playback: AudioContext not initialized");
127
+ return;
128
+ }
129
+ if (this.isPlaying) {
130
+ this.log("[StreamingAudioPlayer] Cannot start playback: Already playing");
131
+ return;
132
+ }
133
+ await this.ensureAudioContextRunning(), this.isPlaying = !0, this.sessionStartTime = this.audioContext.currentTime, this.scheduledTime = this.sessionStartTime, this.lastScheduledChunkEndTime = 0, this.scheduledChunkInfo = [], this.autoContinue = !1, this.log("[StreamingAudioPlayer] Starting playback", {
134
+ sessionStartTime: this.sessionStartTime,
135
+ bufferedChunks: this.audioChunks.length,
136
+ scheduledChunks: this.scheduledChunks,
137
+ activeSources: this.activeSources.size,
138
+ audioContextState: this.audioContext.state
139
+ }), this.scheduleAllChunks();
140
+ }
141
+ /**
142
+ * Schedule all pending chunks
143
+ */
144
+ scheduleAllChunks() {
145
+ for (; this.scheduledChunks < this.audioChunks.length; )
146
+ this.scheduleNextChunk();
147
+ }
148
+ /**
149
+ * Schedule next audio chunk
150
+ */
151
+ scheduleNextChunk() {
152
+ if (!this.audioContext) {
153
+ this.log("[StreamingAudioPlayer] Cannot schedule chunk: AudioContext not initialized");
154
+ return;
155
+ }
156
+ if (!this.isPlaying || this.isPaused) {
157
+ this.log("[StreamingAudioPlayer] Cannot schedule chunk: Not playing or paused");
158
+ return;
159
+ }
160
+ this.audioContext.state === "suspended" && this.ensureAudioContextRunning().catch((n) => {
161
+ a.error("[StreamingAudioPlayer] Failed to ensure AudioContext running in scheduleNextChunk:", n);
162
+ });
163
+ const t = this.scheduledChunks;
164
+ if (t >= this.audioChunks.length) {
165
+ this.log(`[StreamingAudioPlayer] No more chunks to schedule (chunkIndex: ${t}, totalChunks: ${this.audioChunks.length})`);
166
+ return;
167
+ }
168
+ const e = this.audioChunks[t];
169
+ if (e.data.length === 0 && !e.isLast) {
170
+ this.scheduledChunks++;
171
+ return;
172
+ }
173
+ const i = e.data, u = e.isLast, o = this.pcmToAudioBuffer(i);
174
+ if (!o) {
175
+ a.error("Failed to create AudioBuffer from PCM data"), c("audio_buffer_creation_failed", "error", {
176
+ session_id: this.sessionId
177
+ });
178
+ return;
179
+ }
180
+ try {
181
+ const n = this.audioContext.createBufferSource();
182
+ n.buffer = o, n.connect(this.gainNode);
183
+ const l = this.scheduledTime;
184
+ n.start(l);
185
+ const h = Math.max(l, this.audioContext.currentTime);
186
+ this.scheduledChunkInfo.push({
187
+ startTime: h,
188
+ duration: o.duration
189
+ }), this.activeSources.add(n), n.onended = () => {
190
+ if (this.activeSources.delete(n), this.activeSources.size === 0) {
191
+ const d = this.audioChunks[this.scheduledChunks - 1];
192
+ d && !d.isLast ? (this.log("All audio chunks ended but end=false, pausing and setting autoContinue"), c("audio_buffer_stall", "warning", {
193
+ scheduled_chunks: this.scheduledChunks,
194
+ audio_time: this.getCurrentTime()
195
+ }), this.autoContinue = !0, this.pause()) : u && (this.log("Last audio chunk ended, marking playback as ended"), this.markEnded());
196
+ }
197
+ }, this.scheduledTime += o.duration, this.lastScheduledChunkEndTime = this.scheduledTime - this.sessionStartTime - this.pausedTimeOffset, this.scheduledChunks++, this.log(`[StreamingAudioPlayer] Scheduled chunk ${t + 1}/${this.audioChunks.length}`, {
198
+ startTime: this.scheduledTime - o.duration,
199
+ duration: o.duration,
200
+ nextScheduleTime: this.scheduledTime,
201
+ isLast: u,
202
+ activeSources: this.activeSources.size
203
+ });
204
+ } catch (n) {
205
+ a.error("Failed to schedule audio chunk:", n), c("schedule_chunk_failed", "error", {
206
+ session_id: this.sessionId,
207
+ reason: n instanceof Error ? n.message : String(n)
208
+ });
209
+ }
210
+ }
211
+ /**
212
+ * Convert PCM data to AudioBuffer
213
+ * Input: 16-bit PCM (int16), Output: AudioBuffer (float32 [-1, 1])
214
+ */
215
+ pcmToAudioBuffer(t) {
216
+ if (!this.audioContext)
217
+ return null;
218
+ if (t.length === 0) {
219
+ const l = Math.floor(this.sampleRate * 0.01), h = this.audioContext.createBuffer(
220
+ this.channelCount,
221
+ l,
222
+ this.sampleRate
223
+ );
224
+ for (let d = 0; d < this.channelCount; d++)
225
+ h.getChannelData(d).fill(0);
226
+ return h;
227
+ }
228
+ const e = new Uint8Array(t), i = new Int16Array(e.buffer, 0, e.length / 2), u = i.length / this.channelCount, o = this.audioContext.createBuffer(
229
+ this.channelCount,
230
+ u,
231
+ this.sampleRate
232
+ );
233
+ for (let n = 0; n < this.channelCount; n++) {
234
+ const l = o.getChannelData(n);
235
+ for (let h = 0; h < u; h++) {
236
+ const d = h * this.channelCount + n;
237
+ l[h] = i[d] / 32768;
238
+ }
239
+ }
240
+ return o;
241
+ }
242
+ /**
243
+ * Get current playback time (seconds)
244
+ * Returns total actual playback duration
245
+ * @internal
246
+ */
247
+ getCurrentTime() {
248
+ if (!this.audioContext || !this.isPlaying)
249
+ return 0;
250
+ if (this.isPaused)
251
+ return this.pausedAt;
252
+ const t = this.audioContext.currentTime;
253
+ if (this.activeSources.size === 0 && this.scheduledChunks > 0)
254
+ return Math.max(0, this.lastScheduledChunkEndTime);
255
+ let e = 0;
256
+ for (let i = 0; i < this.scheduledChunkInfo.length; i++) {
257
+ const u = this.scheduledChunkInfo[i], o = u.startTime + u.duration;
258
+ if (t < u.startTime)
259
+ break;
260
+ if (o <= t)
261
+ e += u.duration;
262
+ else {
263
+ const n = t - u.startTime;
264
+ e += n;
265
+ break;
266
+ }
267
+ }
268
+ return Math.max(0, e);
269
+ }
270
+ /**
271
+ * Get total duration of buffered audio (seconds)
272
+ * Calculate total duration of all buffered chunks
273
+ * @internal
274
+ */
275
+ getBufferedDuration() {
276
+ if (!this.audioContext)
277
+ return 0;
278
+ let t = 0;
279
+ for (const e of this.audioChunks) {
280
+ const i = e.data.length / (this.sampleRate * this.channelCount * 2);
281
+ t += i;
282
+ }
283
+ return t;
284
+ }
285
+ /**
286
+ * Get current AudioContext time
287
+ * @returns Current AudioContext time in seconds, or 0 if AudioContext is not initialized
288
+ */
289
+ getAudioContextTime() {
290
+ var t;
291
+ return ((t = this.audioContext) == null ? void 0 : t.currentTime) ?? 0;
292
+ }
293
+ /**
294
+ * Pause playback
295
+ */
296
+ pause() {
297
+ !this.isPlaying || this.isPaused || !this.audioContext || (this.pausedAt = this.getCurrentTime(), this.pausedAudioContextTime = this.audioContext.currentTime, this.isPaused = !0, this.audioContext.state === "running" && this.audioContext.suspend().catch((t) => {
298
+ a.error("Failed to suspend AudioContext:", t), this.isPaused = !1;
299
+ }), this.log("Playback paused", {
300
+ pausedAt: this.pausedAt,
301
+ pausedAudioContextTime: this.pausedAudioContextTime,
302
+ audioContextState: this.audioContext.state
303
+ }));
304
+ }
305
+ /**
306
+ * Resume playback
307
+ */
308
+ async resume() {
309
+ if (!this.isPaused || !this.audioContext || !this.isPlaying)
310
+ return;
311
+ if (this.autoContinue = !1, this.audioContext.state === "suspended")
312
+ try {
313
+ await this.audioContext.resume();
314
+ } catch (e) {
315
+ throw a.error("Failed to resume AudioContext:", e), e;
316
+ }
317
+ const t = this.audioContext.currentTime;
318
+ this.sessionStartTime = this.pausedAudioContextTime - this.pausedAt - this.pausedTimeOffset, this.isPaused = !1, this.scheduledChunks < this.audioChunks.length && this.scheduleAllChunks(), this.log("Playback resumed", {
319
+ pausedAt: this.pausedAt,
320
+ pausedAudioContextTime: this.pausedAudioContextTime,
321
+ currentAudioContextTime: t,
322
+ adjustedSessionStartTime: this.sessionStartTime,
323
+ audioContextState: this.audioContext.state
324
+ });
325
+ }
326
+ /**
327
+ * Stop playback
328
+ */
329
+ stop() {
330
+ if (this.audioContext) {
331
+ this.isPaused && this.audioContext.state === "suspended" && (this.audioContext.resume().catch(() => {
332
+ }), this.isPaused = !1), this.isPlaying = !1, this.isPaused = !1, this.isResuming = !1, this.sessionStartTime = 0, this.scheduledTime = 0;
333
+ for (const t of this.activeSources) {
334
+ t.onended = null;
335
+ try {
336
+ t.stop(0);
337
+ } catch {
338
+ }
339
+ try {
340
+ t.disconnect();
341
+ } catch {
342
+ }
343
+ }
344
+ this.activeSources.clear(), this.audioChunks = [], this.scheduledChunks = 0, this.autoContinue = !1, this.log("[StreamingAudioPlayer] Playback stopped, state reset");
345
+ }
346
+ }
347
+ /**
348
+ * Enable or disable auto-start (for delayed start scenarios)
349
+ */
350
+ setAutoStart(t) {
351
+ this.autoStartEnabled = t, this.log(`Auto-start ${t ? "enabled" : "disabled"}`);
352
+ }
353
+ /**
354
+ * Start playback manually (for delayed start scenarios)
355
+ * This allows starting playback after transition animation completes
356
+ */
357
+ async play() {
358
+ if (!this.isPlaying) {
359
+ this.autoStartEnabled = !0;
360
+ try {
361
+ await this.startPlayback();
362
+ } catch (t) {
363
+ a.error("[StreamingAudioPlayer] Failed to start playback from play():", t);
364
+ }
365
+ }
366
+ }
367
+ /**
368
+ * Mark playback as ended
369
+ */
370
+ markEnded() {
371
+ var t;
372
+ this.log("Playback ended"), this.isPlaying = !1, (t = this.onEndedCallback) == null || t.call(this);
373
+ }
374
+ /**
375
+ * Set ended callback
376
+ */
377
+ onEnded(t) {
378
+ this.onEndedCallback = t;
379
+ }
380
+ /**
381
+ * Check if playing
382
+ */
383
+ isPlayingNow() {
384
+ return this.isPlaying && !this.isPaused;
385
+ }
386
+ /**
387
+ * Dispose and cleanup
388
+ */
389
+ dispose() {
390
+ this.stop(), this.audioContext && this.stateChangeHandler && (this.audioContext.removeEventListener("statechange", this.stateChangeHandler), this.stateChangeHandler = void 0), this.audioContext && (this.audioContext.close(), this.audioContext = null, this.gainNode = null), this.audioChunks = [], this.scheduledChunks = 0, this.sessionStartTime = 0, this.pausedTimeOffset = 0, this.pausedAt = 0, this.pausedAudioContextTime = 0, this.scheduledTime = 0, this.onEndedCallback = void 0, this.log("StreamingAudioPlayer disposed");
391
+ }
392
+ /**
393
+ * Flush buffered audio
394
+ * - hard: stops all playing sources and clears all chunks
395
+ * - soft (default): clears UNSCHEDULED chunks only
396
+ */
397
+ flush(t) {
398
+ if ((t == null ? void 0 : t.hard) === !0) {
399
+ this.stop(), this.audioChunks = [], this.scheduledChunks = 0, this.sessionStartTime = 0, this.pausedAt = 0, this.scheduledTime = 0, this.log("Flushed (hard)");
400
+ return;
401
+ }
402
+ this.scheduledChunks < this.audioChunks.length && this.audioChunks.splice(this.scheduledChunks), this.log("Flushed (soft)", { remainingScheduled: this.scheduledChunks });
403
+ }
404
+ /**
405
+ * Set volume (0.0 - 1.0)
406
+ * Note: This only controls avatar audio player volume, does not affect system volume
407
+ * @param volume Volume value, range 0.0 to 1.0 (0.0 is mute, 1.0 is max volume)
408
+ * @internal
409
+ */
410
+ setVolume(t) {
411
+ (t < 0 || t > 1) && (a.warn(`[StreamingAudioPlayer] Volume out of range: ${t}, clamping to [0, 1]`), t = Math.max(0, Math.min(1, t))), this.volume = t, this.gainNode && (this.gainNode.gain.value = t);
412
+ }
413
+ /**
414
+ * Get current volume
415
+ * @returns Current volume value (0.0 - 1.0)
416
+ * @internal
417
+ */
418
+ getVolume() {
419
+ return this.volume;
420
+ }
421
+ /**
422
+ * Debug logging
423
+ */
424
+ log(t, e) {
425
+ this.debug && a.log(`[StreamingAudioPlayer] ${t}`, e || "");
426
+ }
427
+ }
428
+ export {
429
+ p as StreamingAudioPlayer
430
+ };
package/dist/_headers ADDED
@@ -0,0 +1,2 @@
1
+ /*.wasm
2
+ Content-Type: application/wasm