@spatialwalk/avatarkit 1.0.0-beta.15 → 1.0.0-beta.17
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/CHANGELOG.md +41 -1
- package/README.md +124 -78
- package/dist/{StreamingAudioPlayer-Dj1w6HZz.js → StreamingAudioPlayer-a8MwHQ3Q.js} +20 -21
- package/dist/StreamingAudioPlayer-a8MwHQ3Q.js.map +1 -0
- package/dist/audio/StreamingAudioPlayer.d.ts.map +1 -1
- package/dist/avatar_core_wasm-BPIbbUx_.js +1664 -0
- package/dist/avatar_core_wasm-BPIbbUx_.js.map +1 -0
- package/dist/avatar_core_wasm.wasm +0 -0
- package/dist/core/AvatarController.d.ts +69 -9
- package/dist/core/AvatarController.d.ts.map +1 -1
- package/dist/core/AvatarDownloader.d.ts +1 -11
- package/dist/core/AvatarDownloader.d.ts.map +1 -1
- package/dist/core/AvatarKit.d.ts +5 -0
- package/dist/core/AvatarKit.d.ts.map +1 -1
- package/dist/core/AvatarManager.d.ts +0 -1
- package/dist/core/AvatarManager.d.ts.map +1 -1
- package/dist/core/AvatarView.d.ts +22 -6
- package/dist/core/AvatarView.d.ts.map +1 -1
- package/dist/{index-Bh6KkhoR.js → index-suaZGA5u.js} +1912 -1564
- package/dist/index-suaZGA5u.js.map +1 -0
- package/dist/index.js +9 -8
- package/dist/renderer/webgl/reorderData.d.ts.map +1 -1
- package/dist/types/index.d.ts +8 -9
- package/dist/types/index.d.ts.map +1 -1
- package/dist/vanilla/vite.config.d.ts.map +1 -1
- package/dist/wasm/avatarCoreAdapter.d.ts +58 -9
- package/dist/wasm/avatarCoreAdapter.d.ts.map +1 -1
- package/dist/wasm/avatarCoreMemory.d.ts +5 -1
- package/dist/wasm/avatarCoreMemory.d.ts.map +1 -1
- package/package.json +4 -2
- package/dist/StreamingAudioPlayer-Dj1w6HZz.js.map +0 -1
- package/dist/avatar_core_wasm-D-1WNg4B.js +0 -1676
- package/dist/avatar_core_wasm-D-1WNg4B.js.map +0 -1
- package/dist/index-Bh6KkhoR.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,46 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.0.0-beta.17] - 2025-01-24
|
|
9
|
+
|
|
10
|
+
### ✨ New Features
|
|
11
|
+
- **Audio-Only Fallback Mechanism** - SDK now includes automatic fallback to audio-only playback when animation data is unavailable
|
|
12
|
+
- Network mode: Automatically enters audio-only mode when server returns an error
|
|
13
|
+
- Host mode: Automatically enters audio-only mode when empty animation data is provided
|
|
14
|
+
- Once in audio-only mode, subsequent animation data for that session is ignored
|
|
15
|
+
- Fallback mode is interruptible, just like normal playback mode
|
|
16
|
+
|
|
17
|
+
### 🔧 API Changes
|
|
18
|
+
- **Playback Mode Configuration** - Moved playback mode configuration from `AvatarView` constructor to `AvatarKit.initialize()`
|
|
19
|
+
- Playback mode is now determined by `drivingServiceMode` in `AvatarKit.initialize()` configuration
|
|
20
|
+
- `AvatarView` constructor now only requires `avatar` and `container` parameters
|
|
21
|
+
- Removed `AvatarViewOptions` interface
|
|
22
|
+
- `container` parameter is now required (no longer optional)
|
|
23
|
+
|
|
24
|
+
### 🔧 Improvements
|
|
25
|
+
- Extended transition animation duration from 200ms to 400ms for smoother end-of-playback transitions
|
|
26
|
+
|
|
27
|
+
### 📚 Documentation
|
|
28
|
+
- Updated README.md to use "SDK mode" and "Host mode" terminology instead of "Network mode" and "External data mode"
|
|
29
|
+
- Added fallback mechanism documentation
|
|
30
|
+
- Updated API reference to reflect new constructor signature
|
|
31
|
+
|
|
32
|
+
## [1.0.0-beta.16] - 2025-11-21
|
|
33
|
+
|
|
34
|
+
### ✨ New Features
|
|
35
|
+
- **Multi-AvatarView Support** - SDK now supports multiple `AvatarView` instances simultaneously
|
|
36
|
+
- Each `AvatarView` instance can manage its own character independently
|
|
37
|
+
- Multiple characters can be displayed and controlled at the same time
|
|
38
|
+
- Each instance maintains its own rendering context, playback state, and network connection
|
|
39
|
+
- Removed the previous limitation of "only one AvatarView instance at a time"
|
|
40
|
+
|
|
41
|
+
### 📚 Documentation
|
|
42
|
+
- Updated README.md to reflect multi-character support capabilities
|
|
43
|
+
- Added multi-character usage examples
|
|
44
|
+
- Removed outdated limitation notes about single AvatarView instance
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
8
48
|
## [1.0.0-beta.15] - 2025-01-20
|
|
9
49
|
|
|
10
50
|
### ✨ New Features
|
|
@@ -158,7 +198,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
158
198
|
- **External Data Mode**:
|
|
159
199
|
- External components fully control audio and animation data acquisition
|
|
160
200
|
- SDK only responsible for synchronized playback of externally provided data
|
|
161
|
-
- Use `controller.
|
|
201
|
+
- Use `controller.playback()`, `controller.yieldAudioData()` and `controller.yieldFramesData()` methods
|
|
162
202
|
|
|
163
203
|
### ✨ New Features
|
|
164
204
|
|
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@ Real-time virtual avatar rendering SDK based on 3D Gaussian Splatting, supportin
|
|
|
6
6
|
|
|
7
7
|
- **3D Gaussian Splatting Rendering** - Based on the latest point cloud rendering technology, providing high-quality 3D virtual avatars
|
|
8
8
|
- **Audio-Driven Real-Time Animation Rendering** - Users provide audio data, SDK handles receiving animation data and rendering
|
|
9
|
+
- **Multi-Character Support** - Support multiple avatar instances simultaneously, each with independent state and rendering
|
|
9
10
|
- **WebGPU/WebGL Dual Rendering Backend** - Automatically selects the best rendering backend for compatibility
|
|
10
11
|
- **WASM High-Performance Computing** - Uses C++ compiled WebAssembly modules for geometric calculations
|
|
11
12
|
- **TypeScript Support** - Complete type definitions and IntelliSense
|
|
@@ -31,8 +32,13 @@ import {
|
|
|
31
32
|
} from '@spatialwalk/avatarkit'
|
|
32
33
|
|
|
33
34
|
// 1. Initialize SDK
|
|
35
|
+
import { DrivingServiceMode } from '@spatialwalk/avatarkit'
|
|
36
|
+
|
|
34
37
|
const configuration: Configuration = {
|
|
35
38
|
environment: Environment.test,
|
|
39
|
+
drivingServiceMode: DrivingServiceMode.sdk, // Optional, 'sdk' is default
|
|
40
|
+
// - DrivingServiceMode.sdk: SDK mode (network mode) - SDK handles WebSocket communication
|
|
41
|
+
// - DrivingServiceMode.host: Host mode (external data mode) - Host app provides audio and animation data
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
await AvatarKit.initialize('your-app-id', configuration)
|
|
@@ -47,12 +53,11 @@ const avatar = await avatarManager.load('character-id', (progress) => {
|
|
|
47
53
|
})
|
|
48
54
|
|
|
49
55
|
// 3. Create view (automatically creates Canvas and AvatarController)
|
|
50
|
-
//
|
|
56
|
+
// The playback mode is determined by drivingServiceMode in AvatarKit configuration
|
|
57
|
+
// - DrivingServiceMode.sdk: SDK mode (network mode) - SDK handles WebSocket communication
|
|
58
|
+
// - DrivingServiceMode.host: Host mode (external data mode) - Host app provides audio and animation data
|
|
51
59
|
const container = document.getElementById('avatar-container')
|
|
52
|
-
const avatarView = new AvatarView(avatar,
|
|
53
|
-
container: container,
|
|
54
|
-
playbackMode: 'network' // Optional, 'network' is default
|
|
55
|
-
})
|
|
60
|
+
const avatarView = new AvatarView(avatar, container)
|
|
56
61
|
|
|
57
62
|
// 4. Start real-time communication (network mode only)
|
|
58
63
|
await avatarView.avatarController.start()
|
|
@@ -75,21 +80,26 @@ import { AvatarPlaybackMode } from '@spatialwalk/avatarkit'
|
|
|
75
80
|
|
|
76
81
|
// 3. Create view with external data mode
|
|
77
82
|
const container = document.getElementById('avatar-container')
|
|
78
|
-
const avatarView = new AvatarView(avatar,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
const avatarView = new AvatarView(avatar, container)
|
|
84
|
+
|
|
85
|
+
// 4. Start playback with initial data (obtained from your service)
|
|
86
|
+
// Note: Audio and animation data should be obtained from your backend service
|
|
87
|
+
const initialAudioChunks = [{ data: audioData1, isLast: false }, { data: audioData2, isLast: false }]
|
|
88
|
+
const initialKeyframes = animationData1 // Animation keyframes from your service
|
|
82
89
|
|
|
83
90
|
// 4. Start playback with initial data (obtained from your service)
|
|
84
91
|
// Note: Audio and animation data should be obtained from your backend service
|
|
85
92
|
const initialAudioChunks = [{ data: audioData1, isLast: false }, { data: audioData2, isLast: false }]
|
|
86
93
|
const initialKeyframes = animationData1 // Animation keyframes from your service
|
|
87
94
|
|
|
88
|
-
|
|
95
|
+
// Step 1: Send audio first to get reqId (required for session management)
|
|
96
|
+
const reqId = await avatarView.avatarController.playback(initialAudioChunks, initialKeyframes)
|
|
89
97
|
|
|
90
98
|
// 5. Stream additional data as needed
|
|
91
|
-
|
|
92
|
-
avatarView.avatarController.
|
|
99
|
+
// Important: Always send audio first to get reqId, then use that reqId for animation data
|
|
100
|
+
const currentReqId = avatarView.avatarController.yieldAudioData(audioData3, false)
|
|
101
|
+
// Step 2: Use the reqId to send animation data (mismatched reqId will be discarded)
|
|
102
|
+
avatarView.avatarController.yieldFramesData(animationData2, currentReqId || reqId)
|
|
93
103
|
```
|
|
94
104
|
|
|
95
105
|
### Complete Examples
|
|
@@ -121,20 +131,33 @@ The SDK uses a three-layer architecture for clear separation of concerns:
|
|
|
121
131
|
|
|
122
132
|
### Playback Modes
|
|
123
133
|
|
|
124
|
-
The SDK supports two playback modes, configured
|
|
134
|
+
The SDK supports two playback modes, configured in `AvatarKit.initialize()`:
|
|
125
135
|
|
|
126
|
-
#### 1.
|
|
136
|
+
#### 1. SDK Mode (Default)
|
|
137
|
+
- Configured via `drivingServiceMode: DrivingServiceMode.sdk` in `AvatarKit.initialize()`
|
|
127
138
|
- SDK handles WebSocket communication automatically
|
|
128
139
|
- Send audio data via `AvatarController.send()`
|
|
129
140
|
- SDK receives animation data from backend and synchronizes playback
|
|
130
141
|
- Best for: Real-time audio input scenarios
|
|
131
142
|
|
|
132
|
-
#### 2.
|
|
133
|
-
-
|
|
134
|
-
-
|
|
143
|
+
#### 2. Host Mode
|
|
144
|
+
- Configured via `drivingServiceMode: DrivingServiceMode.host` in `AvatarKit.initialize()`
|
|
145
|
+
- Host application manages its own network/data fetching
|
|
146
|
+
- Host application provides both audio and animation data
|
|
135
147
|
- SDK only handles synchronized playback
|
|
136
148
|
- Best for: Custom data sources, pre-recorded content, or custom network implementations
|
|
137
149
|
|
|
150
|
+
**Note:** The playback mode is determined by `drivingServiceMode` in `AvatarKit.initialize()` configuration.
|
|
151
|
+
|
|
152
|
+
### Fallback Mechanism
|
|
153
|
+
|
|
154
|
+
The SDK includes a fallback mechanism to ensure audio playback continues even when animation data is unavailable:
|
|
155
|
+
|
|
156
|
+
- **Network Mode**: If the server returns an error or fails to provide animation data, the SDK automatically enters audio-only mode and continues playing audio independently
|
|
157
|
+
- **Host Mode**: If empty animation data is provided (empty array or undefined), the SDK automatically enters audio-only mode
|
|
158
|
+
- Once in audio-only mode, any subsequent animation data for that session will be ignored, and only audio will continue playing
|
|
159
|
+
- The fallback mode is interruptible, just like normal playback mode
|
|
160
|
+
|
|
138
161
|
### Data Flow
|
|
139
162
|
|
|
140
163
|
#### Network Mode Flow
|
|
@@ -162,10 +185,10 @@ RenderSystem → WebGPU/WebGL → Canvas rendering
|
|
|
162
185
|
```
|
|
163
186
|
External data source (audio + animation)
|
|
164
187
|
↓
|
|
165
|
-
AvatarController.
|
|
188
|
+
AvatarController.playback(initialAudio, initialKeyframes) // Start playback
|
|
166
189
|
↓
|
|
167
|
-
AvatarController.
|
|
168
|
-
AvatarController.
|
|
190
|
+
AvatarController.yieldAudioData() // Stream additional audio
|
|
191
|
+
AvatarController.yieldFramesData() // Stream additional animation
|
|
169
192
|
↓
|
|
170
193
|
AvatarController → AnimationPlayer (synchronized playback)
|
|
171
194
|
↓
|
|
@@ -258,8 +281,6 @@ manager.clearCache()
|
|
|
258
281
|
|
|
259
282
|
3D rendering view (rendering layer), responsible for 3D rendering only. Internally automatically creates and manages `AvatarController`.
|
|
260
283
|
|
|
261
|
-
**⚠️ Important Limitation:** Currently, the SDK only supports one AvatarView instance at a time. If you need to switch characters, you must first call the `dispose()` method to clean up the current AvatarView, then create a new instance.
|
|
262
|
-
|
|
263
284
|
**Playback Mode Configuration:**
|
|
264
285
|
- The playback mode is fixed when creating `AvatarView` and persists throughout its lifecycle
|
|
265
286
|
- Cannot be changed after creation
|
|
@@ -268,18 +289,9 @@ manager.clearCache()
|
|
|
268
289
|
import { AvatarPlaybackMode } from '@spatialwalk/avatarkit'
|
|
269
290
|
|
|
270
291
|
// Create view (Canvas is automatically added to container)
|
|
271
|
-
//
|
|
292
|
+
// Create view (playback mode is determined by drivingServiceMode in AvatarKit configuration)
|
|
272
293
|
const container = document.getElementById('avatar-container')
|
|
273
|
-
const avatarView = new AvatarView(avatar
|
|
274
|
-
container: container,
|
|
275
|
-
playbackMode: AvatarPlaybackMode.network // Optional, default is 'network'
|
|
276
|
-
})
|
|
277
|
-
|
|
278
|
-
// External data mode
|
|
279
|
-
const avatarView = new AvatarView(avatar: Avatar, {
|
|
280
|
-
container: container,
|
|
281
|
-
playbackMode: AvatarPlaybackMode.external
|
|
282
|
-
})
|
|
294
|
+
const avatarView = new AvatarView(avatar, container)
|
|
283
295
|
|
|
284
296
|
// Get playback mode
|
|
285
297
|
const mode = avatarView.playbackMode // 'network' | 'external'
|
|
@@ -291,24 +303,20 @@ avatarView.dispose()
|
|
|
291
303
|
**Character Switching Example:**
|
|
292
304
|
|
|
293
305
|
```typescript
|
|
294
|
-
//
|
|
306
|
+
// To switch characters, simply dispose the old view and create a new one
|
|
295
307
|
if (currentAvatarView) {
|
|
296
308
|
currentAvatarView.dispose()
|
|
297
|
-
currentAvatarView = null
|
|
298
309
|
}
|
|
299
310
|
|
|
300
311
|
// Load new character
|
|
301
312
|
const newAvatar = await avatarManager.load('new-character-id')
|
|
302
313
|
|
|
303
|
-
// Create new AvatarView
|
|
304
|
-
currentAvatarView = new AvatarView(newAvatar,
|
|
305
|
-
container: container,
|
|
306
|
-
playbackMode: AvatarPlaybackMode.network
|
|
307
|
-
})
|
|
314
|
+
// Create new AvatarView
|
|
315
|
+
currentAvatarView = new AvatarView(newAvatar, container)
|
|
308
316
|
|
|
309
317
|
// Network mode: start connection
|
|
310
318
|
if (currentAvatarView.playbackMode === AvatarPlaybackMode.network) {
|
|
311
|
-
await currentAvatarView.
|
|
319
|
+
await currentAvatarView.controller.start()
|
|
312
320
|
}
|
|
313
321
|
```
|
|
314
322
|
|
|
@@ -341,24 +349,54 @@ avatarView.avatarController.close()
|
|
|
341
349
|
#### External Data Mode Methods
|
|
342
350
|
|
|
343
351
|
```typescript
|
|
344
|
-
//
|
|
345
|
-
await avatarView.avatarController.
|
|
346
|
-
initialAudioChunks?: Array<{ data: Uint8Array, isLast: boolean }>, //
|
|
347
|
-
initialKeyframes?: any[] //
|
|
352
|
+
// Playback existing audio and animation data (starts a new conversation)
|
|
353
|
+
const reqId = await avatarView.avatarController.playback(
|
|
354
|
+
initialAudioChunks?: Array<{ data: Uint8Array, isLast: boolean }>, // Existing audio chunks (16kHz mono PCM16)
|
|
355
|
+
initialKeyframes?: any[] // Existing animation keyframes (obtained from your service)
|
|
348
356
|
)
|
|
357
|
+
// Returns: reqId - New request ID for this conversation session
|
|
349
358
|
|
|
350
|
-
// Stream additional audio chunks (after
|
|
351
|
-
avatarView.avatarController.
|
|
359
|
+
// Stream additional audio chunks (after playback() is called)
|
|
360
|
+
const reqId = avatarView.avatarController.yieldAudioData(
|
|
352
361
|
data: Uint8Array, // Audio chunk data
|
|
353
362
|
isLast: boolean = false // Whether this is the last chunk
|
|
354
363
|
)
|
|
364
|
+
// Returns: reqId - Request ID for this audio session
|
|
355
365
|
|
|
356
|
-
// Stream additional animation keyframes (after
|
|
357
|
-
avatarView.avatarController.
|
|
358
|
-
keyframes: any[]
|
|
366
|
+
// Stream additional animation keyframes (after playback() is called)
|
|
367
|
+
avatarView.avatarController.yieldFramesData(
|
|
368
|
+
keyframes: any[], // Additional animation keyframes (obtained from your service)
|
|
369
|
+
reqId: string // Request ID (required). Use getCurrentReqId() or yieldAudioData() to get reqId.
|
|
359
370
|
)
|
|
360
371
|
```
|
|
361
372
|
|
|
373
|
+
**⚠️ Important: Request ID (reqId) Management**
|
|
374
|
+
|
|
375
|
+
For each conversation session, you **must**:
|
|
376
|
+
1. **First send audio data** to get a reqId:
|
|
377
|
+
- `playback()` returns a reqId when starting a new conversation
|
|
378
|
+
- `yieldAudioData()` returns a reqId for the current audio session
|
|
379
|
+
2. **Then use that reqId** to send animation data:
|
|
380
|
+
- `yieldFramesData()` requires a valid reqId parameter
|
|
381
|
+
- Animation data with mismatched reqId will be **discarded**
|
|
382
|
+
- Use `getCurrentReqId()` to retrieve the current active reqId
|
|
383
|
+
|
|
384
|
+
**Example Flow:**
|
|
385
|
+
```typescript
|
|
386
|
+
// Step 1: Send audio first to get reqId
|
|
387
|
+
const reqId = await avatarView.avatarController.playback(initialAudioChunks, initialKeyframes)
|
|
388
|
+
// or
|
|
389
|
+
const reqId = avatarView.avatarController.yieldAudioData(audioChunk, false)
|
|
390
|
+
|
|
391
|
+
// Step 2: Use the reqId to send animation data
|
|
392
|
+
avatarView.avatarController.yieldFramesData(keyframes, reqId)
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
**Why reqId is required:**
|
|
396
|
+
- Ensures audio and animation data belong to the same conversation session
|
|
397
|
+
- Prevents data from different sessions from being mixed
|
|
398
|
+
- Automatically discards mismatched animation data for data integrity
|
|
399
|
+
|
|
362
400
|
#### Common Methods (Both Modes)
|
|
363
401
|
|
|
364
402
|
```typescript
|
|
@@ -382,9 +420,10 @@ avatarView.avatarController.onError = (error: Error) => {}
|
|
|
382
420
|
|
|
383
421
|
**Important Notes:**
|
|
384
422
|
- `start()` and `close()` are only available in network mode
|
|
385
|
-
- `
|
|
423
|
+
- `playback()`, `yieldAudioData()`, and `yieldFramesData()` are only available in external data mode
|
|
386
424
|
- `pause()`, `resume()`, `interrupt()`, and `clear()` are available in both modes
|
|
387
425
|
- The playback mode is determined when creating `AvatarView` and cannot be changed
|
|
426
|
+
- **Request ID (reqId)**: In external data mode, always send audio data first to obtain a reqId, then use that reqId when sending animation data. Animation data with mismatched reqId will be discarded.
|
|
388
427
|
|
|
389
428
|
## 🔧 Configuration
|
|
390
429
|
|
|
@@ -393,11 +432,15 @@ avatarView.avatarController.onError = (error: Error) => {}
|
|
|
393
432
|
```typescript
|
|
394
433
|
interface Configuration {
|
|
395
434
|
environment: Environment
|
|
435
|
+
drivingServiceMode?: DrivingServiceMode // Optional, default is 'sdk' (network mode)
|
|
396
436
|
}
|
|
397
437
|
```
|
|
398
438
|
|
|
399
439
|
**Description:**
|
|
400
440
|
- `environment`: Specifies the environment (cn/us/test), SDK will automatically use the corresponding API address and WebSocket address based on the environment
|
|
441
|
+
- `drivingServiceMode`: Specifies the driving service mode
|
|
442
|
+
- `DrivingServiceMode.sdk` (default): SDK mode - SDK handles WebSocket communication automatically
|
|
443
|
+
- `DrivingServiceMode.host`: Host mode - Host application provides audio and animation data
|
|
401
444
|
- `sessionToken`: Set separately via `AvatarKit.setSessionToken()`, not in Configuration
|
|
402
445
|
|
|
403
446
|
```typescript
|
|
@@ -408,22 +451,20 @@ enum Environment {
|
|
|
408
451
|
}
|
|
409
452
|
```
|
|
410
453
|
|
|
411
|
-
###
|
|
454
|
+
### AvatarView Constructor
|
|
412
455
|
|
|
413
456
|
```typescript
|
|
414
|
-
|
|
415
|
-
playbackMode?: AvatarPlaybackMode // Playback mode, default is 'network'
|
|
416
|
-
container?: HTMLElement // Canvas container element
|
|
417
|
-
}
|
|
457
|
+
constructor(avatar: Avatar, container: HTMLElement)
|
|
418
458
|
```
|
|
419
459
|
|
|
420
|
-
**
|
|
421
|
-
- `
|
|
422
|
-
|
|
423
|
-
-
|
|
424
|
-
-
|
|
425
|
-
- Canvas
|
|
426
|
-
|
|
460
|
+
**Parameters:**
|
|
461
|
+
- `avatar`: Avatar 实例
|
|
462
|
+
- `container`: Canvas 容器元素(必选)
|
|
463
|
+
- Canvas 自动使用容器的完整尺寸(宽度和高度)
|
|
464
|
+
- Canvas 宽高比适应容器尺寸 - 设置容器尺寸以控制宽高比
|
|
465
|
+
- Canvas 会自动添加到容器中
|
|
466
|
+
|
|
467
|
+
**Note:** 播放模式由 `AvatarKit.initialize()` 配置中的 `drivingServiceMode` 决定,而不是在构造函数参数中
|
|
427
468
|
- SDK automatically handles resize events via ResizeObserver
|
|
428
469
|
|
|
429
470
|
```typescript
|
|
@@ -518,8 +559,7 @@ avatarView.avatarController.onError = (error: Error) => {
|
|
|
518
559
|
```typescript
|
|
519
560
|
// Initialize
|
|
520
561
|
const container = document.getElementById('avatar-container')
|
|
521
|
-
const avatarView = new AvatarView(avatar,
|
|
522
|
-
container: container,
|
|
562
|
+
const avatarView = new AvatarView(avatar, container)
|
|
523
563
|
playbackMode: AvatarPlaybackMode.network
|
|
524
564
|
})
|
|
525
565
|
await avatarView.avatarController.start()
|
|
@@ -537,16 +577,16 @@ avatarView.dispose() // Automatically cleans up all resources
|
|
|
537
577
|
```typescript
|
|
538
578
|
// Initialize
|
|
539
579
|
const container = document.getElementById('avatar-container')
|
|
540
|
-
const avatarView = new AvatarView(avatar,
|
|
541
|
-
container: container,
|
|
542
|
-
playbackMode: AvatarPlaybackMode.external
|
|
543
|
-
})
|
|
580
|
+
const avatarView = new AvatarView(avatar, container)
|
|
544
581
|
|
|
545
582
|
// Use
|
|
546
583
|
const initialAudioChunks = [{ data: audioData1, isLast: false }]
|
|
547
|
-
|
|
548
|
-
avatarView.avatarController.
|
|
549
|
-
|
|
584
|
+
// Step 1: Send audio first to get reqId
|
|
585
|
+
const reqId = await avatarView.avatarController.playback(initialAudioChunks, initialKeyframes)
|
|
586
|
+
// Step 2: Stream additional audio (returns reqId)
|
|
587
|
+
const currentReqId = avatarView.avatarController.yieldAudioData(audioChunk, false)
|
|
588
|
+
// Step 3: Use reqId to send animation data (mismatched reqId will be discarded)
|
|
589
|
+
avatarView.avatarController.yieldFramesData(keyframes, currentReqId || reqId)
|
|
550
590
|
|
|
551
591
|
// Cleanup
|
|
552
592
|
avatarView.avatarController.clear() // Clear all data and resources
|
|
@@ -554,8 +594,7 @@ avatarView.dispose() // Automatically cleans up all resources
|
|
|
554
594
|
```
|
|
555
595
|
|
|
556
596
|
**⚠️ Important Notes:**
|
|
557
|
-
-
|
|
558
|
-
- When switching characters, must first call `dispose()` to clean up old AvatarView, then create new instance
|
|
597
|
+
- When disposing AvatarView instances, must call `dispose()` to properly clean up resources
|
|
559
598
|
- Not properly cleaning up may cause resource leaks and rendering errors
|
|
560
599
|
- In network mode, call `close()` before `dispose()` to properly close WebSocket connections
|
|
561
600
|
- In external data mode, call `clear()` before `dispose()` to clear all playback data
|
|
@@ -586,7 +625,7 @@ The `send()` method receives audio data in `ArrayBuffer` format:
|
|
|
586
625
|
|
|
587
626
|
#### External Data Mode
|
|
588
627
|
|
|
589
|
-
The `
|
|
628
|
+
The `playback()` method starts playback with existing audio and animation data, generating a new reqId and interrupting any existing conversation. Then use `yieldAudioData()` to stream additional audio:
|
|
590
629
|
|
|
591
630
|
**Audio Format Requirements:**
|
|
592
631
|
- Same as network mode: 16kHz mono PCM16 format
|
|
@@ -594,18 +633,25 @@ The `play()` method starts playback with initial data, then use `sendAudioChunk(
|
|
|
594
633
|
|
|
595
634
|
**Usage:**
|
|
596
635
|
```typescript
|
|
597
|
-
//
|
|
636
|
+
// Playback existing audio and animation data (starts a new conversation)
|
|
598
637
|
// Note: Audio and animation data should be obtained from your backend service
|
|
599
638
|
const initialAudioChunks = [
|
|
600
639
|
{ data: audioData1, isLast: false },
|
|
601
640
|
{ data: audioData2, isLast: false }
|
|
602
641
|
]
|
|
603
|
-
await avatarController.
|
|
642
|
+
const reqId = await avatarController.playback(initialAudioChunks, initialKeyframes)
|
|
643
|
+
// Returns: reqId - New request ID for this conversation session
|
|
604
644
|
|
|
605
645
|
// Stream additional audio chunks
|
|
606
|
-
avatarController.
|
|
646
|
+
const reqId = avatarController.yieldAudioData(audioChunk, isLast)
|
|
647
|
+
// Returns: reqId - Request ID for this audio session
|
|
607
648
|
```
|
|
608
649
|
|
|
650
|
+
**⚠️ Request ID Workflow:**
|
|
651
|
+
1. **Send audio first** → Get reqId from `playback()` or `yieldAudioData()`
|
|
652
|
+
2. **Send animation with reqId** → Use the reqId from step 1 in `yieldFramesData()`
|
|
653
|
+
3. **Data matching** → Only animation data with matching reqId will be accepted
|
|
654
|
+
|
|
609
655
|
**Resampling (Both Modes):**
|
|
610
656
|
- If your audio source is at a different sample rate (e.g., 24kHz, 48kHz), you **must** resample it to 16kHz before sending
|
|
611
657
|
- For high-quality resampling, use Web Audio API's `OfflineAudioContext` with anti-aliasing filtering
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
var C = Object.defineProperty;
|
|
2
2
|
var g = (h, t, e) => t in h ? C(h, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : h[t] = e;
|
|
3
3
|
var i = (h, t, e) => g(h, typeof t != "symbol" ? t + "" : t, e);
|
|
4
|
-
import { A as m, e as f, a as c, l as u } from "./index-
|
|
4
|
+
import { A as m, e as f, a as c, l as u } from "./index-suaZGA5u.js";
|
|
5
5
|
class y {
|
|
6
6
|
constructor(t) {
|
|
7
7
|
// AudioContext is managed internally
|
|
@@ -30,7 +30,7 @@ class y {
|
|
|
30
30
|
i(this, "audioChunks", []);
|
|
31
31
|
i(this, "scheduledChunks", 0);
|
|
32
32
|
// Number of chunks already scheduled
|
|
33
|
-
i(this, "activeSources",
|
|
33
|
+
i(this, "activeSources", /* @__PURE__ */ new Set());
|
|
34
34
|
// Event callbacks
|
|
35
35
|
i(this, "onEndedCallback");
|
|
36
36
|
this.sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, this.sampleRate = (t == null ? void 0 : t.sampleRate) ?? m.audio.sampleRate, this.channelCount = (t == null ? void 0 : t.channelCount) ?? 1, this.debug = (t == null ? void 0 : t.debug) ?? !1;
|
|
@@ -98,7 +98,7 @@ class y {
|
|
|
98
98
|
sessionStartTime: this.sessionStartTime,
|
|
99
99
|
bufferedChunks: this.audioChunks.length,
|
|
100
100
|
scheduledChunks: this.scheduledChunks,
|
|
101
|
-
activeSources: this.activeSources.
|
|
101
|
+
activeSources: this.activeSources.size
|
|
102
102
|
}), this.scheduleAllChunks();
|
|
103
103
|
}
|
|
104
104
|
/**
|
|
@@ -130,7 +130,7 @@ class y {
|
|
|
130
130
|
this.scheduledChunks++;
|
|
131
131
|
return;
|
|
132
132
|
}
|
|
133
|
-
const
|
|
133
|
+
const r = e.data, o = e.isLast, a = this.pcmToAudioBuffer(r);
|
|
134
134
|
if (!a) {
|
|
135
135
|
u.error("Failed to create AudioBuffer from PCM data"), c.logEvent("character_player", "error", {
|
|
136
136
|
sessionId: this.sessionId,
|
|
@@ -140,15 +140,14 @@ class y {
|
|
|
140
140
|
}
|
|
141
141
|
try {
|
|
142
142
|
const s = this.audioContext.createBufferSource();
|
|
143
|
-
s.buffer = a, s.connect(this.audioContext.destination), s.start(this.scheduledTime), this.activeSources.
|
|
144
|
-
|
|
145
|
-
o >= 0 && this.activeSources.splice(o, 1), d && this.activeSources.length === 0 && (this.log("Last audio chunk ended, marking playback as ended"), this.markEnded());
|
|
143
|
+
s.buffer = a, s.connect(this.audioContext.destination), s.start(this.scheduledTime), this.activeSources.add(s), s.onended = () => {
|
|
144
|
+
this.activeSources.delete(s), o && this.activeSources.size === 0 && (this.log("Last audio chunk ended, marking playback as ended"), this.markEnded());
|
|
146
145
|
}, this.scheduledTime += a.duration, this.scheduledChunks++, this.log(`[StreamingAudioPlayer] Scheduled chunk ${t + 1}/${this.audioChunks.length}`, {
|
|
147
146
|
startTime: this.scheduledTime - a.duration,
|
|
148
147
|
duration: a.duration,
|
|
149
148
|
nextScheduleTime: this.scheduledTime,
|
|
150
|
-
isLast:
|
|
151
|
-
activeSources: this.activeSources.
|
|
149
|
+
isLast: o,
|
|
150
|
+
activeSources: this.activeSources.size
|
|
152
151
|
});
|
|
153
152
|
} catch (s) {
|
|
154
153
|
u.errorWithError("Failed to schedule audio chunk:", s), c.logEvent("character_player", "error", {
|
|
@@ -166,25 +165,25 @@ class y {
|
|
|
166
165
|
if (!this.audioContext)
|
|
167
166
|
return null;
|
|
168
167
|
if (t.length === 0) {
|
|
169
|
-
const
|
|
168
|
+
const l = Math.floor(this.sampleRate * 0.01), n = this.audioContext.createBuffer(
|
|
170
169
|
this.channelCount,
|
|
171
|
-
|
|
170
|
+
l,
|
|
172
171
|
this.sampleRate
|
|
173
172
|
);
|
|
174
|
-
for (let
|
|
175
|
-
n.getChannelData(
|
|
173
|
+
for (let d = 0; d < this.channelCount; d++)
|
|
174
|
+
n.getChannelData(d).fill(0);
|
|
176
175
|
return n;
|
|
177
176
|
}
|
|
178
|
-
const e = new Uint8Array(t),
|
|
177
|
+
const e = new Uint8Array(t), r = new Int16Array(e.buffer, 0, e.length / 2), o = r.length / this.channelCount, a = this.audioContext.createBuffer(
|
|
179
178
|
this.channelCount,
|
|
180
|
-
|
|
179
|
+
o,
|
|
181
180
|
this.sampleRate
|
|
182
181
|
);
|
|
183
182
|
for (let s = 0; s < this.channelCount; s++) {
|
|
184
|
-
const
|
|
185
|
-
for (let n = 0; n <
|
|
186
|
-
const
|
|
187
|
-
|
|
183
|
+
const l = a.getChannelData(s);
|
|
184
|
+
for (let n = 0; n < o; n++) {
|
|
185
|
+
const d = n * this.channelCount + s;
|
|
186
|
+
l[n] = r[d] / 32768;
|
|
188
187
|
}
|
|
189
188
|
}
|
|
190
189
|
return a;
|
|
@@ -251,7 +250,7 @@ class y {
|
|
|
251
250
|
} catch {
|
|
252
251
|
}
|
|
253
252
|
}
|
|
254
|
-
this.activeSources
|
|
253
|
+
this.activeSources.clear(), this.audioChunks = [], this.scheduledChunks = 0, this.log("[StreamingAudioPlayer] Playback stopped, state reset");
|
|
255
254
|
}
|
|
256
255
|
}
|
|
257
256
|
/**
|
|
@@ -332,4 +331,4 @@ class y {
|
|
|
332
331
|
export {
|
|
333
332
|
y as StreamingAudioPlayer
|
|
334
333
|
};
|
|
335
|
-
//# sourceMappingURL=StreamingAudioPlayer-
|
|
334
|
+
//# sourceMappingURL=StreamingAudioPlayer-a8MwHQ3Q.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StreamingAudioPlayer-a8MwHQ3Q.js","sources":["../audio/StreamingAudioPlayer.ts"],"sourcesContent":["/**\n * Streaming Audio Player\n * Implements real-time audio playback using Web Audio API\n * Supports dynamic PCM chunk addition without Workers\n */\n\nimport { APP_CONFIG } from '../config/app-config'\nimport { AvatarKit } from '../core/AvatarKit'\nimport { errorToMessage } from '../utils/error-utils'\nimport { logger } from '../utils/logger'\n\nexport interface StreamingAudioPlayerOptions {\n sampleRate?: number // PCM sample rate (default: APP_CONFIG.audio.sampleRate, backend requires 16kHz)\n channelCount?: number // Number of channels (default: 1)\n debug?: boolean\n}\n\nexport class StreamingAudioPlayer {\n // AudioContext is managed internally\n private audioContext: AudioContext | null = null\n private sampleRate: number\n private channelCount: number\n private debug: boolean\n\n // Session-level state\n private sessionId: string\n private sessionStartTime = 0 // AudioContext time when session started\n private pausedTimeOffset = 0 // Accumulated paused time\n private pausedAt = 0 // Time when paused\n private pausedAudioContextTime = 0 // audioContext.currentTime when paused (for resume calculation)\n private scheduledTime = 0 // Next chunk schedule time in AudioContext time\n\n // Playback state\n private isPlaying = false\n private isPaused = false\n private autoStartEnabled = true // Control whether to auto-start when buffer is ready\n\n // Audio buffer queue\n private audioChunks: Array<{ data: Uint8Array, isLast: boolean }> = []\n private scheduledChunks = 0 // Number of chunks already scheduled\n private activeSources = new Set<AudioBufferSourceNode>()\n\n // Event callbacks\n private onEndedCallback?: () => void\n\n constructor(options?: StreamingAudioPlayerOptions) {\n this.sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`\n this.sampleRate = options?.sampleRate ?? APP_CONFIG.audio.sampleRate\n this.channelCount = options?.channelCount ?? 1\n this.debug = options?.debug ?? false\n }\n\n /**\n * Initialize audio context (create and ensure it's ready)\n */\n async initialize(): Promise<void> {\n if (this.audioContext) {\n return\n }\n\n try {\n // Create AudioContext\n this.audioContext = new AudioContext({\n sampleRate: this.sampleRate,\n })\n\n // Resume context (required for some browsers)\n if (this.audioContext.state === 'suspended') {\n await this.audioContext.resume()\n }\n\n this.log('AudioContext initialized', {\n sessionId: this.sessionId,\n sampleRate: this.audioContext.sampleRate,\n state: this.audioContext.state,\n })\n }\n catch (error) {\n const message = errorToMessage(error)\n AvatarKit.logEvent('activeAudioSessionFailed', 'warning', {\n sessionId: this.sessionId,\n reason: message,\n })\n logger.error('Failed to initialize AudioContext:', message)\n throw error instanceof Error ? error : new Error(message)\n }\n }\n\n /**\n * Add audio chunk (16-bit PCM)\n */\n addChunk(pcmData: Uint8Array, isLast: boolean = false): void {\n if (!this.audioContext) {\n logger.error('AudioContext not initialized')\n return\n }\n\n // Store chunk with metadata\n this.audioChunks.push({ data: pcmData, isLast })\n\n // Track buffer underrun warning\n if (this.isPlaying && this.audioChunks.length === this.scheduledChunks) {\n // Buffer underrun detected - chunks consumed faster than added\n }\n\n this.log(`Added chunk ${this.audioChunks.length}`, {\n size: pcmData.length,\n totalChunks: this.audioChunks.length,\n isLast,\n isPlaying: this.isPlaying,\n scheduledChunks: this.scheduledChunks,\n })\n\n // Auto-start if we have any audio chunks and auto-start is enabled\n if (!this.isPlaying && this.autoStartEnabled && this.audioChunks.length > 0) {\n this.log('[StreamingAudioPlayer] Auto-starting playback from addChunk')\n this.startPlayback()\n }\n // Schedule next chunk if already playing and not paused\n else if (this.isPlaying && !this.isPaused) {\n this.log('[StreamingAudioPlayer] Already playing, scheduling next chunk')\n this.scheduleNextChunk()\n } else {\n this.log('[StreamingAudioPlayer] Not playing and no chunks, waiting for more chunks')\n }\n }\n\n /**\n * Start new session (stop current and start fresh)\n */\n async startNewSession(audioChunks: Array<{ data: Uint8Array, isLast: boolean }>): Promise<void> {\n // Stop current session if playing\n this.stop()\n\n // Generate new session ID to prevent data mixing\n this.sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`\n\n // Reset session state\n this.audioChunks = []\n this.scheduledChunks = 0\n this.pausedTimeOffset = 0\n this.pausedAt = 0\n this.pausedAudioContextTime = 0\n // Don't set sessionStartTime or scheduledTime here - let startPlayback() set them\n this.log('Starting new session', {\n chunks: audioChunks.length,\n })\n\n // Add audio chunks with their metadata\n for (const chunk of audioChunks) {\n this.addChunk(chunk.data, chunk.isLast)\n }\n }\n\n /**\n * Start playback\n */\n private startPlayback(): void {\n if (!this.audioContext) {\n this.log('[StreamingAudioPlayer] Cannot start playback: AudioContext not initialized')\n return\n }\n if (this.isPlaying) {\n this.log('[StreamingAudioPlayer] Cannot start playback: Already playing')\n return\n }\n\n this.isPlaying = true\n this.sessionStartTime = this.audioContext.currentTime\n this.scheduledTime = this.sessionStartTime\n\n this.log('[StreamingAudioPlayer] Starting playback', {\n sessionStartTime: this.sessionStartTime,\n bufferedChunks: this.audioChunks.length,\n scheduledChunks: this.scheduledChunks,\n activeSources: this.activeSources.size,\n })\n\n // Schedule all available chunks\n this.scheduleAllChunks()\n }\n\n /**\n * Schedule all pending chunks\n */\n private scheduleAllChunks(): void {\n while (this.scheduledChunks < this.audioChunks.length) {\n this.scheduleNextChunk()\n }\n }\n\n /**\n * Schedule next audio chunk\n */\n private scheduleNextChunk(): void {\n if (!this.audioContext) {\n this.log('[StreamingAudioPlayer] Cannot schedule chunk: AudioContext not initialized')\n return\n }\n if (!this.isPlaying || this.isPaused) {\n this.log('[StreamingAudioPlayer] Cannot schedule chunk: Not playing or paused')\n return\n }\n\n const chunkIndex = this.scheduledChunks\n if (chunkIndex >= this.audioChunks.length) {\n this.log(`[StreamingAudioPlayer] No more chunks to schedule (chunkIndex: ${chunkIndex}, totalChunks: ${this.audioChunks.length})`)\n return\n }\n\n const chunk = this.audioChunks[chunkIndex]\n\n // 当音频块为空且不是最后一个块时,跳过调度\n if (chunk.data.length === 0 && !chunk.isLast) {\n this.scheduledChunks++\n return\n }\n\n const pcmData = chunk.data\n const isLast = chunk.isLast\n const audioBuffer = this.pcmToAudioBuffer(pcmData)\n\n if (!audioBuffer) {\n const errorMessage = 'Failed to create AudioBuffer from PCM data'\n logger.error(errorMessage)\n AvatarKit.logEvent('character_player', 'error', {\n sessionId: this.sessionId,\n event: 'audio_buffer_creation_failed',\n })\n return\n }\n\n try {\n // Create and configure source node\n const source = this.audioContext.createBufferSource()\n source.buffer = audioBuffer\n source.connect(this.audioContext.destination)\n\n // Schedule playback\n source.start(this.scheduledTime)\n\n // Track active source for hard-cancel\n this.activeSources.add(source)\n source.onended = () => {\n // Remove from active list when it ends\n this.activeSources.delete(source)\n\n // Check if this was the last chunk and all sources have ended\n if (isLast && this.activeSources.size === 0) {\n this.log('Last audio chunk ended, marking playback as ended')\n this.markEnded()\n }\n }\n\n // Update scheduled time for next chunk\n this.scheduledTime += audioBuffer.duration\n\n this.scheduledChunks++\n\n this.log(`[StreamingAudioPlayer] Scheduled chunk ${chunkIndex + 1}/${this.audioChunks.length}`, {\n startTime: this.scheduledTime - audioBuffer.duration,\n duration: audioBuffer.duration,\n nextScheduleTime: this.scheduledTime,\n isLast,\n activeSources: this.activeSources.size,\n })\n }\n catch (err) {\n logger.errorWithError('Failed to schedule audio chunk:', err)\n AvatarKit.logEvent('character_player', 'error', {\n sessionId: this.sessionId,\n event: 'schedule_chunk_failed',\n reason: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n /**\n * Convert PCM data to AudioBuffer\n * Input: 16-bit PCM (int16), Output: AudioBuffer (float32 [-1, 1])\n */\n private pcmToAudioBuffer(pcmData: Uint8Array): AudioBuffer | null {\n if (!this.audioContext) {\n return null\n }\n\n // Handle empty PCM data (e.g., when isLast is true)\n if (pcmData.length === 0) {\n // For empty chunks (typically the last chunk), create minimal silence\n // Use a very short duration to avoid playback stuttering\n const silenceDuration = 0.01 // 1ms - minimal silence to maintain timing\n const numSamples = Math.floor(this.sampleRate * silenceDuration)\n\n const audioBuffer = this.audioContext.createBuffer(\n this.channelCount,\n numSamples,\n this.sampleRate,\n )\n\n // Fill with silence (all zeros)\n for (let channel = 0; channel < this.channelCount; channel++) {\n const channelData = audioBuffer.getChannelData(channel)\n channelData.fill(0) // Fill with silence\n }\n\n return audioBuffer\n }\n\n // Create aligned copy to avoid byte offset issues\n // Int16Array requires byteOffset to be a multiple of 2\n const alignedData = new Uint8Array(pcmData)\n const int16Array = new Int16Array(alignedData.buffer, 0, alignedData.length / 2)\n\n // Calculate number of samples\n const numSamples = int16Array.length / this.channelCount\n\n // Create AudioBuffer\n const audioBuffer = this.audioContext.createBuffer(\n this.channelCount,\n numSamples,\n this.sampleRate,\n )\n\n // Convert int16 to float32 [-1, 1]\n for (let channel = 0; channel < this.channelCount; channel++) {\n const channelData = audioBuffer.getChannelData(channel)\n\n for (let i = 0; i < numSamples; i++) {\n const sampleIndex = i * this.channelCount + channel\n // Normalize int16 (-32768 to 32767) to float32 (-1 to 1)\n channelData[i] = int16Array[sampleIndex] / 32768.0\n }\n }\n\n return audioBuffer\n }\n\n /**\n * Get current playback time (seconds)\n */\n getCurrentTime(): number {\n if (!this.audioContext || !this.isPlaying) {\n return 0\n }\n\n if (this.isPaused) {\n return this.pausedAt\n }\n\n // Calculate elapsed time using session start time and paused offset\n const currentAudioTime = this.audioContext.currentTime\n const elapsed = currentAudioTime - this.sessionStartTime - this.pausedTimeOffset\n\n return Math.max(0, elapsed)\n }\n\n /**\n * Pause playback\n */\n pause(): void {\n if (!this.isPlaying || this.isPaused || !this.audioContext) {\n return\n }\n\n // 1. 记录逻辑时间(用于 getCurrentTime 返回固定值)\n this.pausedAt = this.getCurrentTime()\n\n // 2. 记录 AudioContext 时间戳(关键!用于恢复计算)\n this.pausedAudioContextTime = this.audioContext.currentTime\n\n // 3. 设置暂停标志\n this.isPaused = true\n\n // 4. 挂起 AudioContext 以暂停所有活动的音频源\n if (this.audioContext.state === 'running') {\n this.audioContext.suspend().catch((err) => {\n logger.errorWithError('Failed to suspend AudioContext:', err)\n // 如果挂起失败,恢复状态\n this.isPaused = false\n })\n }\n\n this.log('Playback paused', {\n pausedAt: this.pausedAt,\n pausedAudioContextTime: this.pausedAudioContextTime,\n audioContextState: this.audioContext.state,\n })\n }\n\n /**\n * Resume playback\n */\n async resume(): Promise<void> {\n if (!this.isPaused || !this.audioContext || !this.isPlaying) {\n return\n }\n\n // 1. 首先恢复 AudioContext(使 currentTime 继续)\n if (this.audioContext.state === 'suspended') {\n try {\n await this.audioContext.resume()\n }\n catch (err) {\n logger.errorWithError('Failed to resume AudioContext:', err)\n throw err\n }\n }\n\n // 2. 调整 sessionStartTime,使 getCurrentTime() 从 pausedAt 继续\n // 数学推导:\n // 恢复后,我们希望:getCurrentTime() = pausedAt + (currentAudioTime - pausedAudioContextTime)\n // 当前公式:getCurrentTime() = currentAudioTime - sessionStartTime - pausedTimeOffset\n //\n // 令两者相等:\n // pausedAt + (currentAudioTime - pausedAudioContextTime) = currentAudioTime - sessionStartTime - pausedTimeOffset\n // => sessionStartTime = pausedAudioContextTime - pausedAt - pausedTimeOffset\n const currentAudioTime = this.audioContext.currentTime\n this.sessionStartTime = this.pausedAudioContextTime - this.pausedAt - this.pausedTimeOffset\n\n // 3. 清除暂停标志\n this.isPaused = false\n\n // 4. 继续调度未调度的音频块(如果在暂停期间有新数据到达)\n if (this.scheduledChunks < this.audioChunks.length) {\n this.scheduleAllChunks()\n }\n\n this.log('Playback resumed', {\n pausedAt: this.pausedAt,\n pausedAudioContextTime: this.pausedAudioContextTime,\n currentAudioContextTime: currentAudioTime,\n adjustedSessionStartTime: this.sessionStartTime,\n audioContextState: this.audioContext.state,\n })\n }\n\n /**\n * Stop playback\n */\n stop(): void {\n if (!this.audioContext) {\n return\n }\n\n // 如果暂停,先恢复 AudioContext(以便正确停止源)\n if (this.isPaused && this.audioContext.state === 'suspended') {\n this.audioContext.resume().catch(() => {\n // 忽略恢复错误,因为我们要停止播放\n })\n this.isPaused = false\n }\n\n this.isPlaying = false\n this.isPaused = false\n this.sessionStartTime = 0 // Reset session start time\n this.scheduledTime = 0 // Reset scheduled time for next session\n\n // Hard stop all scheduled sources immediately\n for (const source of this.activeSources) {\n source.onended = null\n try {\n source.stop(0)\n }\n catch {}\n try {\n source.disconnect()\n }\n catch {}\n }\n this.activeSources.clear()\n\n // 清理音频块和调度状态,确保下次播放时状态干净\n this.audioChunks = []\n this.scheduledChunks = 0\n\n this.log('[StreamingAudioPlayer] Playback stopped, state reset')\n\n // Note: Individual source nodes will stop automatically\n // We just reset our state\n }\n\n /**\n * Enable or disable auto-start (for delayed start scenarios)\n */\n setAutoStart(enabled: boolean): void {\n this.autoStartEnabled = enabled\n this.log(`Auto-start ${enabled ? 'enabled' : 'disabled'}`)\n }\n\n /**\n * Start playback manually (for delayed start scenarios)\n * This allows starting playback after transition animation completes\n */\n play(): void {\n if (this.isPlaying) {\n return\n }\n // Enable auto-start when manually starting playback\n this.autoStartEnabled = true\n this.startPlayback()\n }\n\n /**\n * Mark playback as ended\n */\n markEnded(): void {\n this.log('Playback ended')\n this.isPlaying = false\n this.onEndedCallback?.()\n }\n\n /**\n * Set ended callback\n */\n onEnded(callback: () => void): void {\n this.onEndedCallback = callback\n }\n\n /**\n * Check if playing\n */\n isPlayingNow(): boolean {\n return this.isPlaying && !this.isPaused\n }\n\n /**\n * Get total duration of buffered audio\n */\n getBufferedDuration(): number {\n if (!this.audioContext) {\n return 0\n }\n\n let totalSamples = 0\n for (const chunk of this.audioChunks) {\n totalSamples += chunk.data.length / 2 / this.channelCount // 16-bit = 2 bytes per sample\n }\n\n return totalSamples / this.sampleRate\n }\n\n /**\n * Get remaining duration (buffered - played) in seconds\n */\n getRemainingDuration(): number {\n const total = this.getBufferedDuration()\n const played = this.getCurrentTime()\n return Math.max(0, total - played)\n }\n\n\n /**\n * Dispose and cleanup\n */\n dispose(): void {\n this.stop()\n\n // Close AudioContext\n if (this.audioContext) {\n this.audioContext.close()\n this.audioContext = null\n }\n\n // Clear session state\n this.audioChunks = []\n this.scheduledChunks = 0\n this.sessionStartTime = 0\n this.pausedTimeOffset = 0\n this.pausedAt = 0\n this.pausedAudioContextTime = 0\n this.scheduledTime = 0\n this.onEndedCallback = undefined\n\n this.log('StreamingAudioPlayer disposed')\n }\n\n /**\n * Flush buffered audio\n * - hard: stops all playing sources and clears all chunks\n * - soft (default): clears UNSCHEDULED chunks only\n */\n flush(options?: { hard?: boolean }): void {\n const hard = options?.hard === true\n if (hard) {\n this.stop()\n this.audioChunks = []\n this.scheduledChunks = 0\n this.sessionStartTime = 0\n this.pausedAt = 0\n this.scheduledTime = 0\n this.log('Flushed (hard)')\n return\n }\n\n // Soft flush: drop unscheduled region\n if (this.scheduledChunks < this.audioChunks.length) {\n this.audioChunks.splice(this.scheduledChunks)\n }\n this.log('Flushed (soft)', { remainingScheduled: this.scheduledChunks })\n }\n\n /**\n * Debug logging\n */\n private log(message: string, data?: unknown): void {\n if (this.debug) {\n logger.log(`[StreamingAudioPlayer] ${message}`, data || '')\n }\n }\n}\n"],"names":["StreamingAudioPlayer","options","__publicField","APP_CONFIG","error","message","errorToMessage","AvatarKit","logger","pcmData","isLast","audioChunks","chunk","chunkIndex","audioBuffer","source","err","numSamples","channel","alignedData","int16Array","channelData","i","sampleIndex","elapsed","currentAudioTime","enabled","_a","callback","totalSamples","total","played","data"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StreamingAudioPlayer.d.ts","sourceRoot":"","sources":["../../audio/StreamingAudioPlayer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,MAAM,WAAW,2BAA2B;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,qBAAa,oBAAoB;IAE/B,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,YAAY,CAAQ;IAC5B,OAAO,CAAC,KAAK,CAAS;IAGtB,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,gBAAgB,CAAI;IAC5B,OAAO,CAAC,gBAAgB,CAAI;IAC5B,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,sBAAsB,CAAI;IAClC,OAAO,CAAC,aAAa,CAAI;IAGzB,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,gBAAgB,CAAO;IAG/B,OAAO,CAAC,WAAW,CAAmD;IACtE,OAAO,CAAC,eAAe,CAAI;IAC3B,OAAO,CAAC,aAAa,
|
|
1
|
+
{"version":3,"file":"StreamingAudioPlayer.d.ts","sourceRoot":"","sources":["../../audio/StreamingAudioPlayer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,MAAM,WAAW,2BAA2B;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,qBAAa,oBAAoB;IAE/B,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,YAAY,CAAQ;IAC5B,OAAO,CAAC,KAAK,CAAS;IAGtB,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,gBAAgB,CAAI;IAC5B,OAAO,CAAC,gBAAgB,CAAI;IAC5B,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,sBAAsB,CAAI;IAClC,OAAO,CAAC,aAAa,CAAI;IAGzB,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,gBAAgB,CAAO;IAG/B,OAAO,CAAC,WAAW,CAAmD;IACtE,OAAO,CAAC,eAAe,CAAI;IAC3B,OAAO,CAAC,aAAa,CAAmC;IAGxD,OAAO,CAAC,eAAe,CAAC,CAAY;gBAExB,OAAO,CAAC,EAAE,2BAA2B;IAOjD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAiCjC;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,GAAE,OAAe,GAAG,IAAI;IAoC5D;;OAEG;IACG,eAAe,CAAC,WAAW,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAwB/F;;OAEG;IACH,OAAO,CAAC,aAAa;IAyBrB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAMzB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAmFzB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAwDxB;;OAEG;IACH,cAAc,IAAI,MAAM;IAgBxB;;OAEG;IACH,KAAK,IAAI,IAAI;IA8Bb;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IA4C7B;;OAEG;IACH,IAAI,IAAI,IAAI;IA0CZ;;OAEG;IACH,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAKpC;;;OAGG;IACH,IAAI,IAAI,IAAI;IASZ;;OAEG;IACH,SAAS,IAAI,IAAI;IAMjB;;OAEG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAInC;;OAEG;IACH,YAAY,IAAI,OAAO;IAIvB;;OAEG;IACH,mBAAmB,IAAI,MAAM;IAa7B;;OAEG;IACH,oBAAoB,IAAI,MAAM;IAO9B;;OAEG;IACH,OAAO,IAAI,IAAI;IAsBf;;;;OAIG;IACH,KAAK,CAAC,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI;IAoBzC;;OAEG;IACH,OAAO,CAAC,GAAG;CAKZ"}
|