@spatius/avatarkit 1.3.1-beta.2 → 1.3.1-beta.4

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,656 +0,0 @@
1
- var __defProp = Object.defineProperty;
2
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- import { A as APP_CONFIG, l as logger, e as errorToMessage, a as logEvent } from "./index-CPolDcOo.js";
5
- class StreamingAudioPlayer {
6
- // Mark if AudioContext is being resumed, avoid concurrent resume requests
7
- constructor(options) {
8
- // AudioContext is managed internally
9
- __publicField(this, "audioContext", null);
10
- __publicField(this, "sampleRate");
11
- __publicField(this, "channelCount");
12
- __publicField(this, "debug");
13
- // Session-level state
14
- __publicField(this, "sessionId");
15
- __publicField(this, "sessionStartTime", 0);
16
- // AudioContext time when session started
17
- __publicField(this, "pausedTimeOffset", 0);
18
- // Accumulated paused time
19
- __publicField(this, "pausedAt", 0);
20
- // Time when paused
21
- __publicField(this, "pausedAudioContextTime", 0);
22
- // audioContext.currentTime when paused (for resume calculation)
23
- __publicField(this, "scheduledTime", 0);
24
- // Next chunk schedule time in AudioContext time
25
- // Playback state
26
- __publicField(this, "isPlaying", false);
27
- __publicField(this, "isPaused", false);
28
- __publicField(this, "autoStartEnabled", true);
29
- // Control whether to auto-start when buffer is ready
30
- __publicField(this, "autoContinue", false);
31
- // Mark if should auto-continue playback (used after auto-pause when end=false and no data)
32
- // Audio buffer queue
33
- __publicField(this, "audioChunks", []);
34
- __publicField(this, "scheduledChunks", 0);
35
- // Number of chunks already scheduled
36
- __publicField(this, "activeSources", /* @__PURE__ */ new Set());
37
- __publicField(this, "lastScheduledChunkEndTime", 0);
38
- // End time of last scheduled chunk (relative time)
39
- __publicField(this, "lastGetCurrentTimeLog", 0);
40
- // Timestamp of last getCurrentTime log (for throttling)
41
- // Track start time (absolute time) and duration of each scheduled chunk for accurate current playback time calculation
42
- __publicField(this, "scheduledChunkInfo", []);
43
- // Volume control
44
- __publicField(this, "gainNode", null);
45
- __publicField(this, "volume", 1);
46
- // Default volume 1.0 (0.0 - 1.0)
47
- // Event callbacks
48
- __publicField(this, "onEndedCallback");
49
- // 音频缓冲卡顿(缓冲耗尽自动暂停 / 有数据自动恢复)通知:true=进入卡顿,false=恢复。
50
- __publicField(this, "onAudioStallCallback");
51
- // AudioContext state management
52
- __publicField(this, "stateChangeHandler");
53
- __publicField(this, "isResuming", false);
54
- this.sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
55
- this.sampleRate = (options == null ? void 0 : options.sampleRate) ?? APP_CONFIG.audio.sampleRate;
56
- this.channelCount = (options == null ? void 0 : options.channelCount) ?? 1;
57
- this.debug = (options == null ? void 0 : options.debug) ?? false;
58
- }
59
- /**
60
- * Initialize audio context (create and ensure it's ready)
61
- */
62
- async initialize() {
63
- if (this.audioContext) {
64
- return;
65
- }
66
- try {
67
- this.audioContext = new AudioContext({
68
- sampleRate: this.sampleRate
69
- });
70
- this.gainNode = this.audioContext.createGain();
71
- this.gainNode.gain.value = this.volume;
72
- this.gainNode.connect(this.audioContext.destination);
73
- if (this.audioContext.state === "suspended") {
74
- await this.audioContext.resume();
75
- }
76
- this.stateChangeHandler = (event) => {
77
- const context = event.target;
78
- if (context.state === "suspended" && this.isPlaying && !this.isPaused) {
79
- this.ensureAudioContextRunning().catch((err) => {
80
- logger.error("[StreamingAudioPlayer] Failed to auto-resume AudioContext after external suspend:", err);
81
- });
82
- }
83
- };
84
- this.audioContext.addEventListener("statechange", this.stateChangeHandler);
85
- this.log("AudioContext initialized", {
86
- sessionId: this.sessionId,
87
- sampleRate: this.audioContext.sampleRate,
88
- state: this.audioContext.state
89
- });
90
- } catch (error) {
91
- const message = errorToMessage(error);
92
- logEvent("audio_session_init_failed", "warning", {
93
- session_id: this.sessionId,
94
- reason: message
95
- });
96
- logger.error("Failed to initialize AudioContext:", message);
97
- throw error instanceof Error ? error : new Error(message);
98
- }
99
- }
100
- /**
101
- * Ensure AudioContext is running (auto-resume if suspended)
102
- * Only auto-resume when playing and not paused, avoid interfering with normal pause/resume logic
103
- *
104
- * Optimizations:
105
- * - Fast path: if already in running state, return directly
106
- * - Avoid concurrent resume: use isResuming flag to prevent duplicate resume requests
107
- * - Handle closed state: if AudioContext is closed, cannot resume
108
- * @internal
109
- */
110
- async ensureAudioContextRunning() {
111
- if (!this.audioContext) {
112
- return;
113
- }
114
- const state = this.audioContext.state;
115
- if (state === "running") {
116
- return;
117
- }
118
- if (state === "closed") {
119
- this.log("AudioContext is closed, cannot resume", {
120
- sessionId: this.sessionId,
121
- state
122
- });
123
- return;
124
- }
125
- if (state === "suspended" && this.isPlaying && !this.isPaused) {
126
- if (this.isResuming) {
127
- this.log("AudioContext resume already in progress, skipping duplicate request", {
128
- sessionId: this.sessionId,
129
- state
130
- });
131
- return;
132
- }
133
- this.isResuming = true;
134
- try {
135
- this.log("AudioContext is suspended during playback, resuming...", {
136
- sessionId: this.sessionId,
137
- state,
138
- isPlaying: this.isPlaying,
139
- isPaused: this.isPaused
140
- });
141
- await this.audioContext.resume();
142
- this.log("AudioContext resumed successfully", {
143
- sessionId: this.sessionId,
144
- state: this.audioContext.state
145
- });
146
- } catch (err) {
147
- logger.error("[StreamingAudioPlayer] Failed to resume AudioContext:", err);
148
- logEvent("audio_context_resume_failed", "error", {
149
- session_id: this.sessionId,
150
- reason: err instanceof Error ? err.message : String(err)
151
- });
152
- } finally {
153
- this.isResuming = false;
154
- }
155
- }
156
- }
157
- /**
158
- * Add audio chunk (16-bit PCM)
159
- */
160
- addChunk(pcmData, isLast = false) {
161
- var _a;
162
- if (!this.audioContext) {
163
- logger.error("AudioContext not initialized");
164
- return;
165
- }
166
- if (this.isPlaying && !this.isPaused && this.audioContext.state === "suspended") {
167
- this.ensureAudioContextRunning().catch((err) => {
168
- logger.error("[StreamingAudioPlayer] Failed to ensure AudioContext running in addChunk:", err);
169
- });
170
- }
171
- this.audioChunks.push({ data: pcmData, isLast });
172
- this.log(`Added chunk ${this.audioChunks.length}`, {
173
- size: pcmData.length,
174
- totalChunks: this.audioChunks.length,
175
- isLast,
176
- isPlaying: this.isPlaying,
177
- scheduledChunks: this.scheduledChunks
178
- });
179
- if (this.autoContinue && this.isPaused) {
180
- this.log("[StreamingAudioPlayer] autoContinue=true, auto-resuming playback");
181
- this.autoContinue = false;
182
- (_a = this.onAudioStallCallback) == null ? void 0 : _a.call(this, false);
183
- this.resume().catch((err) => {
184
- logger.error("Failed to auto-resume playback:", err);
185
- });
186
- }
187
- if (!this.isPlaying && this.autoStartEnabled && this.audioChunks.length > 0) {
188
- this.log("[StreamingAudioPlayer] Auto-starting playback from addChunk");
189
- this.startPlayback().catch((err) => {
190
- logger.error("[StreamingAudioPlayer] Failed to start playback from addChunk:", err);
191
- });
192
- } else if (this.isPlaying && !this.isPaused) {
193
- this.log("[StreamingAudioPlayer] Already playing, scheduling next chunk");
194
- this.scheduleNextChunk();
195
- } else {
196
- this.log("[StreamingAudioPlayer] Not playing and no chunks, waiting for more chunks");
197
- }
198
- }
199
- /**
200
- * Start new session (stop current and start fresh)
201
- */
202
- async startNewSession(audioChunks, leadingSilenceMs = 0) {
203
- this.stop();
204
- this.sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
205
- this.audioChunks = [];
206
- this.scheduledChunks = 0;
207
- this.pausedTimeOffset = 0;
208
- this.pausedAt = 0;
209
- this.pausedAudioContextTime = 0;
210
- this.autoContinue = false;
211
- this.log("Starting new session", {
212
- chunks: audioChunks.length,
213
- leadingSilenceMs
214
- });
215
- if (leadingSilenceMs > 0) {
216
- const silenceSamples = Math.floor(leadingSilenceMs / 1e3 * this.sampleRate) * this.channelCount;
217
- const silencePcm = new Uint8Array(silenceSamples * 2);
218
- this.addChunk(silencePcm, false);
219
- }
220
- for (const chunk of audioChunks) {
221
- this.addChunk(chunk.data, chunk.isLast);
222
- }
223
- }
224
- /**
225
- * Start playback
226
- */
227
- async startPlayback() {
228
- if (!this.audioContext) {
229
- this.log("[StreamingAudioPlayer] Cannot start playback: AudioContext not initialized");
230
- return;
231
- }
232
- if (this.isPlaying) {
233
- this.log("[StreamingAudioPlayer] Cannot start playback: Already playing");
234
- return;
235
- }
236
- this.isPlaying = true;
237
- this.sessionStartTime = this.audioContext.currentTime;
238
- this.scheduledTime = this.sessionStartTime;
239
- this.lastScheduledChunkEndTime = 0;
240
- this.scheduledChunkInfo = [];
241
- this.autoContinue = false;
242
- await this.ensureAudioContextRunning();
243
- this.log("[StreamingAudioPlayer] Starting playback", {
244
- sessionStartTime: this.sessionStartTime,
245
- bufferedChunks: this.audioChunks.length,
246
- scheduledChunks: this.scheduledChunks,
247
- activeSources: this.activeSources.size,
248
- audioContextState: this.audioContext.state
249
- });
250
- this.scheduleAllChunks();
251
- }
252
- /**
253
- * Schedule all pending chunks
254
- */
255
- scheduleAllChunks() {
256
- while (this.scheduledChunks < this.audioChunks.length) {
257
- this.scheduleNextChunk();
258
- }
259
- }
260
- /**
261
- * Schedule next audio chunk
262
- */
263
- scheduleNextChunk() {
264
- if (!this.audioContext) {
265
- this.log("[StreamingAudioPlayer] Cannot schedule chunk: AudioContext not initialized");
266
- return;
267
- }
268
- if (!this.isPlaying || this.isPaused) {
269
- this.log("[StreamingAudioPlayer] Cannot schedule chunk: Not playing or paused");
270
- return;
271
- }
272
- if (this.audioContext.state === "suspended") {
273
- this.ensureAudioContextRunning().catch((err) => {
274
- logger.error("[StreamingAudioPlayer] Failed to ensure AudioContext running in scheduleNextChunk:", err);
275
- });
276
- }
277
- const chunkIndex = this.scheduledChunks;
278
- if (chunkIndex >= this.audioChunks.length) {
279
- this.log(`[StreamingAudioPlayer] No more chunks to schedule (chunkIndex: ${chunkIndex}, totalChunks: ${this.audioChunks.length})`);
280
- return;
281
- }
282
- const chunk = this.audioChunks[chunkIndex];
283
- if (chunk.data.length === 0 && !chunk.isLast) {
284
- this.scheduledChunks++;
285
- return;
286
- }
287
- const pcmData = chunk.data;
288
- const isLast = chunk.isLast;
289
- const audioBuffer = this.pcmToAudioBuffer(pcmData);
290
- if (!audioBuffer) {
291
- const errorMessage = "Failed to create AudioBuffer from PCM data";
292
- logger.error(errorMessage);
293
- logEvent("audio_buffer_creation_failed", "error", {
294
- session_id: this.sessionId
295
- });
296
- return;
297
- }
298
- try {
299
- const source = this.audioContext.createBufferSource();
300
- source.buffer = audioBuffer;
301
- source.connect(this.gainNode);
302
- const chunkStartTime = this.scheduledTime;
303
- source.start(chunkStartTime);
304
- const actualStartTime = Math.max(chunkStartTime, this.audioContext.currentTime);
305
- this.scheduledChunkInfo.push({
306
- startTime: actualStartTime,
307
- duration: audioBuffer.duration
308
- });
309
- this.activeSources.add(source);
310
- source.onended = () => {
311
- var _a;
312
- this.activeSources.delete(source);
313
- if (this.activeSources.size === 0) {
314
- const lastChunk = this.audioChunks[this.scheduledChunks - 1];
315
- const sessionEnded = !!(lastChunk == null ? void 0 : lastChunk.isLast);
316
- if (!sessionEnded) {
317
- this.log("All audio chunks ended but end=false, pausing and setting autoContinue");
318
- (_a = this.onAudioStallCallback) == null ? void 0 : _a.call(this, true);
319
- this.autoContinue = true;
320
- this.pause();
321
- } else {
322
- this.log("Last audio chunk ended, marking playback as ended");
323
- this.markEnded();
324
- }
325
- }
326
- };
327
- this.scheduledTime += audioBuffer.duration;
328
- this.lastScheduledChunkEndTime = this.scheduledTime - this.sessionStartTime - this.pausedTimeOffset;
329
- this.scheduledChunks++;
330
- this.log(`[StreamingAudioPlayer] Scheduled chunk ${chunkIndex + 1}/${this.audioChunks.length}`, {
331
- startTime: this.scheduledTime - audioBuffer.duration,
332
- duration: audioBuffer.duration,
333
- nextScheduleTime: this.scheduledTime,
334
- isLast,
335
- activeSources: this.activeSources.size
336
- });
337
- } catch (err) {
338
- logger.error("Failed to schedule audio chunk:", err);
339
- logEvent("schedule_chunk_failed", "error", {
340
- session_id: this.sessionId,
341
- reason: err instanceof Error ? err.message : String(err)
342
- });
343
- }
344
- }
345
- /**
346
- * Convert PCM data to AudioBuffer
347
- * Input: 16-bit PCM (int16), Output: AudioBuffer (float32 [-1, 1])
348
- */
349
- pcmToAudioBuffer(pcmData) {
350
- if (!this.audioContext) {
351
- return null;
352
- }
353
- if (pcmData.length === 0) {
354
- const silenceDuration = 0.01;
355
- const numSamples2 = Math.floor(this.sampleRate * silenceDuration);
356
- const audioBuffer2 = this.audioContext.createBuffer(
357
- this.channelCount,
358
- numSamples2,
359
- this.sampleRate
360
- );
361
- for (let channel = 0; channel < this.channelCount; channel++) {
362
- const channelData = audioBuffer2.getChannelData(channel);
363
- channelData.fill(0);
364
- }
365
- return audioBuffer2;
366
- }
367
- const alignedData = new Uint8Array(pcmData);
368
- const int16Array = new Int16Array(alignedData.buffer, 0, alignedData.length / 2);
369
- const numSamples = int16Array.length / this.channelCount;
370
- const audioBuffer = this.audioContext.createBuffer(
371
- this.channelCount,
372
- numSamples,
373
- this.sampleRate
374
- );
375
- for (let channel = 0; channel < this.channelCount; channel++) {
376
- const channelData = audioBuffer.getChannelData(channel);
377
- for (let i = 0; i < numSamples; i++) {
378
- const sampleIndex = i * this.channelCount + channel;
379
- channelData[i] = int16Array[sampleIndex] / 32768;
380
- }
381
- }
382
- return audioBuffer;
383
- }
384
- /**
385
- * Get current playback time (seconds)
386
- * Returns total actual playback duration
387
- * @internal
388
- */
389
- getCurrentTime() {
390
- if (!this.audioContext || !this.isPlaying) {
391
- return 0;
392
- }
393
- if (this.isPaused) {
394
- return this.pausedAt;
395
- }
396
- const currentAudioTime = this.audioContext.currentTime;
397
- if (this.activeSources.size === 0 && this.scheduledChunks > 0) {
398
- return Math.max(0, this.lastScheduledChunkEndTime);
399
- }
400
- let totalPlayedDuration = 0;
401
- for (let i = 0; i < this.scheduledChunkInfo.length; i++) {
402
- const chunkInfo = this.scheduledChunkInfo[i];
403
- const chunkEndTime = chunkInfo.startTime + chunkInfo.duration;
404
- if (currentAudioTime < chunkInfo.startTime) {
405
- break;
406
- } else if (chunkEndTime <= currentAudioTime) {
407
- totalPlayedDuration += chunkInfo.duration;
408
- } else {
409
- const playedTime = currentAudioTime - chunkInfo.startTime;
410
- totalPlayedDuration += playedTime;
411
- break;
412
- }
413
- }
414
- return Math.max(0, totalPlayedDuration);
415
- }
416
- /**
417
- * Get total duration of buffered audio (seconds)
418
- * Calculate total duration of all buffered chunks
419
- * @internal
420
- */
421
- getBufferedDuration() {
422
- if (!this.audioContext) {
423
- return 0;
424
- }
425
- let totalDuration = 0;
426
- for (const chunk of this.audioChunks) {
427
- const chunkDuration = chunk.data.length / (this.sampleRate * this.channelCount * 2);
428
- totalDuration += chunkDuration;
429
- }
430
- return totalDuration;
431
- }
432
- /**
433
- * Get current AudioContext time
434
- * @returns Current AudioContext time in seconds, or 0 if AudioContext is not initialized
435
- */
436
- getAudioContextTime() {
437
- var _a;
438
- return ((_a = this.audioContext) == null ? void 0 : _a.currentTime) ?? 0;
439
- }
440
- /**
441
- * Pause playback
442
- */
443
- pause() {
444
- if (!this.isPlaying || this.isPaused || !this.audioContext) {
445
- return;
446
- }
447
- this.pausedAt = this.getCurrentTime();
448
- this.pausedAudioContextTime = this.audioContext.currentTime;
449
- this.isPaused = true;
450
- if (this.audioContext.state === "running") {
451
- this.audioContext.suspend().catch((err) => {
452
- logger.error("Failed to suspend AudioContext:", err);
453
- this.isPaused = false;
454
- });
455
- }
456
- this.log("Playback paused", {
457
- pausedAt: this.pausedAt,
458
- pausedAudioContextTime: this.pausedAudioContextTime,
459
- audioContextState: this.audioContext.state
460
- });
461
- }
462
- /**
463
- * Resume playback
464
- */
465
- async resume() {
466
- if (!this.isPaused || !this.audioContext || !this.isPlaying) {
467
- return;
468
- }
469
- this.autoContinue = false;
470
- if (this.audioContext.state === "suspended") {
471
- try {
472
- await this.audioContext.resume();
473
- } catch (err) {
474
- logger.error("Failed to resume AudioContext:", err);
475
- throw err;
476
- }
477
- }
478
- const currentAudioTime = this.audioContext.currentTime;
479
- this.sessionStartTime = this.pausedAudioContextTime - this.pausedAt - this.pausedTimeOffset;
480
- this.isPaused = false;
481
- if (this.scheduledChunks < this.audioChunks.length) {
482
- this.scheduleAllChunks();
483
- }
484
- this.log("Playback resumed", {
485
- pausedAt: this.pausedAt,
486
- pausedAudioContextTime: this.pausedAudioContextTime,
487
- currentAudioContextTime: currentAudioTime,
488
- adjustedSessionStartTime: this.sessionStartTime,
489
- audioContextState: this.audioContext.state
490
- });
491
- }
492
- /**
493
- * Stop playback
494
- */
495
- stop() {
496
- if (!this.audioContext) {
497
- return;
498
- }
499
- if (this.isPaused && this.audioContext.state === "suspended") {
500
- this.audioContext.resume().catch(() => {
501
- });
502
- this.isPaused = false;
503
- }
504
- this.isPlaying = false;
505
- this.isPaused = false;
506
- this.isResuming = false;
507
- this.sessionStartTime = 0;
508
- this.scheduledTime = 0;
509
- for (const source of this.activeSources) {
510
- source.onended = null;
511
- try {
512
- source.stop(0);
513
- } catch {
514
- }
515
- try {
516
- source.disconnect();
517
- } catch {
518
- }
519
- }
520
- this.activeSources.clear();
521
- this.audioChunks = [];
522
- this.scheduledChunks = 0;
523
- this.autoContinue = false;
524
- this.log("[StreamingAudioPlayer] Playback stopped, state reset");
525
- }
526
- /**
527
- * Enable or disable auto-start (for delayed start scenarios)
528
- */
529
- setAutoStart(enabled) {
530
- this.autoStartEnabled = enabled;
531
- this.log(`Auto-start ${enabled ? "enabled" : "disabled"}`);
532
- }
533
- /**
534
- * Start playback manually (for delayed start scenarios)
535
- * This allows starting playback after transition animation completes
536
- */
537
- async play() {
538
- if (this.isPlaying) {
539
- return;
540
- }
541
- this.autoStartEnabled = true;
542
- try {
543
- await this.startPlayback();
544
- } catch (err) {
545
- logger.error("[StreamingAudioPlayer] Failed to start playback from play():", err);
546
- }
547
- }
548
- /**
549
- * Mark playback as ended
550
- */
551
- markEnded() {
552
- var _a;
553
- this.log("Playback ended");
554
- this.isPlaying = false;
555
- (_a = this.onEndedCallback) == null ? void 0 : _a.call(this);
556
- }
557
- /**
558
- * Set ended callback
559
- */
560
- onEnded(callback) {
561
- this.onEndedCallback = callback;
562
- }
563
- /**
564
- * 设置音频缓冲卡顿回调:缓冲耗尽自动暂停时 stalled=true,有数据自动恢复时 stalled=false。
565
- */
566
- onAudioStall(callback) {
567
- this.onAudioStallCallback = callback;
568
- }
569
- /**
570
- * Check if playing
571
- */
572
- isPlayingNow() {
573
- return this.isPlaying && !this.isPaused;
574
- }
575
- /**
576
- * Dispose and cleanup
577
- */
578
- dispose() {
579
- this.stop();
580
- if (this.audioContext && this.stateChangeHandler) {
581
- this.audioContext.removeEventListener("statechange", this.stateChangeHandler);
582
- this.stateChangeHandler = void 0;
583
- }
584
- if (this.audioContext) {
585
- this.audioContext.close();
586
- this.audioContext = null;
587
- this.gainNode = null;
588
- }
589
- this.audioChunks = [];
590
- this.scheduledChunks = 0;
591
- this.sessionStartTime = 0;
592
- this.pausedTimeOffset = 0;
593
- this.pausedAt = 0;
594
- this.pausedAudioContextTime = 0;
595
- this.scheduledTime = 0;
596
- this.onEndedCallback = void 0;
597
- this.log("StreamingAudioPlayer disposed");
598
- }
599
- /**
600
- * Flush buffered audio
601
- * - hard: stops all playing sources and clears all chunks
602
- * - soft (default): clears UNSCHEDULED chunks only
603
- */
604
- flush(options) {
605
- const hard = (options == null ? void 0 : options.hard) === true;
606
- if (hard) {
607
- this.stop();
608
- this.audioChunks = [];
609
- this.scheduledChunks = 0;
610
- this.sessionStartTime = 0;
611
- this.pausedAt = 0;
612
- this.scheduledTime = 0;
613
- this.log("Flushed (hard)");
614
- return;
615
- }
616
- if (this.scheduledChunks < this.audioChunks.length) {
617
- this.audioChunks.splice(this.scheduledChunks);
618
- }
619
- this.log("Flushed (soft)", { remainingScheduled: this.scheduledChunks });
620
- }
621
- /**
622
- * Set volume (0.0 - 1.0)
623
- * Note: This only controls avatar audio player volume, does not affect system volume
624
- * @param volume Volume value, range 0.0 to 1.0 (0.0 is mute, 1.0 is max volume)
625
- * @internal
626
- */
627
- setVolume(volume) {
628
- if (volume < 0 || volume > 1) {
629
- logger.warn(`[StreamingAudioPlayer] Volume out of range: ${volume}, clamping to [0, 1]`);
630
- volume = Math.max(0, Math.min(1, volume));
631
- }
632
- this.volume = volume;
633
- if (this.gainNode) {
634
- this.gainNode.gain.value = volume;
635
- }
636
- }
637
- /**
638
- * Get current volume
639
- * @returns Current volume value (0.0 - 1.0)
640
- * @internal
641
- */
642
- getVolume() {
643
- return this.volume;
644
- }
645
- /**
646
- * Debug logging
647
- */
648
- log(message, data) {
649
- if (this.debug) {
650
- logger.log(`[StreamingAudioPlayer] ${message}`, data || "");
651
- }
652
- }
653
- }
654
- export {
655
- StreamingAudioPlayer
656
- };