@spatialwalk/avatarkit-rtc 1.0.0-beta.7 → 1.0.0-beta.9
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/README.md +2 -571
- package/dist/assets/animation-worker-DOGeTjF0.js.map +1 -0
- package/dist/core/AvatarPlayer.d.ts +8 -3
- package/dist/core/AvatarPlayer.d.ts.map +1 -1
- package/dist/core/RTCProvider.d.ts +12 -5
- package/dist/core/RTCProvider.d.ts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index10.js +8 -2
- package/dist/index10.js.map +1 -1
- package/dist/index11.js.map +1 -1
- package/dist/index12.js +9 -5
- package/dist/index12.js.map +1 -1
- package/dist/index14.js.map +1 -1
- package/dist/index15.js +1 -1
- package/dist/index2.js +40 -17
- package/dist/index2.js.map +1 -1
- package/dist/index3.js +138 -39
- package/dist/index3.js.map +1 -1
- package/dist/index4.js +99 -68
- package/dist/index4.js.map +1 -1
- package/dist/index5.js +6 -2
- package/dist/index5.js.map +1 -1
- package/dist/index6.js +78 -18
- package/dist/index6.js.map +1 -1
- package/dist/index8.js +4 -1
- package/dist/index8.js.map +1 -1
- package/dist/index9.js +5 -1
- package/dist/index9.js.map +1 -1
- package/dist/providers/agora/AgoraProvider.d.ts.map +1 -1
- package/dist/providers/agora/types.d.ts.map +1 -1
- package/dist/providers/base/BaseProvider.d.ts +9 -13
- package/dist/providers/base/BaseProvider.d.ts.map +1 -1
- package/dist/providers/livekit/LiveKitProvider.d.ts +4 -2
- package/dist/providers/livekit/LiveKitProvider.d.ts.map +1 -1
- package/dist/providers/livekit/animation-worker.d.ts.map +1 -1
- package/dist/types/index.d.ts +21 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +14 -5
- package/dist/assets/animation-worker-CUXZycUw.js.map +0 -1
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 /** @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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"}
|
|
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, RTCPrepareConnectionConfig } from '../types';\nimport { AnimationHandler } from './AnimationHandler';\nimport type { AnimationTrackCallbacks } 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<(...args: unknown[]) => void>> =\n 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 * Pre-warm a future RTC connection when the provider supports it.\n * This is best-effort and does not change the connected state.\n */\n async prepareConnection(config: RTCPrepareConnectionConfig): Promise<void> {\n await this.provider.prepareConnection?.(config);\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({\n audio: true,\n });\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: (...args: unknown[]) => void): 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, handler);\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: (...args: unknown[]) => void): void {\n if (event === 'stalled') {\n this.eventHandlers.get(event)?.delete(handler);\n } else {\n this.provider.off(event, handler);\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(\n 'Cannot reconnect: no previous connection. Call connect() first.',\n );\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(\n protobufData,\n transitionFrameCount,\n );\n },\n onTransitionEnd: (protobufData, transitionFrameCount) => {\n this.animationHandler.handleTransitionToIdle(\n protobufData,\n transitionFrameCount,\n );\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 // Keep session bookkeeping in sync even if visual idle is deferred.\n this.hasActiveAnimationSession = false;\n this.isTrackingPrimed = false;\n logger.info(\n 'AvatarPlayer',\n 'Deferring idleStart while transition is active',\n );\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 (\n stats.framesLost > 0 ||\n stats.framesRecovered > 0 ||\n stats.framesDropped > 0\n ) {\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
|
@@ -2,7 +2,7 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
import { BaseProvider } from "./index9.js";
|
|
5
|
-
import { isLiveKitConfig } from "./index5.js";
|
|
5
|
+
import { isLiveKitPrepareConfig, isLiveKitConfig } from "./index5.js";
|
|
6
6
|
import { VP8Extractor } from "./index10.js";
|
|
7
7
|
import { getInsertableStreamsMethod } from "./index11.js";
|
|
8
8
|
import { logger } from "./index7.js";
|
|
@@ -29,11 +29,19 @@ function patchRTCPeerConnection() {
|
|
|
29
29
|
listener.handleEvent(event);
|
|
30
30
|
}
|
|
31
31
|
};
|
|
32
|
-
return originalAddEventListener.call(
|
|
32
|
+
return originalAddEventListener.call(
|
|
33
|
+
this,
|
|
34
|
+
type,
|
|
35
|
+
wrappedListener,
|
|
36
|
+
options
|
|
37
|
+
);
|
|
33
38
|
}
|
|
34
39
|
return originalAddEventListener.call(this, type, listener, options);
|
|
35
40
|
};
|
|
36
|
-
const originalOnTrackDescriptor = Object.getOwnPropertyDescriptor(
|
|
41
|
+
const originalOnTrackDescriptor = Object.getOwnPropertyDescriptor(
|
|
42
|
+
RTCPeerConnection.prototype,
|
|
43
|
+
"ontrack"
|
|
44
|
+
);
|
|
37
45
|
if (originalOnTrackDescriptor) {
|
|
38
46
|
Object.defineProperty(RTCPeerConnection.prototype, "ontrack", {
|
|
39
47
|
get: originalOnTrackDescriptor.get,
|
|
@@ -104,6 +112,13 @@ class LiveKitProvider extends BaseProvider {
|
|
|
104
112
|
__publicField(this, "isMicrophoneEnabled", false);
|
|
105
113
|
globalLiveKitProviderRegistry.add(this);
|
|
106
114
|
}
|
|
115
|
+
createRoom(livekit) {
|
|
116
|
+
const { Room } = livekit;
|
|
117
|
+
return new Room({
|
|
118
|
+
videoCaptureDefaults: void 0,
|
|
119
|
+
audioCaptureDefaults: void 0
|
|
120
|
+
});
|
|
121
|
+
}
|
|
107
122
|
/**
|
|
108
123
|
* Load LiveKit SDK dynamically.
|
|
109
124
|
* @internal
|
|
@@ -115,12 +130,31 @@ class LiveKitProvider extends BaseProvider {
|
|
|
115
130
|
try {
|
|
116
131
|
this.livekitSDK = await import("livekit-client");
|
|
117
132
|
return this.livekitSDK;
|
|
118
|
-
} catch
|
|
133
|
+
} catch {
|
|
119
134
|
throw new Error(
|
|
120
135
|
"❌ Failed to load livekit-client.\nPlease ensure it is installed: pnpm add livekit-client"
|
|
121
136
|
);
|
|
122
137
|
}
|
|
123
138
|
}
|
|
139
|
+
async prepareConnection(config) {
|
|
140
|
+
logger.info("LiveKit", "prepareConnection() called with config:", {
|
|
141
|
+
hasUrl: "url" in config,
|
|
142
|
+
hasToken: "token" in config
|
|
143
|
+
});
|
|
144
|
+
if (!isLiveKitPrepareConfig(config)) {
|
|
145
|
+
throw new Error("LiveKitProvider requires url in prepareConnection config");
|
|
146
|
+
}
|
|
147
|
+
const livekit = await this.loadSDK();
|
|
148
|
+
if (!this.room) {
|
|
149
|
+
this.room = this.createRoom(livekit);
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
await this.room.prepareConnection(config.url, config.token);
|
|
153
|
+
logger.info("LiveKit", "prepareConnection() completed");
|
|
154
|
+
} catch (error) {
|
|
155
|
+
logger.warn("LiveKit", "prepareConnection() failed (non-fatal):", error);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
124
158
|
/**
|
|
125
159
|
* Map LiveKit connection state to string.
|
|
126
160
|
* @internal
|
|
@@ -296,7 +330,10 @@ class LiveKitProvider extends BaseProvider {
|
|
|
296
330
|
passive: true
|
|
297
331
|
});
|
|
298
332
|
}
|
|
299
|
-
logger.warn(
|
|
333
|
+
logger.warn(
|
|
334
|
+
"LiveKit",
|
|
335
|
+
"Audio playback blocked by autoplay policy, waiting for user interaction"
|
|
336
|
+
);
|
|
300
337
|
}
|
|
301
338
|
/**
|
|
302
339
|
* Remove user gesture listeners after playback is unlocked.
|
|
@@ -308,7 +345,11 @@ class LiveKitProvider extends BaseProvider {
|
|
|
308
345
|
}
|
|
309
346
|
this.awaitingAudioUnlock = false;
|
|
310
347
|
for (const eventName of this.audioUnlockEventNames) {
|
|
311
|
-
window.removeEventListener(
|
|
348
|
+
window.removeEventListener(
|
|
349
|
+
eventName,
|
|
350
|
+
this.handleAudioUnlockGesture,
|
|
351
|
+
true
|
|
352
|
+
);
|
|
312
353
|
}
|
|
313
354
|
}
|
|
314
355
|
/**
|
|
@@ -320,7 +361,9 @@ class LiveKitProvider extends BaseProvider {
|
|
|
320
361
|
return error.name === "NotAllowedError";
|
|
321
362
|
}
|
|
322
363
|
if (error instanceof Error) {
|
|
323
|
-
return error.name === "NotAllowedError" || /didn't interact|user didn't interact|NotAllowedError/i.test(
|
|
364
|
+
return error.name === "NotAllowedError" || /didn't interact|user didn't interact|NotAllowedError/i.test(
|
|
365
|
+
error.message
|
|
366
|
+
);
|
|
324
367
|
}
|
|
325
368
|
return false;
|
|
326
369
|
}
|
|
@@ -367,7 +410,11 @@ class LiveKitProvider extends BaseProvider {
|
|
|
367
410
|
if (this.isAutoplayBlockedError(error)) {
|
|
368
411
|
this.addAudioUnlockListeners();
|
|
369
412
|
} else {
|
|
370
|
-
logger.warn(
|
|
413
|
+
logger.warn(
|
|
414
|
+
"LiveKit",
|
|
415
|
+
`Failed to call room.startAudio() (${reason}):`,
|
|
416
|
+
error
|
|
417
|
+
);
|
|
371
418
|
}
|
|
372
419
|
return;
|
|
373
420
|
}
|
|
@@ -388,19 +435,26 @@ class LiveKitProvider extends BaseProvider {
|
|
|
388
435
|
config
|
|
389
436
|
});
|
|
390
437
|
if (!isLiveKitConfig(config)) {
|
|
391
|
-
logger.error(
|
|
392
|
-
|
|
438
|
+
logger.error(
|
|
439
|
+
"LiveKit",
|
|
440
|
+
"Config validation failed - missing url or roomName"
|
|
441
|
+
);
|
|
442
|
+
throw new Error(
|
|
443
|
+
"LiveKitProvider requires url and roomName in connection config"
|
|
444
|
+
);
|
|
393
445
|
}
|
|
394
446
|
const livekitConfig = config;
|
|
395
|
-
logger.info(
|
|
447
|
+
logger.info(
|
|
448
|
+
"LiveKit",
|
|
449
|
+
"Config validated, connecting to:",
|
|
450
|
+
livekitConfig.url
|
|
451
|
+
);
|
|
396
452
|
this.removeAudioUnlockListeners();
|
|
397
453
|
const livekit = await this.loadSDK();
|
|
398
454
|
logger.info("LiveKit", "SDK loaded successfully");
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
audioCaptureDefaults: void 0
|
|
403
|
-
});
|
|
455
|
+
if (!this.room) {
|
|
456
|
+
this.room = this.createRoom(livekit);
|
|
457
|
+
}
|
|
404
458
|
this.setConnectionState("connecting");
|
|
405
459
|
this.setupEventListeners(livekit);
|
|
406
460
|
try {
|
|
@@ -408,15 +462,31 @@ class LiveKitProvider extends BaseProvider {
|
|
|
408
462
|
await this.room.connect(livekitConfig.url, livekitConfig.token);
|
|
409
463
|
logger.info("LiveKit", "Room connected, state:", this.room.state);
|
|
410
464
|
logger.info("LiveKit", "Room name:", this.room.name);
|
|
411
|
-
logger.info(
|
|
412
|
-
|
|
465
|
+
logger.info(
|
|
466
|
+
"LiveKit",
|
|
467
|
+
"Local participant:",
|
|
468
|
+
(_a = this.room.localParticipant) == null ? void 0 : _a.identity
|
|
469
|
+
);
|
|
470
|
+
logger.info(
|
|
471
|
+
"LiveKit",
|
|
472
|
+
"Remote participants:",
|
|
473
|
+
this.room.remoteParticipants.size
|
|
474
|
+
);
|
|
413
475
|
this.room.remoteParticipants.forEach((participant, sid) => {
|
|
414
|
-
logger.info(
|
|
476
|
+
logger.info(
|
|
477
|
+
"LiveKit",
|
|
478
|
+
`Remote participant: ${participant.identity}, sid: ${sid}`
|
|
479
|
+
);
|
|
415
480
|
participant.trackPublications.forEach((pub, trackSid) => {
|
|
416
|
-
logger.info(
|
|
481
|
+
logger.info(
|
|
482
|
+
"LiveKit",
|
|
483
|
+
` Track: ${pub.trackName}, kind: ${pub.kind}, subscribed: ${pub.isSubscribed}, sid: ${trackSid}`
|
|
484
|
+
);
|
|
417
485
|
});
|
|
418
486
|
});
|
|
419
|
-
this.setConnectionState(
|
|
487
|
+
this.setConnectionState(
|
|
488
|
+
this.mapLiveKitConnectionState(livekit, this.room.state)
|
|
489
|
+
);
|
|
420
490
|
} catch (error) {
|
|
421
491
|
logger.error("LiveKit", "Connection failed:", error);
|
|
422
492
|
this.setConnectionState("failed");
|
|
@@ -444,7 +514,10 @@ class LiveKitProvider extends BaseProvider {
|
|
|
444
514
|
logger.info("LiveKit", `Participant connected: ${participant.identity}`);
|
|
445
515
|
});
|
|
446
516
|
room.on(RoomEvent.ParticipantDisconnected, (participant) => {
|
|
447
|
-
logger.warn(
|
|
517
|
+
logger.warn(
|
|
518
|
+
"LiveKit",
|
|
519
|
+
`Participant disconnected: ${participant.identity}`
|
|
520
|
+
);
|
|
448
521
|
});
|
|
449
522
|
room.on(RoomEvent.TrackPublished, (publication, participant) => {
|
|
450
523
|
logger.info("LiveKit", "TrackPublished:", {
|
|
@@ -458,14 +531,22 @@ class LiveKitProvider extends BaseProvider {
|
|
|
458
531
|
this.setConnectionState("disconnected");
|
|
459
532
|
this.emit("disconnected");
|
|
460
533
|
});
|
|
461
|
-
room.on(
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
534
|
+
room.on(
|
|
535
|
+
RoomEvent.AudioPlaybackStatusChanged,
|
|
536
|
+
(canPlaybackAudio) => {
|
|
537
|
+
logger.info(
|
|
538
|
+
"LiveKit",
|
|
539
|
+
`Audio playback status changed: ${canPlaybackAudio ? "enabled" : "blocked"}`
|
|
540
|
+
);
|
|
541
|
+
if (canPlaybackAudio) {
|
|
542
|
+
void this.ensureAudioPlaybackUnlocked(
|
|
543
|
+
"audio-playback-status-changed"
|
|
544
|
+
);
|
|
545
|
+
} else {
|
|
546
|
+
this.addAudioUnlockListeners();
|
|
547
|
+
}
|
|
467
548
|
}
|
|
468
|
-
|
|
549
|
+
);
|
|
469
550
|
this.room.on(
|
|
470
551
|
RoomEvent.TrackSubscribed,
|
|
471
552
|
(track, publication, participant) => {
|
|
@@ -484,7 +565,9 @@ class LiveKitProvider extends BaseProvider {
|
|
|
484
565
|
audioElement.autoplay = true;
|
|
485
566
|
audioElement.controls = false;
|
|
486
567
|
audioElement.style.display = "none";
|
|
487
|
-
const previousAudioElement = this.audioElements.get(
|
|
568
|
+
const previousAudioElement = this.audioElements.get(
|
|
569
|
+
participant.identity
|
|
570
|
+
);
|
|
488
571
|
if (previousAudioElement && previousAudioElement !== audioElement) {
|
|
489
572
|
previousAudioElement.remove();
|
|
490
573
|
}
|
|
@@ -498,13 +581,24 @@ class LiveKitProvider extends BaseProvider {
|
|
|
498
581
|
(_b = (_a = this.audioCallbacks).onAudioReceived) == null ? void 0 : _b.call(_a, participant);
|
|
499
582
|
}
|
|
500
583
|
} else if (track.kind === Track.Kind.Video) {
|
|
501
|
-
logger.info(
|
|
584
|
+
logger.info(
|
|
585
|
+
"LiveKit",
|
|
586
|
+
"Video track received, setting up animation transform..."
|
|
587
|
+
);
|
|
502
588
|
if (mediaStreamTrack) {
|
|
503
|
-
const receiver = this.findVideoReceiverByTrackId(
|
|
589
|
+
const receiver = this.findVideoReceiverByTrackId(
|
|
590
|
+
mediaStreamTrack.id
|
|
591
|
+
);
|
|
504
592
|
if (receiver) {
|
|
505
|
-
this.receiverParticipantMap.set(receiver, {
|
|
593
|
+
this.receiverParticipantMap.set(receiver, {
|
|
594
|
+
participant,
|
|
595
|
+
trackName
|
|
596
|
+
});
|
|
506
597
|
if (!receiver.transform || !this.transformedReceivers.has(receiver)) {
|
|
507
|
-
this.applyAnimationReceiverTransform(
|
|
598
|
+
this.applyAnimationReceiverTransform(
|
|
599
|
+
receiver,
|
|
600
|
+
mediaStreamTrack
|
|
601
|
+
);
|
|
508
602
|
}
|
|
509
603
|
}
|
|
510
604
|
}
|
|
@@ -570,9 +664,14 @@ class LiveKitProvider extends BaseProvider {
|
|
|
570
664
|
if (((_a = publication.trackName) == null ? void 0 : _a.includes("animation")) && publication.track) {
|
|
571
665
|
const mediaStreamTrack = publication.track.mediaStreamTrack;
|
|
572
666
|
if (mediaStreamTrack) {
|
|
573
|
-
const receiver = this.findVideoReceiverByTrackId(
|
|
667
|
+
const receiver = this.findVideoReceiverByTrackId(
|
|
668
|
+
mediaStreamTrack.id
|
|
669
|
+
);
|
|
574
670
|
if (receiver) {
|
|
575
|
-
this.applyAnimationReceiverTransform(
|
|
671
|
+
this.applyAnimationReceiverTransform(
|
|
672
|
+
receiver,
|
|
673
|
+
mediaStreamTrack
|
|
674
|
+
);
|
|
576
675
|
}
|
|
577
676
|
}
|
|
578
677
|
}
|
|
@@ -652,12 +751,12 @@ class LiveKitProvider extends BaseProvider {
|
|
|
652
751
|
}
|
|
653
752
|
/**
|
|
654
753
|
* Get the native LiveKit Room instance.
|
|
655
|
-
*
|
|
754
|
+
*
|
|
656
755
|
* Allows advanced users to access LiveKit-specific features
|
|
657
756
|
* not exposed through the unified API.
|
|
658
|
-
*
|
|
757
|
+
*
|
|
659
758
|
* @returns The LiveKit Room instance, or null if not connected
|
|
660
|
-
*
|
|
759
|
+
*
|
|
661
760
|
* @example
|
|
662
761
|
* ```typescript
|
|
663
762
|
* const room = provider.getNativeClient();
|