From d519244ec7c2a5ac79a86fa756cfc6c43aae5f05 Mon Sep 17 00:00:00 2001 From: xue <2144439972@qq.com> Date: Fri, 24 Apr 2026 18:57:13 +0800 Subject: [PATCH] =?utf8?q?=E5=A2=9E=E5=8A=A0=E5=9B=9E=E7=AD=94=E5=93=8D?= =?utf8?q?=E5=BA=9412S=E8=B6=85=E6=97=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../thunderbird/include/ThunderBirdModel.h | 6 ++ .../include/ThunderBirdPresenter.h | 3 + .../thunderbird/src/ThunderBirdMainView.cpp | 1 + .../thunderbird/src/ThunderBirdModel.cpp | 80 ++++++++++++++++++- .../thunderbird/src/ThunderBirdPresenter.cpp | 32 ++++++++ 5 files changed, 119 insertions(+), 3 deletions(-) diff --git a/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/include/ThunderBirdModel.h b/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/include/ThunderBirdModel.h index 87c37bff8..d1c526b0c 100644 --- a/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/include/ThunderBirdModel.h +++ b/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/include/ThunderBirdModel.h @@ -42,6 +42,11 @@ namespace OHOS /** 仅当当前在提醒界面时启动 3 秒退出定时器(供 LN_HTTP_SERVICE_CMD_AUDIO_STOP 使用,避免哄睡结束误触) */ void StartCountUpTimerForDialogueExitIfInSpeaking(void); void StopCountUpTimer(void); + /** 进入对话「说话」界面后:8s 内须收到服务端文本(SetTextStr),否则定时器触发模拟语音键回「聆听」 */ + void StartSpeakingTtsWaitTimer(void); + void StopSpeakingTtsWaitTimer(void); + /** 由 one-shot 定时器经 PostGraphicEvent 调用 */ + void HandleSpeakingTtsWaitExpired(void); void StartFactoryResetTimer(uint32_t delaySeconds); void StopFactoryResetTimer(void); void HandleFactoryReset(void); @@ -485,6 +490,7 @@ namespace OHOS void* m_storyAudioPlaybackTimeoutTimer; // 故事模式音频播放超时定时器(5秒内无音频播放则退出) void* m_factoryResetTimer; // 恢复出厂设置定时器 void* m_stressAudioLoopTimer; // 压测音频循环定时器(每1.5秒播放一次) + void* m_speakingTtsWaitTimer; // 说话界面等待收到文本 8s 超时 // 对话模式相关标志 bool m_dialogueModeFinished = false; // 对话模式是否已finished(收到finished=true) diff --git a/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/include/ThunderBirdPresenter.h b/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/include/ThunderBirdPresenter.h index 234616c6d..3048c385c 100644 --- a/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/include/ThunderBirdPresenter.h +++ b/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/include/ThunderBirdPresenter.h @@ -45,6 +45,9 @@ namespace OHOS void SetDialogueing(bool dialogueing);//设置对话模式标志位 + /** 与在「说话」界面按语音键一致:打断并回到「聆听」(供「说话」界面收文本超时等调用) */ + void SimulateVoiceKeySpeakingToListening(void); + // FUNC键长按功能 void RegisterFuncKeyLongPressEvent(void); void HandleFuncKeyLongPress(void); diff --git a/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/src/ThunderBirdMainView.cpp b/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/src/ThunderBirdMainView.cpp index 7fed3991f..ecf9c2466 100644 --- a/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/src/ThunderBirdMainView.cpp +++ b/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/src/ThunderBirdMainView.cpp @@ -1233,6 +1233,7 @@ namespace OHOS WaitForVideoSwitchComplete(2000); } ThunderBirdModel::GetInstance()->StopCountUpTimer(); + ThunderBirdModel::GetInstance()->StopSpeakingTtsWaitTimer(); ThunderBirdPresenter::GetInstance()->SetDialogueing(false); ln_ws_end_audio_chat();//结束对话 AI_ForceInterrupt(); diff --git a/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/src/ThunderBirdModel.cpp b/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/src/ThunderBirdModel.cpp index 0dbef42cb..0261aa81a 100644 --- a/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/src/ThunderBirdModel.cpp +++ b/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/src/ThunderBirdModel.cpp @@ -196,9 +196,10 @@ extern "C" { m_bootTime(0), m_lastCat1RestartTime(0), m_storyModeTimeoutTimer(nullptr), - m_storyAudioPlaybackTimeoutTimer(nullptr), - m_stressAudioLoopTimer(nullptr), - m_networkConnectingReason(NetworkConnectingReason::NONE), + m_storyAudioPlaybackTimeoutTimer(nullptr), + m_stressAudioLoopTimer(nullptr), + m_speakingTtsWaitTimer(nullptr), + m_networkConnectingReason(NetworkConnectingReason::NONE), m_networkConnectingTimeoutCount(0) { // 初始化时加载提醒数据 @@ -246,6 +247,7 @@ extern "C" { ThunderBirdModel::~ThunderBirdModel() { + StopSpeakingTtsWaitTimer(); // 停止所有定时器 StopTimeDisplayTimer(); StopOtaCountdown(); @@ -422,6 +424,71 @@ extern "C" { WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Count up timer stopped"); } + + static void SpeakingTtsWaitTimerCallback(uint8_t data) + { + (void)data; + GraphicService::GetInstance()->PostGraphicEvent( + std::bind([]() { + ThunderBirdModel::GetInstance()->HandleSpeakingTtsWaitExpired(); + })); + } + + void ThunderBirdModel::StartSpeakingTtsWaitTimer(void) + { + if (m_stressTestDialogueActive) { + return; + } + ThunderBirdPresenter *pre = ThunderBirdPresenter::GetInstance(); + if (pre == nullptr || !pre->isDialogueing) { + return; + } + StopSpeakingTtsWaitTimer(); + m_speakingTtsWaitTimer = + osTimerNew((osTimerFunc_t)SpeakingTtsWaitTimerCallback, osTimerOnce, nullptr, nullptr); + if (m_speakingTtsWaitTimer == nullptr) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "StartSpeakingTtsWaitTimer: osTimerNew failed"); + return; + } + static constexpr uint32_t kSpeakingTtsWaitMs = 12000; + osStatus_t ret = osTimerStart(m_speakingTtsWaitTimer, kSpeakingTtsWaitMs); + if (ret != osOK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "StartSpeakingTtsWaitTimer: osTimerStart failed ret=%d", (int)ret); + (void)osTimerDelete(m_speakingTtsWaitTimer); + m_speakingTtsWaitTimer = nullptr; + return; + } + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "StartSpeakingTtsWaitTimer: %ums 内须收到服务端文本", (unsigned)kSpeakingTtsWaitMs); + } + + void ThunderBirdModel::StopSpeakingTtsWaitTimer(void) + { + if (m_speakingTtsWaitTimer != nullptr) { + (void)osTimerStop(m_speakingTtsWaitTimer); + (void)osTimerDelete(m_speakingTtsWaitTimer); + m_speakingTtsWaitTimer = nullptr; + } + } + + void ThunderBirdModel::HandleSpeakingTtsWaitExpired(void) + { + StopSpeakingTtsWaitTimer(); + ThunderBirdMainView *view = ThunderBirdMainView::GetInstance(); + if (view == nullptr) { + return; + } + if (view->GetCurrentView() != THUNDERBIRD_DIALOGUEVIEW || + view->GetCurrentSubView() != ThunderBirdDialogueView::THUNDERBIRD_DIALOGUEVIEW_SPEAKING) { + return; + } + ThunderBirdPresenter *pre = ThunderBirdPresenter::GetInstance(); + if (pre == nullptr || !pre->isDialogueing) { + return; + } + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, + "HandleSpeakingTtsWaitExpired: 8s 内未收到文本,模拟语音键进入聆听"); + pre->SimulateVoiceKeySpeakingToListening(); + } // ---------------------- 仅当当前在提醒界面时启动 3 秒退出定时器(供 LN_HTTP_SERVICE_CMD_AUDIO_STOP 使用,避免哄睡结束误触) ---------------------- void ThunderBirdModel::StartCountUpTimerForDialogueExitIfInSpeaking(void) @@ -1377,6 +1444,9 @@ extern "C" { // 直接推送到对话界面 if(ThunderBirdDialogueView::GetInstance() && currentSubView == ThunderBirdDialogueView::THUNDERBIRD_DIALOGUEVIEW_SPEAKING) { + if (!m_stressTestDialogueActive) { + StopSpeakingTtsWaitTimer(); + } ThunderBirdDialogueView::GetInstance()->UpdateDialogueText(textId); WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "第一个文本直接显示在对话界面: %s", textId); } @@ -3741,6 +3811,7 @@ extern "C" { currentSubView == ThunderBirdDialogueView::THUNDERBIRD_DIALOGUEVIEW_SPEAKING)) { WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdModel::HandleBindAndUnbind: 从对话模式跳转绑定成功,先退出对话模式"); StopCountUpTimer(); + StopSpeakingTtsWaitTimer(); ThunderBirdPresenter::GetInstance()->SetDialogueing(false); ln_ws_end_audio_chat();//结束对话 AI_ForceInterrupt(); @@ -3777,6 +3848,7 @@ extern "C" { currentSubView == ThunderBirdDialogueView::THUNDERBIRD_DIALOGUEVIEW_SPEAKING)) { WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdModel::HandleBindAndUnbind: 从对话模式跳转解绑警告/绑定成功,先退出对话模式"); StopCountUpTimer(); + StopSpeakingTtsWaitTimer(); ThunderBirdPresenter::GetInstance()->SetDialogueing(false); ln_ws_end_audio_chat();//结束对话 AI_ForceInterrupt(); @@ -4025,6 +4097,7 @@ extern "C" { if (currentSubView == ThunderBirdDialogueView::THUNDERBIRD_DIALOGUEVIEW_SPEAKING) { WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdModel::HandleAudioRecordingStart: speaking 中收到录音开始,交由 VAD 流程处理,只切换到聆听界面"); + StopSpeakingTtsWaitTimer(); auto* mainView = ThunderBirdMainView::GetInstance(); if (mainView && mainView->listenIcon) { mainView->listenIcon->SetVisible(true); @@ -4074,6 +4147,7 @@ extern "C" { if (ThunderBirdMainView::GetInstance() && ThunderBirdMainView::GetInstance()->listenIcon) { ThunderBirdMainView::GetInstance()->listenIcon->SetVisible(false); } + StartSpeakingTtsWaitTimer(); } else if (currentView == THUNDERBIRD_LULLABYVIEW && currentSubView == ThunderBirdLullabyView::THUNDERBIRD_LULLABYVIEW_3 ) { diff --git a/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/src/ThunderBirdPresenter.cpp b/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/src/ThunderBirdPresenter.cpp index 376c64ca9..1b4d34abe 100644 --- a/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/src/ThunderBirdPresenter.cpp +++ b/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/src/ThunderBirdPresenter.cpp @@ -75,6 +75,7 @@ namespace OHOS ThunderBirdModel::GetInstance()->StopTimeDisplayTimer(); ThunderBirdModel::GetInstance()->StopOtaCountdown(); ThunderBirdModel::GetInstance()->StopCountUpTimer(); + ThunderBirdModel::GetInstance()->StopSpeakingTtsWaitTimer(); //ThunderBirdModel::GetInstance()->StopReminderHideTimer(); // 停止录音 @@ -424,6 +425,34 @@ namespace OHOS // ==================== 辅助函数 ==================== + void ThunderBirdPresenter::SimulateVoiceKeySpeakingToListening(void) + { + if (ThunderBirdMainView::GetInstance() && ThunderBirdMainView::IsSwitchingVideo()) { + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "SimulateVoiceKeySpeakingToListening: 等待视频切换完成"); + ThunderBirdMainView::WaitForVideoSwitchComplete(2000); + } + ThunderBirdModel::GetInstance()->StopCountUpTimer(); + ThunderBirdModel::GetInstance()->StopSpeakingTtsWaitTimer(); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "SimulateVoiceKeySpeakingToListening: 与语音键一致,SPEAKING→LISTENING"); + AI_ForceInterrupt(); + ln_ws_end_audio_chat(); + ln_ws_send_voice_chat_interrupt("新对话开始"); + auto *dialogueView = ThunderBirdDialogueView::GetInstance(); + if (dialogueView) { + dialogueView->IsReconnect = true; + } + auto *mainView = ThunderBirdMainView::GetInstance(); + if (mainView && mainView->listenIcon) { + mainView->listenIcon->SetVisible(true); + } + ln_ws_set_vad_recognition_enabled(true); + if (mainView) { + const int16_t cv = mainView->GetCurrentView(); + mainView->ChangeView(cv, THUNDERBIRD_DIALOGUEVIEW, + ThunderBirdDialogueView::THUNDERBIRD_DIALOGUEVIEW_LISTENING); + } + } + /** * @brief 处理对话功能 */ @@ -431,6 +460,7 @@ namespace OHOS { // 在对话界面进行按键操作时,取消退出对话的定时器(用户有操作,说明要继续对话) ThunderBirdModel::GetInstance()->StopCountUpTimer(); + ThunderBirdModel::GetInstance()->StopSpeakingTtsWaitTimer(); WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "HandleDialogueViewNavigation: 取消退出对话定时器"); WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "HandleDialogueFunction: currentView=%d, currentSubView=%d", currentView, currentSubView); @@ -455,6 +485,7 @@ namespace OHOS mainView->listenIcon->SetVisible(false); } ThunderBirdMainView::GetInstance()->ChangeView(currentView, THUNDERBIRD_DIALOGUEVIEW, ThunderBirdDialogueView::THUNDERBIRD_DIALOGUEVIEW_SPEAKING); + ThunderBirdModel::GetInstance()->StartSpeakingTtsWaitTimer(); } else if(currentView == THUNDERBIRD_DIALOGUEVIEW && currentSubView == ThunderBirdDialogueView::THUNDERBIRD_DIALOGUEVIEW_SPEAKING) { // 检查 WebSocket 连接状态,如果正在重连,等待重连完成 // int wsState = ln_ws_get_connection_state(); @@ -1286,6 +1317,7 @@ namespace OHOS // 先停止定时器,避免在退出过程中继续触发 ThunderBirdModel::GetInstance()->StopCountUpTimer(); + ThunderBirdModel::GetInstance()->StopSpeakingTtsWaitTimer(); ln_ws_end_audio_chat();//结束对话 -- 2.17.1