@xmov/avatar 2.0.0-alpha.65 → 2.0.0-alpha.66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/avatar.cjs +1 -1
- package/dist/agent/avatar.modern.js +1 -1
- package/dist/agent/avatar.module.js +1 -1
- package/dist/agent/avatar.umd.js +1 -1
- package/dist/agent/index.d.ts +2 -0
- package/dist/control/ttsa.d.ts +6 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.modern.js +1 -1
- package/dist/index.modern.js.map +1 -1
- package/dist/index.module.js +1 -1
- package/dist/index.module.js.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/control/ttsa.ts +45 -5
- package/src/index.ts +25 -3
package/package.json
CHANGED
package/src/control/ttsa.ts
CHANGED
|
@@ -24,6 +24,7 @@ import '../proto/protobuf.min';
|
|
|
24
24
|
import '../proto/face_data_pb';
|
|
25
25
|
|
|
26
26
|
const proxyProtobuf = protobuf.roots.default;
|
|
27
|
+
const CLIENT_QUIT_ACK_TIMEOUT_MS = 3000;
|
|
27
28
|
|
|
28
29
|
const pointNameObj = {
|
|
29
30
|
'connect_sdk': '连接SDK',
|
|
@@ -63,6 +64,9 @@ interface ITtsaOptions {
|
|
|
63
64
|
export default class Ttsa {
|
|
64
65
|
private TAG = "[TTSA]";
|
|
65
66
|
private ws: Socket;
|
|
67
|
+
private connectedOnce = false;
|
|
68
|
+
private quitRequested = false;
|
|
69
|
+
private quitPromise: Promise<void> | null = null;
|
|
66
70
|
private room: string;
|
|
67
71
|
private session_id: string;
|
|
68
72
|
private token: string;
|
|
@@ -182,6 +186,11 @@ export default class Ttsa {
|
|
|
182
186
|
});
|
|
183
187
|
|
|
184
188
|
ws.on("connect", () => {
|
|
189
|
+
if (this.quitRequested) {
|
|
190
|
+
ws.disconnect();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
this.connectedOnce = true;
|
|
185
194
|
// 连接成功时清理重连定时器
|
|
186
195
|
this.clearReconnectTimer();
|
|
187
196
|
this.initWsTimeoutTimer();
|
|
@@ -204,6 +213,9 @@ export default class Ttsa {
|
|
|
204
213
|
}
|
|
205
214
|
});
|
|
206
215
|
ws.on("first_start_timestamp", (e: any) => {
|
|
216
|
+
if (this.quitRequested) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
207
219
|
(window as any).avatarSDKLogger.log(this.TAG, "first_start_timestamp", e);
|
|
208
220
|
// 前端在发送first_start_timestamp后运行帧索引
|
|
209
221
|
this.firstStartTimestamp(e.server_time);
|
|
@@ -342,7 +354,7 @@ export default class Ttsa {
|
|
|
342
354
|
this.clearOldWsTimeoutTimer();
|
|
343
355
|
// 停止客户端心跳
|
|
344
356
|
this.stopHeartbeat();
|
|
345
|
-
if(e === 'io client disconnect') {
|
|
357
|
+
if (this.quitRequested || e === 'io client disconnect') {
|
|
346
358
|
return;
|
|
347
359
|
}
|
|
348
360
|
// 增加状态检查,避免重复处理断开事件
|
|
@@ -393,6 +405,10 @@ export default class Ttsa {
|
|
|
393
405
|
}) as any;
|
|
394
406
|
ws.on("client_quit", (e) => {
|
|
395
407
|
(window as any).avatarSDKLogger.warn(this.TAG, "client_quit", "断开原因", e);
|
|
408
|
+
// A quit broadcast may precede its ACK. Let the local quit flow own teardown.
|
|
409
|
+
if (this.quitRequested) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
396
412
|
ws.disconnect();
|
|
397
413
|
this.session_speak_req_id = 0;
|
|
398
414
|
if (Object.keys(e).length) {
|
|
@@ -442,6 +458,9 @@ export default class Ttsa {
|
|
|
442
458
|
};
|
|
443
459
|
initWsTimeoutTimer = () => {
|
|
444
460
|
this.clearOldWsTimeoutTimer();
|
|
461
|
+
if (this.quitRequested) {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
445
464
|
|
|
446
465
|
this.wsConnectTimer = window.setTimeout(async () => {
|
|
447
466
|
|
|
@@ -658,6 +677,10 @@ export default class Ttsa {
|
|
|
658
677
|
return this.ws.connected;
|
|
659
678
|
}
|
|
660
679
|
|
|
680
|
+
get hasConnected(): boolean {
|
|
681
|
+
return this.connectedOnce;
|
|
682
|
+
}
|
|
683
|
+
|
|
661
684
|
_setResumeInfoCallback(lastSessionId: string, _getResumeInfo: () => IResumeParams) {
|
|
662
685
|
this._isResume = true
|
|
663
686
|
this._lastSessionId = lastSessionId
|
|
@@ -682,6 +705,26 @@ export default class Ttsa {
|
|
|
682
705
|
return `${this._uniqueSpeakId}-${this.session_id}`
|
|
683
706
|
}
|
|
684
707
|
|
|
708
|
+
quit(stopReason: string, timeoutMs = CLIENT_QUIT_ACK_TIMEOUT_MS): Promise<void> {
|
|
709
|
+
if (this.quitPromise) {
|
|
710
|
+
return this.quitPromise;
|
|
711
|
+
}
|
|
712
|
+
this.quitRequested = true;
|
|
713
|
+
this.clearReconnectTimer();
|
|
714
|
+
this.clearOldWsTimeoutTimer();
|
|
715
|
+
this.stopHeartbeat();
|
|
716
|
+
this.quitPromise = this.sendClientQuit(stopReason, timeoutMs);
|
|
717
|
+
return this.quitPromise;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
private async sendClientQuit(stopReason: string, timeoutMs: number): Promise<void> {
|
|
721
|
+
if (!this.ws.connected) {
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
// ACK confirms receipt; no application-level ACK payload is assumed here.
|
|
725
|
+
await this.ws.timeout(timeoutMs).emitWithAck("client_quit", { stop_reason: stopReason });
|
|
726
|
+
}
|
|
727
|
+
|
|
685
728
|
close() {
|
|
686
729
|
// 使用 disconnect() 而不是 close(),disconnect() 会停止所有重连尝试
|
|
687
730
|
if (this.ws) {
|
|
@@ -690,10 +733,7 @@ export default class Ttsa {
|
|
|
690
733
|
this.clearOldWsTimeoutTimer();
|
|
691
734
|
this.stopHeartbeat();
|
|
692
735
|
this.session_speak_req_id = 0;
|
|
693
|
-
|
|
694
|
-
clearTimeout(this.reconnectTimer);
|
|
695
|
-
this.reconnectTimer = -1;
|
|
696
|
-
}
|
|
736
|
+
this.clearReconnectTimer();
|
|
697
737
|
if (this.syncTimestampInterval) {
|
|
698
738
|
clearInterval(this.syncTimestampInterval);
|
|
699
739
|
this.syncTimestampInterval = 0; // 重置为初始值 0,保持状态统一
|
package/src/index.ts
CHANGED
|
@@ -1424,9 +1424,7 @@ export default class XmovAvatar {
|
|
|
1424
1424
|
|
|
1425
1425
|
// 薄客户端模式:通知 server 端销毁,然后清理本地资源
|
|
1426
1426
|
if (this._isClientMode) {
|
|
1427
|
-
this.
|
|
1428
|
-
await this.destroyClient();
|
|
1429
|
-
this.onStatusChange(AvatarStatus.close);
|
|
1427
|
+
await this.disconnectClientForDestroy(stop_reason);
|
|
1430
1428
|
return;
|
|
1431
1429
|
}
|
|
1432
1430
|
|
|
@@ -1441,6 +1439,30 @@ export default class XmovAvatar {
|
|
|
1441
1439
|
return res
|
|
1442
1440
|
}
|
|
1443
1441
|
|
|
1442
|
+
protected async disconnectClientForDestroy(stopReason: string): Promise<void> {
|
|
1443
|
+
this.replayData = null;
|
|
1444
|
+
try {
|
|
1445
|
+
if (this._isClientMode) {
|
|
1446
|
+
this._apiForwarder?.destroy(stopReason);
|
|
1447
|
+
} else if (!this.ttsa?.hasConnected) {
|
|
1448
|
+
// Initialization can own a session/request before TTSA first connects.
|
|
1449
|
+
// Mark an in-flight connection as quitting before waiting for DELETE.
|
|
1450
|
+
await this.ttsa?.quit(stopReason);
|
|
1451
|
+
await this.resourceManager.stopSession(stopReason);
|
|
1452
|
+
} else {
|
|
1453
|
+
this.ttsa.sendSdkPoint('close_session', {
|
|
1454
|
+
reason: stopReason,
|
|
1455
|
+
});
|
|
1456
|
+
await this.ttsa.quit(stopReason);
|
|
1457
|
+
}
|
|
1458
|
+
} catch (error) {
|
|
1459
|
+
(window as any).avatarSDKLogger.warn(this.TAG, "TTSA client_quit 未确认,继续释放本地资源", error);
|
|
1460
|
+
} finally {
|
|
1461
|
+
await this.destroyClient();
|
|
1462
|
+
this.onStatusChange(AvatarStatus.close);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1444
1466
|
idle() {
|
|
1445
1467
|
if (this._isClientMode) { this._apiForwarder?.idle(); return; }
|
|
1446
1468
|
if(this.enableClientInterrupt) {
|