UNPKG

@spatialwalk/avatarkit

Version:

SPAvatar SDK - 3D Gaussian Splatting Avatar Rendering SDK

320 lines (319 loc) 11.2 kB
var g = Object.defineProperty; var f = (h, t, e) => t in h ? g(h, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : h[t] = e; var i = (h, t, e) => f(h, typeof t != "symbol" ? t + "" : t, e); import { A as C, e as m, a as c, l as r } from "./index-bQnEVIkT.js"; class A { constructor(t) { // AudioContext is managed internally i(this, "audioContext", null); i(this, "sampleRate"); i(this, "channelCount"); i(this, "debug"); // Session-level state i(this, "sessionId"); i(this, "sessionStartTime", 0); // AudioContext time when session started i(this, "pausedTimeOffset", 0); // Accumulated paused time i(this, "pausedAt", 0); // Time when paused i(this, "scheduledTime", 0); // Next chunk schedule time in AudioContext time // Playback state i(this, "isPlaying", !1); i(this, "isPaused", !1); i(this, "autoStartEnabled", !0); // Control whether to auto-start when buffer is ready // Audio buffer queue i(this, "audioChunks", []); i(this, "scheduledChunks", 0); // Number of chunks already scheduled i(this, "activeSources", []); // Event callbacks i(this, "onEndedCallback"); 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; } /** * Initialize audio context (create and ensure it's ready) */ async initialize() { if (!this.audioContext) try { this.audioContext = new AudioContext({ sampleRate: this.sampleRate }), this.audioContext.state === "suspended" && await this.audioContext.resume(), this.log("AudioContext initialized", { sessionId: this.sessionId, sampleRate: this.audioContext.sampleRate, state: this.audioContext.state }); } catch (t) { const e = m(t); throw c.logEvent("activeAudioSessionFailed", "warning", { sessionId: this.sessionId, reason: e }), r.error("Failed to initialize AudioContext:", e), t instanceof Error ? t : new Error(e); } } /** * Add audio chunk (16-bit PCM) */ addChunk(t, e = !1) { if (!this.audioContext) { r.error("AudioContext not initialized"); return; } this.audioChunks.push({ data: t, isLast: e }), this.log(`Added chunk ${this.audioChunks.length}`, { size: t.length, totalChunks: this.audioChunks.length, isLast: e, isPlaying: this.isPlaying, scheduledChunks: this.scheduledChunks }), !this.isPlaying && this.autoStartEnabled && this.audioChunks.length > 0 ? (this.log("[StreamingAudioPlayer] Auto-starting playback from addChunk"), this.startPlayback()) : this.isPlaying ? (this.log("[StreamingAudioPlayer] Already playing, scheduling next chunk"), this.scheduleNextChunk()) : this.log("[StreamingAudioPlayer] Not playing and no chunks, waiting for more chunks"); } /** * Start new session (stop current and start fresh) */ async startNewSession(t) { 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.log("Starting new session", { chunks: t.length }); for (const e of t) this.addChunk(e.data, e.isLast); } /** * Start playback */ startPlayback() { if (!this.audioContext) { this.log("[StreamingAudioPlayer] Cannot start playback: AudioContext not initialized"); return; } if (this.isPlaying) { this.log("[StreamingAudioPlayer] Cannot start playback: Already playing"); return; } this.isPlaying = !0, this.sessionStartTime = this.audioContext.currentTime, this.scheduledTime = this.sessionStartTime, this.log("[StreamingAudioPlayer] Starting playback", { sessionStartTime: this.sessionStartTime, bufferedChunks: this.audioChunks.length, scheduledChunks: this.scheduledChunks, activeSources: this.activeSources.length }), this.scheduleAllChunks(); } /** * Schedule all pending chunks */ scheduleAllChunks() { for (; this.scheduledChunks < this.audioChunks.length; ) this.scheduleNextChunk(); } /** * Schedule next audio chunk */ scheduleNextChunk() { if (!this.audioContext) { this.log("[StreamingAudioPlayer] Cannot schedule chunk: AudioContext not initialized"); return; } if (!this.isPlaying) { this.log("[StreamingAudioPlayer] Cannot schedule chunk: Not playing"); return; } const t = this.scheduledChunks; if (t >= this.audioChunks.length) { this.log(`[StreamingAudioPlayer] No more chunks to schedule (chunkIndex: ${t}, totalChunks: ${this.audioChunks.length})`); return; } const e = this.audioChunks[t]; if (e.data.length === 0 && !e.isLast) { this.scheduledChunks++; return; } const l = e.data, o = e.isLast, a = this.pcmToAudioBuffer(l); if (!a) { r.error("Failed to create AudioBuffer from PCM data"), c.logEvent("character_player", "error", { sessionId: this.sessionId, event: "audio_buffer_creation_failed" }); return; } try { const s = this.audioContext.createBufferSource(); s.buffer = a, s.connect(this.audioContext.destination), s.start(this.scheduledTime), this.activeSources.push(s), s.onended = () => { const u = this.activeSources.indexOf(s); u >= 0 && this.activeSources.splice(u, 1), o && this.activeSources.length === 0 && (this.log("Last audio chunk ended, marking playback as ended"), this.markEnded()); }, this.scheduledTime += a.duration, this.scheduledChunks++, this.log(`[StreamingAudioPlayer] Scheduled chunk ${t + 1}/${this.audioChunks.length}`, { startTime: this.scheduledTime - a.duration, duration: a.duration, nextScheduleTime: this.scheduledTime, isLast: o, activeSources: this.activeSources.length }); } catch (s) { r.errorWithError("Failed to schedule audio chunk:", s), c.logEvent("character_player", "error", { sessionId: this.sessionId, event: "schedule_chunk_failed", reason: s instanceof Error ? s.message : String(s) }); } } /** * Convert PCM data to AudioBuffer * Input: 16-bit PCM (int16), Output: AudioBuffer (float32 [-1, 1]) */ pcmToAudioBuffer(t) { if (!this.audioContext) return null; if (t.length === 0) { const u = Math.floor(this.sampleRate * 0.01), n = this.audioContext.createBuffer( this.channelCount, u, this.sampleRate ); for (let d = 0; d < this.channelCount; d++) n.getChannelData(d).fill(0); return n; } const e = new Uint8Array(t), l = new Int16Array(e.buffer, 0, e.length / 2), o = l.length / this.channelCount, a = this.audioContext.createBuffer( this.channelCount, o, this.sampleRate ); for (let s = 0; s < this.channelCount; s++) { const u = a.getChannelData(s); for (let n = 0; n < o; n++) { const d = n * this.channelCount + s; u[n] = l[d] / 32768; } } return a; } /** * Get current playback time (seconds) */ getCurrentTime() { if (!this.audioContext || !this.isPlaying) return 0; if (this.isPaused) return this.pausedAt; const e = this.audioContext.currentTime - this.sessionStartTime - this.pausedTimeOffset; return Math.max(0, e); } /** * Pause playback */ pause() { !this.isPlaying || this.isPaused || (this.isPaused = !0, this.pausedAt = this.getCurrentTime(), this.log("Playback paused", { pausedAt: this.pausedAt })); } /** * Resume playback */ async resume() { if (!this.isPaused || !this.audioContext) return; const t = this.audioContext.currentTime - (this.sessionStartTime + this.pausedAt); this.pausedTimeOffset += t, this.isPaused = !1, this.log("Playback resumed", { pauseDuration: t, totalPausedOffset: this.pausedTimeOffset }); } /** * Stop playback */ stop() { if (this.audioContext) { this.isPlaying = !1, this.isPaused = !1, this.sessionStartTime = 0, this.scheduledTime = 0; for (const t of this.activeSources) { t.onended = null; try { t.stop(0); } catch { } try { t.disconnect(); } catch { } } this.activeSources = [], this.audioChunks = [], this.scheduledChunks = 0, this.log("[StreamingAudioPlayer] Playback stopped, state reset"); } } /** * Enable or disable auto-start (for delayed start scenarios) */ setAutoStart(t) { this.autoStartEnabled = t, this.log(`Auto-start ${t ? "enabled" : "disabled"}`); } /** * Start playback manually (for delayed start scenarios) * This allows starting playback after transition animation completes */ play() { this.isPlaying || (this.autoStartEnabled = !0, this.startPlayback()); } /** * Mark playback as ended */ markEnded() { var t; this.log("Playback ended"), this.isPlaying = !1, (t = this.onEndedCallback) == null || t.call(this); } /** * Set ended callback */ onEnded(t) { this.onEndedCallback = t; } /** * Check if playing */ isPlayingNow() { return this.isPlaying && !this.isPaused; } /** * Get total duration of buffered audio */ getBufferedDuration() { if (!this.audioContext) return 0; let t = 0; for (const e of this.audioChunks) t += e.data.length / 2 / this.channelCount; return t / this.sampleRate; } /** * Get remaining duration (buffered - played) in seconds */ getRemainingDuration() { const t = this.getBufferedDuration(), e = this.getCurrentTime(); return Math.max(0, t - e); } /** * Dispose and cleanup */ dispose() { this.stop(), this.audioContext && (this.audioContext.close(), this.audioContext = null), this.audioChunks = [], this.scheduledChunks = 0, this.sessionStartTime = 0, this.pausedTimeOffset = 0, this.pausedAt = 0, this.scheduledTime = 0, this.onEndedCallback = void 0, this.log("StreamingAudioPlayer disposed"); } /** * Flush buffered audio * - hard: stops all playing sources and clears all chunks * - soft (default): clears UNSCHEDULED chunks only */ flush(t) { if ((t == null ? void 0 : t.hard) === !0) { this.stop(), this.audioChunks = [], this.scheduledChunks = 0, this.sessionStartTime = 0, this.pausedAt = 0, this.scheduledTime = 0, this.log("Flushed (hard)"); return; } this.scheduledChunks < this.audioChunks.length && this.audioChunks.splice(this.scheduledChunks), this.log("Flushed (soft)", { remainingScheduled: this.scheduledChunks }); } /** * Debug logging */ log(t, e) { this.debug && r.log(`[StreamingAudioPlayer] ${t}`, e || ""); } } export { A as StreamingAudioPlayer }; //# sourceMappingURL=StreamingAudioPlayer-Bq2-bQiT.js.map