@zeewain/3d-avatar-sdk 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +404 -0
- package/dist/assets/Build/webgl.data.unityweb +0 -0
- package/dist/assets/Build/webgl.framework.js.unityweb +0 -0
- package/dist/assets/Build/webgl.loader.js +1 -0
- package/dist/assets/Build/webgl.wasm.unityweb +0 -0
- package/dist/core/api/avatar.d.ts +23 -0
- package/dist/core/api/broadcast.d.ts +55 -0
- package/dist/core/api/index.d.ts +2 -0
- package/dist/core/config/env.d.ts +21 -0
- package/dist/core/index.d.ts +16 -0
- package/dist/core/interfaces/avatar.d.ts +6 -0
- package/dist/core/interfaces/broadcast.d.ts +52 -0
- package/dist/core/interfaces/config.d.ts +8 -0
- package/dist/core/interfaces/index.d.ts +4 -0
- package/dist/core/interfaces/instance.d.ts +5 -0
- package/dist/core/loaders/index.d.ts +1 -0
- package/dist/core/loaders/unity.d.ts +10 -0
- package/dist/index.d.ts +137 -0
- package/dist/index.es5.d.ts +2 -0
- package/dist/index.es5.js +12878 -0
- package/dist/index.es5.umd.js +12879 -0
- package/dist/index.esm.js +753 -0
- package/dist/index.umd.cjs +763 -0
- package/package.json +90 -0
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
/******************************************************************************
|
|
2
|
+
Copyright (c) Microsoft Corporation.
|
|
3
|
+
|
|
4
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
5
|
+
purpose with or without fee is hereby granted.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
8
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
9
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
10
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
11
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
12
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
13
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
14
|
+
***************************************************************************** */
|
|
15
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
19
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
20
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
21
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
22
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
23
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
24
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
29
|
+
var e = new Error(message);
|
|
30
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
class UnityLoader {
|
|
34
|
+
constructor(config) {
|
|
35
|
+
this.config = Object.assign(Object.assign({}, config), { containerId: config.containerId || 'unity-container' });
|
|
36
|
+
}
|
|
37
|
+
init() {
|
|
38
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
// 创建容器
|
|
41
|
+
const container = this.createContainer();
|
|
42
|
+
// 加载UnityLoader脚本
|
|
43
|
+
this.loadUnityLoader().then(() => {
|
|
44
|
+
// 创建Unity实例
|
|
45
|
+
this.createUnityInstance(container, resolve, reject);
|
|
46
|
+
}).catch(reject);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
createContainer() {
|
|
51
|
+
const container = document.getElementById('unity-container') || document.createElement('div');
|
|
52
|
+
container.style.width = '100%';
|
|
53
|
+
container.style.height = '100%';
|
|
54
|
+
container.style.position = 'relative';
|
|
55
|
+
const containertip = document.getElementById('unity-container-tip');
|
|
56
|
+
if (containertip) {
|
|
57
|
+
containertip.remove();
|
|
58
|
+
}
|
|
59
|
+
return container;
|
|
60
|
+
}
|
|
61
|
+
loadUnityLoader() {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
if (window.createUnityInstance) {
|
|
64
|
+
return resolve();
|
|
65
|
+
}
|
|
66
|
+
const script = document.createElement('script');
|
|
67
|
+
script.src = this.config.loaderUrl;
|
|
68
|
+
script.async = true;
|
|
69
|
+
script.crossOrigin = 'anonymous';
|
|
70
|
+
script.onload = () => {
|
|
71
|
+
if (typeof window.createUnityInstance === 'function') {
|
|
72
|
+
resolve();
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
reject(new Error('createUnityInstance function not found'));
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
script.onerror = (error) => {
|
|
79
|
+
reject(new Error(`Failed to load UnityLoader: ${error}`));
|
|
80
|
+
};
|
|
81
|
+
document.head.appendChild(script);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
createUnityInstance(container, resolve, reject) {
|
|
85
|
+
const canvas = document.createElement('canvas');
|
|
86
|
+
canvas.id = 'unity-canvas';
|
|
87
|
+
canvas.width = 640;
|
|
88
|
+
canvas.height = 960;
|
|
89
|
+
canvas.style.width = '100%';
|
|
90
|
+
canvas.style.height = '100%';
|
|
91
|
+
canvas.style.background = '#ffffff';
|
|
92
|
+
container.appendChild(canvas);
|
|
93
|
+
// 移动设备适配
|
|
94
|
+
if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
|
|
95
|
+
const meta = document.createElement('meta');
|
|
96
|
+
meta.name = 'viewport';
|
|
97
|
+
meta.content = 'width=device-width, height=device-height, initial-scale=1.0, user-scalable=no, shrink-to-fit=yes';
|
|
98
|
+
document.head.appendChild(meta);
|
|
99
|
+
canvas.style.width = '100%';
|
|
100
|
+
canvas.style.height = '100%';
|
|
101
|
+
canvas.style.position = 'fixed';
|
|
102
|
+
}
|
|
103
|
+
if (typeof window.createUnityInstance === 'function') {
|
|
104
|
+
window.createUnityInstance(canvas, {
|
|
105
|
+
dataUrl: this.config.dataUrl,
|
|
106
|
+
frameworkUrl: this.config.frameworkUrl,
|
|
107
|
+
codeUrl: this.config.codeUrl,
|
|
108
|
+
companyName: '广州紫为云科技有限公司',
|
|
109
|
+
productName: '数字人SDK',
|
|
110
|
+
productVersion: '1.0'
|
|
111
|
+
}, (progress) => {
|
|
112
|
+
if (this.config.onProgress) {
|
|
113
|
+
this.config.onProgress(progress);
|
|
114
|
+
}
|
|
115
|
+
}).then((instance) => {
|
|
116
|
+
resolve(instance);
|
|
117
|
+
}).catch((error) => {
|
|
118
|
+
reject(error);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
reject(new Error('createUnityInstance is not defined on window.'));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
class AvatarAPI {
|
|
128
|
+
constructor(unityInstance, containerId) {
|
|
129
|
+
this.unityInstance = null;
|
|
130
|
+
this.avatarSDK = 'AvatarSDK';
|
|
131
|
+
// 存储待处理的 Promise 解析函数
|
|
132
|
+
this.pendingCallbacks = new Map();
|
|
133
|
+
this.unityInstance = unityInstance;
|
|
134
|
+
this.containerId = containerId;
|
|
135
|
+
// 注册单一全局回调函数
|
|
136
|
+
window.uniAvatarCallback = (operation, code, message, motionId, motionRemainingTime) => {
|
|
137
|
+
this.handleUnityCallback(operation, code, message, motionId, motionRemainingTime);
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// 统一的回调处理
|
|
141
|
+
handleUnityCallback(operation, code, message, motionId, motionRemainingTime) {
|
|
142
|
+
const callback = this.pendingCallbacks.get(operation);
|
|
143
|
+
if (!callback) {
|
|
144
|
+
console.warn(`No pending callback for operation: ${operation}`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const response = {
|
|
148
|
+
success: code === 0,
|
|
149
|
+
message,
|
|
150
|
+
motionId,
|
|
151
|
+
motionRemainingTime
|
|
152
|
+
};
|
|
153
|
+
if (code === 0) {
|
|
154
|
+
callback.resolve(response);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
callback.reject(new Error(`Operation failed: ${message}`));
|
|
158
|
+
}
|
|
159
|
+
this.pendingCallbacks.delete(operation);
|
|
160
|
+
}
|
|
161
|
+
// 发送消息到 Unity 的辅助方法
|
|
162
|
+
sendUnityMessage(methodName, parameter) {
|
|
163
|
+
if (!this.unityInstance || typeof this.unityInstance.SendMessage !== 'function') {
|
|
164
|
+
throw new Error('Unity instance not initialized');
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
const paramString = parameter !== undefined ? JSON.stringify(parameter) : '';
|
|
168
|
+
this.unityInstance.SendMessage(this.avatarSDK, methodName, paramString);
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
console.error('Failed to send message to Unity:', error);
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// 检查并设置回调
|
|
176
|
+
setupCallback(operation) {
|
|
177
|
+
return new Promise((resolve, reject) => {
|
|
178
|
+
this.pendingCallbacks.set(operation, { resolve, reject });
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
// 公共 API 方法
|
|
182
|
+
initializeAvatar(token, avatarCode) {
|
|
183
|
+
const promise = this.setupCallback('initializeAvatar');
|
|
184
|
+
this.sendUnityMessage('InitializeAvatar', {
|
|
185
|
+
token,
|
|
186
|
+
avatarCode,
|
|
187
|
+
callbackFun: 'uniAvatarCallback',
|
|
188
|
+
operationType: 'initializeAvatar'
|
|
189
|
+
});
|
|
190
|
+
return promise;
|
|
191
|
+
}
|
|
192
|
+
playMotion(token, clipCode) {
|
|
193
|
+
const promise = this.setupCallback('playMotion');
|
|
194
|
+
console.log();
|
|
195
|
+
this.sendUnityMessage('PlayMotion', {
|
|
196
|
+
token,
|
|
197
|
+
clipCode,
|
|
198
|
+
callbackFun: 'uniAvatarCallback',
|
|
199
|
+
operationType: 'playMotion'
|
|
200
|
+
});
|
|
201
|
+
return promise;
|
|
202
|
+
}
|
|
203
|
+
getCurrentMotion(getRemainingTime) {
|
|
204
|
+
if (!this.unityInstance) {
|
|
205
|
+
return Promise.reject(new Error('Unity instance not initialized'));
|
|
206
|
+
}
|
|
207
|
+
const promise = this.setupCallback('getCurrentMotion');
|
|
208
|
+
this.sendUnityMessage('GetCurrentMotion', {
|
|
209
|
+
getRemainingTime,
|
|
210
|
+
callbackFun: 'uniAvatarCallback',
|
|
211
|
+
operationType: 'getCurrentMotion'
|
|
212
|
+
});
|
|
213
|
+
return promise;
|
|
214
|
+
}
|
|
215
|
+
unloadAvatar(token) {
|
|
216
|
+
const promise = this.setupCallback('unloadAvatar');
|
|
217
|
+
this.sendUnityMessage('UnloadAvatar', {
|
|
218
|
+
token,
|
|
219
|
+
callbackFun: 'uniAvatarCallback',
|
|
220
|
+
operationType: 'unloadAvatar'
|
|
221
|
+
});
|
|
222
|
+
return promise;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function getBytes(stream, onChunk) {
|
|
227
|
+
const reader = stream.getReader();
|
|
228
|
+
let result;
|
|
229
|
+
while (!(result = await reader.read()).done) {
|
|
230
|
+
onChunk(result.value);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function getLines(onLine) {
|
|
234
|
+
let buffer;
|
|
235
|
+
let position;
|
|
236
|
+
let fieldLength;
|
|
237
|
+
let discardTrailingNewline = false;
|
|
238
|
+
return function onChunk(arr) {
|
|
239
|
+
if (buffer === undefined) {
|
|
240
|
+
buffer = arr;
|
|
241
|
+
position = 0;
|
|
242
|
+
fieldLength = -1;
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
buffer = concat(buffer, arr);
|
|
246
|
+
}
|
|
247
|
+
const bufLength = buffer.length;
|
|
248
|
+
let lineStart = 0;
|
|
249
|
+
while (position < bufLength) {
|
|
250
|
+
if (discardTrailingNewline) {
|
|
251
|
+
if (buffer[position] === 10) {
|
|
252
|
+
lineStart = ++position;
|
|
253
|
+
}
|
|
254
|
+
discardTrailingNewline = false;
|
|
255
|
+
}
|
|
256
|
+
let lineEnd = -1;
|
|
257
|
+
for (; position < bufLength && lineEnd === -1; ++position) {
|
|
258
|
+
switch (buffer[position]) {
|
|
259
|
+
case 58:
|
|
260
|
+
if (fieldLength === -1) {
|
|
261
|
+
fieldLength = position - lineStart;
|
|
262
|
+
}
|
|
263
|
+
break;
|
|
264
|
+
case 13:
|
|
265
|
+
discardTrailingNewline = true;
|
|
266
|
+
case 10:
|
|
267
|
+
lineEnd = position;
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (lineEnd === -1) {
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
onLine(buffer.subarray(lineStart, lineEnd), fieldLength);
|
|
275
|
+
lineStart = position;
|
|
276
|
+
fieldLength = -1;
|
|
277
|
+
}
|
|
278
|
+
if (lineStart === bufLength) {
|
|
279
|
+
buffer = undefined;
|
|
280
|
+
}
|
|
281
|
+
else if (lineStart !== 0) {
|
|
282
|
+
buffer = buffer.subarray(lineStart);
|
|
283
|
+
position -= lineStart;
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function getMessages(onId, onRetry, onMessage) {
|
|
288
|
+
let message = newMessage();
|
|
289
|
+
const decoder = new TextDecoder();
|
|
290
|
+
return function onLine(line, fieldLength) {
|
|
291
|
+
if (line.length === 0) {
|
|
292
|
+
onMessage === null || onMessage === void 0 ? void 0 : onMessage(message);
|
|
293
|
+
message = newMessage();
|
|
294
|
+
}
|
|
295
|
+
else if (fieldLength > 0) {
|
|
296
|
+
const field = decoder.decode(line.subarray(0, fieldLength));
|
|
297
|
+
const valueOffset = fieldLength + (line[fieldLength + 1] === 32 ? 2 : 1);
|
|
298
|
+
const value = decoder.decode(line.subarray(valueOffset));
|
|
299
|
+
switch (field) {
|
|
300
|
+
case 'data':
|
|
301
|
+
message.data = message.data
|
|
302
|
+
? message.data + '\n' + value
|
|
303
|
+
: value;
|
|
304
|
+
break;
|
|
305
|
+
case 'event':
|
|
306
|
+
message.event = value;
|
|
307
|
+
break;
|
|
308
|
+
case 'id':
|
|
309
|
+
onId(message.id = value);
|
|
310
|
+
break;
|
|
311
|
+
case 'retry':
|
|
312
|
+
const retry = parseInt(value, 10);
|
|
313
|
+
if (!isNaN(retry)) {
|
|
314
|
+
onRetry(message.retry = retry);
|
|
315
|
+
}
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
function concat(a, b) {
|
|
322
|
+
const res = new Uint8Array(a.length + b.length);
|
|
323
|
+
res.set(a);
|
|
324
|
+
res.set(b, a.length);
|
|
325
|
+
return res;
|
|
326
|
+
}
|
|
327
|
+
function newMessage() {
|
|
328
|
+
return {
|
|
329
|
+
data: '',
|
|
330
|
+
event: '',
|
|
331
|
+
id: '',
|
|
332
|
+
retry: undefined,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
var __rest = (undefined && undefined.__rest) || function (s, e) {
|
|
337
|
+
var t = {};
|
|
338
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
339
|
+
t[p] = s[p];
|
|
340
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
341
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
342
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
343
|
+
t[p[i]] = s[p[i]];
|
|
344
|
+
}
|
|
345
|
+
return t;
|
|
346
|
+
};
|
|
347
|
+
const EventStreamContentType = 'text/event-stream';
|
|
348
|
+
const DefaultRetryInterval = 1000;
|
|
349
|
+
const LastEventId = 'last-event-id';
|
|
350
|
+
function fetchEventSource(input, _a) {
|
|
351
|
+
var { signal: inputSignal, headers: inputHeaders, onopen: inputOnOpen, onmessage, onclose, onerror, openWhenHidden, fetch: inputFetch } = _a, rest = __rest(_a, ["signal", "headers", "onopen", "onmessage", "onclose", "onerror", "openWhenHidden", "fetch"]);
|
|
352
|
+
return new Promise((resolve, reject) => {
|
|
353
|
+
const headers = Object.assign({}, inputHeaders);
|
|
354
|
+
if (!headers.accept) {
|
|
355
|
+
headers.accept = EventStreamContentType;
|
|
356
|
+
}
|
|
357
|
+
let curRequestController;
|
|
358
|
+
function onVisibilityChange() {
|
|
359
|
+
curRequestController.abort();
|
|
360
|
+
if (!document.hidden) {
|
|
361
|
+
create();
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (!openWhenHidden) {
|
|
365
|
+
document.addEventListener('visibilitychange', onVisibilityChange);
|
|
366
|
+
}
|
|
367
|
+
let retryInterval = DefaultRetryInterval;
|
|
368
|
+
let retryTimer = 0;
|
|
369
|
+
function dispose() {
|
|
370
|
+
document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
371
|
+
window.clearTimeout(retryTimer);
|
|
372
|
+
curRequestController.abort();
|
|
373
|
+
}
|
|
374
|
+
inputSignal === null || inputSignal === void 0 ? void 0 : inputSignal.addEventListener('abort', () => {
|
|
375
|
+
dispose();
|
|
376
|
+
resolve();
|
|
377
|
+
});
|
|
378
|
+
const fetch = inputFetch !== null && inputFetch !== void 0 ? inputFetch : window.fetch;
|
|
379
|
+
const onopen = inputOnOpen !== null && inputOnOpen !== void 0 ? inputOnOpen : defaultOnOpen;
|
|
380
|
+
async function create() {
|
|
381
|
+
var _a;
|
|
382
|
+
curRequestController = new AbortController();
|
|
383
|
+
try {
|
|
384
|
+
const response = await fetch(input, Object.assign(Object.assign({}, rest), { headers, signal: curRequestController.signal }));
|
|
385
|
+
await onopen(response);
|
|
386
|
+
await getBytes(response.body, getLines(getMessages(id => {
|
|
387
|
+
if (id) {
|
|
388
|
+
headers[LastEventId] = id;
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
delete headers[LastEventId];
|
|
392
|
+
}
|
|
393
|
+
}, retry => {
|
|
394
|
+
retryInterval = retry;
|
|
395
|
+
}, onmessage)));
|
|
396
|
+
onclose === null || onclose === void 0 ? void 0 : onclose();
|
|
397
|
+
dispose();
|
|
398
|
+
resolve();
|
|
399
|
+
}
|
|
400
|
+
catch (err) {
|
|
401
|
+
if (!curRequestController.signal.aborted) {
|
|
402
|
+
try {
|
|
403
|
+
const interval = (_a = onerror === null || onerror === void 0 ? void 0 : onerror(err)) !== null && _a !== void 0 ? _a : retryInterval;
|
|
404
|
+
window.clearTimeout(retryTimer);
|
|
405
|
+
retryTimer = window.setTimeout(create, interval);
|
|
406
|
+
}
|
|
407
|
+
catch (innerErr) {
|
|
408
|
+
dispose();
|
|
409
|
+
reject(innerErr);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
create();
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
function defaultOnOpen(response) {
|
|
418
|
+
const contentType = response.headers.get('content-type');
|
|
419
|
+
if (!(contentType === null || contentType === void 0 ? void 0 : contentType.startsWith(EventStreamContentType))) {
|
|
420
|
+
throw new Error(`Expected content-type to be ${EventStreamContentType}, Actual: ${contentType}`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// src/core/config/env.ts
|
|
425
|
+
/**
|
|
426
|
+
* 环境配置映射
|
|
427
|
+
*/
|
|
428
|
+
const ENV_MAP = {
|
|
429
|
+
dev: {
|
|
430
|
+
apiBaseUrl: 'https://dev.local.zeewain.com/api/dh-talker'
|
|
431
|
+
},
|
|
432
|
+
test: {
|
|
433
|
+
apiBaseUrl: 'https://test.local.zeewain.com/api/dh-talker'
|
|
434
|
+
},
|
|
435
|
+
prod: {
|
|
436
|
+
apiBaseUrl: 'https://aiip.zeewain.com/api/dh-talker'
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
/**
|
|
440
|
+
* 获取当前环境配置
|
|
441
|
+
* @param env 环境类型
|
|
442
|
+
* @returns 环境配置对象
|
|
443
|
+
*/
|
|
444
|
+
function getEnvConfig(env = 'dev') {
|
|
445
|
+
return ENV_MAP[env] || ENV_MAP.dev;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// src/core/interfaces/broadcast.ts
|
|
449
|
+
/**
|
|
450
|
+
* 流式播报服务接口定义
|
|
451
|
+
*/
|
|
452
|
+
// 播报类型枚举
|
|
453
|
+
var BroadcastType;
|
|
454
|
+
(function (BroadcastType) {
|
|
455
|
+
BroadcastType["TEXT"] = "text";
|
|
456
|
+
BroadcastType["CUSTOM_AUDIO"] = "customAudio"; // 自定义音频智能体播报
|
|
457
|
+
})(BroadcastType || (BroadcastType = {}));
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* 流式播报服务类
|
|
461
|
+
*/
|
|
462
|
+
class BroadcastService {
|
|
463
|
+
constructor(env = 'prod', getToken, unityInstance, instanceId, callbacks) {
|
|
464
|
+
this.env = env;
|
|
465
|
+
this.getToken = getToken;
|
|
466
|
+
this.unityInstance = unityInstance;
|
|
467
|
+
this.controller = null;
|
|
468
|
+
this.avatarSDK = 'AvatarSDK';
|
|
469
|
+
// 存储待处理的 Promise 解析函数
|
|
470
|
+
this.pendingCallbacks = new Map();
|
|
471
|
+
// 事件回调
|
|
472
|
+
this.callbacks = {};
|
|
473
|
+
this.instanceId = instanceId;
|
|
474
|
+
if (callbacks) {
|
|
475
|
+
this.callbacks = callbacks;
|
|
476
|
+
}
|
|
477
|
+
// 注册统一全局回调函数
|
|
478
|
+
window.uniBroadcastCallback = (operation, code, message) => {
|
|
479
|
+
this.handleUnityCallback(operation, code, message);
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
// 统一的回调处理
|
|
483
|
+
handleUnityCallback(operation, code, message) {
|
|
484
|
+
var _a, _b, _c, _d, _e, _f;
|
|
485
|
+
const callback = this.pendingCallbacks.get(operation);
|
|
486
|
+
if (!callback) {
|
|
487
|
+
console.warn(`No pending callback for operation: ${operation}`);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
const response = {
|
|
491
|
+
success: code === 0,
|
|
492
|
+
message
|
|
493
|
+
};
|
|
494
|
+
if (code === 0) {
|
|
495
|
+
callback.resolve(response);
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
callback.reject(new Error(`Operation failed: ${message}`));
|
|
499
|
+
}
|
|
500
|
+
this.pendingCallbacks.delete(operation);
|
|
501
|
+
// 触发事件回调
|
|
502
|
+
switch (operation) {
|
|
503
|
+
case 'pauseBroadcast':
|
|
504
|
+
if (code === 0) {
|
|
505
|
+
(_b = (_a = this.callbacks).onPause) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
506
|
+
}
|
|
507
|
+
break;
|
|
508
|
+
case 'resumeBroadcast':
|
|
509
|
+
if (code === 0) {
|
|
510
|
+
(_d = (_c = this.callbacks).onResume) === null || _d === void 0 ? void 0 : _d.call(_c);
|
|
511
|
+
}
|
|
512
|
+
break;
|
|
513
|
+
case 'stopBroadcast':
|
|
514
|
+
if (code === 0) {
|
|
515
|
+
(_f = (_e = this.callbacks).onStop) === null || _f === void 0 ? void 0 : _f.call(_e);
|
|
516
|
+
}
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
// 设置回调的公共方法
|
|
521
|
+
setupCallback(operation) {
|
|
522
|
+
return new Promise((resolve, reject) => {
|
|
523
|
+
this.pendingCallbacks.set(operation, { resolve, reject });
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* 获取API基础URL
|
|
528
|
+
*/
|
|
529
|
+
getApiBaseUrl() {
|
|
530
|
+
return getEnvConfig(this.env).apiBaseUrl;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* 获取播报API路径
|
|
534
|
+
* @param type 播报类型
|
|
535
|
+
*/
|
|
536
|
+
getBroadcastApiPath(type) {
|
|
537
|
+
switch (type) {
|
|
538
|
+
case BroadcastType.TEXT:
|
|
539
|
+
return '/user/agent/broadcast/text';
|
|
540
|
+
case BroadcastType.CUSTOM_AUDIO:
|
|
541
|
+
return '/user/agent/broadcast/customAudio';
|
|
542
|
+
default:
|
|
543
|
+
throw new Error(`未知的播报类型: ${type}`);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* 设置回调
|
|
548
|
+
* @param callbacks 回调对象
|
|
549
|
+
*/
|
|
550
|
+
setCallbacks(callbacks) {
|
|
551
|
+
this.callbacks = Object.assign(Object.assign({}, this.callbacks), callbacks);
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* 开始播报
|
|
555
|
+
* @param params 播报参数
|
|
556
|
+
*/
|
|
557
|
+
startBroadcast(token, params) {
|
|
558
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
559
|
+
var _a, _b;
|
|
560
|
+
yield this.stopBroadcast(token);
|
|
561
|
+
this.controller = new AbortController();
|
|
562
|
+
(_b = (_a = this.callbacks).onStart) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
563
|
+
// 通知Unity开始新任务(清空队列)
|
|
564
|
+
this.sendUnityMessage('StartBroadcast', {
|
|
565
|
+
token,
|
|
566
|
+
callbackFun: 'uniBroadcastCallback',
|
|
567
|
+
operationType: 'startBroadcast'
|
|
568
|
+
});
|
|
569
|
+
try {
|
|
570
|
+
const apiUrl = `${this.getApiBaseUrl()}${this.getBroadcastApiPath(params.type)}`;
|
|
571
|
+
const requestBody = {
|
|
572
|
+
humanCode: params.humanCode,
|
|
573
|
+
speed: params.speed,
|
|
574
|
+
volume: params.volume,
|
|
575
|
+
isSubtitle: params.isSubtitle
|
|
576
|
+
};
|
|
577
|
+
// 根据播报类型
|
|
578
|
+
if (params.type === BroadcastType.TEXT) {
|
|
579
|
+
if (!params.text || !params.voiceCode) {
|
|
580
|
+
throw new Error('文本播报需要提供text和voiceCode参数');
|
|
581
|
+
}
|
|
582
|
+
requestBody.text = params.text;
|
|
583
|
+
requestBody.voiceCode = params.voiceCode;
|
|
584
|
+
}
|
|
585
|
+
else if (params.type === BroadcastType.CUSTOM_AUDIO) {
|
|
586
|
+
if (!params.audioUrl) {
|
|
587
|
+
throw new Error('自定义音频播报需要提供audioUrl参数');
|
|
588
|
+
}
|
|
589
|
+
requestBody.text = params.text;
|
|
590
|
+
requestBody.audioUrl = params.audioUrl;
|
|
591
|
+
}
|
|
592
|
+
console.log('请求参数:', requestBody);
|
|
593
|
+
yield fetchEventSource(apiUrl, {
|
|
594
|
+
method: 'POST',
|
|
595
|
+
headers: {
|
|
596
|
+
'Content-Type': 'application/json',
|
|
597
|
+
'x_auth_token': this.getToken()
|
|
598
|
+
},
|
|
599
|
+
body: JSON.stringify(requestBody),
|
|
600
|
+
signal: this.controller.signal,
|
|
601
|
+
openWhenHidden: true,
|
|
602
|
+
onmessage: (event) => {
|
|
603
|
+
try {
|
|
604
|
+
const response = JSON.parse(event.data);
|
|
605
|
+
console.log('收到播报数据:', response);
|
|
606
|
+
if (params.type === BroadcastType.CUSTOM_AUDIO && params.audioUrl && !response.data.voiceUrl) {
|
|
607
|
+
response.data.voiceUrl = params.audioUrl;
|
|
608
|
+
}
|
|
609
|
+
// 错误处理
|
|
610
|
+
if (response.code !== 0) {
|
|
611
|
+
throw new Error(response.message || 'Unknown server error');
|
|
612
|
+
}
|
|
613
|
+
if (response.data.voiceUrl) {
|
|
614
|
+
console.log('发送数据到Unity:', response);
|
|
615
|
+
this.sendUnityMessage('AppendBroadcast', {
|
|
616
|
+
token,
|
|
617
|
+
response,
|
|
618
|
+
callbackFun: 'uniBroadcastCallback',
|
|
619
|
+
operationType: 'appendBroadcast'
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
catch (error) {
|
|
624
|
+
this.handleError(error);
|
|
625
|
+
}
|
|
626
|
+
},
|
|
627
|
+
onclose: () => {
|
|
628
|
+
var _a, _b;
|
|
629
|
+
// 流结束时调用完成回调
|
|
630
|
+
(_b = (_a = this.callbacks).onFinish) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
catch (error) {
|
|
635
|
+
this.handleError(error);
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* 暂停播报
|
|
641
|
+
*/
|
|
642
|
+
pauseBroadcast(token) {
|
|
643
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
644
|
+
const promise = this.setupCallback('pauseBroadcast');
|
|
645
|
+
// 发送暂停消息到Unity
|
|
646
|
+
this.sendUnityMessage('PauseBroadcast', {
|
|
647
|
+
token,
|
|
648
|
+
callbackFun: 'uniBroadcastCallback',
|
|
649
|
+
operationType: 'pauseBroadcast'
|
|
650
|
+
});
|
|
651
|
+
yield promise;
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* 继续播报
|
|
656
|
+
*/
|
|
657
|
+
resumeBroadcast(token) {
|
|
658
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
659
|
+
const promise = this.setupCallback('resumeBroadcast');
|
|
660
|
+
// 发送继续消息到Unity
|
|
661
|
+
this.sendUnityMessage('ResumeBroadcast', {
|
|
662
|
+
token,
|
|
663
|
+
callbackFun: 'uniBroadcastCallback',
|
|
664
|
+
operationType: 'resumeBroadcast'
|
|
665
|
+
});
|
|
666
|
+
// 等待Unity确认
|
|
667
|
+
yield promise;
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* 中断播报
|
|
672
|
+
*/
|
|
673
|
+
stopBroadcast(token) {
|
|
674
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
675
|
+
// 如果已有停止操作在进行,直接返回
|
|
676
|
+
const promise = this.setupCallback('stopBroadcast');
|
|
677
|
+
// 中止请求
|
|
678
|
+
if (this.controller) {
|
|
679
|
+
this.controller.abort();
|
|
680
|
+
}
|
|
681
|
+
// 发送停止消息到Unity
|
|
682
|
+
this.sendUnityMessage('StopBroadcast', {
|
|
683
|
+
token,
|
|
684
|
+
callbackFun: 'uniBroadcastCallback',
|
|
685
|
+
operationType: 'stopBroadcast'
|
|
686
|
+
});
|
|
687
|
+
this.controller = null;
|
|
688
|
+
yield promise;
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* 错误处理
|
|
693
|
+
* @param error 错误对象
|
|
694
|
+
*/
|
|
695
|
+
handleError(error) {
|
|
696
|
+
var _a, _b;
|
|
697
|
+
(_b = (_a = this.callbacks).onError) === null || _b === void 0 ? void 0 : _b.call(_a, error);
|
|
698
|
+
console.error('播报错误:', error);
|
|
699
|
+
}
|
|
700
|
+
// 发送消息到 Unity 的辅助方法
|
|
701
|
+
sendUnityMessage(methodName, parameter) {
|
|
702
|
+
if (!this.unityInstance || typeof this.unityInstance.SendMessage !== 'function') {
|
|
703
|
+
throw new Error('Unity instance not initialized');
|
|
704
|
+
}
|
|
705
|
+
try {
|
|
706
|
+
const paramString = parameter !== undefined ? JSON.stringify(parameter) : '';
|
|
707
|
+
this.unityInstance.SendMessage(this.avatarSDK, methodName, paramString);
|
|
708
|
+
}
|
|
709
|
+
catch (error) {
|
|
710
|
+
console.error('Failed to send message to Unity:', error);
|
|
711
|
+
throw error;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
class ZEEAvatarLoader {
|
|
717
|
+
constructor(config) {
|
|
718
|
+
this.api = null;
|
|
719
|
+
this.unityInstance = null;
|
|
720
|
+
this.loader = new UnityLoader(config);
|
|
721
|
+
}
|
|
722
|
+
init() {
|
|
723
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
724
|
+
this.unityInstance = yield this.loader.init();
|
|
725
|
+
this.api = new AvatarAPI(this.unityInstance, this.loader.config.containerId || 'unity-container');
|
|
726
|
+
return this.api;
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
getAPI() {
|
|
730
|
+
return this.api;
|
|
731
|
+
}
|
|
732
|
+
getInstance() {
|
|
733
|
+
return this.unityInstance;
|
|
734
|
+
}
|
|
735
|
+
getContainerId() {
|
|
736
|
+
return this.loader.config.containerId || 'unity-container';
|
|
737
|
+
}
|
|
738
|
+
destroy() {
|
|
739
|
+
if (this.unityInstance) {
|
|
740
|
+
const container = document.getElementById(this.loader.config.containerId || 'unity-container');
|
|
741
|
+
if (container) {
|
|
742
|
+
container.remove();
|
|
743
|
+
}
|
|
744
|
+
if (typeof this.unityInstance.Quit === 'function') {
|
|
745
|
+
this.unityInstance.Quit();
|
|
746
|
+
}
|
|
747
|
+
this.unityInstance = null;
|
|
748
|
+
this.api = null;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
export { AvatarAPI, BroadcastService, ZEEAvatarLoader };
|