@zeewain/3d-avatar-sdk 1.1.0 → 1.2.0

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 CHANGED
@@ -1,753 +1,1606 @@
1
- # 数字人SDK 使用说明
2
-
3
- ## 1. 产品说明
4
- ### 1.1 产品概述
5
- 紫为云3D写真数字人SDK采用多项AI技术实现:
6
- - 仿真数字人渲染
7
- - 音频唇形精准驱动
8
- - 多终端语音交互
9
- - 支持PC/Web/APP/H5全平台
10
-
11
- **应用场景**:智能客服|智慧政务|文旅会展|数字营销
12
-
13
- ### 1.2 产品功能
14
- | 模块 | 核心能力 |
15
- |:--------------:|--------------------------------------------------------------------------|
16
- | **语音模块** | • 语音/文本对话管理 • 多终端接入控制 • 专属音色 |
17
- | **交互模块** | • 语音对话交互 • TTS语音播报 • 大屏字幕同步显示 |
18
- | **系统功能** | • 多背景切换 • 对话能力配置 • 实时测试体验 • 统一错误管理 |
19
-
20
- ## 2. SDK使用说明
21
- ### 2.1 核心功能
22
- | 功能 | 说明 | 必需性 |
23
- |--------------------|----------------------------------------------------------------------|:--------:|
24
- | **数字人渲染** | Web端加载并渲染3D数字人模型 | ✅ |
25
- | **数字人播报** | 通过文本/音频驱动口型表情 | ✅ |
26
- | **播报控制** | 启动/暂停/继续/停止播报 | ✅ |
27
- | **动作控制** | 播放预设动作库(招手/点头等) | ✅ |
28
- | **错误管理** | 统一的错误处理系统,分类错误码,中文化错误消息 | ✅ |
29
-
30
- ### 2.2 安装方式
31
- #### 2.2.1 npm安装
32
-
33
- ```bash
34
- npm install @zeewain/3d-avatar-sdk
35
- ```
36
-
37
- #### 2.2.2 在项目中引入
38
-
39
- ##### ES6+ 模块方式(推荐)
40
- 适用于现代浏览器和构建工具(WebpackViteRollup等)
41
- ```javascript
42
- // 导入核心模块
43
- import { ZEEAvatarLoader, AvatarService, BroadcastService } from '@zeewain/3d-avatar-sdk'
44
-
45
- // 导入类型定义(TypeScript 项目)
46
- import {
47
- IAvatarAPI,
48
- IAvatarCallbackResponse,
49
- AvatarCameraType,
50
- BroadcastType,
51
- IBroadcastParams,
52
- IBroadcastCallbacks,
53
- // 错误管理相关类型
54
- SDKError,
55
- NetworkErrorCode,
56
- OperationErrorCode,
57
- ResourceErrorCode,
58
- SystemErrorCode,
59
- ConfigErrorCode
60
- } from '@zeewain/3d-avatar-sdk'
61
- ```
62
-
63
- ##### ES5 兼容方式
64
- 适用于需要支持 IE11 等老旧浏览器的项目
65
- ```javascript
66
- // ES5 模块导入(包含完整 polyfill)
67
- import { ZEEAvatarLoader, AvatarService, BroadcastService } from '@zeewain/3d-avatar-sdk/es5'
68
-
69
- // ES5 CommonJS 导入
70
- const { ZEEAvatarLoader, AvatarService, BroadcastService } = require('@zeewain/3d-avatar-sdk/es5')
71
- ```
72
-
73
- #### 2.2.3 通过 CDN 引入
74
-
75
- ##### 现代浏览器(ES6+)
76
- ```html
77
- <!-- ES 模块方式 -->
78
- <script type="module">
79
- import { ZEEAvatarLoader, AvatarService, BroadcastService } from 'https://unpkg.com/@zeewain/3d-avatar-sdk'
80
- // 使用 SDK...
81
- </script>
82
- ```
83
-
84
- ##### 传统浏览器(UMD 全局变量)
85
- ```html
86
- <!-- UMD 方式,支持现代浏览器 -->
87
- <script src="https://unpkg.com/@zeewain/3d-avatar-sdk"></script>
88
- <script>
89
- const { ZEEAvatarLoader, AvatarService, BroadcastService } = window.ZEEAvatarSDK;
90
- // 使用 SDK...
91
- </script>
92
- ```
93
-
94
- ##### 老旧浏览器(IE11 兼容)
95
- ```html
96
- <!-- ES5 兼容版本,包含完整 polyfill -->
97
- <script src="https://unpkg.com/@zeewain/3d-avatar-sdk/es5"></script>
98
- <script>
99
- const { ZEEAvatarLoader, AvatarService, BroadcastService } = window.ZEEAvatarSDK;
100
- // 使用 SDK...
101
- </script>
102
- ```
103
-
104
- #### 2.2.4 浏览器兼容性
105
-
106
- | 版本类型 | 支持的浏览器 | 文件大小 | 使用场景 |
107
- |:--------:|:------------|:--------:|:---------|
108
- | **ES6+** | Chrome 60+, Firefox 55+, Safari 10.1+, Edge 16+ | ~31KB | 现代 Web 应用(推荐) |
109
- | **ES5** | 包括 IE11 在内的所有浏览器 | ~170KB | 需要支持老旧浏览器 |
110
-
111
- #### 2.2.5 自动版本选择
112
-
113
- SDK 使用现代化的 `exports` 字段,支持自动版本选择:
114
-
115
- - **现代构建工具**(Webpack 5+, Vite, Rollup):自动选择 ES 模块版本
116
- - **Node.js 环境**:自动选择 CommonJS 版本
117
- - **浏览器环境**:根据导入方式自动选择合适版本
118
- - **老旧工具**:回退到传统的 `main`/`module` 字段
119
-
120
- ### 2.3 接口说明
121
- #### 2.3.1 SDK初始化
122
- 首先需要配置Unity WebGL构建文件的路径和认证信息:
123
-
124
- ```javascript
125
- // SDK配置
126
- const config = {
127
- // Unity构建文件路径配置
128
- loaderUrl: './assets/Build/webgl.loader.js',
129
- dataUrl: './assets/Build/webgl.data.unityweb',
130
- frameworkUrl: './assets/Build/webgl.framework.js.unityweb',
131
- codeUrl: './assets/Build/webgl.wasm.unityweb',
132
-
133
- // 容器配置
134
- containerId: 'unity-container', // 页面中放置Unity容器的元素ID
135
-
136
- // 认证配置
137
- token: 'your-auth-token', // 用户认证token
138
- env: 'prod', // 环境:'prod' | 'test' | 'custom',默认'prod'
139
- apiUrl: 'https://your-custom-api.com', // 仅当 env='custom' 时需要
140
-
141
- // 可选回调
142
- onProgress: (progress) => {
143
- console.log(`加载进度: ${Math.round(progress * 100)}%`);
144
- }
145
- };
146
-
147
- // 创建SDK实例
148
- const loader = new ZEEAvatarLoader(config);
149
- ```
150
-
151
- > **💡 提示**:Unity 资源文件较大(约19MB),建议:
152
- > - 将资源文件部署到 CDN 以提高加载速度
153
- > - 在生产环境中配置适当的缓存策略
154
- > - 考虑使用 IndexedDB 缓存以加速二次加载
155
-
156
- #### 2.3.2 初始化Unity环境
157
- 调用init()方法加载并初始化Unity环境,获取Avatar API实例:
158
-
159
- ```javascript
160
- let avatarAPI;
161
- let broadcastService;
162
-
163
- try {
164
- // 初始化SDK,返回Avatar API实例
165
- avatarAPI = await loader.init();
166
- console.log('SDK 初始化成功!');
167
-
168
- // 获取Unity实例(可选)
169
- const unityInstance = loader.getInstance();
170
- const containerId = loader.getContainerId();
171
-
172
- // 创建播报服务实例
173
- broadcastService = new BroadcastService({
174
- unityInstance: unityInstance,
175
- callbacks: {
176
- onStart: () => console.log('播报开始'),
177
- onFinish: () => console.log('播报完成'),
178
- onError: (error) => console.error('播报错误:', error),
179
- onPause: () => console.log('播报暂停'),
180
- onResume: () => console.log('播报恢复'),
181
- onStop: () => console.log('播报停止')
182
- }
183
- });
184
-
185
- } catch (error) {
186
- console.error('SDK 初始化失败:', error);
187
- }
188
- ```
189
-
190
- #### 2.3.3 销毁SDK实例
191
- 在不需要Unity的时候释放Unity实例:
192
- ```javascript
193
- // 销毁播报服务
194
- if (broadcastService) {
195
- broadcastService.destroy();
196
- }
197
-
198
- // 销毁SDK实例
199
- loader.destroy();
200
- console.log('Unity环境已销毁');
201
- ```
202
-
203
- #### 2.3.4 加载数字人
204
- ```javascript
205
- try {
206
- const response = await avatarAPI.initializeAvatar(avatarCode, 'whole');
207
- if (response.success) {
208
- console.log('数字人加载成功');
209
- } else {
210
- console.error('数字人加载失败:', response.message);
211
- }
212
- } catch (error) {
213
- console.error('加载异常:', error);
214
- }
215
- ```
216
-
217
- | 参数 | 类型 | 必填项 | 说明 |
218
- | :---: | :---: | :---:| :---: |
219
- | avatarCode | string |✅ | 数字人编码 |
220
- | cameraType | AvatarCameraType |❌ | 摄像机类型:'whole' \| 'half' \| 'face',默认'whole' |
221
-
222
- **返回值**:`Promise<IAvatarCallbackResponse>` - 包含操作结果的Promise对象
223
-
224
- #### 2.3.5 播放动作
225
- ```javascript
226
- try {
227
- const response = await avatarAPI.playMotion(clipCode);
228
- if (response.success) {
229
- console.log('动作播放成功');
230
- }
231
- } catch (error) {
232
- console.error('动作播放失败:', error);
233
- }
234
- ```
235
-
236
- | 参数 | 类型 | 必填项 | 说明 |
237
- | :---: | :---: | :---:| :---: |
238
- | clipCode | string |✅ | 动作编码 |
239
-
240
- **返回值**:`Promise<IAvatarCallbackResponse>` -Promise对象
241
-
242
- #### 2.3.6 获取当前动作信息
243
- ```javascript
244
- try {
245
- // 获取动作编码和剩余时间
246
- const response = await avatarAPI.getCurrentMotion(true);
247
- if (response.success) {
248
- console.log('动作编码:', response.data.motionId);
249
- console.log('剩余时间:', response.data.motionRemainingTime); // 待机状态时返回0
250
- }
251
-
252
- // 仅获取动作编码
253
- const response2 = await avatarAPI.getCurrentMotion(false);
254
- console.log('动作编码:', response2.motionId);
255
- } catch (error) {
256
- console.error('获取动作信息失败:', error);
257
- }
258
- ```
259
-
260
- | 参数 | 类型 | 必填项 | 说明 |
261
- | :---: | :---: | :---:| :---: |
262
- | getRemainingTime | boolean |✅ | 是否获取动作剩余时间|
263
-
264
- **返回值**:`Promise<IAvatarCallbackResponse>` - 包含动作信息的Promise对象
265
-
266
- #### 2.3.7 设置摄像机类型
267
- ```javascript
268
- try {
269
- const response = await avatarAPI.setCamera('face');
270
- if (response.success) {
271
- console.log('摄像机设置成功');
272
- }
273
- } catch (error) {
274
- console.error('摄像机设置失败:', error);
275
- }
276
- ```
277
-
278
- | 参数 | 类型 | 必填项 | 说明 |
279
- | :---: | :---: | :---:| :---: |
280
- | type | AvatarCameraType |✅ | 摄像机类型:'whole' \| 'half' \| 'face'|
281
-
282
- #### 2.3.8 播报服务
283
- ```javascript
284
- // 文本播报
285
- try {
286
- await broadcastService.startBroadcast({
287
- type: BroadcastType.TEXT, // 或 'text'
288
- humanCode: avatarCode,
289
- text: "欢迎使用紫为云3D数字人交互SDK",
290
- voiceCode: "VOICE001",
291
- volume: 1.0,
292
- speed: 1.0,
293
- isSubtitle: false
294
- });
295
- } catch (error) {
296
- console.error('文本播报失败:', error);
297
- }
298
-
299
- // 音频播报
300
- try {
301
- await broadcastService.startBroadcast({
302
- type: BroadcastType.AUDIO, // 或 'audio'
303
- humanCode: avatarCode,
304
- audioUrl: "https://example.com/audio.mp3",
305
- text: "音频字幕内容",
306
- volume: 1.0,
307
- isSubtitle: true
308
- });
309
- } catch (error) {
310
- console.error('音频播报失败:', error);
311
- }
312
- ```
313
-
314
- **播报参数说明**:
315
-
316
- | 参数 | 类型 | 必填项 | 说明 |
317
- | :---: | :---: | :---:| :---: |
318
- | type | BroadcastType |✅ | 播报类型:'text'或'audio' |
319
- | humanCode | string |✅ | 数字人编码 |
320
- | text | string |✅ | 播报文本内容 |
321
- | voiceCode | string |文本播报✅<br/>音频播报❌| 音色编码 |
322
- | audioUrl | string |文本播报❌<br/>音频播报✅| 音频URL (支持mp3、wav)|
323
- | volume | number |❌ | 音量 (0-1.0) 默认:1.0|
324
- | speed | number |❌ | 语速 (0.5-2.0)默认:1.0|
325
- | isSubtitle | boolean |❌ | 是否启用字幕 (暂不支持)|
326
-
327
- #### 2.3.9 播报控制
328
- ```javascript
329
- // 暂停播报
330
- try {
331
- await broadcastService.pauseBroadcast();
332
- console.log('播报已暂停');
333
- } catch (error) {
334
- console.error('暂停失败:', error);
335
- }
336
-
337
- // 恢复播报
338
- try {
339
- await broadcastService.resumeBroadcast();
340
- console.log('播报已恢复');
341
- } catch (error) {
342
- console.error('恢复失败:', error);
343
- }
344
-
345
- // 停止播报
346
- try {
347
- await broadcastService.stopBroadcast();
348
- console.log('播报已停止');
349
- } catch (error) {
350
- console.error('停止失败:', error);
351
- }
352
- ```
353
-
354
- #### 2.3.10 卸载数字人
355
- ```javascript
356
- try {
357
- const response = await avatarAPI.unloadAvatar();
358
- if (response.success) {
359
- console.log('数字人卸载成功');
360
- }
361
- } catch (error) {
362
- console.error('数字人卸载失败:', error);
363
- }
364
- ```
365
-
366
- **返回值**:`Promise<IAvatarCallbackResponse>` - 包含操作结果的Promise对象
367
-
368
- #### 2.3.11 批量操作
369
- ```javascript
370
- // 批量执行多个操作
371
- try {
372
- const results = await avatarAPI.batchExecute([
373
- { method: 'initializeAvatar', params: ['avatar001', 'whole'] },
374
- { method: 'playMotion', params: ['motion001'] },
375
- { method: 'setCamera', params: ['face'] }
376
- ]);
377
-
378
- results.forEach((result, index) => {
379
- if (result.success) {
380
- console.log(`操作 ${index + 1} 成功`);
381
- } else {
382
- console.error(`操作 ${index + 1} 失败:`, result.message);
383
- }
384
- });
385
- } catch (error) {
386
- console.error('批量操作异常:', error);
387
- }
388
- ```
389
-
390
- #### 2.3.12 播报状态管理
391
- ```javascript
392
- // 获取播报状态
393
- const status = broadcastService.getStatus();
394
- console.log('播报状态:', {
395
- isActive: status.isActive,
396
- pendingCallbacks: status.pendingCallbacks,
397
- hasController: status.hasController
398
- });
399
-
400
- // 更新播报回调
401
- broadcastService.updateCallbacks({
402
- onStart: () => console.log('新的开始回调'),
403
- onError: (error) => console.error('新的错误回调:', error)
404
- });
405
- ```
406
-
407
- ### 2.4 错误处理
408
- SDK中的操作都返回Promise,建议使用try-catch处理异常。SDK使用统一的错误管理系统,所有错误都继承自`SDKError`类:
409
-
410
- ```javascript
411
- // 导入错误类型和错误码(TypeScript项目)
412
- import { SDKError, NetworkErrorCode, OperationErrorCode, ResourceErrorCode, SystemErrorCode, ConfigErrorCode } from '@zeewain/3d-avatar-sdk'
413
-
414
- // 统一错误处理示例
415
- try {
416
- const response = await avatarAPI.initializeAvatar('avatar001');
417
- if (response.success) {
418
- console.log('数字人加载成功');
419
- }
420
- } catch (error) {
421
- // 处理SDK错误
422
- if (error instanceof SDKError) {
423
- handleSDKError(error);
424
- } else {
425
- // 处理其他系统异常
426
- console.error('系统异常:', error.message);
427
- }
428
- }
429
-
430
- // SDK错误处理
431
- function handleSDKError(error) {
432
- console.error('SDK错误:', error.message, '错误码:', error.code);
433
-
434
- // 根据错误类别处理
435
- switch (error.category) {
436
- case 'NETWORK':
437
- handleNetworkError(error);
438
- break;
439
- case 'OPERATION':
440
- handleOperationError(error);
441
- break;
442
- case 'RESOURCE':
443
- handleResourceError(error);
444
- break;
445
- case 'SYSTEM':
446
- handleSystemError(error);
447
- break;
448
- case 'CONFIG':
449
- handleConfigError(error);
450
- break;
451
- default:
452
- console.error('未知错误类别:', error.category);
453
- }
454
- }
455
-
456
- // 网络错误处理
457
- function handleNetworkError(error) {
458
- switch (error.code) {
459
- case NetworkErrorCode.CONNECTION_FAILED:
460
- console.error('网络连接失败,请检查网络状态');
461
- break;
462
- case NetworkErrorCode.REQUEST_TIMEOUT:
463
- console.error('请求超时,请稍后重试');
464
- break;
465
- case NetworkErrorCode.SERVER_ERROR:
466
- console.error('服务器错误,请稍后重试');
467
- break;
468
- case NetworkErrorCode.UNAUTHORIZED:
469
- console.error('认证失效,请重新登录');
470
- // 跳转到登录页面或刷新token
471
- break;
472
- }
473
- }
474
-
475
- // 操作错误处理
476
- function handleOperationError(error) {
477
- switch (error.code) {
478
- case OperationErrorCode.UNITY_NOT_INITIALIZED:
479
- console.error('Unity实例未初始化');
480
- break;
481
- case OperationErrorCode.AVATAR_NOT_LOADED:
482
- console.error('数字人未加载,请先加载数字人');
483
- break;
484
- case OperationErrorCode.OPERATION_FAILED:
485
- console.error('操作执行失败');
486
- break;
487
- case OperationErrorCode.OPERATION_TIMEOUT:
488
- console.error('操作超时,请稍后重试');
489
- break;
490
- case OperationErrorCode.OPERATION_CANCELLED:
491
- console.error('操作已取消');
492
- break;
493
- }
494
- }
495
-
496
- // 资源错误处理
497
- function handleResourceError(error) {
498
- switch (error.code) {
499
- case ResourceErrorCode.LOAD_FAILED:
500
- console.error('资源加载失败');
501
- break;
502
- case ResourceErrorCode.FILE_CORRUPTED:
503
- console.error('资源文件损坏');
504
- break;
505
- case ResourceErrorCode.NOT_FOUND:
506
- console.error('资源不存在');
507
- break;
508
- case ResourceErrorCode.UNSUPPORTED_FORMAT:
509
- console.error('资源格式不支持');
510
- break;
511
- }
512
- }
513
-
514
- // 系统错误处理
515
- function handleSystemError(error) {
516
- if (error instanceof SDKError) {
517
- switch (error.code) {
518
- case SystemErrorCode.OUT_OF_MEMORY:
519
- console.error('内存不足,请关闭其他应用');
520
- break;
521
- case SystemErrorCode.GPU_NOT_SUPPORTED:
522
- console.error('GPU不支持,请更新浏览器或显卡驱动');
523
- break;
524
- case SystemErrorCode.WEBGL_NOT_SUPPORTED:
525
- console.error('浏览器不支持WebGL');
526
- break;
527
- case SystemErrorCode.BROWSER_NOT_COMPATIBLE:
528
- console.error('浏览器不兼容,请更新浏览器');
529
- break;
530
- }
531
- } else {
532
- console.error('系统异常:', error.message);
533
- // 可以上报错误日志
534
- }
535
- }
536
-
537
- // 配置错误处理
538
- function handleConfigError(error) {
539
- switch (error.code) {
540
- case ConfigErrorCode.INVALID_CONFIG:
541
- console.error('配置参数无效');
542
- break;
543
- case ConfigErrorCode.MISSING_REQUIRED_PARAM:
544
- console.error('缺少必需参数');
545
- break;
546
- case ConfigErrorCode.PARAM_OUT_OF_RANGE:
547
- console.error('参数值超出范围');
548
- break;
549
- case ConfigErrorCode.INVALID_JSON_FORMAT:
550
- console.error('JSON格式错误');
551
- break;
552
- }
553
- }
554
-
555
- // 播报错误处理
556
- const broadcastService = new BroadcastService({
557
- unityInstance: unityInstance,
558
- callbacks: {
559
- onError: (error) => {
560
- // 所有错误都是SDKError类型
561
- console.error('播报错误:', error.message, '错误码:', error.code);
562
- handleSDKError(error);
563
- }
564
- }
565
- });
566
- ```
567
-
568
- ### 2.5 SDK错误码参考表
569
-
570
- SDK使用统一的错误管理系统,所有错误都通过`SDKError`抛出:
571
-
572
- ##### 网络错误 (1xxx)
573
- | 错误码 | 常量名 | 描述说明 | 常见发生场景 |
574
- | :---: | :---: | :---: | :---: |
575
- | 1001 | `NetworkErrorCode.CONNECTION_FAILED` | 网络连接失败 | 网络不可用、防火墙阻止、DNS解析失败 |
576
- | 1002 | `NetworkErrorCode.REQUEST_TIMEOUT` | 请求超时 | 网络延迟过高、服务器响应慢 |
577
- | 1003 | `NetworkErrorCode.SERVER_ERROR` | 服务器错误 | 服务器内部错误、API异常 |
578
- | 1004 | `NetworkErrorCode.UNAUTHORIZED` | 未授权访问 | Token失效、权限不足 |
579
-
580
- ##### 操作错误 (2xxx)
581
- | 错误码 | 常量名 | 描述说明 | 常见发生场景 |
582
- | :---: | :---: | :---: | :---: |
583
- | 2001 | `OperationErrorCode.UNITY_NOT_INITIALIZED` | Unity实例未初始化 | 在Unity加载完成前调用API |
584
- | 2002 | `OperationErrorCode.AVATAR_NOT_LOADED` | 数字人未加载 | 在数字人加载完成前调用相关API |
585
- | 2003 | `OperationErrorCode.OPERATION_FAILED` | 操作执行失败 | Unity操作执行失败、消息发送失败、播报处理失败等 |
586
- | 2004 | `OperationErrorCode.OPERATION_TIMEOUT` | 操作超时 | 操作执行时间过长 |
587
- | 2005 | `OperationErrorCode.OPERATION_CANCELLED` | 操作被取消 | 用户主动取消或系统中断 |
588
-
589
- ##### 资源错误 (3xxx)
590
- | 错误码 | 常量名 | 描述说明 | 常见发生场景 |
591
- | :---: | :---: | :---: | :---: |
592
- | 3001 | `ResourceErrorCode.LOAD_FAILED` | 资源加载失败 | 文件不存在、下载失败 |
593
- | 3002 | `ResourceErrorCode.FILE_CORRUPTED` | 资源文件损坏 | 文件格式错误、数据损坏 |
594
- | 3003 | `ResourceErrorCode.NOT_FOUND` | 资源不存在 | 指定的资源路径不存在 |
595
- | 3004 | `ResourceErrorCode.UNSUPPORTED_FORMAT` | 资源格式不支持 | 音频格式不支持、模型格式不兼容 |
596
-
597
- ##### 系统错误 (4xxx)
598
- | 错误码 | 常量名 | 描述说明 | 常见发生场景 |
599
- | :---: | :---: | :---: | :---: |
600
- | 4001 | `SystemErrorCode.OUT_OF_MEMORY` | 内存不足 | 系统内存不足、浏览器内存限制 |
601
- | 4002 | `SystemErrorCode.GPU_NOT_SUPPORTED` | GPU不支持 | 显卡不支持WebGL、驱动过旧 |
602
- | 4003 | `SystemErrorCode.WEBGL_NOT_SUPPORTED` | WebGL不支持 | 浏览器不支持WebGL |
603
- | 4004 | `SystemErrorCode.BROWSER_NOT_COMPATIBLE` | 浏览器不兼容 | 浏览器版本过低、功能不支持 |
604
-
605
- ##### 配置错误 (5xxx)
606
- | 错误码 | 常量名 | 描述说明 | 常见发生场景 |
607
- | :---: | :---: | :---: | :---: |
608
- | 5001 | `ConfigErrorCode.INVALID_CONFIG` | 配置参数无效 | 配置格式错误、参数类型不匹配 |
609
- | 5002 | `ConfigErrorCode.MISSING_REQUIRED_PARAM` | 必需参数缺失 | 缺少必需的配置项或参数 |
610
- | 5003 | `ConfigErrorCode.PARAM_OUT_OF_RANGE` | 参数值超出范围 | 参数值超出有效范围 |
611
- | 5004 | `ConfigErrorCode.INVALID_JSON_FORMAT` | JSON格式错误 | JSON解析失败、格式不正确 |
612
-
613
- #### 2.5.1 错误码使用示例
614
-
615
- ```javascript
616
- // 检查具体的错误码
617
- try {
618
- await avatarAPI.initializeAvatar('avatar001');
619
- } catch (error) {
620
- if (error instanceof SDKError) {
621
- // 使用错误码常量进行判断
622
- if (error.code === OperationErrorCode.UNITY_NOT_INITIALIZED) {
623
- console.error('Unity实例未初始化');
624
- } else if (error.code === OperationErrorCode.OPERATION_FAILED) {
625
- console.error('操作执行失败');
626
- } else if (error.code === NetworkErrorCode.UNAUTHORIZED) {
627
- console.error('认证失效,请重新登录');
628
- }
629
-
630
- // 或者使用错误类别进行判断
631
- switch (error.category) {
632
- case 'NETWORK':
633
- console.error('网络错误:', error.message);
634
- break;
635
- case 'OPERATION':
636
- console.error('操作错误:', error.message);
637
- break;
638
- // ... 其他类别
639
- }
640
- }
641
- }
642
- ```
643
-
644
- ## 3. 注意事项
645
- ### 3.1 Unity构建文件
646
- - 确保正确配置Unity构建文件的路径。
647
- - 如果文件部署在不同域名下,需要配置CORS。
648
- ### 3.2 内存管理
649
- - 在不需要使用数字人时,调用unloadAvatar释放资源。
650
- - 在页面卸载前,调用destroy释放整个Unity环境。
651
- - 当卸载数字人后,发现浏览器内存未降低,请不用担心,Unity堆内存由引擎自主管理。
652
- ### 3.3 数字人加载和卸载时机
653
- - 数字人加载成功后,才能卸载数字人。
654
- - 在未成功加载数字人的时候,提前卸载数字人,卸载会成功,但是会导致第二次加载数字人失败;解决方案:卸载Unity实例化,重新初始化Unity,再初始化数字人即可解决。
655
- ### 3.4 错误处理
656
- - 所有异步操作都返回Promise,务必使用try-catch处理。
657
- - 检查response.success字段判断操作是否成功。
658
- - **统一错误管理系统**:所有错误都通过`SDKError`抛出,通过错误码和错误类别进行精确处理。
659
- - SDK错误码采用分类编码:网络错误(1xxx)、操作错误(2xxx)、资源错误(3xxx)、系统错误(4xxx)、配置错误(5xxx)。
660
- - 所有错误消息已本地化为中文,可直接显示给用户。
661
- ### 3.5 性能考虑
662
- - 避免频繁加载/卸载数字人。
663
- - 音频文件不易过大。(建议<5MB)
664
- ### 3.6 兼容性
665
-
666
- #### 3.6.1 浏览器要求
667
- - **基本要求**:浏览器支持 WebGL 2.0
668
- - **ES6+ 版本**:Chrome 60+, Firefox 55+, Safari 10.1+, Edge 16+
669
- - **ES5 版本**:包括 IE11 在内的所有现代浏览器
670
-
671
- #### 3.6.2 模块系统兼容性
672
- | 环境 | 推荐版本 | 导入方式 |
673
- |:-----|:---------|:---------|
674
- | **现代 Web 应用** | ES6+ | `import { ... } from '@zeewain/3d-avatar-sdk'` |
675
- | **IE11 兼容项目** | ES5 | `import { ... } from '@zeewain/3d-avatar-sdk/es5'` |
676
- | **CDN 引入** | 根据需要 | 见 2.2.3 CDN 引入方式 |
677
-
678
- #### 3.6.3 性能优化建议
679
- - **移动设备**:注意性能限制,考虑降低渲染质量
680
- - **资源缓存**:Web端建议对Unity资源文件使用 IndexedDB 缓存
681
- - **网络优化**:将Unity资源部署到CDN以提高加载速度
682
-
683
- ### 3.7 认证配置
684
- - token在SDK初始化时配置,无需在每个API调用时传递
685
- - token失效时,需要重新初始化SDK或更新全局配置
686
- - 支持生产环境('prod')、测试环境('test')和自定义环境('custom')
687
-
688
- ## 4. 常见问题
689
- ### 4.1 Unity内容无法加载怎么办?
690
-
691
- 1. 确保Unity构建文件路径配置正确;
692
- 2. 检查浏览器控制台是否有CORS错误;
693
- 3. 确认网络可以访问构建文件URL;
694
- 4. 验证Unity版本与SDK兼容性;
695
-
696
- ### 4.2 播报没有声音怎么办?
697
-
698
- 1. 检查音量参数是否大于0;
699
- 2. 确认音频文件URL可访问且格式受支持(MP3/WAV);
700
- 3. 验证是否设置了正确的音色编码;
701
- 4. 检查浏览器是否阻止了自动播放(可能需要用户交互);
702
-
703
- ### 4.3 动作播放不生效怎么办?
704
-
705
- 1. 确认数字人已成功加载;
706
- 2. 检查动作编码是否正确;
707
- 3. 确保token有效且有权限;
708
- 4. 查看Unity控制台是否有错误日志;
709
-
710
- ### 4.4 获取当前动作时间不生效或者返回时间为0怎么办?
711
-
712
- 1. 确保getCurrentMotion接口参数是true,如果是false是无法获取当前动作剩余时间的;
713
- 2. getCurrentMotion接口参数是true,但只能获取非待机动作的剩余时间,该当前动作如果是属于待机动作的话,返回时间就为0;
714
-
715
- ### 4.5 如何获取更多音色编码、角色编码?
716
-
717
- 1. 请联系技术支持获取可用音色编码列表;
718
-
719
- ### 4.6 如何处理SDK错误?
720
-
721
- 1. **统一错误管理系统**:所有错误都通过`SDKError`抛出,使用try-catch捕获异常;
722
- 2. **错误码分类**:根据错误码前缀快速定位问题类型(1xxx网络、2xxx操作、3xxx资源、4xxx系统、5xxx配置);
723
- 3. **错误消息本地化**:所有错误消息已中文化,可直接显示给用户;
724
- 4. **调试技巧**:使用`error.category`进行错误分类处理,使用`error.code`进行精确判断;
725
- 5. **最佳实践**:建议根据错误类别统一处理,提供友好的用户提示;
726
-
727
- ### 4.7 新版本API迁移说明
728
-
729
- 如果您从旧版本SDK升级,主要变化:
730
-
731
- 1. **类名变化**:`AvatarAPI` → `AvatarService`(但通过`IAvatarAPI`接口使用)
732
- 2. **初始化方式**:token在SDK初始化时配置,不再在每个方法中传递
733
- 3. **返回值**:所有方法都返回Promise,不再使用回调函数
734
- 4. **参数简化**:方法参数更简洁,移除了重复的token参数
735
- 5. **类型安全**:完整的TypeScript类型定义,更好的开发体验
736
- 6. **错误管理升级**:
737
- - 统一的`SDKError`错误类型,替代原有的`UnityServiceError`
738
- - 采用分类错误码系统(1xxx-5xxx),便于精确处理
739
- - 所有错误消息中文化,提供更友好的用户体验
740
- - 支持错误类别判断,简化错误处理逻辑
741
- - 所有错误都通过异常抛出,统一错误处理方式
742
-
743
- ---
744
-
745
- ## 📋 更新日志
746
-
747
- ### 最新版本特性
748
- - ✅ **统一错误管理系统**:全新的`SDKError`错误类型,替代原有的`UnityServiceError`
749
- - ✅ **分类错误码**:采用5类错误码系统(1xxx-5xxx),便于精确处理和调试
750
- - ✅ **中文化错误消息**:所有错误消息本地化为中文,提升用户体验
751
- - ✅ **TypeScript完整支持**:完整的错误类型定义,更好的开发体验
752
- - ✅ **统一异常处理**:所有错误都通过异常抛出,简化错误处理逻辑
753
-
1
+ # ZEE 3D Avatar SDK 使用说明
2
+
3
+
4
+ [![npm version](https://badge.fury.io/js/@zeewain/3d-avatar-sdk.svg)](https://badge.fury.io/js/@zeewain/3d-avatar-sdk)
5
+
6
+
7
+ ## 🚀 产品概述
8
+
9
+ 紫为云3D写真数字人SDK采用多项AI技术实现:
10
+ - **仿真数字人渲染** - 高保真3D数字人模型渲染
11
+ - **音频唇形精准驱动** - 智能口型同步技术
12
+ - **多终端语音交互** - 支持PC/Web/APP/H5全平台
13
+ - **统一SDK架构** - 单一入口,简化集成流程
14
+
15
+ **应用场景**:智能客服 | 智慧政务 | 文旅会展 | 数字营销 | 在线教育
16
+
17
+ ## 核心特性
18
+
19
+ ### 🎯 统一架构设计
20
+ - **单一入口**:`ZEEAvatarSDK` 类统一管理所有功能
21
+ - **组合模式**:内部整合Avatar控制、播报服务等模块
22
+ - **多实例支持**:同一页面可创建多个独立SDK实例
23
+ - **回调隔离**:每个实例拥有独立的回调函数命名空间
24
+
25
+ ### 🛡️ 完善的错误管理
26
+ - **统一错误类型**:`SDKError` 统一错误处理
27
+ - **分类错误码**:网络(1xxx)、操作(2xxx)、资源(3xxx)、系统(4xxx)、配置(5xxx)
28
+ - **中文化消息**:所有错误消息本地化,用户友好
29
+ - **详细调试信息**:错误类别、错误码、详细描述
30
+
31
+ ### 🔧 开发者友好
32
+ - **完整TypeScript支持**:全类型定义,智能提示
33
+ - **Promise-based API**:现代异步编程模式
34
+ - **统一配置管理**:一次配置,全局生效
35
+ - **生命周期管理**:完善的资源清理机制
36
+
37
+ ## 📦 安装方式
38
+
39
+ > **WebGL文件来源说明**:
40
+ > - 所有WebGL构建文件(webgl.loader.jswebgl.data.unitywebwebgl.framework.js.unityweb、webgl.wasm.unityweb)均来源于SDK包(@zeewain/3d-avatar-sdk)的`dist/assets/Build/`目录。
41
+ > - 使用SDK前,请先将这些文件从 `node_modules/@zeewain/3d-avatar-sdk/dist/assets/Build/` 目录拷贝到你项目的静态资源目录,如 `public` 目录或你的CDN服务器上。
42
+ > - 然后在SDK初始化时,通过config参数设置这些WebGL文件的路径。
43
+
44
+ ### npm/yarn 安装(推荐)
45
+
46
+ ```bash
47
+ # npm
48
+ npm install @zeewain/3d-avatar-sdk
49
+
50
+ # yarn
51
+ yarn add @zeewain/3d-avatar-sdk
52
+
53
+ # pnpm
54
+ pnpm add @zeewain/3d-avatar-sdk
55
+ ```
56
+
57
+ ### CDN 引入
58
+
59
+ ```html
60
+ <!-- ES 模块方式(现代浏览器) -->
61
+ <script type="module">
62
+ import { ZEEAvatarSDK } from 'https://unpkg.com/@zeewain/3d-avatar-sdk'
63
+ // 使用 SDK...
64
+ </script>
65
+
66
+ <!-- UMD 方式(全局变量) -->
67
+ <script src="https://unpkg.com/@zeewain/3d-avatar-sdk"></script>
68
+ <script>
69
+ const { ZEEAvatarSDK } = window.ZEEAvatarSDKLib;
70
+ // 使用 SDK...
71
+ </script>
72
+
73
+ <!-- ES5 兼容版本(支持IE11) -->
74
+ <script src="https://unpkg.com/@zeewain/3d-avatar-sdk/es5"></script>
75
+ <script>
76
+ const { ZEEAvatarSDK } = window.ZEEAvatarSDKLib;
77
+ // 使用 SDK...
78
+ </script>
79
+ ```
80
+
81
+ > **💡 重要说明**:
82
+ > - **SDK类名**:`ZEEAvatarSDK`(这是您在代码中使用的类)
83
+ > - **UMD全局变量名**:`ZEEAvatarSDKLib`(这是挂载到window对象的变量名)
84
+ > - 使用UMD方式时,需要从 `window.ZEEAvatarSDKLib` 中解构出 `ZEEAvatarSDK` 类
85
+
86
+ ## 🎮 快速开始
87
+
88
+ ### 基础使用示例
89
+
90
+ ```javascript
91
+ import { ZEEAvatarSDK, AvatarCameraType, BroadcastType } from '@zeewain/3d-avatar-sdk'
92
+
93
+ // 1. 创建SDK实例
94
+ const sdk = new ZEEAvatarSDK({
95
+ // Unity构建文件配置
96
+ loaderUrl: './assets/Build/webgl.loader.js',
97
+ dataUrl: './assets/Build/webgl.data.unityweb',
98
+ frameworkUrl: './assets/Build/webgl.framework.js.unityweb',
99
+ codeUrl: './assets/Build/webgl.wasm.unityweb',
100
+
101
+ // 基础配置
102
+ containerId: 'unity-container',
103
+ token: 'your-auth-token',
104
+ env: 'prod',
105
+
106
+ // 加载进度回调
107
+ onProgress: (progress) => {
108
+ console.log(`加载进度: ${Math.round(progress * 100)}%`);
109
+ }
110
+ });
111
+
112
+ // 2. 初始化数字人
113
+ async function initializeAvatar() {
114
+ try {
115
+ const response = await sdk.initializeAvatar('avatar001', AvatarCameraType.WHOLE);
116
+ if (response.success) {
117
+ console.log('数字人初始化成功!');
118
+
119
+ // 3. 播放动作
120
+ await sdk.playMotion('wave_hand');
121
+
122
+ // 4. 开始播报
123
+ await sdk.startBroadcast({
124
+ type: BroadcastType.TEXT,
125
+ humanCode: 'avatar001',
126
+ text: '欢迎使用ZEE 3D Avatar SDK!',
127
+ voiceCode: 'VOICE001',
128
+ volume: 1.0,
129
+ speed: 1.0,
130
+ isSubtitle: false
131
+ });
132
+ }
133
+ } catch (error) {
134
+ console.error('初始化失败:', error);
135
+ }
136
+ }
137
+
138
+ // 5. 页面卸载时清理资源
139
+ window.addEventListener('beforeunload', () => {
140
+ sdk.destroy();
141
+ });
142
+
143
+ // 开始初始化
144
+ initializeAvatar();
145
+ ```
146
+
147
+ ### 完整应用示例
148
+
149
+ ```javascript
150
+ import {
151
+ ZEEAvatarSDK,
152
+ AvatarCameraType,
153
+ BroadcastType,
154
+ SDKError,
155
+ NetworkErrorCode,
156
+ OperationErrorCode
157
+ } from '@zeewain/3d-avatar-sdk'
158
+
159
+ class AvatarApp {
160
+ constructor() {
161
+ this.sdk = null;
162
+ this.isInitialized = false;
163
+ this.setupUI();
164
+ }
165
+
166
+ // 初始化SDK
167
+ async initSDK() {
168
+ try {
169
+ this.sdk = new ZEEAvatarSDK({
170
+ loaderUrl: './assets/Build/webgl.loader.js',
171
+ dataUrl: './assets/Build/webgl.data.unityweb',
172
+ frameworkUrl: './assets/Build/webgl.framework.js.unityweb',
173
+ codeUrl: './assets/Build/webgl.wasm.unityweb',
174
+ containerId: 'unity-container',
175
+ token: 'your-auth-token',
176
+ env: 'prod',
177
+ onProgress: this.onLoadProgress.bind(this)
178
+ });
179
+
180
+ // 初始化数字人
181
+ const response = await this.sdk.initializeAvatar('avatar001', AvatarCameraType.WHOLE);
182
+
183
+ if (response.success) {
184
+ this.isInitialized = true;
185
+ this.updateUI();
186
+ console.log('SDK初始化成功');
187
+ }
188
+ } catch (error) {
189
+ this.handleError(error);
190
+ }
191
+ }
192
+
193
+ // 播放动作
194
+ async playMotion(motionCode) {
195
+ if (!this.isInitialized) {
196
+ console.warn('SDK未初始化');
197
+ return;
198
+ }
199
+
200
+ try {
201
+ const response = await this.sdk.playMotion(motionCode);
202
+ if (response.success) {
203
+ console.log('动作播放成功');
204
+ }
205
+ } catch (error) {
206
+ this.handleError(error);
207
+ }
208
+ }
209
+
210
+ // 文本播报
211
+ async speakText(text, voiceCode = 'VOICE001') {
212
+ if (!this.isInitialized) {
213
+ console.warn('SDK未初始化');
214
+ return;
215
+ }
216
+
217
+ try {
218
+ await this.sdk.startBroadcast({
219
+ type: BroadcastType.TEXT,
220
+ humanCode: 'avatar001',
221
+ text: text,
222
+ voiceCode: voiceCode,
223
+ volume: 1.0,
224
+ speed: 1.0,
225
+ isSubtitle: false
226
+ });
227
+ console.log('播报开始');
228
+ } catch (error) {
229
+ this.handleError(error);
230
+ }
231
+ }
232
+
233
+ // 音频播报
234
+ async playAudio(audioUrl, subtitleText = '') {
235
+ if (!this.isInitialized) {
236
+ console.warn('SDK未初始化');
237
+ return;
238
+ }
239
+
240
+ try {
241
+ await this.sdk.startBroadcast({
242
+ type: BroadcastType.AUDIO,
243
+ humanCode: 'avatar001',
244
+ audioUrl: audioUrl,
245
+ text: subtitleText,
246
+ volume: 1.0,
247
+ isSubtitle: true
248
+ });
249
+ console.log('音频播报开始');
250
+ } catch (error) {
251
+ this.handleError(error);
252
+ }
253
+ }
254
+
255
+ // 播报控制
256
+ async pauseBroadcast() {
257
+ try {
258
+ await this.sdk.pauseBroadcast();
259
+ console.log('播报已暂停');
260
+ } catch (error) {
261
+ this.handleError(error);
262
+ }
263
+ }
264
+
265
+ async resumeBroadcast() {
266
+ try {
267
+ await this.sdk.resumeBroadcast();
268
+ console.log('播报已恢复');
269
+ } catch (error) {
270
+ this.handleError(error);
271
+ }
272
+ }
273
+
274
+ async stopBroadcast() {
275
+ try {
276
+ await this.sdk.stopBroadcast();
277
+ console.log('播报已停止');
278
+ } catch (error) {
279
+ this.handleError(error);
280
+ }
281
+ }
282
+
283
+ // 摄像机控制
284
+ async setCamera(cameraType) {
285
+ if (!this.isInitialized) {
286
+ console.warn('SDK未初始化');
287
+ return;
288
+ }
289
+
290
+ try {
291
+ const response = await this.sdk.setCamera(cameraType);
292
+ if (response.success) {
293
+ console.log('摄像机设置成功');
294
+ }
295
+ } catch (error) {
296
+ this.handleError(error);
297
+ }
298
+ }
299
+
300
+ // 获取当前动作信息
301
+ async getCurrentMotion() {
302
+ if (!this.isInitialized) {
303
+ console.warn('SDK未初始化');
304
+ return;
305
+ }
306
+
307
+ try {
308
+ const response = await this.sdk.getCurrentMotion(true);
309
+ if (response.success) {
310
+ console.log('当前动作:', response.data);
311
+ return response.data;
312
+ }
313
+ } catch (error) {
314
+ this.handleError(error);
315
+ }
316
+ }
317
+
318
+ // 加载进度回调
319
+ onLoadProgress(progress) {
320
+ const percent = Math.round(progress * 100);
321
+ console.log(`加载进度: ${percent}%`);
322
+ // 更新进度条UI
323
+ this.updateProgressBar(percent);
324
+ }
325
+
326
+ // 错误处理
327
+ handleError(error) {
328
+ console.error('SDK错误:', error);
329
+
330
+ if (error instanceof SDKError) {
331
+ switch (error.code) {
332
+ case NetworkErrorCode.CONNECTION_FAILED:
333
+ this.showMessage('网络连接失败,请检查网络状态', 'error');
334
+ break;
335
+ case NetworkErrorCode.UNAUTHORIZED:
336
+ this.showMessage('认证失效,请重新登录', 'error');
337
+ break;
338
+ case OperationErrorCode.UNITY_NOT_INITIALIZED:
339
+ this.showMessage('SDK未初始化,请先初始化', 'error');
340
+ break;
341
+ case OperationErrorCode.AVATAR_NOT_LOADED:
342
+ this.showMessage('数字人未加载,请先加载数字人', 'error');
343
+ break;
344
+ default:
345
+ this.showMessage(`操作失败: ${error.message}`, 'error');
346
+ }
347
+ } else {
348
+ this.showMessage(`系统错误: ${error.message}`, 'error');
349
+ }
350
+ }
351
+
352
+ // UI相关方法
353
+ setupUI() {
354
+ // 设置UI事件监听器
355
+ document.getElementById('init-btn').addEventListener('click', () => this.initSDK());
356
+ document.getElementById('play-motion-btn').addEventListener('click', () => this.playMotion('wave_hand'));
357
+ document.getElementById('speak-btn').addEventListener('click', () => {
358
+ const text = document.getElementById('text-input').value;
359
+ this.speakText(text);
360
+ });
361
+ document.getElementById('pause-btn').addEventListener('click', () => this.pauseBroadcast());
362
+ document.getElementById('resume-btn').addEventListener('click', () => this.resumeBroadcast());
363
+ document.getElementById('stop-btn').addEventListener('click', () => this.stopBroadcast());
364
+
365
+ // 摄像机切换
366
+ document.getElementById('camera-whole').addEventListener('click', () => this.setCamera(AvatarCameraType.WHOLE));
367
+ document.getElementById('camera-half').addEventListener('click', () => this.setCamera(AvatarCameraType.HALF));
368
+ document.getElementById('camera-face').addEventListener('click', () => this.setCamera(AvatarCameraType.FACE));
369
+ }
370
+
371
+ updateUI() {
372
+ // 更新UI状态
373
+ document.getElementById('init-btn').disabled = this.isInitialized;
374
+ document.getElementById('play-motion-btn').disabled = !this.isInitialized;
375
+ document.getElementById('speak-btn').disabled = !this.isInitialized;
376
+ }
377
+
378
+ updateProgressBar(percent) {
379
+ const progressBar = document.getElementById('progress-bar');
380
+ if (progressBar) {
381
+ progressBar.style.width = `${percent}%`;
382
+ progressBar.textContent = `${percent}%`;
383
+ }
384
+ }
385
+
386
+ showMessage(message, type = 'info') {
387
+ const messageDiv = document.getElementById('message');
388
+ if (messageDiv) {
389
+ messageDiv.textContent = message;
390
+ messageDiv.className = `message ${type}`;
391
+ setTimeout(() => {
392
+ messageDiv.textContent = '';
393
+ messageDiv.className = 'message';
394
+ }, 5000);
395
+ }
396
+ }
397
+
398
+ // 清理资源
399
+ destroy() {
400
+ if (this.sdk) {
401
+ this.sdk.destroy();
402
+ this.sdk = null;
403
+ this.isInitialized = false;
404
+ }
405
+ }
406
+ }
407
+
408
+ // 创建应用实例
409
+ const app = new AvatarApp();
410
+
411
+ // 页面卸载时清理
412
+ window.addEventListener('beforeunload', () => {
413
+ app.destroy();
414
+ });
415
+ ```
416
+
417
+ ## 📚 API 参考
418
+
419
+ ### ZEEAvatarSDK 类
420
+
421
+ #### 构造函数
422
+
423
+ ```typescript
424
+ constructor(config: IAvatarSDKConfig)
425
+ ```
426
+
427
+ 创建SDK实例,需要传入完整的配置对象。
428
+
429
+ **参数说明:**
430
+
431
+ ```typescript
432
+ interface IAvatarSDKConfig {
433
+ // Unity构建文件配置(必填)
434
+ loaderUrl: string; // Unity加载器脚本URL
435
+ dataUrl: string; // Unity数据文件URL
436
+ frameworkUrl: string; // Unity框架文件URL
437
+ codeUrl: string; // Unity代码文件URL
438
+
439
+ // 基础配置
440
+ containerId?: string; // 容器DOM元素ID,默认'unity-container'
441
+ token?: string; // 用户认证令牌
442
+ env?: 'dev' | 'test' | 'prod' | 'custom'; // 运行环境,默认'prod'
443
+ apiUrl?: string; // 自定义API地址(env='custom'时需要)
444
+ resourcesUrl?: string; // AB资源文件地址
445
+ idleMotionList?:string[]; // 待机动作编码列表,随机播放(排重方式),可选
446
+ // 回调函数
447
+ onProgress?: (progress: number) => void; // 加载进度回调
448
+ }
449
+ ```
450
+
451
+ #### 核心方法
452
+
453
+ ##### initializeAvatar()
454
+
455
+ ```typescript
456
+ async initializeAvatar(
457
+ avatarCode: string,
458
+ cameraType?: AvatarCameraType
459
+ ): Promise<IAvatarCallbackResponse>
460
+ ```
461
+
462
+ 初始化数字人,这是使用SDK的第一步,会自动完成Unity环境初始化和数字人加载。
463
+
464
+ **参数:**
465
+ - `avatarCode`: 数字人编码(必填)
466
+ - `cameraType`: 摄像机类型,可选值:
467
+ - `AvatarCameraType.WHOLE` - 全身视角(默认)
468
+ - `AvatarCameraType.HALF` - 半身视角
469
+ - `AvatarCameraType.FACE` - 面部视角
470
+
471
+ **返回值:**
472
+ ```typescript
473
+ interface IAvatarCallbackResponse {
474
+ success: boolean; // 操作是否成功
475
+ message: string; // 响应消息
476
+ errorCode?: number; // 错误代码(可选)
477
+ data?: any; // 返回数据(可选)
478
+ }
479
+ ```
480
+
481
+ **使用示例:**
482
+ ```javascript
483
+ try {
484
+ const response = await sdk.initializeAvatar('avatar001', AvatarCameraType.WHOLE);
485
+ if (response.success) {
486
+ console.log('数字人初始化成功');
487
+ }
488
+ } catch (error) {
489
+ console.error('初始化失败:', error);
490
+ }
491
+ ```
492
+
493
+ ##### playMotion()
494
+
495
+ ```typescript
496
+ async playMotion(clipCode: string): Promise<IAvatarCallbackResponse>
497
+ ```
498
+
499
+ 播放数字人动作。
500
+
501
+ **参数:**
502
+ - `clipCode`: 动作编码(必填)
503
+
504
+ **使用示例:**
505
+ ```javascript
506
+ try {
507
+ const response = await sdk.playMotion('wave_hand');
508
+ if (response.success) {
509
+ console.log('动作播放成功');
510
+ }
511
+ } catch (error) {
512
+ console.error('动作播放失败:', error);
513
+ }
514
+ ```
515
+
516
+ ##### getCurrentMotion()
517
+
518
+ ```typescript
519
+ async getCurrentMotion(getRemainingTime?: boolean): Promise<IAvatarCallbackResponse>
520
+ ```
521
+
522
+ 获取当前播放的动作信息。
523
+
524
+ **参数:**
525
+ - `getRemainingTime`: 是否获取剩余时间,默认false
526
+
527
+ **返回值:**
528
+ ```typescript
529
+ interface IAvatarCallbackResponse {
530
+ success: boolean;
531
+ message: string;
532
+ data?: {
533
+ motionId?: string; // 动作ID
534
+ motionRemainingTime?: number; // 动作剩余时间(秒)
535
+ };
536
+ }
537
+ ```
538
+
539
+ **使用示例:**
540
+ ```javascript
541
+ // 获取动作编码和剩余时间
542
+ const response = await sdk.getCurrentMotion(true);
543
+ if (response.success) {
544
+ console.log('动作编码:', response.data.motionId);
545
+ console.log('剩余时间:', response.data.motionRemainingTime);
546
+ }
547
+ ```
548
+
549
+ ##### setCamera()
550
+
551
+ ```typescript
552
+ async setCamera(cameraType: AvatarCameraType): Promise<IAvatarCallbackResponse>
553
+ ```
554
+
555
+ 设置摄像机类型。
556
+
557
+ **参数:**
558
+ - `cameraType`: 摄像机类型
559
+
560
+ **使用示例:**
561
+ ```javascript
562
+ await sdk.setCamera(AvatarCameraType.FACE);
563
+ ```
564
+
565
+ ##### unloadAvatar()
566
+
567
+ ```typescript
568
+ async unloadAvatar(): Promise<IAvatarCallbackResponse>
569
+ ```
570
+
571
+ 卸载数字人,释放数字人相关资源。
572
+
573
+ **使用示例:**
574
+ ```javascript
575
+ try {
576
+ const response = await sdk.unloadAvatar();
577
+ if (response.success) {
578
+ console.log('数字人卸载成功');
579
+ }
580
+ } catch (error) {
581
+ console.error('卸载失败:', error);
582
+ }
583
+ ```
584
+
585
+ #### 播报相关方法
586
+
587
+ ##### startBroadcast()
588
+
589
+ ```typescript
590
+ async startBroadcast(params: IBroadcastParams): Promise<void>
591
+ ```
592
+
593
+ 开始播报,支持文本转语音和音频播报两种模式。
594
+
595
+ **参数:**
596
+ ```typescript
597
+ interface IBroadcastParams {
598
+ type: BroadcastType; // 播报类型:'text' | 'audio'
599
+ humanCode: string; // 数字人编码
600
+ text?: string; // 播报文本(文本播报必填)
601
+ audioUrl?: string; // 音频URL(音频播报必填)
602
+ voiceCode?: string; // 音色编码(文本播报必填)
603
+ speed?: number; // 语速(0.5-2.0),默认1.0
604
+ volume: number; // 音量(0-1.0)
605
+ isSubtitle: boolean; // 是否显示字幕
606
+ motionList:string[]; // 播报过程中才播放的动作集合,每个动作间隔2-3秒循环播放,播报顺序根据`motionPlayMode`参数处理,默认为`随机`,可选
607
+ motionPlayMode?: 'random' | 'sequence'; // 动作播放模式,随机、顺序,随机=`random`,顺序=`sequence`
608
+ }
609
+ ```
610
+
611
+ **使用示例:**
612
+ ```javascript
613
+ // 文本播报
614
+ await sdk.startBroadcast({
615
+ type: BroadcastType.TEXT,
616
+ humanCode: 'avatar001',
617
+ text: '欢迎使用ZEE 3D Avatar SDK',
618
+ voiceCode: 'VOICE001',
619
+ volume: 1.0,
620
+ speed: 1.0,
621
+ isSubtitle: false
622
+ });
623
+
624
+ // 音频播报
625
+ await sdk.startBroadcast({
626
+ type: BroadcastType.AUDIO,
627
+ humanCode: 'avatar001',
628
+ audioUrl: 'https://example.com/audio.mp3',
629
+ text: '音频字幕内容',
630
+ volume: 1.0,
631
+ isSubtitle: true
632
+ });
633
+ ```
634
+
635
+ ##### pauseBroadcast()
636
+
637
+ ```typescript
638
+ async pauseBroadcast(resetIdle?:boolean): Promise<void>
639
+ ```
640
+
641
+ 暂停当前播报。
642
+
643
+ **参数**
644
+
645
+ - `resetIdle`: 动作和口型是否回到待机状态,默认为false
646
+
647
+ ##### resumeBroadcast()
648
+
649
+ ```typescript
650
+ async resumeBroadcast(): Promise<void>
651
+ ```
652
+
653
+ 恢复播报。
654
+
655
+ ##### stopBroadcast()
656
+
657
+ ```typescript
658
+ async stopBroadcast(): Promise<void>
659
+ ```
660
+
661
+ 停止播报。
662
+
663
+ ##### updateBroadcastCallbacks()
664
+
665
+ ```typescript
666
+ updateBroadcastCallbacks(callbacks: IBroadcastCallbacks): void
667
+ ```
668
+
669
+ 更新播报回调函数。
670
+
671
+ **参数:**
672
+ ```typescript
673
+ interface IBroadcastCallbacks {
674
+ onStart?: () => void; // 播报开始
675
+ onFinish?: () => void; // 播报完成
676
+ onError?: (error: Error) => void; // 播报错误
677
+ onPause?: () => void; // 播报暂停
678
+ onResume?: () => void; // 播报恢复
679
+ onStop?: () => void; // 播报停止
680
+ onAbort?: () => void; // 播报中断
681
+ }
682
+ ```
683
+
684
+ **使用示例:**
685
+ ```javascript
686
+ sdk.updateBroadcastCallbacks({
687
+ onStart: () => console.log('播报开始'),
688
+ onFinish: () => console.log('播报完成'),
689
+ onError: (error) => console.error('播报错误:', error)
690
+ });
691
+ ```
692
+
693
+ ##### getBroadcastStatus()
694
+
695
+ ```typescript
696
+ getBroadcastStatus(): {
697
+ isActive: boolean;
698
+ isGeneratingAudio: boolean;
699
+ hasReceivedAudio: boolean;
700
+ pendingCallbacks: number;
701
+ hasController: boolean;
702
+ }
703
+ ```
704
+
705
+ 获取播报状态信息。
706
+
707
+ #### 工具方法
708
+
709
+ ##### updateToken()
710
+
711
+ ```typescript
712
+ updateToken(token: string): void
713
+ ```
714
+
715
+ 更新认证Token。
716
+
717
+ ##### getInstanceId()
718
+
719
+ ```typescript
720
+ getInstanceId(): string
721
+ ```
722
+
723
+ 获取SDK实例的唯一标识符。
724
+
725
+ ##### isSDKInitialized()
726
+
727
+ ```typescript
728
+ isSDKInitialized(): boolean
729
+ ```
730
+
731
+ 检查SDK是否已初始化。
732
+
733
+ ##### isAvatarInitialized()
734
+
735
+ ```typescript
736
+ isAvatarInitialized(): boolean
737
+ ```
738
+
739
+ 检查数字人是否已加载。
740
+
741
+ ##### getConfig()
742
+
743
+ ```typescript
744
+ getConfig(): IAvatarSDKConfig
745
+ ```
746
+
747
+ 获取当前SDK配置。
748
+
749
+ ##### destroy()
750
+
751
+ ```typescript
752
+ destroy(): void
753
+ ```
754
+
755
+ 销毁SDK实例,清理所有资源。**重要:页面卸载前务必调用此方法!**
756
+
757
+ ## 🔧 高级功能
758
+
759
+ ### 多实例支持
760
+
761
+ 新版SDK支持在同一页面创建多个独立的SDK实例,每个实例拥有独立的回调函数命名空间,避免冲突。
762
+
763
+ ```javascript
764
+ // 创建多个SDK实例
765
+ const sdk1 = new ZEEAvatarSDK({
766
+ loaderUrl: './assets/Build/webgl.loader.js',
767
+ dataUrl: './assets/Build/webgl.data.unityweb',
768
+ frameworkUrl: './assets/Build/webgl.framework.js.unityweb',
769
+ codeUrl: './assets/Build/webgl.wasm.unityweb',
770
+ containerId: 'unity-container-1',
771
+ token: 'token1'
772
+ });
773
+
774
+ const sdk2 = new ZEEAvatarSDK({
775
+ loaderUrl: './assets/Build/webgl.loader.js',
776
+ dataUrl: './assets/Build/webgl.data.unityweb',
777
+ frameworkUrl: './assets/Build/webgl.framework.js.unityweb',
778
+ codeUrl: './assets/Build/webgl.wasm.unityweb',
779
+ containerId: 'unity-container-2',
780
+ token: 'token2'
781
+ });
782
+
783
+ // 分别初始化不同的数字人
784
+ await sdk1.initializeAvatar('avatar001');
785
+ await sdk2.initializeAvatar('avatar002');
786
+
787
+ // 独立控制
788
+ await sdk1.playMotion('wave_hand');
789
+ await sdk2.playMotion('bow');
790
+
791
+ // 清理资源
792
+ sdk1.destroy();
793
+ sdk2.destroy();
794
+ ```
795
+
796
+ ### 错误处理最佳实践
797
+
798
+ ```javascript
799
+ import {
800
+ SDKError,
801
+ NetworkErrorCode,
802
+ OperationErrorCode,
803
+ ResourceErrorCode,
804
+ SystemErrorCode,
805
+ ConfigErrorCode
806
+ } from '@zeewain/3d-avatar-sdk'
807
+
808
+ // 统一错误处理函数
809
+ function handleSDKError(error) {
810
+ if (error instanceof SDKError) {
811
+ console.error(`SDK错误 [${error.code}]: ${error.message}`);
812
+
813
+ // 根据错误类别处理
814
+ switch (error.category) {
815
+ case 'NETWORK':
816
+ handleNetworkError(error);
817
+ break;
818
+ case 'OPERATION':
819
+ handleOperationError(error);
820
+ break;
821
+ case 'RESOURCE':
822
+ handleResourceError(error);
823
+ break;
824
+ case 'SYSTEM':
825
+ handleSystemError(error);
826
+ break;
827
+ case 'CONFIG':
828
+ handleConfigError(error);
829
+ break;
830
+ }
831
+ } else {
832
+ console.error('系统错误:', error);
833
+ }
834
+ }
835
+
836
+ // 网络错误处理
837
+ function handleNetworkError(error) {
838
+ switch (error.code) {
839
+ case NetworkErrorCode.CONNECTION_FAILED:
840
+ showUserMessage('网络连接失败,请检查网络状态');
841
+ break;
842
+ case NetworkErrorCode.UNAUTHORIZED:
843
+ showUserMessage('认证失效,请重新登录');
844
+ // 可以触发重新登录流程
845
+ redirectToLogin();
846
+ break;
847
+ case NetworkErrorCode.REQUEST_TIMEOUT:
848
+ showUserMessage('请求超时,请稍后重试');
849
+ break;
850
+ }
851
+ }
852
+
853
+ // 操作错误处理
854
+ function handleOperationError(error) {
855
+ switch (error.code) {
856
+ case OperationErrorCode.UNITY_NOT_INITIALIZED:
857
+ showUserMessage('系统未初始化,请稍后重试');
858
+ break;
859
+ case OperationErrorCode.AVATAR_NOT_LOADED:
860
+ showUserMessage('数字人未加载,请先加载数字人');
861
+ break;
862
+ case OperationErrorCode.OPERATION_TIMEOUT:
863
+ showUserMessage('操作超时,请稍后重试');
864
+ break;
865
+ }
866
+ }
867
+
868
+ // 在SDK操作中使用
869
+ try {
870
+ await sdk.initializeAvatar('avatar001');
871
+ } catch (error) {
872
+ handleSDKError(error);
873
+ }
874
+ ```
875
+
876
+ ### 性能优化建议
877
+
878
+ #### 1. 资源预加载
879
+
880
+ ```javascript
881
+ // 预加载Unity资源
882
+ const preloadPromise = fetch('./assets/Build/webgl.data.unityweb', {
883
+ method: 'HEAD'
884
+ });
885
+
886
+ // 在需要时再创建SDK实例
887
+ preloadPromise.then(() => {
888
+ const sdk = new ZEEAvatarSDK(config);
889
+ sdk.initializeAvatar('avatar001');
890
+ });
891
+ ```
892
+
893
+ #### 2. 懒加载策略
894
+
895
+ ```javascript
896
+ class LazyAvatarLoader {
897
+ constructor() {
898
+ this.sdk = null;
899
+ this.isLoading = false;
900
+ }
901
+
902
+ async loadWhenNeeded() {
903
+ if (this.sdk || this.isLoading) return;
904
+
905
+ this.isLoading = true;
906
+ try {
907
+ this.sdk = new ZEEAvatarSDK(config);
908
+ await this.sdk.initializeAvatar('avatar001');
909
+ } finally {
910
+ this.isLoading = false;
911
+ }
912
+ }
913
+
914
+ async speak(text) {
915
+ await this.loadWhenNeeded();
916
+ return this.sdk.startBroadcast({
917
+ type: BroadcastType.TEXT,
918
+ humanCode: 'avatar001',
919
+ text: text,
920
+ voiceCode: 'VOICE001',
921
+ volume: 1.0,
922
+ isSubtitle: false
923
+ });
924
+ }
925
+ }
926
+ ```
927
+
928
+ #### 3. 内存管理
929
+
930
+ ```javascript
931
+ class AvatarManager {
932
+ constructor() {
933
+ this.sdk = null;
934
+ this.isActive = false;
935
+ }
936
+
937
+ async initialize() {
938
+ if (this.sdk) return;
939
+
940
+ this.sdk = new ZEEAvatarSDK(config);
941
+ await this.sdk.initializeAvatar('avatar001');
942
+ this.isActive = true;
943
+ }
944
+
945
+ async pause() {
946
+ if (this.sdk && this.isActive) {
947
+ await this.sdk.unloadAvatar();
948
+ this.isActive = false;
949
+ }
950
+ }
951
+
952
+ async resume() {
953
+ if (this.sdk && !this.isActive) {
954
+ await this.sdk.initializeAvatar('avatar001');
955
+ this.isActive = true;
956
+ }
957
+ }
958
+
959
+ destroy() {
960
+ if (this.sdk) {
961
+ this.sdk.destroy();
962
+ this.sdk = null;
963
+ this.isActive = false;
964
+ }
965
+ }
966
+ }
967
+
968
+ // 页面可见性变化时管理资源
969
+ const avatarManager = new AvatarManager();
970
+
971
+ document.addEventListener('visibilitychange', () => {
972
+ if (document.hidden) {
973
+ avatarManager.pause();
974
+ } else {
975
+ avatarManager.resume();
976
+ }
977
+ });
978
+ ```
979
+
980
+ ## 🚨 错误码参考
981
+
982
+ ### 网络错误 (1xxx)
983
+ | 错误码 | 常量名 | 描述 | 处理建议 |
984
+ |:------:|:-------|:-----|:---------|
985
+ | 1001 | `NetworkErrorCode.CONNECTION_FAILED` | 网络连接失败 | 检查网络状态,重试连接 |
986
+ | 1002 | `NetworkErrorCode.REQUEST_TIMEOUT` | 请求超时 | 检查网络延迟,稍后重试 |
987
+ | 1003 | `NetworkErrorCode.SERVER_ERROR` | 服务器错误 | 联系技术支持 |
988
+ | 1004 | `NetworkErrorCode.UNAUTHORIZED` | 未授权访问 | 更新Token或重新登录 |
989
+
990
+ ### 操作错误 (2xxx)
991
+ | 错误码 | 常量名 | 描述 | 处理建议 |
992
+ |:------:|:-------|:-----|:---------|
993
+ | 2001 | `OperationErrorCode.UNITY_NOT_INITIALIZED` | Unity实例未初始化 | 先调用initializeAvatar() |
994
+ | 2002 | `OperationErrorCode.AVATAR_NOT_LOADED` | 数字人未加载 | 确保数字人已成功加载 |
995
+ | 2003 | `OperationErrorCode.OPERATION_FAILED` | 操作执行失败 | 检查参数和网络状态 |
996
+ | 2004 | `OperationErrorCode.OPERATION_TIMEOUT` | 操作超时 | 稍后重试 |
997
+ | 2005 | `OperationErrorCode.OPERATION_CANCELLED` | 操作被取消 | 重新执行操作 |
998
+
999
+ ### 资源错误 (3xxx)
1000
+ | 错误码 | 常量名 | 描述 | 处理建议 |
1001
+ |:------:|:-------|:-----|:---------|
1002
+ | 3001 | `ResourceErrorCode.LOAD_FAILED` | 资源加载失败 | 检查资源URL和网络 |
1003
+ | 3002 | `ResourceErrorCode.FILE_CORRUPTED` | 资源文件损坏 | 重新下载资源文件 |
1004
+ | 3003 | `ResourceErrorCode.NOT_FOUND` | 资源不存在 | 检查资源路径 |
1005
+ | 3004 | `ResourceErrorCode.UNSUPPORTED_FORMAT` | 资源格式不支持 | 使用支持的格式 |
1006
+
1007
+ ### 系统错误 (4xxx)
1008
+ | 错误码 | 常量名 | 描述 | 处理建议 |
1009
+ |:------:|:-------|:-----|:---------|
1010
+ | 4001 | `SystemErrorCode.OUT_OF_MEMORY` | 内存不足 | 关闭其他应用,释放内存 |
1011
+ | 4002 | `SystemErrorCode.GPU_NOT_SUPPORTED` | GPU不支持 | 更新显卡驱动 |
1012
+ | 4003 | `SystemErrorCode.WEBGL_NOT_SUPPORTED` | WebGL不支持 | 更新浏览器 |
1013
+ | 4004 | `SystemErrorCode.BROWSER_NOT_COMPATIBLE` | 浏览器不兼容 | 使用推荐浏览器 |
1014
+
1015
+ ### 配置错误 (5xxx)
1016
+ | 错误码 | 常量名 | 描述 | 处理建议 |
1017
+ |:------:|:-------|:-----|:---------|
1018
+ | 5001 | `ConfigErrorCode.INVALID_CONFIG` | 配置参数无效 | 检查配置格式 |
1019
+ | 5002 | `ConfigErrorCode.MISSING_REQUIRED_PARAM` | 必需参数缺失 | 补充必需参数 |
1020
+ | 5003 | `ConfigErrorCode.PARAM_OUT_OF_RANGE` | 参数值超出范围 | 调整参数值 |
1021
+ | 5004 | `ConfigErrorCode.INVALID_JSON_FORMAT` | JSON格式错误 | 检查JSON格式 |
1022
+
1023
+ ## 🌍 浏览器兼容性
1024
+
1025
+ ### 支持的浏览器版本
1026
+
1027
+ | 浏览器 | 最低版本 | 推荐版本 | 备注 |
1028
+ |:-------|:---------|:---------|:-----|
1029
+ | **Chrome** | 60+ | 90+ | 最佳性能 |
1030
+ | **Firefox** | 55+ | 85+ | 良好支持 |
1031
+ | **Safari** | 10.1+ | 14+ | 需要WebGL 2.0 |
1032
+ | **Edge** | 16+ | 90+ | 基于Chromium |
1033
+ | **IE** | 11 | - | 需要ES5版本 |
1034
+
1035
+ ### 模块系统兼容性
1036
+
1037
+ ```javascript
1038
+ // 现代浏览器 (ES6+)
1039
+ import { ZEEAvatarSDK } from '@zeewain/3d-avatar-sdk'
1040
+
1041
+ // IE11 兼容
1042
+ import { ZEEAvatarSDK } from '@zeewain/3d-avatar-sdk/es5'
1043
+
1044
+ // CommonJS (Node.js)
1045
+ const { ZEEAvatarSDK } = require('@zeewain/3d-avatar-sdk')
1046
+ ```
1047
+
1048
+ ### 功能检测
1049
+
1050
+ ```javascript
1051
+ // 检查WebGL支持
1052
+ function checkWebGLSupport() {
1053
+ try {
1054
+ const canvas = document.createElement('canvas');
1055
+ const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
1056
+ return !!gl;
1057
+ } catch (e) {
1058
+ return false;
1059
+ }
1060
+ }
1061
+
1062
+ // 检查浏览器兼容性
1063
+ function checkBrowserCompatibility() {
1064
+ const checks = {
1065
+ webgl: checkWebGLSupport(),
1066
+ promise: typeof Promise !== 'undefined',
1067
+ fetch: typeof fetch !== 'undefined',
1068
+ es6: typeof Symbol !== 'undefined'
1069
+ };
1070
+
1071
+ return checks;
1072
+ }
1073
+
1074
+ // 使用检测结果
1075
+ const compatibility = checkBrowserCompatibility();
1076
+ if (!compatibility.webgl) {
1077
+ console.error('浏览器不支持WebGL');
1078
+ }
1079
+ ```
1080
+
1081
+ ## 📋 最佳实践
1082
+
1083
+ ### 1. 生命周期管理
1084
+
1085
+ ```javascript
1086
+ class AvatarComponent {
1087
+ constructor() {
1088
+ this.sdk = null;
1089
+ this.isDestroyed = false;
1090
+ }
1091
+
1092
+ async mount() {
1093
+ if (this.isDestroyed) return;
1094
+
1095
+ this.sdk = new ZEEAvatarSDK(config);
1096
+
1097
+ // 设置错误处理
1098
+ this.setupErrorHandling();
1099
+
1100
+ // 初始化数字人
1101
+ await this.sdk.initializeAvatar('avatar001');
1102
+ }
1103
+
1104
+ async unmount() {
1105
+ this.isDestroyed = true;
1106
+
1107
+ if (this.sdk) {
1108
+ try {
1109
+ await this.sdk.unloadAvatar();
1110
+ } catch (error) {
1111
+ console.warn('卸载数字人时出错:', error);
1112
+ } finally {
1113
+ this.sdk.destroy();
1114
+ this.sdk = null;
1115
+ }
1116
+ }
1117
+ }
1118
+
1119
+ setupErrorHandling() {
1120
+ // 全局错误处理
1121
+ window.addEventListener('error', (event) => {
1122
+ if (event.error && event.error.name === 'SDKError') {
1123
+ this.handleSDKError(event.error);
1124
+ }
1125
+ });
1126
+ }
1127
+ }
1128
+ ```
1129
+
1130
+ ### 2. 状态管理
1131
+
1132
+ ```javascript
1133
+ class AvatarState {
1134
+ constructor() {
1135
+ this.state = {
1136
+ isInitialized: false,
1137
+ isLoading: false,
1138
+ isSpeaking: false,
1139
+ currentMotion: null,
1140
+ error: null
1141
+ };
1142
+
1143
+ this.listeners = [];
1144
+ }
1145
+
1146
+ setState(newState) {
1147
+ this.state = { ...this.state, ...newState };
1148
+ this.notifyListeners();
1149
+ }
1150
+
1151
+ subscribe(listener) {
1152
+ this.listeners.push(listener);
1153
+ return () => {
1154
+ this.listeners = this.listeners.filter(l => l !== listener);
1155
+ };
1156
+ }
1157
+
1158
+ notifyListeners() {
1159
+ this.listeners.forEach(listener => listener(this.state));
1160
+ }
1161
+ }
1162
+
1163
+ // 使用状态管理
1164
+ const avatarState = new AvatarState();
1165
+
1166
+ avatarState.subscribe((state) => {
1167
+ console.log('状态变化:', state);
1168
+ updateUI(state);
1169
+ });
1170
+ ```
1171
+
1172
+ ### 3. 配置管理
1173
+
1174
+ ```javascript
1175
+ // 配置文件 config.js
1176
+ export const avatarConfig = {
1177
+ development: {
1178
+ loaderUrl: './assets/Build/webgl.loader.js',
1179
+ dataUrl: './assets/Build/webgl.data.unityweb',
1180
+ frameworkUrl: './assets/Build/webgl.framework.js.unityweb',
1181
+ codeUrl: './assets/Build/webgl.wasm.unityweb',
1182
+ containerId: 'unity-container',
1183
+ env: 'dev',
1184
+ token: 'dev-token'
1185
+ },
1186
+ production: {
1187
+ loaderUrl: 'https://cdn.example.com/webgl.loader.js',
1188
+ dataUrl: 'https://cdn.example.com/webgl.data.unityweb',
1189
+ frameworkUrl: 'https://cdn.example.com/webgl.framework.js.unityweb',
1190
+ codeUrl: 'https://cdn.example.com/webgl.wasm.unityweb',
1191
+ containerId: 'unity-container',
1192
+ env: 'prod',
1193
+ token: process.env.AVATAR_TOKEN
1194
+ }
1195
+ };
1196
+
1197
+ // 使用配置
1198
+ const config = avatarConfig[process.env.NODE_ENV || 'development'];
1199
+ const sdk = new ZEEAvatarSDK(config);
1200
+ ```
1201
+
1202
+ ## 🔄 从旧版本迁移
1203
+
1204
+ ### 主要变化
1205
+
1206
+ | 旧版本 | 新版本 | 变化说明 |
1207
+ |:-------|:-------|:---------|
1208
+ | 多个类 | 单个类 | `ZEEAvatarLoader` + `AvatarService` + `BroadcastService` → `ZEEAvatarSDK` |
1209
+ | 分步初始化 | 统一初始化 | `init()` + `initializeAvatar()` → `initializeAvatar()` |
1210
+ | 手动管理 | 自动管理 | 内部自动管理各个服务实例 |
1211
+ | 回调冲突 | 回调隔离 | 支持多实例,回调函数完全隔离 |
1212
+ | 暴露内部 | 封装内部 | 移除 `getUnityInstance()` 等内部方法 |
1213
+
1214
+ ### 迁移示例
1215
+
1216
+ **旧版本代码:**
1217
+ ```javascript
1218
+ // 旧版本需要创建多个实例
1219
+ const loader = new ZEEAvatarLoader(config);
1220
+ const avatarAPI = await loader.init();
1221
+ const broadcastService = new BroadcastService({
1222
+ unityInstance: loader.getInstance(),
1223
+ callbacks: { /* ... */ }
1224
+ });
1225
+
1226
+ // 分步初始化
1227
+ await avatarAPI.initializeAvatar('avatar001', 'whole');
1228
+ ```
1229
+
1230
+ **新版本代码:**
1231
+ ```javascript
1232
+ // 新版本只需要一个实例
1233
+ const sdk = new ZEEAvatarSDK(config);
1234
+
1235
+ // 统一初始化
1236
+ await sdk.initializeAvatar('avatar001', AvatarCameraType.WHOLE);
1237
+ ```
1238
+
1239
+ ### 完整迁移指南
1240
+
1241
+ ```javascript
1242
+ // 旧版本完整代码
1243
+ class OldAvatarApp {
1244
+ async init() {
1245
+ // 1. 创建加载器
1246
+ this.loader = new ZEEAvatarLoader(config);
1247
+
1248
+ // 2. 初始化Unity
1249
+ this.avatarAPI = await this.loader.init();
1250
+
1251
+ // 3. 创建播报服务
1252
+ this.broadcastService = new BroadcastService({
1253
+ unityInstance: this.loader.getInstance(),
1254
+ callbacks: {
1255
+ onStart: () => console.log('播报开始'),
1256
+ onFinish: () => console.log('播报完成')
1257
+ }
1258
+ });
1259
+
1260
+ // 4. 初始化数字人
1261
+ await this.avatarAPI.initializeAvatar('avatar001', 'whole');
1262
+ }
1263
+
1264
+ async speak(text) {
1265
+ await this.broadcastService.startBroadcast({
1266
+ type: 'text',
1267
+ humanCode: 'avatar001',
1268
+ text: text,
1269
+ voiceCode: 'VOICE001',
1270
+ volume: 1.0,
1271
+ isSubtitle: false
1272
+ });
1273
+ }
1274
+
1275
+ destroy() {
1276
+ this.broadcastService?.destroy();
1277
+ this.loader?.destroy();
1278
+ }
1279
+ }
1280
+
1281
+ // 新版本对应代码
1282
+ class NewAvatarApp {
1283
+ async init() {
1284
+ // 1. 创建SDK实例
1285
+ this.sdk = new ZEEAvatarSDK(config);
1286
+
1287
+ // 2. 设置播报回调
1288
+ this.sdk.updateBroadcastCallbacks({
1289
+ onStart: () => console.log('播报开始'),
1290
+ onFinish: () => console.log('播报完成')
1291
+ });
1292
+
1293
+ // 3. 统一初始化(包含Unity初始化和数字人加载)
1294
+ await this.sdk.initializeAvatar('avatar001', AvatarCameraType.WHOLE);
1295
+ }
1296
+
1297
+ async speak(text) {
1298
+ await this.sdk.startBroadcast({
1299
+ type: BroadcastType.TEXT,
1300
+ humanCode: 'avatar001',
1301
+ text: text,
1302
+ voiceCode: 'VOICE001',
1303
+ volume: 1.0,
1304
+ isSubtitle: false
1305
+ });
1306
+ }
1307
+
1308
+ destroy() {
1309
+ this.sdk?.destroy();
1310
+ }
1311
+ }
1312
+ ```
1313
+
1314
+ ## ❓ 常见问题
1315
+
1316
+ ### Q1: Unity内容无法加载怎么办?
1317
+
1318
+ **A1:** 请按以下步骤排查:
1319
+
1320
+ 1. **检查文件路径**:确保Unity构建文件路径配置正确
1321
+ 2. **CORS问题**:如果文件在不同域名,需要配置CORS
1322
+ 3. **网络连接**:确认网络可以访问构建文件URL
1323
+ 4. **浏览器兼容性**:确认浏览器支持WebGL 2.0
1324
+
1325
+ ```javascript
1326
+ // 检查资源可访问性
1327
+ async function checkResourcesAvailability(config) {
1328
+ const resources = [
1329
+ config.loaderUrl,
1330
+ config.dataUrl,
1331
+ config.frameworkUrl,
1332
+ config.codeUrl
1333
+ ];
1334
+
1335
+ for (const url of resources) {
1336
+ try {
1337
+ const response = await fetch(url, { method: 'HEAD' });
1338
+ if (!response.ok) {
1339
+ console.error(`资源不可访问: ${url}`);
1340
+ }
1341
+ } catch (error) {
1342
+ console.error(`资源检查失败: ${url}`, error);
1343
+ }
1344
+ }
1345
+ }
1346
+ ```
1347
+
1348
+ ### Q2: 播报没有声音怎么办?
1349
+
1350
+ **A2:** 请检查以下几点:
1351
+
1352
+ 1. **音量设置**:确认volume参数大于0
1353
+ 2. **音频格式**:确认音频文件格式为MP3或WAV
1354
+ 3. **音色编码**:确认voiceCode正确
1355
+ 4. **浏览器策略**:现代浏览器需要用户交互才能播放音频
1356
+
1357
+ ```javascript
1358
+ // 处理浏览器自动播放限制
1359
+ async function enableAudioPlayback() {
1360
+ try {
1361
+ // 创建一个静音的音频上下文
1362
+ const audioContext = new (window.AudioContext || window.webkitAudioContext)();
1363
+
1364
+ // 用户交互后恢复音频上下文
1365
+ document.addEventListener('click', async () => {
1366
+ if (audioContext.state === 'suspended') {
1367
+ await audioContext.resume();
1368
+ }
1369
+ }, { once: true });
1370
+ } catch (error) {
1371
+ console.warn('音频上下文初始化失败:', error);
1372
+ }
1373
+ }
1374
+ ```
1375
+
1376
+ ### Q3: 如何处理多实例场景?
1377
+
1378
+ **A3:** 新版SDK原生支持多实例,每个实例都有独立的回调命名空间:
1379
+
1380
+ ```javascript
1381
+ // 创建多个实例
1382
+ const instances = [];
1383
+
1384
+ for (let i = 0; i < 3; i++) {
1385
+ const sdk = new ZEEAvatarSDK({
1386
+ ...config,
1387
+ containerId: `unity-container-${i}`
1388
+ });
1389
+
1390
+ instances.push(sdk);
1391
+
1392
+ // 每个实例独立初始化
1393
+ await sdk.initializeAvatar(`avatar00${i + 1}`);
1394
+ }
1395
+
1396
+ // 独立控制每个实例
1397
+ instances[0].playMotion('wave_hand');
1398
+ instances[1].playMotion('bow');
1399
+ instances[2].startBroadcast({
1400
+ type: BroadcastType.TEXT,
1401
+ humanCode: 'avatar003',
1402
+ text: '我是第三个实例',
1403
+ voiceCode: 'VOICE001',
1404
+ volume: 1.0,
1405
+ isSubtitle: false
1406
+ });
1407
+ ```
1408
+
1409
+ ### Q4: 如何优化加载性能?
1410
+
1411
+ **A4:** 建议采用以下优化策略:
1412
+
1413
+ ```javascript
1414
+ // 1. 资源预加载
1415
+ const preloadResources = async (config) => {
1416
+ const resources = [
1417
+ config.dataUrl,
1418
+ config.frameworkUrl,
1419
+ config.codeUrl
1420
+ ];
1421
+
1422
+ // 并行预加载
1423
+ await Promise.all(
1424
+ resources.map(url =>
1425
+ fetch(url, { method: 'HEAD' })
1426
+ )
1427
+ );
1428
+ };
1429
+
1430
+ // 2. 懒加载策略
1431
+ class LazyAvatarManager {
1432
+ constructor(config) {
1433
+ this.config = config;
1434
+ this.sdk = null;
1435
+ this.loadPromise = null;
1436
+ }
1437
+
1438
+ async load() {
1439
+ if (this.loadPromise) return this.loadPromise;
1440
+
1441
+ this.loadPromise = this.doLoad();
1442
+ return this.loadPromise;
1443
+ }
1444
+
1445
+ async doLoad() {
1446
+ if (this.sdk) return this.sdk;
1447
+
1448
+ this.sdk = new ZEEAvatarSDK(this.config);
1449
+ await this.sdk.initializeAvatar('avatar001');
1450
+ return this.sdk;
1451
+ }
1452
+ }
1453
+
1454
+ // 3. 缓存策略
1455
+ const avatarCache = new Map();
1456
+
1457
+ function getCachedSDK(key, config) {
1458
+ if (!avatarCache.has(key)) {
1459
+ avatarCache.set(key, new ZEEAvatarSDK(config));
1460
+ }
1461
+ return avatarCache.get(key);
1462
+ }
1463
+ ```
1464
+
1465
+ ### Q5: 如何处理内存泄漏?
1466
+
1467
+ **A5:** 务必正确管理SDK生命周期:
1468
+
1469
+ ```javascript
1470
+ class AvatarManager {
1471
+ constructor() {
1472
+ this.sdk = null;
1473
+ this.isDestroyed = false;
1474
+
1475
+ // 监听页面卸载事件
1476
+ window.addEventListener('beforeunload', () => {
1477
+ this.destroy();
1478
+ });
1479
+
1480
+ // 监听页面可见性变化
1481
+ document.addEventListener('visibilitychange', () => {
1482
+ if (document.hidden) {
1483
+ this.pause();
1484
+ } else {
1485
+ this.resume();
1486
+ }
1487
+ });
1488
+ }
1489
+
1490
+ async init() {
1491
+ if (this.isDestroyed) return;
1492
+
1493
+ this.sdk = new ZEEAvatarSDK(config);
1494
+ await this.sdk.initializeAvatar('avatar001');
1495
+ }
1496
+
1497
+ async pause() {
1498
+ if (this.sdk) {
1499
+ await this.sdk.unloadAvatar();
1500
+ }
1501
+ }
1502
+
1503
+ async resume() {
1504
+ if (this.sdk) {
1505
+ await this.sdk.initializeAvatar('avatar001');
1506
+ }
1507
+ }
1508
+
1509
+ destroy() {
1510
+ this.isDestroyed = true;
1511
+
1512
+ if (this.sdk) {
1513
+ this.sdk.destroy();
1514
+ this.sdk = null;
1515
+ }
1516
+ }
1517
+ }
1518
+ ```
1519
+
1520
+ ## 🆚 新旧版本对比
1521
+
1522
+ ### 使用体验对比
1523
+
1524
+ | 对比项 | 旧版本 | 新版本 | 改进说明 |
1525
+ |:-------|:-------|:-------|:---------|
1526
+ | **类数量** | 3个类 | 1个类 | 简化使用 |
1527
+ | **初始化步骤** | 4步 | 1步 | 大幅简化 |
1528
+ | **多实例支持** | ❌ | ✅ | 新增功能 |
1529
+ | **回调冲突** | 有冲突 | 完全隔离 | 问题解决 |
1530
+ | **内部暴露** | 过度暴露 | 合理封装 | 更安全 |
1531
+ | **错误处理** | 分散处理 | 统一管理 | 更规范 |
1532
+
1533
+ ### 代码量对比
1534
+
1535
+ **旧版本:**
1536
+ ```javascript
1537
+ // 需要 15+ 行代码初始化
1538
+ const loader = new ZEEAvatarLoader(config);
1539
+ const avatarAPI = await loader.init();
1540
+ const broadcastService = new BroadcastService({
1541
+ unityInstance: loader.getInstance(),
1542
+ callbacks: { /* ... */ }
1543
+ });
1544
+ await avatarAPI.initializeAvatar('avatar001', 'whole');
1545
+ ```
1546
+
1547
+ **新版本:**
1548
+ ```javascript
1549
+ // 只需要 3 行代码
1550
+ const sdk = new ZEEAvatarSDK(config);
1551
+ await sdk.initializeAvatar('avatar001', AvatarCameraType.WHOLE);
1552
+ ```
1553
+
1554
+
1555
+ **ZEE 3D Avatar SDK** - 让3D数字人集成变得简单而强大! 🚀
1556
+
1557
+ ---
1558
+
1559
+ ## 📖 旧版本文档
1560
+
1561
+ > **注意**:以下是旧版本SDK的使用说明,仅供未更新的用户参考。**强烈建议升级到新版本以获得更好的开发体验。**
1562
+
1563
+ <details>
1564
+ <summary>点击展开旧版本文档</summary>
1565
+
1566
+ ### 旧版本使用方式
1567
+
1568
+ #### 1. 创建多个实例
1569
+ ```javascript
1570
+ // 旧版本需要分别创建多个实例
1571
+ const loader = new ZEEAvatarLoader(config);
1572
+ const avatarAPI = await loader.init();
1573
+ const broadcastService = new BroadcastService({
1574
+ unityInstance: loader.getInstance(),
1575
+ callbacks: {
1576
+ onStart: () => console.log('播报开始'),
1577
+ onFinish: () => console.log('播报完成'),
1578
+ onError: (error) => console.error('播报错误:', error)
1579
+ }
1580
+ });
1581
+ ```
1582
+
1583
+ #### 2. 分步初始化
1584
+ ```javascript
1585
+ // 旧版本需要分步初始化
1586
+ await avatarAPI.initializeAvatar('avatar001', 'whole');
1587
+ ```
1588
+
1589
+ #### 3. 手动管理生命周期
1590
+ ```javascript
1591
+ // 旧版本需要手动管理各个实例
1592
+ broadcastService.destroy();
1593
+ loader.destroy();
1594
+ ```
1595
+
1596
+ **⚠️ 旧版本问题**:
1597
+ - 多实例时回调函数冲突
1598
+ - 初始化流程复杂
1599
+ - 内部实例暴露过多
1600
+ - 错误处理不统一
1601
+
1602
+ **🚀 升级建议**:
1603
+ 请尽快升级到新版本,享受更简单、更强大的SDK体验!
1604
+
1605
+ </details>
1606
+