|
|
|
@ -48,6 +48,7 @@ class MobileElfController extends GetxController { |
|
|
|
StreamSubscription<AgentServiceEvent>? _eventSub; |
|
|
|
Timer? _callTimer; |
|
|
|
Timer? _pttTimer; |
|
|
|
Timer? _pttSafetyTimer; |
|
|
|
bool _inCallMode = false; |
|
|
|
bool _callStarting = false; // 防止并发 startCall |
|
|
|
// PTT 松开后仍有异步事件待处理(recognition_result / assistant_response / tts) |
|
|
|
@ -56,6 +57,8 @@ class MobileElfController extends GetxController { |
|
|
|
bool _pttStarting = false; |
|
|
|
// BLE/AI 唤醒触发的会话(非通话、非PTT模式下的唤醒) |
|
|
|
bool _bleWakeupActive = false; |
|
|
|
// PTT 被中止(轻点松手),用于忽略原生层的残留事件 |
|
|
|
bool _pttAborted = false; |
|
|
|
final _storage = GetStorage(); |
|
|
|
// 滚动:layout 变化前是否已在底部 |
|
|
|
bool _atBottom = true; |
|
|
|
@ -107,6 +110,7 @@ class MobileElfController extends GetxController { |
|
|
|
_eventSub?.cancel(); |
|
|
|
_callTimer?.cancel(); |
|
|
|
_pttTimer?.cancel(); |
|
|
|
_pttSafetyTimer?.cancel(); |
|
|
|
AgentService.stopConversation(); |
|
|
|
scrollController.dispose(); |
|
|
|
super.onClose(); |
|
|
|
@ -127,6 +131,11 @@ class MobileElfController extends GetxController { |
|
|
|
final sessionActive = _inCallMode || pttRecording.value || _pttResultPending || _bleWakeupActive; |
|
|
|
switch (event.type) { |
|
|
|
case AgentServiceEventType.recognitionStarted: |
|
|
|
// 忽略刚刚中止的 PTT 会话的残留事件,避免误触发 BLE 唤醒 |
|
|
|
if (_pttAborted && !sessionActive) { |
|
|
|
_pttAborted = false; |
|
|
|
break; |
|
|
|
} |
|
|
|
// BLE/AI 唤醒:即使没有主动会话也激活 |
|
|
|
if (!sessionActive) _bleWakeupActive = true; |
|
|
|
status.value = MobileElfStatus.recording; |
|
|
|
@ -175,6 +184,7 @@ class MobileElfController extends GetxController { |
|
|
|
case AgentServiceEventType.ttsCompleted: |
|
|
|
if (!sessionActive) break; |
|
|
|
_pttResultPending = false; |
|
|
|
_pttSafetyTimer?.cancel(); |
|
|
|
_endBleWakeup(); // BLE 唤醒一轮结束,清理 native PTT 状态 |
|
|
|
if (_inCallMode) { |
|
|
|
status.value = MobileElfStatus.recording; |
|
|
|
@ -189,6 +199,7 @@ class MobileElfController extends GetxController { |
|
|
|
case AgentServiceEventType.ttsStopped: |
|
|
|
if (!sessionActive) break; |
|
|
|
_pttResultPending = false; |
|
|
|
_pttSafetyTimer?.cancel(); |
|
|
|
_endBleWakeup(); |
|
|
|
status.value = _inCallMode ? MobileElfStatus.recording : MobileElfStatus.idle; |
|
|
|
micAlive.value = _inCallMode; |
|
|
|
@ -197,6 +208,7 @@ class MobileElfController extends GetxController { |
|
|
|
case AgentServiceEventType.error: |
|
|
|
if (!sessionActive) break; |
|
|
|
_pttResultPending = false; |
|
|
|
_pttSafetyTimer?.cancel(); |
|
|
|
_endBleWakeup(); |
|
|
|
final msg = event.data['message']?.toString() ?? '未知错误'; |
|
|
|
status.value = MobileElfStatus.error; |
|
|
|
@ -240,6 +252,7 @@ class MobileElfController extends GetxController { |
|
|
|
Future<void> startCall() async { |
|
|
|
if (inCall.value || _callStarting) return; |
|
|
|
_callStarting = true; |
|
|
|
_pttAborted = false; |
|
|
|
_checkHeartbeat(); |
|
|
|
try { |
|
|
|
// 若有正在播报的 PTT/BLE 唤醒会话,先强制中断 TTS/AI 流, |
|
|
|
@ -281,6 +294,7 @@ class MobileElfController extends GetxController { |
|
|
|
_inCallMode = false; |
|
|
|
_pttStarting = false; |
|
|
|
_pttResultPending = false; // 强制结束所有等待 |
|
|
|
_pttSafetyTimer?.cancel(); |
|
|
|
_bleWakeupActive = false; |
|
|
|
inCall.value = false; |
|
|
|
_callTimer?.cancel(); |
|
|
|
@ -306,6 +320,7 @@ class MobileElfController extends GetxController { |
|
|
|
Future<void> startPtt() async { |
|
|
|
if (pttRecording.value || _pttStarting) return; |
|
|
|
_pttStarting = true; |
|
|
|
_pttAborted = false; |
|
|
|
_checkHeartbeat(); |
|
|
|
// BLE 唤醒/PTT 等待结果期间用户按下 PTT,先中断当前会话(同 startCall 逻辑) |
|
|
|
if (_bleWakeupActive || _pttResultPending) { |
|
|
|
@ -343,10 +358,33 @@ class MobileElfController extends GetxController { |
|
|
|
} |
|
|
|
if (!pttRecording.value) return; |
|
|
|
pttRecording.value = false; |
|
|
|
_pttResultPending = true; // 等待 fin_result/assistant_response/tts 异步回来 |
|
|
|
_pttTimer?.cancel(); |
|
|
|
_pttTimer = null; |
|
|
|
|
|
|
|
// 如果还在连接中(还未开始识别),说明用户轻点了一下就松手, |
|
|
|
// 直接恢复空闲,不需要等待异步事件回来 |
|
|
|
if (status.value == MobileElfStatus.connecting) { |
|
|
|
_pttAborted = true; // 标记中止,忽略原生层的残留事件 |
|
|
|
_pttResultPending = false; |
|
|
|
status.value = MobileElfStatus.idle; |
|
|
|
micAlive.value = false; |
|
|
|
AgentService.stopConversation(); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
_pttResultPending = true; // 等待 fin_result/assistant_response/tts 异步回来 |
|
|
|
AgentService.stopConversation(); |
|
|
|
|
|
|
|
// 安全超时:防止原生层未返回事件导致状态永久卡住 |
|
|
|
_pttSafetyTimer?.cancel(); |
|
|
|
_pttSafetyTimer = Timer(const Duration(seconds: 8), () { |
|
|
|
if (_pttResultPending && !_inCallMode) { |
|
|
|
Logger.info('$_tag PTT safety timeout — force reset to idle'); |
|
|
|
_pttResultPending = false; |
|
|
|
status.value = MobileElfStatus.idle; |
|
|
|
micAlive.value = false; |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
void interruptSpeaking() { |
|
|
|
|