@xmov/avatar 2.0.0-beta.12 → 2.0.0-beta.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmov/avatar",
3
- "version": "2.0.0-beta.12",
3
+ "version": "2.0.0-beta.13",
4
4
  "description": "魔珐数字人 JS-SDK",
5
5
  "type": "module",
6
6
  "source": "src/index.ts",
@@ -493,11 +493,29 @@ export default class RenderScheduler {
493
493
  code: EErrorCode.EVENT_TRIGGERED,
494
494
  message: 'WARNING: 有新的speak_start事件,但是上一轮仍未结束,触发兜底结束',
495
495
  });
496
- this.interrupt('new_speak_start');
496
+ const currentSpeakStart = d.flatMap(item => item.e ?? []).find(ev => ev.type === 'speak_start');
497
+ const newSpeakId = currentSpeakStart?.speech_id ?? 0;
498
+ const playingSpeechId = this.audioRenderer.speech_id;
499
+ // 仅当正在播放的确实是上一轮(sid 小于新一轮)时才打断旧轮次。
500
+ // 若事件晚于音频到达(playingSpeechId 已是新一轮 sid),此时播放的就是新一轮,
501
+ // 不能用 interrupt()(其目标取 audioRenderer.speech_id),否则会误清新一轮的音频队列、
502
+ // 字幕事件,并因 lastSpeechId/oldSpeechId 更新导致后续音视频帧全部被丢弃,
503
+ // 表现为"有口型、无字幕、无音频"。
504
+ if (playingSpeechId !== -1 && playingSpeechId < newSpeakId) {
505
+ this.interrupt('new_speak_start', playingSpeechId);
506
+ // stop() 内部有 oldSpeechId >= speech_id 的防重入守卫:当 speech_id 停留在
507
+ // 异常残留值(如初始值 0,长会话中 isPlaying 卡死为 true)时 stop 会被拦截,
508
+ // isPlaying/speech_id 无法复位,导致新一轮音频在 _updateAudio 中永远走缓存分支
509
+ // 不播放(日志表现:缓存数据长度持续增长且无"开始播放")。
510
+ // 这里检测到 stop 未生效(speech_id 未复位为 -1)时,用 pause() 强制复位,
511
+ // pause 会 flush 已缓存音频并把 speech_id/oldSpeechId 重置,新一轮可正常起播。
512
+ if (this.audioRenderer.speech_id !== -1) {
513
+ this.audioRenderer.pause();
514
+ }
515
+ }
497
516
  // 新 speak_start 已到来,立即复位 interrupt 并更新轮次状态,
498
517
  // 避免依赖 UIRenderer 在 sf 帧处理事件时(sf==当前帧且事件在 compose 后被处理)被漏处理,
499
518
  // 导致新轮次实际说话期间仍被强制渲染为闭嘴表情
500
- const currentSpeakStart = d.flatMap(item => item.e ?? []).find(ev => ev.type === 'speak_start');
501
519
  if (currentSpeakStart) {
502
520
  this.currentSpeechId = currentSpeakStart.speech_id ?? 0;
503
521
  this.avatarRenderer.setInterrupt(false);
@@ -889,10 +907,13 @@ export default class RenderScheduler {
889
907
  this.avatarRenderer.setWalkConfig(walkConfig);
890
908
  }
891
909
 
892
- interrupt(type?: string): number{
910
+ // targetSpeechId: 显式指定要打断的轮次 sid。缺省时取 audioRenderer.speech_id(当前播放轮次)。
911
+ // 当事件晚于音频到达时,audioRenderer.speech_id 可能已经切换到新一轮,此时必须显式传旧 sid,
912
+ // 避免误清新一轮的音频与字幕。
913
+ interrupt(type?: string, targetSpeechId?: number): number{
893
914
  const start = Date.now();
894
915
  // 插入中断字幕和语音结束事件
895
- const speech_id = this.audioRenderer.speech_id;
916
+ const speech_id = targetSpeechId ?? this.audioRenderer.speech_id;
896
917
  if(type === "in_offline_mode") {
897
918
  this.lastSpeechId = -1;
898
919
  } else {