---
-description: K8吊坠 VC 录音/上传/播放/LED/按键设计动作矩阵(7PIN_1_5)
+description: K8吊坠 VC 录音/上传/播放/LED/按键设计思路(7PIN_1_5)
alwaysApply: true
globs: 2.sdk/project/7PIN_1_5/**/vc_*.py,2.sdk/project/7PIN_1_5/**/protocol.py
---
-# VC 录音上传与播放 — 设计动作(7PIN_1_5)
+# VC 录音上传与播放 — 设计思路(7PIN_1_5)
-**权威设计稿**:`1.doc/K8_diaozhui/design/录音上传与播放-演示文稿.md`
-**量产配置**:`products/configs/k8/default_config.py`(`product_k8.ini` → `DEV_TYPE=k8`)
-**实现骨架**:`vc_ux` / `vc_key_router` / `vc_media_arb`
+详细演示稿:`1.doc/K8_diaozhui/design/录音上传与播放-演示文稿.md`
+量产配置:`products/configs/k8v3/default_config.py`(`product_k8v3.ini`)
+主代码:`vc_session` / `vc_session_io` / `vc_session_media`,按键走 `vc_key_router`
-## k8 配置摘要(与代码绑定)
+---
+
+## 三种声音,别混
-| 配置项 | k8 值 | 用途 |
-|--------|-------|------|
-| `RECORD_START_TONE_FILE` | `U:/static/start.mp3` | **开始录音** local 提醒(非按键滴声) |
-| `RECORD_START_TONE_GAP_MS` | 180 | 长对话:start 播完再开上行 |
-| `VAD_SILENCE_DEFAULT` | 1500 | 长对话 VAD 断句 |
-| `PTT_HINT` | 长按说话话术 | PTT **idle 短按** → notify(`_ptt_play_idle_hint`) |
-| `PTT_MIN_RECORD_MS` | 400 | PTT 长按过短判定 |
-| `PTT_RECORD_TOO_SHORT_TTS` | 见配置 | PTT **长按过短** → notify(`_ptt_abort_short_record`) |
-| `POWEROFF_CMD_CLOSE_ONLY` | False | 语音「关机」可走真断电 |
-| `AI_ALARM_ENABLED` | False | 无闹钟 |
+- **对话 TTS**:云端 LLM 回复,走 `play_pcm`
+- **提示 TTS**:notify(模式切换、PTT 教操作等),走 `play_tts`
+- **本地音**:`start.mp3`、`think.mp3` 等,走 `_vc_local_play`
-**不配置**:`KEY_PRESS_TONE_FILE`(`key_prompt.mp3` 非 K8 需求;按键本身无确认音)。
+同时只能占一路:谁响谁优先;本地提示音播放时**不要停录音**。
+
+---
-**PTT 两条 notify 勿混**:`PTT_HINT` = 没长按、教操作;`PTT_RECORD_TOO_SHORT_TTS` = 已长按但松太快、取消录音。
+## 灯怎么亮
-## 术语 ↔ 代码
+- 正在上传/录音 → 蓝闪(listening)
+- 等云端想回复 → 蓝绿交替(thinking)
+- 正在播对话 → 绿(playing)
+- 没事 → 灭或 idle
+
+长对话 TTS 播完后可以自动再开麦;Power 短按结束会话后会清对话 id。
+
+---
-| 设计列 | 实现 |
-|--------|------|
-| **llm tts** | `play_pcm` / `tts.started` |
-| **notify tts** | `play_tts`(`vc_notify` 种类) |
-| **local play** | `_vc_local_play`、`audio_montage_play` |
+## 长对话:用户一直在说,端侧怎么想
-**仲裁**:llm 播 → notify/local skip;notify 播 → 关 llm+local;local 播 → 关 llm+notify;边录边播 local **不停录**。
+云端逻辑是:你停一下它认为一句结束,就开始想怎么答(LLM);你若马上又开口,它会打断(canceled)并把前后文字拼在一起。
-## 录音 / LED
+**端侧要跟云端配合,不能对着干**:LLM 已经开始了,就不要再无限开麦续传,否则 LLM 永远答不完,灯一直蓝闪却没有声音。
-- **开始上传**:以首帧 PCM 上行(`VC_Speech_Start_now`)为起点;此前三路 media end。
-- **结束**:VAD → `asr.completed`;PTT 松手 `audio.completed`。
-- **灯**:上传→listening;ASR 后 LLM→thinking;`tts.started`→playing;否则 idle。
+### 正常说一句(中间有停顿)
-## 按键 — AI 键(`VC_mode` 0/1)
+说完 → 云端 `asr.completed` → 先暂停上行,等约 2 秒:
-| UX | 长对话单击 | PTT 单击 | PTT 长按 |
-|----|------------|----------|----------|
-| idle | 开录 + `start.mp3`;**仅 Power 短按后再开用新 sid**,否则旧 sid | notify `PTT_HINT`;idle | press 开录 + `start.mp3` / release 结束 |
-| recording | 停录立即重开,旧 sid | (无) | 同单击 |
-| thinking | 打断后重开,旧 sid | 停对话→idle | 停对话后开录 |
-| tts | 停播后重开,旧 sid | 停播→idle | 停播后开录 |
+- 2 秒内没再说话 → 关麦,蓝闪等 LLM
+- 2 秒内又说了几个字 → 当作同一句继续,合并后再断句
-**PTT 异常**:录音 < `PTT_MIN_RECORD_MS` → **notify** `PTT_RECORD_TOO_SHORT_TTS` + cancel + idle。
+### LLM 已经开始之后
-## 按键 — Power 键
+- **收到 LLM 就关麦**(不再边想边传新音频)
+- 若你还接着说了,云端会 canceled;**第一次** canceled 允许再开麦把尾巴接上(补半句)
+- **同一轮里第二次** canceled → 不再续说,停麦,切 thinking,等云端用已有文字答完
+- **一轮最长约 25 秒**(`LONG_DIALOGUE_MAX_TURN_MS`),到了强制提交,同样停麦等回复
+
+### 别踩的坑(改代码时记住)
+
+- LLM 之后还无限 `continue merge` → 和云端打断打架
+- 每次 LLM 都把 canceled 计数清零 → 保护永远触发不了
+- 停麦了灯还蓝闪 → 用户以为还在录
+- 停麦后会话挂着不动 → 云端可能 IdleTimeout
+
+### 联调看这几条日志
+
+- 正常:`continue after completed`、第一次 canceled 后有 `keep/restore speech`
+- 提交:`[VC][cancel_storm]`、`[VC][turn_cap]`
+- 异常:`Deny START by canceled` 后长时间无 TTS
+
+---
+
+## k8v3 相关配置(默认值)
+
+- `VAD_SILENCE_DEFAULT=1000` — 云端断句静音,storage 可覆盖
+- `LONG_DIALOGUE_ASR_CONTINUE_MS=2000` — 句末 2 秒内续说算同一句
+- `LONG_DIALOGUE_LLM_CANCEL_MAX=2` — 一轮里第二次 canceled 强制提交
+- `LONG_DIALOGUE_MAX_TURN_MS=25000` — 单轮最长 25 秒
+- `RECORD_START_TONE_FILE` — 开录提示 `start.mp3`(不是按键滴声)
+
+---
+
+## AI 键(长对话 VC_mode=0)
+
+- **idle 短按**:开录 + start 音;Power 短按结束会话后,下次才是新对话 id
+- **recording 短按**:停录并立刻重开,同一对话
+- **thinking / tts 短按**:打断播报,再开录
+
+## AI 键(PTT VC_mode=1)
+
+- idle 短按 → 播「请长按说话」提示,不进录音
+- 长按 → start 音 + 录音,松手结束
+- 按太短 → 播「说话时间太短」并取消
+
+## Power 键
+
+- 短按:结束当前对话(Close WS),清 sid,可进休眠
+- 长按 3 秒:关机
+
+---
-| UX | 短按 |
-|----|------|
-| idle | cancel(如需)→ Close WS → idle/休眠 → **清 sid** |
-| recording | cancel 本次 ASR 上传 → Close → 清 sid |
-| thinking / tts | cancel 对话/播报 → Close → 清 sid |
+## 改代码常去的函数
-**长按 Power 3s**:关机提示音 + 断电(`vc_buttons`)。
+长对话连续说:`_vc_note_asr_result_activity`、`_long_dialogue_handle_continue_speech_cancel`、`_vc_ld_force_commit_turn`、`_vc_long_dialogue_turn_cap_tick`
-## 代码锚点
+按键:`KeyRouter.dispatch_press`、`handle_power_short`、`_vc_play_record_start_tone`
-- `KeyRouter.dispatch_press` / `handle_ptt_release` / `handle_power_short`
-- `_vc_play_record_start_tone`、`_vc_restart_record_same_dialog`
-- `_ptt_play_idle_hint`、`_ptt_abort_short_record`、`_ptt_stop_dialog_to_idle`
-- `Close()` 清 `_cur_dialog_id`(Power 后下次 AI 为新对话)
+会话结束:`Close()` 清 `_cur_dialog_id`
except Exception:
return False
+ def _vc_asr_continue_merge_ms(self):
+ try:
+ if "vc_config" in globals() and vc_config:
+ return int(vc_config.get_int("LONG_DIALOGUE_ASR_CONTINUE_MS", 2000))
+ except Exception:
+ pass
+ return 2000
+
+ def _vc_ld_cancel_storm_max(self):
+ try:
+ if "vc_config" in globals() and vc_config:
+ return int(vc_config.get_int("LONG_DIALOGUE_LLM_CANCEL_MAX", 2))
+ except Exception:
+ pass
+ return 2
+
+ def _vc_ld_reset_merge_freeze(self):
+ try:
+ self._vc_ld_merge_frozen = 0
+ self._vc_ld_cancel_in_wall = 0
+ self._turn_has_llm_created = 0
+ self._vc_ld_awaiting_reply = 0
+ except Exception:
+ pass
+
+ def _vc_ld_on_tts_started(self):
+ try:
+ self._vc_ld_merge_frozen = 0
+ self._vc_ld_cancel_in_wall = 0
+ self._turn_has_llm_created = 0
+ self._vc_ld_awaiting_reply = 0
+ except Exception:
+ pass
+
+ def _vc_ld_bump_cancel_in_wall(self):
+ """单轮墙钟内累计 canceled(不随 llm.created 清零);达上限强制提交。"""
+ if not self._vc_is_long_dialogue_mode():
+ return False
+ if getattr(self, "_vc_pending_interrupt_uplink", 0) == 1:
+ return False
+ if getattr(self, "_vc_ld_merge_frozen", 0) == 1:
+ return True
+ try:
+ n = int(getattr(self, "_vc_ld_cancel_in_wall", 0)) + 1
+ self._vc_ld_cancel_in_wall = n
+ mx = self._vc_ld_cancel_storm_max()
+ if mx > 0 and n >= mx:
+ self._vc_ld_merge_frozen = 1
+ self._vc_asr_continue_merge = 0
+ self._vc_pending_asr_completed_stop = 0
+ try:
+ print("[VC][cancel_storm] n={} max={} -> force commit".format(n, mx))
+ except Exception:
+ pass
+ return True
+ except Exception:
+ pass
+ return False
+
+ def _vc_ld_force_commit_turn(self, reason="force_commit"):
+ """停止续说开麦,关上行/停录,等云端用已有 ASR 走完 LLM→TTS。"""
+ if not self._vc_is_long_dialogue_mode():
+ return
+ try:
+ self._vc_asr_continue_merge = 0
+ self._vc_pending_asr_completed_stop = 0
+ self._vc_defer_vad_uplink_stop = 0
+ self._server_vad_silence = 1
+ self._vc_ld_awaiting_reply = 1
+ if getattr(self, "VC_record_state_flag", 0) != 0:
+ try:
+ self._uplink_mark_end(reason)
+ except Exception:
+ pass
+ self.request_uplink_stop(reason)
+ if self._vc_effective_asr_text():
+ try:
+ self._vc_publish_led("thinking", reason)
+ except Exception:
+ pass
+ except Exception:
+ pass
+
+ def _vc_asr_continue_merge_active(self):
+ """asr.completed 后短窗内仍有 asr.result → 同轮续说 merge。"""
+ if not self._vc_is_long_dialogue_mode():
+ return False
+ try:
+ if getattr(self, "_vc_asr_continue_merge", 0) != 1:
+ return False
+ comp = getattr(self, "_vc_asr_completed_ms", 0)
+ last = getattr(self, "_vc_last_asr_result_ms", 0)
+ if not comp or not last:
+ return False
+ win = self._vc_asr_continue_merge_ms()
+ now = utime.ticks_ms()
+ if utime.ticks_diff(now, comp) > win or utime.ticks_diff(now, last) > win:
+ self._vc_asr_continue_merge = 0
+ return False
+ return True
+ except Exception:
+ return False
+
+ def _vc_should_hold_mic_for_asr_continue(self):
+ # llm 已起:云端 barge-in 由 canceled+ASR 承接,端侧不再 hold 开麦
+ if getattr(self, "_turn_has_llm_created", 0) == 1:
+ return False
+ if getattr(self, "_vc_vad_had_active_after_silence", 0) == 1:
+ return True
+ return self._vc_asr_continue_merge_active()
+
+ def _vc_note_asr_result_activity(self):
+ try:
+ if getattr(self, "_vc_ld_merge_frozen", 0) == 1:
+ return
+ self._vc_last_asr_result_ms = utime.ticks_ms()
+ # llm 已起后迟到的 asr.result 只更新文本,禁止 continue merge 再开上行
+ if getattr(self, "_turn_has_llm_created", 0) == 1:
+ return
+ if getattr(self, "_turn_has_asr_completed", 0) == 1:
+ self._vc_asr_continue_merge = 1
+ self._vc_pending_asr_completed_stop = 0
+ if getattr(self, "record_data_save_flag", 0) == 0:
+ self._server_vad_silence = 0
+ self._vc_defer_vad_uplink_stop = 0
+ try:
+ print("[VC][ASR] continue after completed -> resume uplink gate")
+ except Exception:
+ pass
+ except Exception:
+ pass
+
def _vc_flush_deferred_vad_uplink_stop(self, reason="asr.completed"):
- """长对话:asr.completed 再停麦,避免句中 vad.silence 把一句话切成两段。"""
+ """长对话 asr.completed:vad.silence 句末立停;无 vad 则留 merge 窗再停麦。"""
try:
- if not getattr(self, "_vc_defer_vad_uplink_stop", 0):
+ if not self._vc_is_long_dialogue_mode():
return
- self._vc_defer_vad_uplink_stop = 0
+ if getattr(self, "record_data_save_flag", 0) != 0:
+ return
+ if getattr(self, "_vc_vad_had_active_after_silence", 0) == 1:
+ try:
+ print("[VC][VAD] asr.completed defer stop (vad pause merged)")
+ except Exception:
+ pass
+ return
+ try:
+ self._vc_asr_completed_ms = utime.ticks_ms()
+ except Exception:
+ pass
+ had_defer = getattr(self, "_vc_defer_vad_uplink_stop", 0)
+ if had_defer:
+ self._vc_defer_vad_uplink_stop = 0
+ elif not self._vc_effective_asr_text():
+ return
+ if had_defer:
+ self._server_vad_silence = 1
+ try:
+ self._uplink_mark_end(reason)
+ except Exception:
+ pass
+ if getattr(self, "VC_record_state_flag", 0) != 0 and self._vc_vad_uplink_stop_eligible():
+ self.request_uplink_stop(reason)
+ return
+ # 无 vad.silence:先 silence_gate,merge 窗内若有新 asr.result 则恢复;超时再关麦
+ self._vc_pending_asr_completed_stop = 1
self._server_vad_silence = 1
+ try:
+ self._uplink_mark_end("asr.completed/pending_stop")
+ except Exception:
+ pass
+ except Exception:
+ pass
+
+ def _vc_asr_completed_stop_tick(self):
+ """merge 窗结束仍无续说 → 执行延后关麦。"""
+ try:
+ if not self._vc_is_long_dialogue_mode():
+ return
+ if getattr(self, "_vc_pending_asr_completed_stop", 0) != 1:
+ return
+ if self._vc_asr_continue_merge_active():
+ return
+ comp = getattr(self, "_vc_asr_completed_ms", 0)
+ if not comp:
+ return
+ if utime.ticks_diff(utime.ticks_ms(), comp) < self._vc_asr_continue_merge_ms():
+ return
+ self._vc_pending_asr_completed_stop = 0
if getattr(self, "VC_record_state_flag", 0) != 0 and self._vc_vad_uplink_stop_eligible():
- self.request_uplink_stop(reason)
+ try:
+ print("[VC][ASR] merge window end -> stop uplink")
+ except Exception:
+ pass
+ self.request_uplink_stop("asr.completed/merge_timeout")
+ except Exception:
+ pass
+
+ def _long_dialogue_cancel_preserve_speech(self):
+ """长对话 canceled:非用户打断时,录音中或 asr 续说窗内均保持/恢复上行。"""
+ if not self._vc_is_long_dialogue_mode():
+ return False
+ try:
+ if getattr(self, "_vc_ld_merge_frozen", 0) == 1:
+ return False
+ if getattr(self, "_vc_pending_interrupt_uplink", 0) == 1:
+ return False
+ if getattr(self, "is_ruppt_tts", 0) == 1:
+ return False
+ # llm 已起:达 cancel 上限后不再保录(由 cancel_storm 提交)
+ if getattr(self, "_turn_has_llm_created", 0) == 1:
+ if int(getattr(self, "_vc_ld_cancel_in_wall", 0)) >= self._vc_ld_cancel_storm_max():
+ return False
+ if getattr(self, "VC_record_state_flag", 0) != 0:
+ return True
+ if self._vc_asr_continue_merge_active():
+ return True
+ if getattr(self, "_vc_asr_continue_merge", 0) == 1:
+ return True
+ except Exception:
+ pass
+ return False
+
+ def _vc_ld_first_post_llm_cancel_barge(self):
+ """llm 已起后第 1 次 canceled(补半句):即使 merge 未置位也允许保录开麦。"""
+ if not self._vc_is_long_dialogue_mode():
+ return False
+ if getattr(self, "_vc_ld_merge_frozen", 0) == 1:
+ return False
+ if getattr(self, "_turn_has_llm_created", 0) != 1:
+ return False
+ if int(getattr(self, "_vc_ld_cancel_in_wall", 0)) != 1:
+ return False
+ return bool(self._vc_effective_asr_text())
+
+ def _vc_ld_should_continue_speech_on_cancel(self):
+ if getattr(self, "_vc_ld_merge_frozen", 0) == 1:
+ return False
+ if self._vc_asr_continue_merge_active():
+ return True
+ if getattr(self, "_vc_asr_continue_merge", 0) == 1:
+ return True
+ return self._vc_ld_first_post_llm_cancel_barge()
+
+ def _long_dialogue_overlap_cancel_keep_recording(self):
+ return self._long_dialogue_cancel_preserve_speech()
+
+ def _long_dialogue_handle_continue_speech_cancel(self):
+ """canceled 但用户仍在说:清 TTS,保持/恢复录音与上行。"""
+ try:
+ self._drop_tts_audio = 1
+ if getattr(self, "AI_audio_queue", None) is not None:
+ if self.AI_audio_queue.empty() != True:
+ self.AI_audio_queue.clear()
+ self._ptt_hard_mute_local_playback()
+ if getattr(self, "VC_Play_flag", 0) == 1 or getattr(self, "play_pcm", None) is not None:
+ self.VC_Play_End()
+ self.fsm.await_played = False
+ self._need_audio_played_ack = 0
+ self.fsm.on_server_canceled()
+ self._long_dialogue_publish_listening("canceled/continue_speech")
+ if getattr(self, "VC_record_state_flag", 0) == 0:
+ self.request_uplink_start("canceled/continue_speech")
+ else:
+ self._server_vad_silence = 0
+ self._vc_pending_asr_completed_stop = 0
+ try:
+ print("[KEY] long_dialogue canceled -> keep/restore speech")
+ except Exception:
+ pass
+ except Exception:
+ pass
+
+ def _vc_long_dialogue_max_turn_ms(self):
+ """长对话单轮录音墙钟上限(ms);0=不限制。merge 续说期间累计,不因 silence_gate 清零。"""
+ try:
+ if "vc_config" in globals() and vc_config:
+ return int(vc_config.get_int("LONG_DIALOGUE_MAX_TURN_MS", 25000))
+ except Exception:
+ pass
+ return 25000
+
+ def _vc_turn_wall_clear(self):
+ try:
+ self._vc_turn_wall_start_ms = 0
+ self._vc_turn_cap_fired = 0
+ if hasattr(self, "_vc_ld_reset_merge_freeze"):
+ self._vc_ld_reset_merge_freeze()
+ except Exception:
+ pass
+
+ def _vc_turn_wall_arm_if_needed(self):
+ """真开录时启动单轮墙钟;句中 vad.active 续说不重置。"""
+ try:
+ if not self._vc_is_long_dialogue_mode():
+ return
+ if getattr(self, "_vc_turn_wall_start_ms", 0):
+ return
+ self._vc_turn_wall_start_ms = utime.ticks_ms()
+ self._vc_turn_cap_fired = 0
+ except Exception:
+ pass
+
+ def _vc_long_dialogue_turn_cap_tick(self):
+ """录音线程周期检查:超 LONG_DIALOGUE_MAX_TURN_MS 强制停录提交(方案2+上限)。"""
+ try:
+ if not self._vc_is_long_dialogue_mode():
+ return
+ if getattr(self, "record_data_save_flag", 0) != 0:
+ return
+ if getattr(self, "VC_record_state_flag", 0) < 2:
+ return
+ if getattr(self, "_vc_turn_cap_fired", 0) == 1:
+ return
+ max_ms = self._vc_long_dialogue_max_turn_ms()
+ if max_ms <= 0:
+ return
+ start = getattr(self, "_vc_turn_wall_start_ms", 0)
+ if not start:
+ return
+ if utime.ticks_diff(utime.ticks_ms(), start) < max_ms:
+ return
+ self._vc_turn_cap_fired = 1
+ self._vc_pending_asr_completed_stop = 0
+ self._vc_asr_continue_merge = 0
+ self._vc_ld_merge_frozen = 1
+ try:
+ print("[VC][turn_cap] force stop elapsed_ms>={} max_ms={}".format(
+ utime.ticks_diff(utime.ticks_ms(), start), max_ms))
+ except Exception:
+ pass
+ self._vc_defer_vad_uplink_stop = 0
+ self._vc_vad_had_active_after_silence = 0
+ self._server_vad_silence = 1
+ try:
+ self._uplink_mark_end("max_turn_ms")
+ except Exception:
+ pass
+ self.request_uplink_stop("max_turn_ms")
+ if hasattr(self, "_vc_ld_force_commit_turn"):
+ self._vc_ld_force_commit_turn("max_turn_ms")
except Exception:
pass
def _long_dialogue_should_hold_listening_led(self):
"""长对话打断等待/录音:保持蓝慢闪(含 cancel→开麦 窗口)。"""
try:
+ if getattr(self, "_vc_ld_awaiting_reply", 0) == 1:
+ return False
if getattr(self, "_vc_defer_listening_led", 0) == 1:
return False
if getattr(self, "VC_mode", -1) != 0:
self._server_vad_silence = 0
self._vc_defer_vad_uplink_stop = 0
self._vc_vad_had_active_after_silence = 0
+ self._vc_turn_wall_start_ms = 0
+ self._vc_turn_cap_fired = 0
+ self._vc_asr_completed_ms = 0
+ self._vc_last_asr_result_ms = 0
+ self._vc_asr_continue_merge = 0
+ self._vc_pending_asr_completed_stop = 0
+ self._turn_has_llm_created = 0
+ self._vc_ld_cancel_in_wall = 0
+ self._vc_ld_merge_frozen = 0
+ self._vc_ld_awaiting_reply = 0
# 单轮状态标志:用于判定 completed 是否应自动进入下一轮
self._turn_has_asr_completed = 0
self._turn_has_asr_result = 0
pass
if self._vc_is_long_dialogue_mode():
try:
+ if hasattr(self, "_vc_ld_on_tts_started"):
+ self._vc_ld_on_tts_started()
if hasattr(self, "_long_dialogue_on_server_tts_started"):
self._long_dialogue_on_server_tts_started()
except Exception:
pass
if ev == "canceled":
try:
- if (
- self._vc_is_long_dialogue_mode()
- and hasattr(self, "_long_dialogue_is_stale_server_cancel")
- and self._long_dialogue_is_stale_server_cancel()
- ):
- self._long_dialogue_handle_stale_cancel()
+ keep_speech = False
+ if self._vc_is_long_dialogue_mode():
+ storm = False
+ if hasattr(self, "_vc_ld_bump_cancel_in_wall"):
+ storm = self._vc_ld_bump_cancel_in_wall()
+ if storm and hasattr(self, "_vc_ld_force_commit_turn"):
+ self._vc_ld_force_commit_turn("cancel_storm")
+ elif (
+ hasattr(self, "_long_dialogue_cancel_preserve_speech")
+ and self._long_dialogue_cancel_preserve_speech()
+ ) or (
+ hasattr(self, "_vc_ld_first_post_llm_cancel_barge")
+ and self._vc_ld_first_post_llm_cancel_barge()
+ ):
+ keep_speech = True
+ if keep_speech:
+ if (
+ hasattr(self, "_vc_ld_should_continue_speech_on_cancel")
+ and self._vc_ld_should_continue_speech_on_cancel()
+ and hasattr(self, "_long_dialogue_handle_continue_speech_cancel")
+ ):
+ self._long_dialogue_handle_continue_speech_cancel()
+ elif hasattr(self, "_long_dialogue_handle_stale_cancel"):
+ self._long_dialogue_handle_stale_cancel()
return
except Exception:
pass
self._fsm_apply_actions(actions)
if ev == "canceled":
try:
+ preserve = False
+ if hasattr(self, "_long_dialogue_cancel_preserve_speech"):
+ preserve = self._long_dialogue_cancel_preserve_speech()
if (
self._vc_is_long_dialogue_mode()
and hasattr(self, "_long_dialogue_end_interrupt")
+ and not preserve
and not (
hasattr(self, "_long_dialogue_is_stale_server_cancel")
and self._long_dialogue_is_stale_server_cancel()
self._llm_completed_ms = 0
self._vc_defer_vad_uplink_stop = 0
self._vc_vad_had_active_after_silence = 0
+ self._vc_asr_completed_ms = 0
+ self._vc_asr_continue_merge = 0
+ self._vc_pending_asr_completed_stop = 0
try:
self._asr_completed_tone_done = 0
except Exception:
self._turn_has_asr_result = 1
# 有效 ASR 后勿再执行空 ASR 提示音播完开麦
self._vc_reopen_uplink_after_empty_hint = 0
+ if hasattr(self, "_vc_note_asr_result_activity"):
+ self._vc_note_asr_result_activity()
except Exception:
pass
if self.AI_ASR_str == "":#没识别到
self._server_vad_silence = 1
self._uplink_mark_end("vad.silence")
elif jsload_data["event"] == "llm.created" :#思考(LLM)
+ try:
+ self._turn_has_llm_created = 1
+ except Exception:
+ pass
+ try:
+ if (
+ self._vc_is_long_dialogue_mode()
+ and getattr(self, "VC_record_state_flag", 0) != 0
+ and getattr(self, "record_data_save_flag", 0) == 0
+ ):
+ self.request_uplink_stop("llm.created")
+ except Exception:
+ pass
try:
if hasattr(self, "_vc_reset_long_idle_timer"):
self._vc_reset_long_idle_timer("llm.created")