@spatialwalk/avatarkit-rtc 1.0.0-beta.5 → 1.0.0-beta.7
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/dist/core/AvatarPlayer.d.ts.map +1 -1
- package/dist/core/RTCProvider.d.ts +0 -10
- package/dist/core/RTCProvider.d.ts.map +1 -1
- package/dist/index10.js +64 -346
- package/dist/index10.js.map +1 -1
- package/dist/index11.js +14 -104
- package/dist/index11.js.map +1 -1
- package/dist/index12.js +386 -14
- package/dist/index12.js.map +1 -1
- package/dist/index2.js +32 -4
- package/dist/index2.js.map +1 -1
- package/dist/index3.js +131 -26
- package/dist/index3.js.map +1 -1
- package/dist/index4.js +1 -17
- package/dist/index4.js.map +1 -1
- package/dist/index6.js +128 -16
- package/dist/index6.js.map +1 -1
- package/dist/index9.js.map +1 -1
- package/dist/providers/agora/AgoraProvider.d.ts +0 -8
- package/dist/providers/agora/AgoraProvider.d.ts.map +1 -1
- package/dist/providers/base/BaseProvider.d.ts +0 -8
- package/dist/providers/base/BaseProvider.d.ts.map +1 -1
- package/dist/providers/livekit/LiveKitProvider.d.ts +0 -8
- package/dist/providers/livekit/LiveKitProvider.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index2.js
CHANGED
|
@@ -29,6 +29,10 @@ class AvatarPlayer {
|
|
|
29
29
|
__publicField(this, "lastConnectionConfig", null);
|
|
30
30
|
/** @internal */
|
|
31
31
|
__publicField(this, "isReconnecting", false);
|
|
32
|
+
/** @internal */
|
|
33
|
+
__publicField(this, "hasActiveAnimationSession", false);
|
|
34
|
+
/** @internal */
|
|
35
|
+
__publicField(this, "isTrackingPrimed", false);
|
|
32
36
|
this.provider = provider;
|
|
33
37
|
this.avatarView = avatarView;
|
|
34
38
|
if ((options == null ? void 0 : options.logLevel) !== void 0) {
|
|
@@ -39,6 +43,8 @@ class AvatarPlayer {
|
|
|
39
43
|
enableJitterBuffer: options == null ? void 0 : options.enableJitterBuffer,
|
|
40
44
|
maxBufferDelayMs: options == null ? void 0 : options.maxBufferDelayMs,
|
|
41
45
|
onStreamStalled: () => {
|
|
46
|
+
this.hasActiveAnimationSession = false;
|
|
47
|
+
this.isTrackingPrimed = false;
|
|
42
48
|
this.emit("stalled");
|
|
43
49
|
}
|
|
44
50
|
});
|
|
@@ -315,9 +321,23 @@ class AvatarPlayer {
|
|
|
315
321
|
async setupAnimationCallbacks() {
|
|
316
322
|
const callbacks = {
|
|
317
323
|
onAnimationData: (protobufData, metadata) => {
|
|
318
|
-
if (
|
|
319
|
-
this.
|
|
320
|
-
|
|
324
|
+
if (!this.hasActiveAnimationSession) {
|
|
325
|
+
if (!this.isTrackingPrimed) {
|
|
326
|
+
this.animationHandler.resetTracking();
|
|
327
|
+
}
|
|
328
|
+
this.hasActiveAnimationSession = true;
|
|
329
|
+
this.isTrackingPrimed = false;
|
|
330
|
+
if (!metadata.isStart) {
|
|
331
|
+
logger.info(
|
|
332
|
+
"AvatarPlayer",
|
|
333
|
+
`Session start inferred from frame seq=${metadata.frameSeq ?? "n/a"}`
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
} else if (metadata.isStart) {
|
|
337
|
+
logger.info(
|
|
338
|
+
"AvatarPlayer",
|
|
339
|
+
`Ignoring duplicate session start frame seq=${metadata.frameSeq ?? "n/a"}`
|
|
340
|
+
);
|
|
321
341
|
}
|
|
322
342
|
this.animationHandler.handleAnimationData(
|
|
323
343
|
protobufData,
|
|
@@ -326,7 +346,6 @@ class AvatarPlayer {
|
|
|
326
346
|
);
|
|
327
347
|
},
|
|
328
348
|
onTransition: (protobufData, transitionFrameCount) => {
|
|
329
|
-
this.provider.pauseRemoteAudio();
|
|
330
349
|
this.animationHandler.handleTransitionData(protobufData, transitionFrameCount);
|
|
331
350
|
},
|
|
332
351
|
onTransitionEnd: (protobufData, transitionFrameCount) => {
|
|
@@ -337,6 +356,13 @@ class AvatarPlayer {
|
|
|
337
356
|
onSessionEnd: () => {
|
|
338
357
|
},
|
|
339
358
|
onIdleStart: () => {
|
|
359
|
+
if (this.animationHandler.isInTransition()) {
|
|
360
|
+
logger.info("AvatarPlayer", "Ignoring idleStart while transition is active");
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
this.hasActiveAnimationSession = false;
|
|
364
|
+
this.animationHandler.resetTracking();
|
|
365
|
+
this.isTrackingPrimed = true;
|
|
340
366
|
this.animationHandler.startIdle();
|
|
341
367
|
},
|
|
342
368
|
onStreamStats: (stats) => {
|
|
@@ -360,6 +386,8 @@ class AvatarPlayer {
|
|
|
360
386
|
});
|
|
361
387
|
this.provider.on("disconnected", () => {
|
|
362
388
|
this._isConnected = false;
|
|
389
|
+
this.hasActiveAnimationSession = false;
|
|
390
|
+
this.isTrackingPrimed = false;
|
|
363
391
|
this.animationHandler.startIdle();
|
|
364
392
|
});
|
|
365
393
|
}
|
package/dist/index2.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index2.js","sources":["../src/core/AvatarPlayer.ts"],"sourcesContent":["/**\n * AvatarPlayer - Unified entry point for RTC avatar streaming.\n *\n * This class provides a unified API that shields applications from\n * differences between different RTC providers (LiveKit, Agora, etc.)\n * and integrates seamlessly with @spatialwalk/avatarkit for rendering.\n *\n * @packageDocumentation\n */\n\nimport type { RTCProvider } from './RTCProvider';\nimport type { RTCConnectionConfig } from '../types';\nimport { AnimationHandler } from './AnimationHandler';\nimport type { AnimationTrackCallbacks, RTCProviderEvents } from './types';\nimport { configureLogger, logger, type LogLevel } from '../utils';\n\n// Import AvatarView type from avatarkit\nimport type { AvatarView, KeyframeData } from '@spatialwalk/avatarkit';\n\n/**\n * Options for configuring AvatarPlayer.\n */\nexport interface AvatarPlayerOptions {\n /**\n * Log level for SDK output.\n * - 'info': Show all logs (debug mode)\n * - 'warning': Show warnings and errors (default)\n * - 'error': Show only errors\n * - 'none': Disable all logs\n */\n logLevel?: LogLevel;\n\n /**\n * Enable jitter buffer for smoother animation playback.\n * When enabled, frames are buffered and rendered in sequence order at a steady 25fps,\n * absorbing network jitter and out-of-order delivery.\n * Default: true\n */\n enableJitterBuffer?: boolean;\n\n /**\n * Maximum delay (in ms) a frame can sit in the jitter buffer before being rendered.\n * Only used when enableJitterBuffer is true.\n * Default: 80 (2 frames at 25fps)\n */\n maxBufferDelayMs?: number;\n}\n\n/**\n * Unified Avatar Player.\n *\n * Main entry point for applications to interact with RTC avatar streaming.\n * Handles connection management, audio publishing, animation rendering,\n * and coordinates with avatarkit for display.\n *\n * @example\n * ```typescript\n * import { AvatarPlayer, LiveKitProvider } from '@spatialwalk/avatarkit-rtc';\n * import { AvatarView, Avatar } from '@spatialwalk/avatarkit';\n *\n * // Create avatar and view (from avatarkit)\n * const avatar = await Avatar.create(characterId);\n * const avatarView = new AvatarView(avatar, container);\n *\n * // Create player\n * const provider = new LiveKitProvider();\n * const player = new AvatarPlayer(provider, avatarView);\n *\n * // Connect\n * await player.connect({ url: 'wss://...', token: '...' });\n *\n * // Option 1: Use microphone (most common)\n * await player.startPublishing();\n * await player.stopPublishing();\n *\n * // Option 2: Use custom audio source\n * const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n * await player.publishAudio(stream.getAudioTracks()[0]);\n * await player.unpublishAudio();\n * stream.getTracks().forEach(t => t.stop());\n *\n * // Disconnect\n * await player.disconnect();\n * ```\n */\nexport class AvatarPlayer {\n /** @internal */\n private provider: RTCProvider;\n\n /** @internal */\n private avatarView: AvatarView;\n\n /** @internal */\n private animationHandler: AnimationHandler;\n\n /** @internal */\n private _isConnected = false;\n\n /** @internal */\n private localAudioTrack: MediaStreamTrack | null = null;\n\n /** @internal */\n private microphoneStream: MediaStream | null = null;\n\n /** @internal */\n private eventHandlers: Map<string, Set<Function>> = new Map();\n\n /** @internal */\n private lastConnectionConfig: RTCConnectionConfig | null = null;\n\n /** @internal */\n private isReconnecting = false;\n\n /**\n * Create a new AvatarPlayer instance.\n * @param provider - RTC provider implementation (e.g., LiveKitProvider)\n * @param avatarView - AvatarView instance from @spatialwalk/avatarkit\n * @param options - Optional configuration for transitions\n */\n constructor(\n provider: RTCProvider,\n avatarView: AvatarView,\n options?: AvatarPlayerOptions\n ) {\n this.provider = provider;\n this.avatarView = avatarView;\n\n // Configure logging if specified\n if (options?.logLevel !== undefined) {\n configureLogger({ level: options.logLevel });\n }\n\n // Create internal renderer adapter\n const renderer = this.createRendererAdapter();\n\n this.animationHandler = new AnimationHandler(renderer, {\n enableJitterBuffer: options?.enableJitterBuffer,\n maxBufferDelayMs: options?.maxBufferDelayMs,\n onStreamStalled: () => {\n // AnimationHandler already switches to idle state\n // Just notify external listeners, let them decide what to do (e.g., call reconnect())\n this.emit('stalled');\n },\n });\n\n // Setup provider event handlers\n this.setupProviderEvents();\n }\n\n /**\n * Check if currently connected to RTC server.\n */\n get isConnected(): boolean {\n return this._isConnected;\n }\n\n /**\n * Connect to RTC server.\n * @param config - Connection configuration (URL, token, room name, etc.)\n * @throws Error if already connected or connection fails\n */\n async connect(config: RTCConnectionConfig): Promise<void> {\n if (this._isConnected) {\n throw new Error('Already connected. Please disconnect first.');\n }\n\n // Save config for potential reconnection\n this.lastConnectionConfig = config;\n\n // Setup animation callbacks before connecting\n await this.setupAnimationCallbacks();\n await this.provider.connect(config);\n this._isConnected = true;\n }\n\n /**\n * Disconnect from RTC server.\n * Stops all tracks and cleans up resources.\n */\n async disconnect(): Promise<void> {\n if (!this._isConnected) {\n return;\n }\n\n // Stop microphone if active\n if (this.microphoneStream) {\n await this.stopPublishing();\n } else if (this.localAudioTrack) {\n // Stop custom audio track\n await this.unpublishAudio();\n }\n\n await this.provider.disconnect();\n this.animationHandler.dispose();\n this._isConnected = false;\n }\n\n /**\n * Publish an audio track to the RTC server.\n * \n * The audio source is controlled externally - you can pass any audio track:\n * - Microphone: `getUserMedia({ audio: true })`\n * - Browser audio: `audioElement.captureStream()`\n * - Web Audio API: `audioContext.createMediaStreamDestination().stream`\n * - Screen share audio: `getDisplayMedia({ audio: true })`\n * \n * @param track - MediaStreamTrack to publish (must be an audio track)\n * @throws Error if not connected or track is invalid\n * \n * @example\n * ```typescript\n * // Microphone\n * const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n * await player.publishAudio(stream.getAudioTracks()[0]);\n * \n * // Browser audio element\n * const audioEl = document.querySelector('audio');\n * const stream = audioEl.captureStream();\n * await player.publishAudio(stream.getAudioTracks()[0]);\n * ```\n */\n async publishAudio(track: MediaStreamTrack): Promise<void> {\n if (!this._isConnected) {\n throw new Error('Not connected. Please call connect() first.');\n }\n\n if (track.kind !== 'audio') {\n throw new Error('Invalid track: expected audio track');\n }\n\n // Unpublish existing track if any\n if (this.localAudioTrack) {\n await this.unpublishAudio();\n }\n\n this.localAudioTrack = track;\n await this.provider.publishAudioTrack(track);\n }\n\n /**\n * Stop publishing audio.\n * Note: This does NOT stop the track - the caller is responsible for managing the track lifecycle.\n */\n async unpublishAudio(): Promise<void> {\n if (!this.localAudioTrack) {\n return;\n }\n\n await this.provider.unpublishAudioTrack();\n this.localAudioTrack = null;\n }\n\n /**\n * Start publishing microphone audio.\n * \n * This requests microphone permission and publishes it to the RTC session.\n * For custom audio sources (e.g., audio files), use `publishAudio(track)` instead.\n * \n * @throws Error if not connected, permission denied, or no microphone found\n * \n * @example\n * ```typescript\n * await player.startPublishing();\n * // ... later\n * await player.stopPublishing();\n * ```\n */\n async startPublishing(): Promise<void> {\n if (!this._isConnected) {\n throw new Error('Not connected. Please call connect() first.');\n }\n\n if (this.microphoneStream) {\n return; // Already using microphone\n }\n\n // Request microphone permission\n this.microphoneStream = await navigator.mediaDevices.getUserMedia({ audio: true });\n const track = this.microphoneStream.getAudioTracks()[0];\n \n await this.publishAudio(track);\n }\n\n /**\n * Stop publishing microphone audio and release the microphone.\n * \n * This stops the microphone track and releases the device.\n * If you used `publishAudio()` with a custom track, use `unpublishAudio()` instead.\n */\n async stopPublishing(): Promise<void> {\n if (!this.microphoneStream) {\n return;\n }\n\n await this.unpublishAudio();\n\n // Stop and release microphone\n this.microphoneStream.getTracks().forEach((track) => track.stop());\n this.microphoneStream = null;\n }\n\n /**\n * Get current connection state.\n * @returns Connection state string (e.g., 'connected', 'disconnected')\n */\n getConnectionState(): string {\n return this.provider.getConnectionState();\n }\n\n /**\n * Get the native RTC client object.\n * \n * Returns the underlying RTC client for advanced use cases:\n * - LiveKit: Returns the Room instance\n * - Agora: Returns the IAgoraRTCClient instance\n * \n * @returns The native RTC client object, or null if not connected\n * \n * @example\n * ```typescript\n * // Access LiveKit Room\n * const room = player.getNativeClient() as Room;\n * console.log('Participants:', room?.remoteParticipants.size);\n * \n * // Access Agora Client\n * const client = player.getNativeClient() as IAgoraRTCClient;\n * console.log('State:', client?.connectionState);\n * ```\n */\n getNativeClient(): unknown {\n return this.provider.getNativeClient();\n }\n\n /**\n * Add event listener.\n * @param event - Event name ('connected', 'disconnected', 'error', 'stalled', etc.)\n * @param handler - Event handler function\n */\n on(event: string, handler: Function): void {\n if (event === 'stalled') {\n // Handle stalled event locally\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, new Set());\n }\n this.eventHandlers.get(event)!.add(handler);\n } else {\n // Forward other events to provider\n this.provider.on(event as any, handler as any);\n }\n }\n\n /**\n * Remove event listener.\n * @param event - Event name\n * @param handler - Event handler function (must be same reference as added)\n */\n off(event: string, handler: Function): void {\n if (event === 'stalled') {\n this.eventHandlers.get(event)?.delete(handler);\n } else {\n this.provider.off(event as any, handler as any);\n }\n }\n\n /**\n * Emit an event to local listeners.\n * @internal\n */\n private emit(event: string, ...args: unknown[]): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(...args);\n } catch (e) {\n logger.error('AvatarPlayer', `Error in ${event} handler:`, e);\n }\n });\n }\n }\n\n /**\n * Reconnect to RTC server using the last connection configuration.\n * Useful for recovering from connection issues or stream stalls.\n *\n * @example\n * ```typescript\n * player.on('stalled', async () => {\n * console.log('Stream stalled, attempting reconnection...');\n * await player.reconnect();\n * });\n * ```\n *\n * @throws Error if no previous connection config exists or reconnection fails\n */\n async reconnect(): Promise<void> {\n if (this.isReconnecting) {\n logger.warn('AvatarPlayer', 'Already attempting reconnection, skipping');\n return;\n }\n\n if (!this.lastConnectionConfig) {\n throw new Error('Cannot reconnect: no previous connection. Call connect() first.');\n }\n\n this.isReconnecting = true;\n logger.info('AvatarPlayer', 'Attempting reconnection...');\n\n try {\n // Disconnect first if connected\n if (this._isConnected) {\n await this.disconnect();\n }\n\n // Wait a short moment before reconnecting\n await new Promise((resolve) => setTimeout(resolve, 500));\n\n // Reconnect\n await this.connect(this.lastConnectionConfig);\n logger.info('AvatarPlayer', 'Reconnection successful');\n } catch (error) {\n logger.error('AvatarPlayer', 'Reconnection failed:', error);\n throw error;\n } finally {\n this.isReconnecting = false;\n }\n }\n\n /**\n * Create internal renderer adapter that bridges AnimationHandler to AvatarView.\n * @internal\n */\n private createRendererAdapter() {\n const avatarView = this.avatarView;\n\n return {\n renderFrame(flame: KeyframeData | undefined, startIdle?: boolean): void {\n if (startIdle) {\n // Enable idle rendering mode\n avatarView.renderFlame(undefined as unknown as KeyframeData, true);\n } else if (flame) {\n // Render the animation frame\n avatarView.renderFlame(flame);\n }\n },\n\n async generateTransitionFromIdle(\n targetFrame: KeyframeData,\n frameCount: number\n ): Promise<KeyframeData[]> {\n // Use avatarkit's generateTransition with from=undefined (current idle frame)\n return avatarView.generateTransition({\n to: targetFrame,\n frameCount,\n useLinear: true, // Linear interpolation for idle->speaking\n });\n },\n\n isReady(): boolean {\n return true; // AvatarView is always ready once created\n },\n };\n }\n\n /**\n * Setup animation track callbacks.\n * @internal\n */\n private async setupAnimationCallbacks(): Promise<void> {\n const callbacks: AnimationTrackCallbacks = {\n onAnimationData: (protobufData, metadata) => {\n // Reset tracking on session start\n if (metadata.isStart) {\n this.animationHandler.resetTracking();\n // Start audio playback on session start (fallback if transition didn't trigger it)\n this.provider.playRemoteAudio();\n }\n\n this.animationHandler.handleAnimationData(\n protobufData,\n metadata.frameSeq,\n metadata.isRecovered\n );\n },\n onTransition: (protobufData, transitionFrameCount) => {\n this.provider.pauseRemoteAudio();\n this.animationHandler.handleTransitionData(protobufData, transitionFrameCount);\n },\n onTransitionEnd: (protobufData, transitionFrameCount) => {\n this.animationHandler.handleTransitionToIdle(protobufData, transitionFrameCount);\n },\n onSessionStart: () => {\n // Session started - can be used for UI feedback\n },\n onSessionEnd: () => {\n // Session ended - can be used for UI feedback\n },\n onIdleStart: () => {\n this.animationHandler.startIdle();\n },\n onStreamStats: (stats) => {\n // Log packet loss/recovery stats\n if (stats.framesLost > 0 || stats.framesRecovered > 0 || stats.framesDropped > 0) {\n logger.warn(\n 'AvatarPlayer',\n `Stream stats: lost=${stats.framesLost}, recovered=${stats.framesRecovered}, dropped=${stats.framesDropped}, fps=${stats.framesPerSec}`\n );\n }\n },\n };\n\n await this.provider.subscribeAnimationTrack(callbacks);\n }\n\n /**\n * Setup provider event handlers.\n * @internal\n */\n private setupProviderEvents(): void {\n this.provider.on('connected', () => {\n this._isConnected = true;\n });\n\n this.provider.on('disconnected', () => {\n this._isConnected = false;\n // Return to idle animation when disconnected\n this.animationHandler.startIdle();\n });\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index2.js","sources":["../src/core/AvatarPlayer.ts"],"sourcesContent":["/**\n * AvatarPlayer - Unified entry point for RTC avatar streaming.\n *\n * This class provides a unified API that shields applications from\n * differences between different RTC providers (LiveKit, Agora, etc.)\n * and integrates seamlessly with @spatialwalk/avatarkit for rendering.\n *\n * @packageDocumentation\n */\n\nimport type { RTCProvider } from './RTCProvider';\nimport type { RTCConnectionConfig } from '../types';\nimport { AnimationHandler } from './AnimationHandler';\nimport type { AnimationTrackCallbacks, RTCProviderEvents } from './types';\nimport { configureLogger, logger, type LogLevel } from '../utils';\n\n// Import AvatarView type from avatarkit\nimport type { AvatarView, KeyframeData } from '@spatialwalk/avatarkit';\n\n/**\n * Options for configuring AvatarPlayer.\n */\nexport interface AvatarPlayerOptions {\n /**\n * Log level for SDK output.\n * - 'info': Show all logs (debug mode)\n * - 'warning': Show warnings and errors (default)\n * - 'error': Show only errors\n * - 'none': Disable all logs\n */\n logLevel?: LogLevel;\n\n /**\n * Enable jitter buffer for smoother animation playback.\n * When enabled, frames are buffered and rendered in sequence order at a steady 25fps,\n * absorbing network jitter and out-of-order delivery.\n * Default: true\n */\n enableJitterBuffer?: boolean;\n\n /**\n * Maximum delay (in ms) a frame can sit in the jitter buffer before being rendered.\n * Only used when enableJitterBuffer is true.\n * Default: 80 (2 frames at 25fps)\n */\n maxBufferDelayMs?: number;\n}\n\n/**\n * Unified Avatar Player.\n *\n * Main entry point for applications to interact with RTC avatar streaming.\n * Handles connection management, audio publishing, animation rendering,\n * and coordinates with avatarkit for display.\n *\n * @example\n * ```typescript\n * import { AvatarPlayer, LiveKitProvider } from '@spatialwalk/avatarkit-rtc';\n * import { AvatarView, Avatar } from '@spatialwalk/avatarkit';\n *\n * // Create avatar and view (from avatarkit)\n * const avatar = await Avatar.create(characterId);\n * const avatarView = new AvatarView(avatar, container);\n *\n * // Create player\n * const provider = new LiveKitProvider();\n * const player = new AvatarPlayer(provider, avatarView);\n *\n * // Connect\n * await player.connect({ url: 'wss://...', token: '...' });\n *\n * // Option 1: Use microphone (most common)\n * await player.startPublishing();\n * await player.stopPublishing();\n *\n * // Option 2: Use custom audio source\n * const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n * await player.publishAudio(stream.getAudioTracks()[0]);\n * await player.unpublishAudio();\n * stream.getTracks().forEach(t => t.stop());\n *\n * // Disconnect\n * await player.disconnect();\n * ```\n */\nexport class AvatarPlayer {\n /** @internal */\n private provider: RTCProvider;\n\n /** @internal */\n private avatarView: AvatarView;\n\n /** @internal */\n private animationHandler: AnimationHandler;\n\n /** @internal */\n private _isConnected = false;\n\n /** @internal */\n private localAudioTrack: MediaStreamTrack | null = null;\n\n /** @internal */\n private microphoneStream: MediaStream | null = null;\n\n /** @internal */\n private eventHandlers: Map<string, Set<Function>> = new Map();\n\n /** @internal */\n private lastConnectionConfig: RTCConnectionConfig | null = null;\n\n /** @internal */\n private isReconnecting = false;\n\n /** @internal */\n private hasActiveAnimationSession = false;\n\n /** @internal */\n private isTrackingPrimed = false;\n\n /**\n * Create a new AvatarPlayer instance.\n * @param provider - RTC provider implementation (e.g., LiveKitProvider)\n * @param avatarView - AvatarView instance from @spatialwalk/avatarkit\n * @param options - Optional configuration for transitions\n */\n constructor(\n provider: RTCProvider,\n avatarView: AvatarView,\n options?: AvatarPlayerOptions\n ) {\n this.provider = provider;\n this.avatarView = avatarView;\n\n // Configure logging if specified\n if (options?.logLevel !== undefined) {\n configureLogger({ level: options.logLevel });\n }\n\n // Create internal renderer adapter\n const renderer = this.createRendererAdapter();\n\n this.animationHandler = new AnimationHandler(renderer, {\n enableJitterBuffer: options?.enableJitterBuffer,\n maxBufferDelayMs: options?.maxBufferDelayMs,\n onStreamStalled: () => {\n // AnimationHandler already switches to idle state\n // Just notify external listeners, let them decide what to do (e.g., call reconnect())\n this.hasActiveAnimationSession = false;\n this.isTrackingPrimed = false;\n this.emit('stalled');\n },\n });\n\n // Setup provider event handlers\n this.setupProviderEvents();\n }\n\n /**\n * Check if currently connected to RTC server.\n */\n get isConnected(): boolean {\n return this._isConnected;\n }\n\n /**\n * Connect to RTC server.\n * @param config - Connection configuration (URL, token, room name, etc.)\n * @throws Error if already connected or connection fails\n */\n async connect(config: RTCConnectionConfig): Promise<void> {\n if (this._isConnected) {\n throw new Error('Already connected. Please disconnect first.');\n }\n\n // Save config for potential reconnection\n this.lastConnectionConfig = config;\n\n // Setup animation callbacks before connecting\n await this.setupAnimationCallbacks();\n await this.provider.connect(config);\n this._isConnected = true;\n }\n\n /**\n * Disconnect from RTC server.\n * Stops all tracks and cleans up resources.\n */\n async disconnect(): Promise<void> {\n if (!this._isConnected) {\n return;\n }\n\n // Stop microphone if active\n if (this.microphoneStream) {\n await this.stopPublishing();\n } else if (this.localAudioTrack) {\n // Stop custom audio track\n await this.unpublishAudio();\n }\n\n await this.provider.disconnect();\n this.animationHandler.dispose();\n this._isConnected = false;\n }\n\n /**\n * Publish an audio track to the RTC server.\n * \n * The audio source is controlled externally - you can pass any audio track:\n * - Microphone: `getUserMedia({ audio: true })`\n * - Browser audio: `audioElement.captureStream()`\n * - Web Audio API: `audioContext.createMediaStreamDestination().stream`\n * - Screen share audio: `getDisplayMedia({ audio: true })`\n * \n * @param track - MediaStreamTrack to publish (must be an audio track)\n * @throws Error if not connected or track is invalid\n * \n * @example\n * ```typescript\n * // Microphone\n * const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n * await player.publishAudio(stream.getAudioTracks()[0]);\n * \n * // Browser audio element\n * const audioEl = document.querySelector('audio');\n * const stream = audioEl.captureStream();\n * await player.publishAudio(stream.getAudioTracks()[0]);\n * ```\n */\n async publishAudio(track: MediaStreamTrack): Promise<void> {\n if (!this._isConnected) {\n throw new Error('Not connected. Please call connect() first.');\n }\n\n if (track.kind !== 'audio') {\n throw new Error('Invalid track: expected audio track');\n }\n\n // Unpublish existing track if any\n if (this.localAudioTrack) {\n await this.unpublishAudio();\n }\n\n this.localAudioTrack = track;\n await this.provider.publishAudioTrack(track);\n }\n\n /**\n * Stop publishing audio.\n * Note: This does NOT stop the track - the caller is responsible for managing the track lifecycle.\n */\n async unpublishAudio(): Promise<void> {\n if (!this.localAudioTrack) {\n return;\n }\n\n await this.provider.unpublishAudioTrack();\n this.localAudioTrack = null;\n }\n\n /**\n * Start publishing microphone audio.\n * \n * This requests microphone permission and publishes it to the RTC session.\n * For custom audio sources (e.g., audio files), use `publishAudio(track)` instead.\n * \n * @throws Error if not connected, permission denied, or no microphone found\n * \n * @example\n * ```typescript\n * await player.startPublishing();\n * // ... later\n * await player.stopPublishing();\n * ```\n */\n async startPublishing(): Promise<void> {\n if (!this._isConnected) {\n throw new Error('Not connected. Please call connect() first.');\n }\n\n if (this.microphoneStream) {\n return; // Already using microphone\n }\n\n // Request microphone permission\n this.microphoneStream = await navigator.mediaDevices.getUserMedia({ audio: true });\n const track = this.microphoneStream.getAudioTracks()[0];\n \n await this.publishAudio(track);\n }\n\n /**\n * Stop publishing microphone audio and release the microphone.\n * \n * This stops the microphone track and releases the device.\n * If you used `publishAudio()` with a custom track, use `unpublishAudio()` instead.\n */\n async stopPublishing(): Promise<void> {\n if (!this.microphoneStream) {\n return;\n }\n\n await this.unpublishAudio();\n\n // Stop and release microphone\n this.microphoneStream.getTracks().forEach((track) => track.stop());\n this.microphoneStream = null;\n }\n\n /**\n * Get current connection state.\n * @returns Connection state string (e.g., 'connected', 'disconnected')\n */\n getConnectionState(): string {\n return this.provider.getConnectionState();\n }\n\n /**\n * Get the native RTC client object.\n * \n * Returns the underlying RTC client for advanced use cases:\n * - LiveKit: Returns the Room instance\n * - Agora: Returns the IAgoraRTCClient instance\n * \n * @returns The native RTC client object, or null if not connected\n * \n * @example\n * ```typescript\n * // Access LiveKit Room\n * const room = player.getNativeClient() as Room;\n * console.log('Participants:', room?.remoteParticipants.size);\n * \n * // Access Agora Client\n * const client = player.getNativeClient() as IAgoraRTCClient;\n * console.log('State:', client?.connectionState);\n * ```\n */\n getNativeClient(): unknown {\n return this.provider.getNativeClient();\n }\n\n /**\n * Add event listener.\n * @param event - Event name ('connected', 'disconnected', 'error', 'stalled', etc.)\n * @param handler - Event handler function\n */\n on(event: string, handler: Function): void {\n if (event === 'stalled') {\n // Handle stalled event locally\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, new Set());\n }\n this.eventHandlers.get(event)!.add(handler);\n } else {\n // Forward other events to provider\n this.provider.on(event as any, handler as any);\n }\n }\n\n /**\n * Remove event listener.\n * @param event - Event name\n * @param handler - Event handler function (must be same reference as added)\n */\n off(event: string, handler: Function): void {\n if (event === 'stalled') {\n this.eventHandlers.get(event)?.delete(handler);\n } else {\n this.provider.off(event as any, handler as any);\n }\n }\n\n /**\n * Emit an event to local listeners.\n * @internal\n */\n private emit(event: string, ...args: unknown[]): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(...args);\n } catch (e) {\n logger.error('AvatarPlayer', `Error in ${event} handler:`, e);\n }\n });\n }\n }\n\n /**\n * Reconnect to RTC server using the last connection configuration.\n * Useful for recovering from connection issues or stream stalls.\n *\n * @example\n * ```typescript\n * player.on('stalled', async () => {\n * console.log('Stream stalled, attempting reconnection...');\n * await player.reconnect();\n * });\n * ```\n *\n * @throws Error if no previous connection config exists or reconnection fails\n */\n async reconnect(): Promise<void> {\n if (this.isReconnecting) {\n logger.warn('AvatarPlayer', 'Already attempting reconnection, skipping');\n return;\n }\n\n if (!this.lastConnectionConfig) {\n throw new Error('Cannot reconnect: no previous connection. Call connect() first.');\n }\n\n this.isReconnecting = true;\n logger.info('AvatarPlayer', 'Attempting reconnection...');\n\n try {\n // Disconnect first if connected\n if (this._isConnected) {\n await this.disconnect();\n }\n\n // Wait a short moment before reconnecting\n await new Promise((resolve) => setTimeout(resolve, 500));\n\n // Reconnect\n await this.connect(this.lastConnectionConfig);\n logger.info('AvatarPlayer', 'Reconnection successful');\n } catch (error) {\n logger.error('AvatarPlayer', 'Reconnection failed:', error);\n throw error;\n } finally {\n this.isReconnecting = false;\n }\n }\n\n /**\n * Create internal renderer adapter that bridges AnimationHandler to AvatarView.\n * @internal\n */\n private createRendererAdapter() {\n const avatarView = this.avatarView;\n\n return {\n renderFrame(flame: KeyframeData | undefined, startIdle?: boolean): void {\n if (startIdle) {\n // Enable idle rendering mode\n avatarView.renderFlame(undefined as unknown as KeyframeData, true);\n } else if (flame) {\n // Render the animation frame\n avatarView.renderFlame(flame);\n }\n },\n\n async generateTransitionFromIdle(\n targetFrame: KeyframeData,\n frameCount: number\n ): Promise<KeyframeData[]> {\n // Use avatarkit's generateTransition with from=undefined (current idle frame)\n return avatarView.generateTransition({\n to: targetFrame,\n frameCount,\n useLinear: true, // Linear interpolation for idle->speaking\n });\n },\n\n isReady(): boolean {\n return true; // AvatarView is always ready once created\n },\n };\n }\n\n /**\n * Setup animation track callbacks.\n * @internal\n */\n private async setupAnimationCallbacks(): Promise<void> {\n const callbacks: AnimationTrackCallbacks = {\n onAnimationData: (protobufData, metadata) => {\n // Reset tracking only once per session to avoid duplicate start-packet resets.\n if (!this.hasActiveAnimationSession) {\n if (!this.isTrackingPrimed) {\n this.animationHandler.resetTracking();\n }\n\n this.hasActiveAnimationSession = true;\n this.isTrackingPrimed = false;\n\n if (!metadata.isStart) {\n logger.info(\n 'AvatarPlayer',\n `Session start inferred from frame seq=${metadata.frameSeq ?? 'n/a'}`\n );\n }\n } else if (metadata.isStart) {\n logger.info(\n 'AvatarPlayer',\n `Ignoring duplicate session start frame seq=${metadata.frameSeq ?? 'n/a'}`\n );\n }\n\n this.animationHandler.handleAnimationData(\n protobufData,\n metadata.frameSeq,\n metadata.isRecovered\n );\n },\n onTransition: (protobufData, transitionFrameCount) => {\n this.animationHandler.handleTransitionData(protobufData, transitionFrameCount);\n },\n onTransitionEnd: (protobufData, transitionFrameCount) => {\n this.animationHandler.handleTransitionToIdle(protobufData, transitionFrameCount);\n },\n onSessionStart: () => {\n // Session started - can be used for UI feedback\n },\n onSessionEnd: () => {\n // Session ended - can be used for UI feedback\n },\n onIdleStart: () => {\n // Idle packets can race with start-transition packets; avoid rendering idle mid-transition.\n if (this.animationHandler.isInTransition()) {\n logger.info('AvatarPlayer', 'Ignoring idleStart while transition is active');\n return;\n }\n\n this.hasActiveAnimationSession = false;\n this.animationHandler.resetTracking();\n this.isTrackingPrimed = true;\n this.animationHandler.startIdle();\n },\n onStreamStats: (stats) => {\n // Log packet loss/recovery stats\n if (stats.framesLost > 0 || stats.framesRecovered > 0 || stats.framesDropped > 0) {\n logger.warn(\n 'AvatarPlayer',\n `Stream stats: lost=${stats.framesLost}, recovered=${stats.framesRecovered}, dropped=${stats.framesDropped}, fps=${stats.framesPerSec}`\n );\n }\n },\n };\n\n await this.provider.subscribeAnimationTrack(callbacks);\n }\n\n /**\n * Setup provider event handlers.\n * @internal\n */\n private setupProviderEvents(): void {\n this.provider.on('connected', () => {\n this._isConnected = true;\n });\n\n this.provider.on('disconnected', () => {\n this._isConnected = false;\n this.hasActiveAnimationSession = false;\n this.isTrackingPrimed = false;\n // Return to idle animation when disconnected\n this.animationHandler.startIdle();\n });\n }\n}\n"],"names":[],"mappings":"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"}
|
package/dist/index3.js
CHANGED
|
@@ -3,8 +3,8 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
|
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
import { BaseProvider } from "./index9.js";
|
|
5
5
|
import { isLiveKitConfig } from "./index5.js";
|
|
6
|
-
import { VP8Extractor } from "./
|
|
7
|
-
import { getInsertableStreamsMethod } from "./
|
|
6
|
+
import { VP8Extractor } from "./index10.js";
|
|
7
|
+
import { getInsertableStreamsMethod } from "./index11.js";
|
|
8
8
|
import { logger } from "./index7.js";
|
|
9
9
|
const globalLiveKitProviderRegistry = /* @__PURE__ */ new Set();
|
|
10
10
|
let rtcPeerConnectionPatched = false;
|
|
@@ -85,6 +85,18 @@ class LiveKitProvider extends BaseProvider {
|
|
|
85
85
|
__publicField(this, "audioCallbacks", null);
|
|
86
86
|
/** @internal */
|
|
87
87
|
__publicField(this, "audioElements", /* @__PURE__ */ new Map());
|
|
88
|
+
/** @internal */
|
|
89
|
+
__publicField(this, "awaitingAudioUnlock", false);
|
|
90
|
+
/** @internal */
|
|
91
|
+
__publicField(this, "audioUnlockEventNames", [
|
|
92
|
+
"pointerdown",
|
|
93
|
+
"keydown",
|
|
94
|
+
"touchstart"
|
|
95
|
+
]);
|
|
96
|
+
/** @internal */
|
|
97
|
+
__publicField(this, "handleAudioUnlockGesture", () => {
|
|
98
|
+
void this.ensureAudioPlaybackUnlocked("user-gesture");
|
|
99
|
+
});
|
|
88
100
|
// Microphone publishing
|
|
89
101
|
/** @internal */
|
|
90
102
|
__publicField(this, "localAudioTrack", null);
|
|
@@ -269,6 +281,104 @@ class LiveKitProvider extends BaseProvider {
|
|
|
269
281
|
this.vp8Extractors.delete(receiver);
|
|
270
282
|
}
|
|
271
283
|
}
|
|
284
|
+
/**
|
|
285
|
+
* Add one-time user gesture listeners to recover from autoplay blocking.
|
|
286
|
+
* @internal
|
|
287
|
+
*/
|
|
288
|
+
addAudioUnlockListeners() {
|
|
289
|
+
if (typeof window === "undefined" || this.awaitingAudioUnlock) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
this.awaitingAudioUnlock = true;
|
|
293
|
+
for (const eventName of this.audioUnlockEventNames) {
|
|
294
|
+
window.addEventListener(eventName, this.handleAudioUnlockGesture, {
|
|
295
|
+
capture: true,
|
|
296
|
+
passive: true
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
logger.warn("LiveKit", "Audio playback blocked by autoplay policy, waiting for user interaction");
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Remove user gesture listeners after playback is unlocked.
|
|
303
|
+
* @internal
|
|
304
|
+
*/
|
|
305
|
+
removeAudioUnlockListeners() {
|
|
306
|
+
if (typeof window === "undefined" || !this.awaitingAudioUnlock) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
this.awaitingAudioUnlock = false;
|
|
310
|
+
for (const eventName of this.audioUnlockEventNames) {
|
|
311
|
+
window.removeEventListener(eventName, this.handleAudioUnlockGesture, true);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Detect browser autoplay blocking errors from audio play/start calls.
|
|
316
|
+
* @internal
|
|
317
|
+
*/
|
|
318
|
+
isAutoplayBlockedError(error) {
|
|
319
|
+
if (typeof DOMException !== "undefined" && error instanceof DOMException) {
|
|
320
|
+
return error.name === "NotAllowedError";
|
|
321
|
+
}
|
|
322
|
+
if (error instanceof Error) {
|
|
323
|
+
return error.name === "NotAllowedError" || /didn't interact|user didn't interact|NotAllowedError/i.test(error.message);
|
|
324
|
+
}
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Try to resume all attached remote audio elements.
|
|
329
|
+
* @returns true if at least one element is still blocked by autoplay policy
|
|
330
|
+
* @internal
|
|
331
|
+
*/
|
|
332
|
+
async resumeAttachedAudioElements(reason) {
|
|
333
|
+
let hasAutoplayBlockedElement = false;
|
|
334
|
+
for (const [participantIdentity, audioElement] of this.audioElements) {
|
|
335
|
+
if (!audioElement.paused) {
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
await audioElement.play();
|
|
340
|
+
} catch (error) {
|
|
341
|
+
if (this.isAutoplayBlockedError(error)) {
|
|
342
|
+
hasAutoplayBlockedElement = true;
|
|
343
|
+
}
|
|
344
|
+
logger.warn(
|
|
345
|
+
"LiveKit",
|
|
346
|
+
`Failed to resume remote audio element (${reason}) for ${participantIdentity}:`,
|
|
347
|
+
error
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return hasAutoplayBlockedElement;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Ensure remote audio playback is unlocked and resumed.
|
|
355
|
+
* @internal
|
|
356
|
+
*/
|
|
357
|
+
async ensureAudioPlaybackUnlocked(reason) {
|
|
358
|
+
if (!this.room) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
const roomWithAudioControl = this.room;
|
|
362
|
+
if (!roomWithAudioControl.canPlaybackAudio && typeof roomWithAudioControl.startAudio === "function") {
|
|
363
|
+
try {
|
|
364
|
+
await roomWithAudioControl.startAudio();
|
|
365
|
+
logger.info("LiveKit", `Audio context resumed (${reason})`);
|
|
366
|
+
} catch (error) {
|
|
367
|
+
if (this.isAutoplayBlockedError(error)) {
|
|
368
|
+
this.addAudioUnlockListeners();
|
|
369
|
+
} else {
|
|
370
|
+
logger.warn("LiveKit", `Failed to call room.startAudio() (${reason}):`, error);
|
|
371
|
+
}
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
const hasAutoplayBlockedElement = await this.resumeAttachedAudioElements(reason);
|
|
376
|
+
if (roomWithAudioControl.canPlaybackAudio && !hasAutoplayBlockedElement) {
|
|
377
|
+
this.removeAudioUnlockListeners();
|
|
378
|
+
} else {
|
|
379
|
+
this.addAudioUnlockListeners();
|
|
380
|
+
}
|
|
381
|
+
}
|
|
272
382
|
async connect(config) {
|
|
273
383
|
var _a;
|
|
274
384
|
logger.info("LiveKit", "connect() called with config:", {
|
|
@@ -283,6 +393,7 @@ class LiveKitProvider extends BaseProvider {
|
|
|
283
393
|
}
|
|
284
394
|
const livekitConfig = config;
|
|
285
395
|
logger.info("LiveKit", "Config validated, connecting to:", livekitConfig.url);
|
|
396
|
+
this.removeAudioUnlockListeners();
|
|
286
397
|
const livekit = await this.loadSDK();
|
|
287
398
|
logger.info("LiveKit", "SDK loaded successfully");
|
|
288
399
|
const { Room } = livekit;
|
|
@@ -306,7 +417,6 @@ class LiveKitProvider extends BaseProvider {
|
|
|
306
417
|
});
|
|
307
418
|
});
|
|
308
419
|
this.setConnectionState(this.mapLiveKitConnectionState(livekit, this.room.state));
|
|
309
|
-
this.emit("connected");
|
|
310
420
|
} catch (error) {
|
|
311
421
|
logger.error("LiveKit", "Connection failed:", error);
|
|
312
422
|
this.setConnectionState("failed");
|
|
@@ -325,6 +435,10 @@ class LiveKitProvider extends BaseProvider {
|
|
|
325
435
|
room.on(RoomEvent.Connected, () => {
|
|
326
436
|
this.setConnectionState("connected");
|
|
327
437
|
this.emit("connected");
|
|
438
|
+
const roomWithAudioControl = room;
|
|
439
|
+
if (!roomWithAudioControl.canPlaybackAudio) {
|
|
440
|
+
this.addAudioUnlockListeners();
|
|
441
|
+
}
|
|
328
442
|
});
|
|
329
443
|
room.on(RoomEvent.ParticipantConnected, (participant) => {
|
|
330
444
|
logger.info("LiveKit", `Participant connected: ${participant.identity}`);
|
|
@@ -344,6 +458,14 @@ class LiveKitProvider extends BaseProvider {
|
|
|
344
458
|
this.setConnectionState("disconnected");
|
|
345
459
|
this.emit("disconnected");
|
|
346
460
|
});
|
|
461
|
+
room.on(RoomEvent.AudioPlaybackStatusChanged, (canPlaybackAudio) => {
|
|
462
|
+
logger.info("LiveKit", `Audio playback status changed: ${canPlaybackAudio ? "enabled" : "blocked"}`);
|
|
463
|
+
if (canPlaybackAudio) {
|
|
464
|
+
void this.ensureAudioPlaybackUnlocked("audio-playback-status-changed");
|
|
465
|
+
} else {
|
|
466
|
+
this.addAudioUnlockListeners();
|
|
467
|
+
}
|
|
468
|
+
});
|
|
347
469
|
this.room.on(
|
|
348
470
|
RoomEvent.TrackSubscribed,
|
|
349
471
|
(track, publication, participant) => {
|
|
@@ -362,11 +484,16 @@ class LiveKitProvider extends BaseProvider {
|
|
|
362
484
|
audioElement.autoplay = true;
|
|
363
485
|
audioElement.controls = false;
|
|
364
486
|
audioElement.style.display = "none";
|
|
487
|
+
const previousAudioElement = this.audioElements.get(participant.identity);
|
|
488
|
+
if (previousAudioElement && previousAudioElement !== audioElement) {
|
|
489
|
+
previousAudioElement.remove();
|
|
490
|
+
}
|
|
365
491
|
if (typeof document !== "undefined") {
|
|
366
492
|
document.body.appendChild(audioElement);
|
|
367
493
|
logger.info("LiveKit", "Audio element appended to body");
|
|
368
494
|
}
|
|
369
495
|
this.audioElements.set(participant.identity, audioElement);
|
|
496
|
+
void this.ensureAudioPlaybackUnlocked("audio-track-subscribed");
|
|
370
497
|
if (this.audioCallbacks) {
|
|
371
498
|
(_b = (_a = this.audioCallbacks).onAudioReceived) == null ? void 0 : _b.call(_a, participant);
|
|
372
499
|
}
|
|
@@ -477,7 +604,6 @@ class LiveKitProvider extends BaseProvider {
|
|
|
477
604
|
async unsubscribeAudioTrack() {
|
|
478
605
|
this.audioCallbacks = null;
|
|
479
606
|
this.audioElements.forEach((el) => {
|
|
480
|
-
el.pause();
|
|
481
607
|
el.remove();
|
|
482
608
|
});
|
|
483
609
|
this.audioElements.clear();
|
|
@@ -524,27 +650,6 @@ class LiveKitProvider extends BaseProvider {
|
|
|
524
650
|
this.localAudioTrack = null;
|
|
525
651
|
this.isMicrophoneEnabled = false;
|
|
526
652
|
}
|
|
527
|
-
/**
|
|
528
|
-
* Play remote audio (resume playback)
|
|
529
|
-
*/
|
|
530
|
-
playRemoteAudio() {
|
|
531
|
-
this.audioElements.forEach((audioElement) => {
|
|
532
|
-
if (audioElement.paused) {
|
|
533
|
-
audioElement.play().catch(() => {
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
});
|
|
537
|
-
}
|
|
538
|
-
/**
|
|
539
|
-
* Pause remote audio
|
|
540
|
-
*/
|
|
541
|
-
pauseRemoteAudio() {
|
|
542
|
-
this.audioElements.forEach((audioElement) => {
|
|
543
|
-
if (!audioElement.paused) {
|
|
544
|
-
audioElement.pause();
|
|
545
|
-
}
|
|
546
|
-
});
|
|
547
|
-
}
|
|
548
653
|
/**
|
|
549
654
|
* Get the native LiveKit Room instance.
|
|
550
655
|
*
|
|
@@ -570,8 +675,8 @@ class LiveKitProvider extends BaseProvider {
|
|
|
570
675
|
* @internal
|
|
571
676
|
*/
|
|
572
677
|
cleanup() {
|
|
678
|
+
this.removeAudioUnlockListeners();
|
|
573
679
|
this.audioElements.forEach((el) => {
|
|
574
|
-
el.pause();
|
|
575
680
|
el.remove();
|
|
576
681
|
});
|
|
577
682
|
this.audioElements.clear();
|
package/dist/index3.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index3.js","sources":["../src/providers/livekit/LiveKitProvider.ts"],"sourcesContent":["/**\n * LiveKit Provider Implementation.\n *\n * This provider uses LiveKit's VP8 video track approach\n * to transport animation data via RTCRtpScriptTransform.\n *\n * @packageDocumentation\n */\n\nimport { BaseProvider } from '../base/BaseProvider';\nimport type { RTCConnectionConfig, LiveKitConnectionConfig } from '../../types';\nimport { isLiveKitConfig } from '../../types';\nimport type { AnimationTrackCallbacks, AudioTrackCallbacks } from '../../core/types';\nimport { VP8Extractor } from './VP8Extractor';\nimport { getInsertableStreamsMethod } from './utils';\nimport { logger } from '../../utils';\nimport type {\n LiveKitModule,\n LiveKitRoom,\n LiveKitLocalAudioTrack,\n} from './types';\n\n/**\n * Global registry for LiveKitProvider instances to receive early track events.\n * @internal\n */\nconst globalLiveKitProviderRegistry = new Set<LiveKitProvider>();\n\n/**\n * Flag to ensure we only patch RTCPeerConnection once.\n * @internal\n */\nlet rtcPeerConnectionPatched = false;\n\n/**\n * Patch RTCPeerConnection.prototype to intercept track events at the earliest possible moment.\n * This runs BEFORE any PeerConnection is created by LiveKit.\n * @internal\n */\nfunction patchRTCPeerConnection(): void {\n if (rtcPeerConnectionPatched) return;\n if (typeof RTCPeerConnection === 'undefined') return;\n\n rtcPeerConnectionPatched = true;\n\n const originalAddEventListener = RTCPeerConnection.prototype.addEventListener;\n\n RTCPeerConnection.prototype.addEventListener = function(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions\n ) {\n if (type === 'track') {\n const wrappedListener = function(this: RTCPeerConnection, event: Event) {\n const trackEvent = event as RTCTrackEvent;\n\n if ((trackEvent.track?.kind === 'audio' || trackEvent.track?.kind === 'video') && trackEvent.receiver) {\n for (const provider of globalLiveKitProviderRegistry) {\n provider.handleEarlyTrack(trackEvent.receiver, trackEvent.track);\n }\n }\n\n if (typeof listener === 'function') {\n listener.call(this, event);\n } else {\n listener.handleEvent(event);\n }\n };\n\n return originalAddEventListener.call(this, type, wrappedListener as EventListener, options);\n }\n\n return originalAddEventListener.call(this, type, listener, options);\n };\n\n // Also patch the ontrack setter\n const originalOnTrackDescriptor = Object.getOwnPropertyDescriptor(RTCPeerConnection.prototype, 'ontrack');\n if (originalOnTrackDescriptor) {\n Object.defineProperty(RTCPeerConnection.prototype, 'ontrack', {\n get: originalOnTrackDescriptor.get,\n set: function(handler: ((this: RTCPeerConnection, ev: RTCTrackEvent) => void) | null) {\n if (handler) {\n const wrappedHandler = function(this: RTCPeerConnection, event: RTCTrackEvent) {\n if ((event.track?.kind === 'audio' || event.track?.kind === 'video') && event.receiver) {\n for (const provider of globalLiveKitProviderRegistry) {\n provider.handleEarlyTrack(event.receiver, event.track);\n }\n }\n handler.call(this, event);\n };\n originalOnTrackDescriptor.set?.call(this, wrappedHandler);\n } else {\n originalOnTrackDescriptor.set?.call(this, handler);\n }\n },\n configurable: true,\n enumerable: true,\n });\n }\n}\n\n// Apply the patch immediately when this module loads\npatchRTCPeerConnection();\n\n/**\n * LiveKit Provider.\n *\n * Implements RTCProvider interface for LiveKit platform.\n * Uses RTCRtpScriptTransform to extract animation data from VP8 video tracks.\n *\n * @example\n * ```typescript\n * import { AvatarPlayer, LiveKitProvider } from '@spatialwalk/avatarkit-rtc';\n *\n * const provider = new LiveKitProvider();\n * const player = new AvatarPlayer(provider, renderer);\n *\n * await player.connect({\n * url: 'wss://your-livekit-server.com',\n * token: 'your-token',\n * });\n * ```\n */\nexport class LiveKitProvider extends BaseProvider {\n /** Provider name identifier */\n readonly name = 'livekit';\n\n /** @internal */\n private room: LiveKitRoom | null = null;\n /** @internal */\n private livekitSDK: LiveKitModule | null = null;\n\n // Animation track subscription\n /** @internal */\n private animationCallbacks: AnimationTrackCallbacks | null = null;\n /** @internal */\n private vp8Extractor: VP8Extractor | null = null;\n /** @internal */\n private vp8Extractors: Map<RTCRtpReceiver, VP8Extractor> = new Map();\n /** @internal */\n private transformedReceivers: Set<RTCRtpReceiver> = new Set();\n /** @internal */\n private receiverParticipantMap: Map<RTCRtpReceiver, { participant: unknown; trackName?: string }> = new Map();\n\n // Audio track subscription\n /** @internal */\n private audioCallbacks: AudioTrackCallbacks | null = null;\n /** @internal */\n private audioElements: Map<string, HTMLAudioElement> = new Map();\n\n // Microphone publishing\n /** @internal */\n private localAudioTrack: LiveKitLocalAudioTrack | null = null;\n /** @internal */\n private isMicrophoneEnabled = false;\n\n constructor() {\n super();\n // Register to receive early track events\n globalLiveKitProviderRegistry.add(this);\n }\n\n /**\n * Load LiveKit SDK dynamically.\n * @internal\n */\n private async loadSDK(): Promise<LiveKitModule> {\n if (this.livekitSDK) {\n return this.livekitSDK;\n }\n\n try {\n this.livekitSDK = await import('livekit-client');\n return this.livekitSDK;\n } catch (error) {\n throw new Error(\n '❌ Failed to load livekit-client.\\n' +\n 'Please ensure it is installed: pnpm add livekit-client'\n );\n }\n }\n\n /**\n * Map LiveKit connection state to string.\n * @internal\n */\n private mapLiveKitConnectionState(livekit: typeof import('livekit-client'), state: unknown): string {\n const { ConnectionState: LKConnectionState } = livekit;\n switch (state) {\n case LKConnectionState.Disconnected:\n return 'disconnected';\n case LKConnectionState.Connecting:\n return 'connecting';\n case LKConnectionState.Connected:\n return 'connected';\n case LKConnectionState.Reconnecting:\n return 'reconnecting';\n default:\n return 'failed';\n }\n }\n\n /**\n * Handle early track event from the global RTCPeerConnection patch.\n * This is called BEFORE LiveKit processes the track.\n * @internal\n */\n handleEarlyTrack(receiver: RTCRtpReceiver, track: MediaStreamTrack): void {\n if (!this.room) return;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const roomState = (this.room as any).state;\n if (roomState === 'disconnected' || roomState === 0) return;\n\n // Apply transform to video tracks - animation data is sent as VP8 video\n if (track.kind === 'video' && this.animationCallbacks) {\n this.applyAnimationReceiverTransform(receiver, track);\n }\n }\n\n /**\n * Find all RTCPeerConnection instances in the room\n * @internal\n */\n private findAllPeerConnections(root: unknown): RTCPeerConnection[] {\n if (typeof RTCPeerConnection === 'undefined') return [];\n if (!root || typeof root !== 'object') return [];\n\n const seen = new Set<unknown>();\n const queue: Array<{ v: unknown; d: number }> = [{ v: root, d: 0 }];\n const pcs: RTCPeerConnection[] = [];\n let steps = 0;\n\n while (queue.length && steps < 2000) {\n steps++;\n const { v, d } = queue.shift()!;\n if (!v || typeof v !== 'object') continue;\n if (seen.has(v)) continue;\n seen.add(v);\n\n if (v instanceof RTCPeerConnection) {\n pcs.push(v);\n continue;\n }\n if (d >= 6) continue;\n\n if (Array.isArray(v)) {\n for (const item of v) queue.push({ v: item, d: d + 1 });\n continue;\n }\n\n if (v instanceof Map) {\n for (const item of v.values()) queue.push({ v: item, d: d + 1 });\n continue;\n }\n\n if (v instanceof Set) {\n for (const item of v.values()) queue.push({ v: item, d: d + 1 });\n continue;\n }\n\n try {\n for (const key of Object.keys(v as object)) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n queue.push({ v: (v as any)[key], d: d + 1 });\n }\n } catch {\n // ignore\n }\n }\n\n return pcs;\n }\n\n /**\n * Find video receiver by track ID\n * @internal\n */\n private findVideoReceiverByTrackId(trackId?: string): RTCRtpReceiver | null {\n if (!trackId) return null;\n const pcs = this.findAllPeerConnections(this.room);\n for (const pc of pcs) {\n try {\n const receivers = pc.getReceivers();\n for (const r of receivers) {\n if (r.track?.kind === 'video' && r.track.id === trackId) return r;\n }\n } catch {\n // ignore\n }\n }\n // fallback: any video receiver\n for (const pc of pcs) {\n try {\n const r = pc.getReceivers().find((x) => x.track?.kind === 'video');\n if (r) return r;\n } catch {\n // ignore\n }\n }\n return null;\n }\n\n /**\n * Find audio receiver by track ID\n * @internal\n */\n private findAudioReceiverByTrackId(trackId?: string): RTCRtpReceiver | null {\n if (!trackId) return null;\n const pcs = this.findAllPeerConnections(this.room);\n for (const pc of pcs) {\n try {\n const receivers = pc.getReceivers();\n for (const r of receivers) {\n if (r.track?.kind === 'audio' && r.track.id === trackId) return r;\n }\n } catch {\n // ignore\n }\n }\n // fallback: any audio receiver\n for (const pc of pcs) {\n try {\n const r = pc.getReceivers().find((x) => x.track?.kind === 'audio');\n if (r) return r;\n } catch {\n // ignore\n }\n }\n return null;\n }\n\n /**\n * Apply animation receiver transform on a video receiver\n * @internal\n */\n private applyAnimationReceiverTransform(receiver: RTCRtpReceiver, mediaTrack: MediaStreamTrack): void {\n if (mediaTrack.kind !== 'video') return;\n if (this.transformedReceivers.has(receiver)) return;\n\n const method = getInsertableStreamsMethod();\n if (method !== 'scriptTransform') return;\n if (receiver.transform) return;\n if (!this.animationCallbacks) return;\n\n this.transformedReceivers.add(receiver);\n\n try {\n const extractor = new VP8Extractor();\n this.vp8Extractors.set(receiver, extractor);\n this.vp8Extractor = extractor;\n \n extractor.initialize(receiver, this.animationCallbacks).catch((err) => {\n logger.error('LiveKit', 'Failed to initialize VP8Extractor:', err);\n this.transformedReceivers.delete(receiver);\n this.vp8Extractors.delete(receiver);\n });\n } catch (e) {\n logger.error('LiveKit', 'Failed to apply transform:', e);\n this.transformedReceivers.delete(receiver);\n this.vp8Extractors.delete(receiver);\n }\n }\n\n\n async connect(config: RTCConnectionConfig): Promise<void> {\n logger.info('LiveKit', 'connect() called with config:', {\n hasUrl: 'url' in config,\n hasRoomName: 'roomName' in config,\n hasToken: 'token' in config,\n config,\n });\n\n if (!isLiveKitConfig(config)) {\n logger.error('LiveKit', 'Config validation failed - missing url or roomName');\n throw new Error('LiveKitProvider requires url and roomName in connection config');\n }\n\n const livekitConfig: LiveKitConnectionConfig = config;\n logger.info('LiveKit', 'Config validated, connecting to:', livekitConfig.url);\n\n const livekit = await this.loadSDK();\n logger.info('LiveKit', 'SDK loaded successfully');\n const { Room } = livekit;\n\n this.room = new Room({\n videoCaptureDefaults: undefined,\n audioCaptureDefaults: undefined,\n });\n\n this.setConnectionState('connecting');\n\n // Setup event listeners\n this.setupEventListeners(livekit);\n\n try {\n logger.info('LiveKit', 'Attempting to connect to room...');\n await this.room.connect(livekitConfig.url, livekitConfig.token);\n logger.info('LiveKit', 'Room connected, state:', this.room.state);\n logger.info('LiveKit', 'Room name:', this.room.name);\n logger.info('LiveKit', 'Local participant:', this.room.localParticipant?.identity);\n logger.info('LiveKit', 'Remote participants:', this.room.remoteParticipants.size);\n \n // Log existing participants and their tracks\n this.room.remoteParticipants.forEach((participant: any, sid: string) => {\n logger.info('LiveKit', `Remote participant: ${participant.identity}, sid: ${sid}`);\n participant.trackPublications.forEach((pub: any, trackSid: string) => {\n logger.info('LiveKit', ` Track: ${pub.trackName}, kind: ${pub.kind}, subscribed: ${pub.isSubscribed}, sid: ${trackSid}`);\n });\n });\n\n this.setConnectionState(this.mapLiveKitConnectionState(livekit, this.room.state));\n this.emit('connected');\n } catch (error) {\n logger.error('LiveKit', 'Connection failed:', error);\n this.setConnectionState('failed');\n this.emit('error', error as Error);\n throw error;\n }\n }\n\n /**\n * Setup LiveKit room event listeners\n * @internal\n */\n private setupEventListeners(livekit: LiveKitModule): void {\n if (!this.room) return;\n \n const room = this.room;\n const { RoomEvent, Track } = livekit;\n\n // Connected\n room.on(RoomEvent.Connected, () => {\n this.setConnectionState('connected');\n this.emit('connected');\n });\n\n // Participant connected\n room.on(RoomEvent.ParticipantConnected, (participant: any) => {\n logger.info('LiveKit', `Participant connected: ${participant.identity}`);\n });\n\n // Participant disconnected\n room.on(RoomEvent.ParticipantDisconnected, (participant: any) => {\n logger.warn('LiveKit', `Participant disconnected: ${participant.identity}`);\n });\n\n // Track published\n room.on(RoomEvent.TrackPublished, (publication: any, participant: any) => {\n logger.info('LiveKit', 'TrackPublished:', {\n trackName: publication.trackName,\n kind: publication.kind,\n participant: participant.identity,\n });\n });\n\n // Disconnected\n room.on(RoomEvent.Disconnected, () => {\n this.cleanup();\n this.setConnectionState('disconnected');\n this.emit('disconnected');\n });\n\n // Track subscribed\n this.room.on(\n RoomEvent.TrackSubscribed,\n (track: any, publication: any, participant: any) => {\n logger.info('LiveKit', 'TrackSubscribed:', {\n kind: track.kind,\n trackName: publication.trackName,\n participant: participant.identity,\n });\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const mediaStreamTrack = (track as any).mediaStreamTrack as MediaStreamTrack | undefined;\n const trackName = publication.trackName;\n\n if (track.kind === Track.Kind.Audio) {\n logger.info('LiveKit', 'Audio track received, attaching...');\n // Audio track\n const audioElement = track.attach() as HTMLAudioElement;\n audioElement.id = `audio-${participant.identity}`;\n audioElement.autoplay = true;\n audioElement.controls = false;\n audioElement.style.display = 'none';\n \n if (typeof document !== 'undefined') {\n document.body.appendChild(audioElement);\n logger.info('LiveKit', 'Audio element appended to body');\n }\n \n this.audioElements.set(participant.identity, audioElement);\n\n if (this.audioCallbacks) {\n this.audioCallbacks.onAudioReceived?.(participant);\n }\n } else if (track.kind === Track.Kind.Video) {\n logger.info('LiveKit', 'Video track received, setting up animation transform...');\n // Video track - apply animation transform\n if (mediaStreamTrack) {\n const receiver = this.findVideoReceiverByTrackId(mediaStreamTrack.id);\n if (receiver) {\n this.receiverParticipantMap.set(receiver, { participant, trackName });\n \n // Apply transform if not already applied in handleEarlyTrack\n if (!receiver.transform || !this.transformedReceivers.has(receiver)) {\n this.applyAnimationReceiverTransform(receiver, mediaStreamTrack);\n }\n }\n }\n\n // Attach to hidden video element to trigger data flow\n const videoElement = track.attach() as HTMLVideoElement;\n videoElement.id = `video-animation-${participant.identity}`;\n videoElement.muted = true;\n videoElement.autoplay = true;\n videoElement.style.display = 'none';\n if (typeof document !== 'undefined') {\n document.body.appendChild(videoElement);\n videoElement.play().catch(() => {});\n }\n }\n }\n );\n\n // Track unsubscribed\n this.room.on(\n RoomEvent.TrackUnsubscribed,\n (track: any, _publication: any, participant: any) => {\n if (track.kind === Track.Kind.Audio) {\n track.detach().forEach((el: HTMLElement) => el.remove());\n this.audioElements.delete(participant.identity);\n \n if (this.audioCallbacks) {\n this.audioCallbacks.onAudioLost?.(participant);\n }\n }\n }\n );\n\n // Connection state changed\n room.on(RoomEvent.ConnectionStateChanged, (state: unknown) => {\n this.setConnectionState(this.mapLiveKitConnectionState(livekit, state));\n });\n }\n\n async disconnect(): Promise<void> {\n this.cleanup();\n if (this.room) {\n this.room.disconnect();\n this.room = null;\n }\n this.setConnectionState('disconnected');\n globalLiveKitProviderRegistry.delete(this);\n }\n\n getConnectionState(): string {\n if (!this.room) {\n return 'disconnected';\n }\n return this.connectionState;\n }\n\n /** @internal */\n async subscribeAnimationTrack(callbacks: AnimationTrackCallbacks): Promise<void> {\n this.animationCallbacks = callbacks;\n \n // If room is already connected, check for existing tracks\n if (this.room) {\n const livekit = await this.loadSDK();\n const { ConnectionState: LKConnectionState } = livekit;\n \n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const roomState = (this.room as any).state;\n \n if (roomState === LKConnectionState.Connected || roomState === 'connected') {\n // Check for existing video tracks that might be animation tracks\n const remoteParticipants = this.room.remoteParticipants.values();\n for (const participant of remoteParticipants) {\n // Get video track publications (handle different SDK versions)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const pubs = (participant as any).videoTrackPublications ?? (participant as any).trackPublications;\n if (pubs && typeof pubs.values === 'function') {\n for (const publication of pubs.values()) {\n if (publication.trackName?.includes('animation') && publication.track) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const mediaStreamTrack = (publication.track as any).mediaStreamTrack as MediaStreamTrack | undefined;\n if (mediaStreamTrack) {\n const receiver = this.findVideoReceiverByTrackId(mediaStreamTrack.id);\n if (receiver) {\n this.applyAnimationReceiverTransform(receiver, mediaStreamTrack);\n }\n }\n }\n }\n }\n }\n }\n }\n }\n\n /** @internal */\n async unsubscribeAnimationTrack(): Promise<void> {\n this.animationCallbacks = null;\n \n // Dispose all extractors\n this.vp8Extractors.forEach((extractor) => {\n extractor.dispose();\n });\n this.vp8Extractors.clear();\n \n if (this.vp8Extractor) {\n this.vp8Extractor.dispose();\n this.vp8Extractor = null;\n }\n this.transformedReceivers.clear();\n this.receiverParticipantMap.clear();\n }\n\n /** @internal */\n async subscribeAudioTrack(callbacks: AudioTrackCallbacks): Promise<void> {\n this.audioCallbacks = callbacks;\n // Audio tracks are automatically handled in TrackSubscribed event\n }\n\n /** @internal */\n async unsubscribeAudioTrack(): Promise<void> {\n this.audioCallbacks = null;\n this.audioElements.forEach((el) => {\n el.pause();\n el.remove();\n });\n this.audioElements.clear();\n }\n\n async publishAudioTrack(track: MediaStreamTrack): Promise<void> {\n if (!this.room) {\n throw new Error('Not connected to room');\n }\n\n try {\n const livekit = await this.loadSDK();\n const { createLocalAudioTrack, Track: TrackEnum } = livekit;\n\n // If track is provided, use it; otherwise create from microphone\n if (track) {\n await this.room.localParticipant.publishTrack(track, {\n name: 'user-microphone',\n source: TrackEnum.Source.Microphone,\n red: false,\n });\n } else {\n // Create and publish microphone track\n const localTrack = await createLocalAudioTrack({\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n });\n \n this.localAudioTrack = localTrack;\n \n await this.room.localParticipant.publishTrack(localTrack, {\n name: 'user-microphone',\n source: TrackEnum.Source.Microphone,\n red: false,\n });\n }\n\n this.isMicrophoneEnabled = true;\n } catch (error) {\n logger.error('LiveKit', 'Failed to publish audio track:', error);\n this.isMicrophoneEnabled = false;\n throw error;\n }\n }\n\n async unpublishAudioTrack(): Promise<void> {\n if (!this.room || !this.localAudioTrack) {\n return;\n }\n\n await this.room.localParticipant.unpublishTrack(this.localAudioTrack);\n this.localAudioTrack.stop();\n this.localAudioTrack = null;\n this.isMicrophoneEnabled = false;\n }\n\n /**\n * Play remote audio (resume playback)\n */\n playRemoteAudio(): void {\n this.audioElements.forEach((audioElement) => {\n if (audioElement.paused) {\n audioElement.play().catch(() => {});\n }\n });\n }\n\n /**\n * Pause remote audio\n */\n pauseRemoteAudio(): void {\n this.audioElements.forEach((audioElement) => {\n if (!audioElement.paused) {\n audioElement.pause();\n }\n });\n }\n\n /**\n * Get the native LiveKit Room instance.\n * \n * Allows advanced users to access LiveKit-specific features\n * not exposed through the unified API.\n * \n * @returns The LiveKit Room instance, or null if not connected\n * \n * @example\n * ```typescript\n * const room = provider.getNativeClient();\n * if (room) {\n * // Access LiveKit-specific features\n * console.log('Participants:', room.remoteParticipants.size);\n * }\n * ```\n */\n getNativeClient(): LiveKitRoom | null {\n return this.room;\n }\n\n /**\n * Cleanup resources\n * @internal\n */\n private cleanup(): void {\n // Cleanup audio elements\n this.audioElements.forEach((el) => {\n el.pause();\n el.remove();\n });\n this.audioElements.clear();\n \n // Dispose all extractors\n this.vp8Extractors.forEach((extractor) => extractor.dispose());\n this.vp8Extractors.clear();\n this.vp8Extractor = null;\n \n this.transformedReceivers.clear();\n this.receiverParticipantMap.clear();\n\n // Stop local audio track\n if (this.localAudioTrack) {\n this.localAudioTrack.stop();\n this.localAudioTrack = null;\n }\n this.isMicrophoneEnabled = false;\n }\n}\n"],"names":["_a","_b"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index3.js","sources":["../src/providers/livekit/LiveKitProvider.ts"],"sourcesContent":["/**\n * LiveKit Provider Implementation.\n *\n * This provider uses LiveKit's VP8 video track approach\n * to transport animation data via RTCRtpScriptTransform.\n *\n * @packageDocumentation\n */\n\nimport { BaseProvider } from '../base/BaseProvider';\nimport type { RTCConnectionConfig, LiveKitConnectionConfig } from '../../types';\nimport { isLiveKitConfig } from '../../types';\nimport type { AnimationTrackCallbacks, AudioTrackCallbacks } from '../../core/types';\nimport { VP8Extractor } from './VP8Extractor';\nimport { getInsertableStreamsMethod } from './utils';\nimport { logger } from '../../utils';\nimport type {\n LiveKitModule,\n LiveKitRoom,\n LiveKitLocalAudioTrack,\n} from './types';\n\n/**\n * Global registry for LiveKitProvider instances to receive early track events.\n * @internal\n */\nconst globalLiveKitProviderRegistry = new Set<LiveKitProvider>();\n\n/**\n * Flag to ensure we only patch RTCPeerConnection once.\n * @internal\n */\nlet rtcPeerConnectionPatched = false;\n\n/**\n * Patch RTCPeerConnection.prototype to intercept track events at the earliest possible moment.\n * This runs BEFORE any PeerConnection is created by LiveKit.\n * @internal\n */\nfunction patchRTCPeerConnection(): void {\n if (rtcPeerConnectionPatched) return;\n if (typeof RTCPeerConnection === 'undefined') return;\n\n rtcPeerConnectionPatched = true;\n\n const originalAddEventListener = RTCPeerConnection.prototype.addEventListener;\n\n RTCPeerConnection.prototype.addEventListener = function(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions\n ) {\n if (type === 'track') {\n const wrappedListener = function(this: RTCPeerConnection, event: Event) {\n const trackEvent = event as RTCTrackEvent;\n\n if ((trackEvent.track?.kind === 'audio' || trackEvent.track?.kind === 'video') && trackEvent.receiver) {\n for (const provider of globalLiveKitProviderRegistry) {\n provider.handleEarlyTrack(trackEvent.receiver, trackEvent.track);\n }\n }\n\n if (typeof listener === 'function') {\n listener.call(this, event);\n } else {\n listener.handleEvent(event);\n }\n };\n\n return originalAddEventListener.call(this, type, wrappedListener as EventListener, options);\n }\n\n return originalAddEventListener.call(this, type, listener, options);\n };\n\n // Also patch the ontrack setter\n const originalOnTrackDescriptor = Object.getOwnPropertyDescriptor(RTCPeerConnection.prototype, 'ontrack');\n if (originalOnTrackDescriptor) {\n Object.defineProperty(RTCPeerConnection.prototype, 'ontrack', {\n get: originalOnTrackDescriptor.get,\n set: function(handler: ((this: RTCPeerConnection, ev: RTCTrackEvent) => void) | null) {\n if (handler) {\n const wrappedHandler = function(this: RTCPeerConnection, event: RTCTrackEvent) {\n if ((event.track?.kind === 'audio' || event.track?.kind === 'video') && event.receiver) {\n for (const provider of globalLiveKitProviderRegistry) {\n provider.handleEarlyTrack(event.receiver, event.track);\n }\n }\n handler.call(this, event);\n };\n originalOnTrackDescriptor.set?.call(this, wrappedHandler);\n } else {\n originalOnTrackDescriptor.set?.call(this, handler);\n }\n },\n configurable: true,\n enumerable: true,\n });\n }\n}\n\n// Apply the patch immediately when this module loads\npatchRTCPeerConnection();\n\n/**\n * LiveKit Provider.\n *\n * Implements RTCProvider interface for LiveKit platform.\n * Uses RTCRtpScriptTransform to extract animation data from VP8 video tracks.\n *\n * @example\n * ```typescript\n * import { AvatarPlayer, LiveKitProvider } from '@spatialwalk/avatarkit-rtc';\n *\n * const provider = new LiveKitProvider();\n * const player = new AvatarPlayer(provider, renderer);\n *\n * await player.connect({\n * url: 'wss://your-livekit-server.com',\n * token: 'your-token',\n * });\n * ```\n */\nexport class LiveKitProvider extends BaseProvider {\n /** Provider name identifier */\n readonly name = 'livekit';\n\n /** @internal */\n private room: LiveKitRoom | null = null;\n /** @internal */\n private livekitSDK: LiveKitModule | null = null;\n\n // Animation track subscription\n /** @internal */\n private animationCallbacks: AnimationTrackCallbacks | null = null;\n /** @internal */\n private vp8Extractor: VP8Extractor | null = null;\n /** @internal */\n private vp8Extractors: Map<RTCRtpReceiver, VP8Extractor> = new Map();\n /** @internal */\n private transformedReceivers: Set<RTCRtpReceiver> = new Set();\n /** @internal */\n private receiverParticipantMap: Map<RTCRtpReceiver, { participant: unknown; trackName?: string }> = new Map();\n\n // Audio track subscription\n /** @internal */\n private audioCallbacks: AudioTrackCallbacks | null = null;\n /** @internal */\n private audioElements: Map<string, HTMLAudioElement> = new Map();\n /** @internal */\n private awaitingAudioUnlock = false;\n /** @internal */\n private readonly audioUnlockEventNames: Array<keyof WindowEventMap> = [\n 'pointerdown',\n 'keydown',\n 'touchstart',\n ];\n /** @internal */\n private readonly handleAudioUnlockGesture = (): void => {\n void this.ensureAudioPlaybackUnlocked('user-gesture');\n };\n\n // Microphone publishing\n /** @internal */\n private localAudioTrack: LiveKitLocalAudioTrack | null = null;\n /** @internal */\n private isMicrophoneEnabled = false;\n\n constructor() {\n super();\n // Register to receive early track events\n globalLiveKitProviderRegistry.add(this);\n }\n\n /**\n * Load LiveKit SDK dynamically.\n * @internal\n */\n private async loadSDK(): Promise<LiveKitModule> {\n if (this.livekitSDK) {\n return this.livekitSDK;\n }\n\n try {\n this.livekitSDK = await import('livekit-client');\n return this.livekitSDK;\n } catch (error) {\n throw new Error(\n '❌ Failed to load livekit-client.\\n' +\n 'Please ensure it is installed: pnpm add livekit-client'\n );\n }\n }\n\n /**\n * Map LiveKit connection state to string.\n * @internal\n */\n private mapLiveKitConnectionState(livekit: typeof import('livekit-client'), state: unknown): string {\n const { ConnectionState: LKConnectionState } = livekit;\n switch (state) {\n case LKConnectionState.Disconnected:\n return 'disconnected';\n case LKConnectionState.Connecting:\n return 'connecting';\n case LKConnectionState.Connected:\n return 'connected';\n case LKConnectionState.Reconnecting:\n return 'reconnecting';\n default:\n return 'failed';\n }\n }\n\n /**\n * Handle early track event from the global RTCPeerConnection patch.\n * This is called BEFORE LiveKit processes the track.\n * @internal\n */\n handleEarlyTrack(receiver: RTCRtpReceiver, track: MediaStreamTrack): void {\n if (!this.room) return;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const roomState = (this.room as any).state;\n if (roomState === 'disconnected' || roomState === 0) return;\n\n // Apply transform to video tracks - animation data is sent as VP8 video\n if (track.kind === 'video' && this.animationCallbacks) {\n this.applyAnimationReceiverTransform(receiver, track);\n }\n }\n\n /**\n * Find all RTCPeerConnection instances in the room\n * @internal\n */\n private findAllPeerConnections(root: unknown): RTCPeerConnection[] {\n if (typeof RTCPeerConnection === 'undefined') return [];\n if (!root || typeof root !== 'object') return [];\n\n const seen = new Set<unknown>();\n const queue: Array<{ v: unknown; d: number }> = [{ v: root, d: 0 }];\n const pcs: RTCPeerConnection[] = [];\n let steps = 0;\n\n while (queue.length && steps < 2000) {\n steps++;\n const { v, d } = queue.shift()!;\n if (!v || typeof v !== 'object') continue;\n if (seen.has(v)) continue;\n seen.add(v);\n\n if (v instanceof RTCPeerConnection) {\n pcs.push(v);\n continue;\n }\n if (d >= 6) continue;\n\n if (Array.isArray(v)) {\n for (const item of v) queue.push({ v: item, d: d + 1 });\n continue;\n }\n\n if (v instanceof Map) {\n for (const item of v.values()) queue.push({ v: item, d: d + 1 });\n continue;\n }\n\n if (v instanceof Set) {\n for (const item of v.values()) queue.push({ v: item, d: d + 1 });\n continue;\n }\n\n try {\n for (const key of Object.keys(v as object)) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n queue.push({ v: (v as any)[key], d: d + 1 });\n }\n } catch {\n // ignore\n }\n }\n\n return pcs;\n }\n\n /**\n * Find video receiver by track ID\n * @internal\n */\n private findVideoReceiverByTrackId(trackId?: string): RTCRtpReceiver | null {\n if (!trackId) return null;\n const pcs = this.findAllPeerConnections(this.room);\n for (const pc of pcs) {\n try {\n const receivers = pc.getReceivers();\n for (const r of receivers) {\n if (r.track?.kind === 'video' && r.track.id === trackId) return r;\n }\n } catch {\n // ignore\n }\n }\n // fallback: any video receiver\n for (const pc of pcs) {\n try {\n const r = pc.getReceivers().find((x) => x.track?.kind === 'video');\n if (r) return r;\n } catch {\n // ignore\n }\n }\n return null;\n }\n\n /**\n * Find audio receiver by track ID\n * @internal\n */\n private findAudioReceiverByTrackId(trackId?: string): RTCRtpReceiver | null {\n if (!trackId) return null;\n const pcs = this.findAllPeerConnections(this.room);\n for (const pc of pcs) {\n try {\n const receivers = pc.getReceivers();\n for (const r of receivers) {\n if (r.track?.kind === 'audio' && r.track.id === trackId) return r;\n }\n } catch {\n // ignore\n }\n }\n // fallback: any audio receiver\n for (const pc of pcs) {\n try {\n const r = pc.getReceivers().find((x) => x.track?.kind === 'audio');\n if (r) return r;\n } catch {\n // ignore\n }\n }\n return null;\n }\n\n /**\n * Apply animation receiver transform on a video receiver\n * @internal\n */\n private applyAnimationReceiverTransform(receiver: RTCRtpReceiver, mediaTrack: MediaStreamTrack): void {\n if (mediaTrack.kind !== 'video') return;\n if (this.transformedReceivers.has(receiver)) return;\n\n const method = getInsertableStreamsMethod();\n if (method !== 'scriptTransform') return;\n if (receiver.transform) return;\n if (!this.animationCallbacks) return;\n\n this.transformedReceivers.add(receiver);\n\n try {\n const extractor = new VP8Extractor();\n this.vp8Extractors.set(receiver, extractor);\n this.vp8Extractor = extractor;\n \n extractor.initialize(receiver, this.animationCallbacks).catch((err) => {\n logger.error('LiveKit', 'Failed to initialize VP8Extractor:', err);\n this.transformedReceivers.delete(receiver);\n this.vp8Extractors.delete(receiver);\n });\n } catch (e) {\n logger.error('LiveKit', 'Failed to apply transform:', e);\n this.transformedReceivers.delete(receiver);\n this.vp8Extractors.delete(receiver);\n }\n }\n\n /**\n * Add one-time user gesture listeners to recover from autoplay blocking.\n * @internal\n */\n private addAudioUnlockListeners(): void {\n if (typeof window === 'undefined' || this.awaitingAudioUnlock) {\n return;\n }\n\n this.awaitingAudioUnlock = true;\n for (const eventName of this.audioUnlockEventNames) {\n window.addEventListener(eventName, this.handleAudioUnlockGesture, {\n capture: true,\n passive: true,\n });\n }\n\n logger.warn('LiveKit', 'Audio playback blocked by autoplay policy, waiting for user interaction');\n }\n\n /**\n * Remove user gesture listeners after playback is unlocked.\n * @internal\n */\n private removeAudioUnlockListeners(): void {\n if (typeof window === 'undefined' || !this.awaitingAudioUnlock) {\n return;\n }\n\n this.awaitingAudioUnlock = false;\n for (const eventName of this.audioUnlockEventNames) {\n window.removeEventListener(eventName, this.handleAudioUnlockGesture, true);\n }\n }\n\n /**\n * Detect browser autoplay blocking errors from audio play/start calls.\n * @internal\n */\n private isAutoplayBlockedError(error: unknown): boolean {\n if (typeof DOMException !== 'undefined' && error instanceof DOMException) {\n return error.name === 'NotAllowedError';\n }\n\n if (error instanceof Error) {\n return (\n error.name === 'NotAllowedError'\n || /didn't interact|user didn't interact|NotAllowedError/i.test(error.message)\n );\n }\n\n return false;\n }\n\n /**\n * Try to resume all attached remote audio elements.\n * @returns true if at least one element is still blocked by autoplay policy\n * @internal\n */\n private async resumeAttachedAudioElements(reason: string): Promise<boolean> {\n let hasAutoplayBlockedElement = false;\n\n for (const [participantIdentity, audioElement] of this.audioElements) {\n if (!audioElement.paused) {\n continue;\n }\n\n try {\n await audioElement.play();\n } catch (error) {\n if (this.isAutoplayBlockedError(error)) {\n hasAutoplayBlockedElement = true;\n }\n logger.warn(\n 'LiveKit',\n `Failed to resume remote audio element (${reason}) for ${participantIdentity}:`,\n error\n );\n }\n }\n\n return hasAutoplayBlockedElement;\n }\n\n /**\n * Ensure remote audio playback is unlocked and resumed.\n * @internal\n */\n private async ensureAudioPlaybackUnlocked(reason: string): Promise<void> {\n if (!this.room) {\n return;\n }\n\n const roomWithAudioControl = this.room as LiveKitRoom & {\n startAudio?: () => Promise<void>;\n canPlaybackAudio: boolean;\n };\n\n if (!roomWithAudioControl.canPlaybackAudio && typeof roomWithAudioControl.startAudio === 'function') {\n try {\n await roomWithAudioControl.startAudio();\n logger.info('LiveKit', `Audio context resumed (${reason})`);\n } catch (error) {\n if (this.isAutoplayBlockedError(error)) {\n this.addAudioUnlockListeners();\n } else {\n logger.warn('LiveKit', `Failed to call room.startAudio() (${reason}):`, error);\n }\n return;\n }\n }\n\n const hasAutoplayBlockedElement = await this.resumeAttachedAudioElements(reason);\n if (roomWithAudioControl.canPlaybackAudio && !hasAutoplayBlockedElement) {\n this.removeAudioUnlockListeners();\n } else {\n this.addAudioUnlockListeners();\n }\n }\n\n\n async connect(config: RTCConnectionConfig): Promise<void> {\n logger.info('LiveKit', 'connect() called with config:', {\n hasUrl: 'url' in config,\n hasRoomName: 'roomName' in config,\n hasToken: 'token' in config,\n config,\n });\n\n if (!isLiveKitConfig(config)) {\n logger.error('LiveKit', 'Config validation failed - missing url or roomName');\n throw new Error('LiveKitProvider requires url and roomName in connection config');\n }\n\n const livekitConfig: LiveKitConnectionConfig = config;\n logger.info('LiveKit', 'Config validated, connecting to:', livekitConfig.url);\n\n this.removeAudioUnlockListeners();\n\n const livekit = await this.loadSDK();\n logger.info('LiveKit', 'SDK loaded successfully');\n const { Room } = livekit;\n\n this.room = new Room({\n videoCaptureDefaults: undefined,\n audioCaptureDefaults: undefined,\n });\n\n this.setConnectionState('connecting');\n\n // Setup event listeners\n this.setupEventListeners(livekit);\n\n try {\n logger.info('LiveKit', 'Attempting to connect to room...');\n await this.room.connect(livekitConfig.url, livekitConfig.token);\n logger.info('LiveKit', 'Room connected, state:', this.room.state);\n logger.info('LiveKit', 'Room name:', this.room.name);\n logger.info('LiveKit', 'Local participant:', this.room.localParticipant?.identity);\n logger.info('LiveKit', 'Remote participants:', this.room.remoteParticipants.size);\n \n // Log existing participants and their tracks\n this.room.remoteParticipants.forEach((participant: any, sid: string) => {\n logger.info('LiveKit', `Remote participant: ${participant.identity}, sid: ${sid}`);\n participant.trackPublications.forEach((pub: any, trackSid: string) => {\n logger.info('LiveKit', ` Track: ${pub.trackName}, kind: ${pub.kind}, subscribed: ${pub.isSubscribed}, sid: ${trackSid}`);\n });\n });\n\n this.setConnectionState(this.mapLiveKitConnectionState(livekit, this.room.state));\n } catch (error) {\n logger.error('LiveKit', 'Connection failed:', error);\n this.setConnectionState('failed');\n this.emit('error', error as Error);\n throw error;\n }\n }\n\n /**\n * Setup LiveKit room event listeners\n * @internal\n */\n private setupEventListeners(livekit: LiveKitModule): void {\n if (!this.room) return;\n \n const room = this.room;\n const { RoomEvent, Track } = livekit;\n\n // Connected\n room.on(RoomEvent.Connected, () => {\n this.setConnectionState('connected');\n this.emit('connected');\n\n const roomWithAudioControl = room as LiveKitRoom & { canPlaybackAudio: boolean };\n if (!roomWithAudioControl.canPlaybackAudio) {\n this.addAudioUnlockListeners();\n }\n });\n\n // Participant connected\n room.on(RoomEvent.ParticipantConnected, (participant: any) => {\n logger.info('LiveKit', `Participant connected: ${participant.identity}`);\n });\n\n // Participant disconnected\n room.on(RoomEvent.ParticipantDisconnected, (participant: any) => {\n logger.warn('LiveKit', `Participant disconnected: ${participant.identity}`);\n });\n\n // Track published\n room.on(RoomEvent.TrackPublished, (publication: any, participant: any) => {\n logger.info('LiveKit', 'TrackPublished:', {\n trackName: publication.trackName,\n kind: publication.kind,\n participant: participant.identity,\n });\n });\n\n // Disconnected\n room.on(RoomEvent.Disconnected, () => {\n this.cleanup();\n this.setConnectionState('disconnected');\n this.emit('disconnected');\n });\n\n room.on(RoomEvent.AudioPlaybackStatusChanged, (canPlaybackAudio: boolean) => {\n logger.info('LiveKit', `Audio playback status changed: ${canPlaybackAudio ? 'enabled' : 'blocked'}`);\n\n if (canPlaybackAudio) {\n void this.ensureAudioPlaybackUnlocked('audio-playback-status-changed');\n } else {\n this.addAudioUnlockListeners();\n }\n });\n\n // Track subscribed\n this.room.on(\n RoomEvent.TrackSubscribed,\n (track: any, publication: any, participant: any) => {\n logger.info('LiveKit', 'TrackSubscribed:', {\n kind: track.kind,\n trackName: publication.trackName,\n participant: participant.identity,\n });\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const mediaStreamTrack = (track as any).mediaStreamTrack as MediaStreamTrack | undefined;\n const trackName = publication.trackName;\n\n if (track.kind === Track.Kind.Audio) {\n logger.info('LiveKit', 'Audio track received, attaching...');\n // Audio track\n const audioElement = track.attach() as HTMLAudioElement;\n audioElement.id = `audio-${participant.identity}`;\n audioElement.autoplay = true;\n audioElement.controls = false;\n audioElement.style.display = 'none';\n\n const previousAudioElement = this.audioElements.get(participant.identity);\n if (previousAudioElement && previousAudioElement !== audioElement) {\n previousAudioElement.remove();\n }\n \n if (typeof document !== 'undefined') {\n document.body.appendChild(audioElement);\n logger.info('LiveKit', 'Audio element appended to body');\n }\n \n this.audioElements.set(participant.identity, audioElement);\n void this.ensureAudioPlaybackUnlocked('audio-track-subscribed');\n\n if (this.audioCallbacks) {\n this.audioCallbacks.onAudioReceived?.(participant);\n }\n } else if (track.kind === Track.Kind.Video) {\n logger.info('LiveKit', 'Video track received, setting up animation transform...');\n // Video track - apply animation transform\n if (mediaStreamTrack) {\n const receiver = this.findVideoReceiverByTrackId(mediaStreamTrack.id);\n if (receiver) {\n this.receiverParticipantMap.set(receiver, { participant, trackName });\n \n // Apply transform if not already applied in handleEarlyTrack\n if (!receiver.transform || !this.transformedReceivers.has(receiver)) {\n this.applyAnimationReceiverTransform(receiver, mediaStreamTrack);\n }\n }\n }\n\n // Attach to hidden video element to trigger data flow\n const videoElement = track.attach() as HTMLVideoElement;\n videoElement.id = `video-animation-${participant.identity}`;\n videoElement.muted = true;\n videoElement.autoplay = true;\n videoElement.style.display = 'none';\n if (typeof document !== 'undefined') {\n document.body.appendChild(videoElement);\n videoElement.play().catch(() => {});\n }\n }\n }\n );\n\n // Track unsubscribed\n this.room.on(\n RoomEvent.TrackUnsubscribed,\n (track: any, _publication: any, participant: any) => {\n if (track.kind === Track.Kind.Audio) {\n track.detach().forEach((el: HTMLElement) => el.remove());\n this.audioElements.delete(participant.identity);\n \n if (this.audioCallbacks) {\n this.audioCallbacks.onAudioLost?.(participant);\n }\n }\n }\n );\n\n // Connection state changed\n room.on(RoomEvent.ConnectionStateChanged, (state: unknown) => {\n this.setConnectionState(this.mapLiveKitConnectionState(livekit, state));\n });\n }\n\n async disconnect(): Promise<void> {\n this.cleanup();\n if (this.room) {\n this.room.disconnect();\n this.room = null;\n }\n this.setConnectionState('disconnected');\n globalLiveKitProviderRegistry.delete(this);\n }\n\n getConnectionState(): string {\n if (!this.room) {\n return 'disconnected';\n }\n return this.connectionState;\n }\n\n /** @internal */\n async subscribeAnimationTrack(callbacks: AnimationTrackCallbacks): Promise<void> {\n this.animationCallbacks = callbacks;\n \n // If room is already connected, check for existing tracks\n if (this.room) {\n const livekit = await this.loadSDK();\n const { ConnectionState: LKConnectionState } = livekit;\n \n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const roomState = (this.room as any).state;\n \n if (roomState === LKConnectionState.Connected || roomState === 'connected') {\n // Check for existing video tracks that might be animation tracks\n const remoteParticipants = this.room.remoteParticipants.values();\n for (const participant of remoteParticipants) {\n // Get video track publications (handle different SDK versions)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const pubs = (participant as any).videoTrackPublications ?? (participant as any).trackPublications;\n if (pubs && typeof pubs.values === 'function') {\n for (const publication of pubs.values()) {\n if (publication.trackName?.includes('animation') && publication.track) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const mediaStreamTrack = (publication.track as any).mediaStreamTrack as MediaStreamTrack | undefined;\n if (mediaStreamTrack) {\n const receiver = this.findVideoReceiverByTrackId(mediaStreamTrack.id);\n if (receiver) {\n this.applyAnimationReceiverTransform(receiver, mediaStreamTrack);\n }\n }\n }\n }\n }\n }\n }\n }\n }\n\n /** @internal */\n async unsubscribeAnimationTrack(): Promise<void> {\n this.animationCallbacks = null;\n \n // Dispose all extractors\n this.vp8Extractors.forEach((extractor) => {\n extractor.dispose();\n });\n this.vp8Extractors.clear();\n \n if (this.vp8Extractor) {\n this.vp8Extractor.dispose();\n this.vp8Extractor = null;\n }\n this.transformedReceivers.clear();\n this.receiverParticipantMap.clear();\n }\n\n /** @internal */\n async subscribeAudioTrack(callbacks: AudioTrackCallbacks): Promise<void> {\n this.audioCallbacks = callbacks;\n // Audio tracks are automatically handled in TrackSubscribed event\n }\n\n /** @internal */\n async unsubscribeAudioTrack(): Promise<void> {\n this.audioCallbacks = null;\n this.audioElements.forEach((el) => {\n el.remove();\n });\n this.audioElements.clear();\n }\n\n async publishAudioTrack(track: MediaStreamTrack): Promise<void> {\n if (!this.room) {\n throw new Error('Not connected to room');\n }\n\n try {\n const livekit = await this.loadSDK();\n const { createLocalAudioTrack, Track: TrackEnum } = livekit;\n\n // If track is provided, use it; otherwise create from microphone\n if (track) {\n await this.room.localParticipant.publishTrack(track, {\n name: 'user-microphone',\n source: TrackEnum.Source.Microphone,\n red: false,\n });\n } else {\n // Create and publish microphone track\n const localTrack = await createLocalAudioTrack({\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n });\n \n this.localAudioTrack = localTrack;\n \n await this.room.localParticipant.publishTrack(localTrack, {\n name: 'user-microphone',\n source: TrackEnum.Source.Microphone,\n red: false,\n });\n }\n\n this.isMicrophoneEnabled = true;\n } catch (error) {\n logger.error('LiveKit', 'Failed to publish audio track:', error);\n this.isMicrophoneEnabled = false;\n throw error;\n }\n }\n\n async unpublishAudioTrack(): Promise<void> {\n if (!this.room || !this.localAudioTrack) {\n return;\n }\n\n await this.room.localParticipant.unpublishTrack(this.localAudioTrack);\n this.localAudioTrack.stop();\n this.localAudioTrack = null;\n this.isMicrophoneEnabled = false;\n }\n\n /**\n * Get the native LiveKit Room instance.\n * \n * Allows advanced users to access LiveKit-specific features\n * not exposed through the unified API.\n * \n * @returns The LiveKit Room instance, or null if not connected\n * \n * @example\n * ```typescript\n * const room = provider.getNativeClient();\n * if (room) {\n * // Access LiveKit-specific features\n * console.log('Participants:', room.remoteParticipants.size);\n * }\n * ```\n */\n getNativeClient(): LiveKitRoom | null {\n return this.room;\n }\n\n /**\n * Cleanup resources\n * @internal\n */\n private cleanup(): void {\n this.removeAudioUnlockListeners();\n\n // Cleanup audio elements\n this.audioElements.forEach((el) => {\n el.remove();\n });\n this.audioElements.clear();\n \n // Dispose all extractors\n this.vp8Extractors.forEach((extractor) => extractor.dispose());\n this.vp8Extractors.clear();\n this.vp8Extractor = null;\n \n this.transformedReceivers.clear();\n this.receiverParticipantMap.clear();\n\n // Stop local audio track\n if (this.localAudioTrack) {\n this.localAudioTrack.stop();\n this.localAudioTrack = null;\n }\n this.isMicrophoneEnabled = false;\n }\n}\n"],"names":["_a","_b"],"mappings":"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"}
|