语音唤醒与按键说话

模式

  • 唤醒词模式(默认):始终开启的语音识别器等待触发词(swabbleTriggerWords)。匹配成功后开始捕获,显示带有部分文本的悬浮窗,并在静音后自动发送。
  • 按键说话(按住右 Option 键):按住右 Option 键立即捕获——无需触发词。按住时显示悬浮窗;松开后会在短暂延迟后完成并转发,让你可以调整文本。

Runtime 行为(唤醒词模式)

  • 语音识别器运行在 VoiceWakeRuntime 中。
  • 只有在唤醒词和下一个词之间有明显停顿时才会触发(约 0.55 秒间隔)。悬浮窗/提示音可以在停顿时就开始,甚至在命令开始之前。
  • 静音窗口:语音流动时 2.0 秒,只听到触发词时 5.0 秒。
  • 硬性停止:120 秒,防止失控的 Session。
  • Session 之间的防抖:350 毫秒。
  • 悬浮窗通过 VoiceWakeOverlayController 驱动,带有已提交/临时状态的颜色区分。
  • 发送后,识别器会干净地重启,监听下一个触发词。

生命周期不变量

  • 如果启用了语音唤醒且权限已授予,唤醒词识别器应该处于监听状态(除非在显式的按键说话捕获期间)。
  • 悬浮窗的可见性(包括通过 X 按钮手动关闭)绝不能阻止识别器恢复。

悬浮窗卡住的故障模式(之前的问题)

之前,如果悬浮窗卡在可见状态并且你手动关闭它,语音唤醒可能会显得”死掉”,因为 Runtime 的重启尝试可能被悬浮窗可见性阻塞,并且没有安排后续重启。

加固措施:

  • 唤醒 Runtime 重启不再被悬浮窗可见性阻塞。
  • 悬浮窗关闭完成时会通过 VoiceSessionCoordinator 触发 VoiceWakeRuntime.refresh(...),所以手动 X 关闭总是会恢复监听。

按键说话细节

  • 热键检测使用全局 .flagsChanged 监视器监听右 Option 键keyCode 61 + .option)。我们只观察事件(不吞噬)。
  • 捕获管道运行在 VoicePushToTalk 中:立即启动语音识别,将部分结果流式传输到悬浮窗,并在松开时调用 VoiceWakeForwarder
  • 当按键说话开始时,我们会暂停唤醒词 Runtime 以避免音频捕获冲突;松开后会自动重启。
  • 权限:需要麦克风 + 语音识别权限;监听事件需要辅助功能/输入监控授权。
  • 外接键盘:有些可能不会按预期暴露右 Option 键——如果用户报告问题,提供备用快捷键。

用户设置

  • 语音唤醒开关:启用唤醒词 Runtime。
  • 按住 Cmd+Fn 说话:启用按键说话监视器。在 macOS < 26 上禁用。
  • 语言和麦克风选择器、实时音量表、触发词表、测试器(仅本地;不转发)。
  • 麦克风选择器会在设备断开时保留上次选择,显示断开提示,并临时回退到系统默认设备,直到它重新连接。
  • 声音:在检测到触发词和发送时播放提示音;默认使用 macOS “Glass” 系统声音。你可以为每个事件选择任何 NSSound 可加载的文件(如 MP3/WAV/AIFF),或选择无声音

转发行为

  • 当启用语音唤醒时,转录文本会转发到活动的 Gateway/Agent(与 Mac 应用其他部分使用的本地 vs 远程模式相同)。
  • 回复会发送到最后使用的主 Provider(WhatsApp/Telegram/Discord/WebChat)。如果发送失败,错误会被记录,运行仍然可以通过 WebChat/Session 日志查看。

转发负载

  • VoiceWakeForwarder.prefixedTranscript(_:) 在发送前添加机器提示。唤醒词和按键说话路径共享此逻辑。

快速验证

  • 打开按键说话,按住 Cmd+Fn,说话,松开:悬浮窗应该显示部分结果然后发送。
  • 按住时,菜单栏耳朵图标应该保持放大状态(使用 triggerVoiceEars(ttl:nil));松开后缩小。