表情文件错误时切换眨眼表情
authorxue <2144439972@qq.com>
Tue, 6 Jan 2026 12:11:26 +0000 (20:11 +0800)
committerxue <2144439972@qq.com>
Tue, 6 Jan 2026 12:11:26 +0000 (20:11 +0800)
code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/include/ThunderBirdModel.h
code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/src/ThunderBirdMainView.cpp
code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/src/ThunderBirdModel.cpp
code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/src/ThunderBirdPresenter.cpp

index e0cba1a..f991ed1 100644 (file)
@@ -224,6 +224,9 @@ namespace OHOS
         // 表情视频切换函数
         bool SwitchExpressionVideo(const char* videoPath);
         
+        // 切换到 wink 表情(用于视频切换失败时的回退)
+        void SwitchToWinkingExpression();
+        
         // 哄睡模式处理函数
         void HandleSleepModeStart(const char* theme, int is_intention_result);
         void HandleSleepModeEnd();
index 7ffa17a..f573601 100644 (file)
@@ -623,16 +623,6 @@ namespace OHOS
             return true; // 返回true表示"成功",因为不需要切换
         }
         
-        // 如果正在切换视频,等待完成(最多等待2秒)
-        if (g_isSwitchingVideo)
-        {
-            WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: Video switch in progress, waiting for completion...");
-            if (!WaitForVideoSwitchComplete(2000))
-            {
-                WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: Previous video switch did not complete in time, proceeding anyway");
-            }
-        }
-        
         // 设置切换视频标志,防止屏幕状态通知暂停播放
         g_isSwitchingVideo = true;
         
@@ -762,9 +752,16 @@ namespace OHOS
         int32_t ret = newPlayer->StartVideoPlay();
         if (ret != 0)
         {
-            WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: Failed to start video play, ret=%d", ret);
+            WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: Failed to start video play, ret=%d, fallback to winking", ret);
             delete newPlayer;
             g_isSwitchingVideo = false;
+            // 视频播放失败,切换到 wink 表情(检查 GetInstance 是否为空)
+            ThunderBirdModel* model = ThunderBirdModel::GetInstance();
+            if (model != nullptr) {
+                model->SwitchToWinkingExpression();
+            } else {
+                WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: ThunderBirdModel::GetInstance() is null, cannot switch to winking");
+            }
             return false;
         }
 
@@ -777,10 +774,11 @@ namespace OHOS
         // 通过检查Surface的buffer状态来判断第一帧是否已渲染
         if (lastFrameView_ != nullptr && lastFrameView_->IsVisible() && surface != nullptr)
         {
-            // 等待新播放器开始播放
+            // 等待新播放器开始播放(增加等待时间,因为 Prepare 可能需要更长时间)
             int checkCount = 0;
-            const int maxCheckCount = 50; // 50 * 20ms = 1000ms
+            const int maxCheckCount = 100; // 100 * 20ms = 2000ms(增加等待时间,因为 Prepare 可能需要更长时间)
             bool playerConfirmed = false;
+            bool prepareFailed = false;
             
             while (checkCount < maxCheckCount)
             {
@@ -791,10 +789,58 @@ namespace OHOS
                     WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: New player confirmed playing after %d ms", checkCount * 20);
                     break;
                 }
+                
+                // 检查是否 Prepare 失败(通过检查 playError_ 标志)
+                // 如果 isEntered_ 为 true 但长时间后 isPlayed_ 仍为 false,且 playError_ 为 true,说明 Prepare 失败
+                if (newPlayer != nullptr && newPlayer->isEntered_ && !newPlayer->isPlayed_ && 
+                    checkCount > 20) // 等待至少 400ms 后再检查
+                {
+                    // 检查播放错误标志(Prepare 失败时会设置 playError_)
+                    if (newPlayer->playError_)
+                    {
+                        prepareFailed = true;
+                        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: Prepare failed detected (playError_=true) after %d ms", checkCount * 20);
+                        break;
+                    }
+                    // 如果线程已退出但 isPlayed_ 仍为 false,也说明 Prepare 失败
+                    if (newPlayer->isPlaybackStopped_ || newPlayer->isExited_)
+                    {
+                        prepareFailed = true;
+                        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: Prepare failed detected (isPlaybackStopped_=%d, isExited_=%d) after %d ms", 
+                                     newPlayer->isPlaybackStopped_, newPlayer->isExited_, checkCount * 20);
+                        break;
+                    }
+                }
+                
                 usleep(20000); // 等待20ms
                 checkCount++;
             }
             
+            // 如果超时后仍未播放,或检测到 Prepare 失败,说明视频文件可能有问题,切换到 wink 表情
+            if (!playerConfirmed || prepareFailed)
+            {
+                WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: Video play failed (timeout=%d, prepareFailed=%d) after %d ms, video file may be incomplete (missing moov box), fallback to winking", 
+                             !playerConfirmed, prepareFailed, checkCount * 20);
+                // 停止并删除失败的播放器
+                if (newPlayer != nullptr)
+                {
+                    newPlayer->StopVideoPlay();
+                    delete newPlayer;
+                    newPlayer = nullptr;
+                    videoPlay_ = nullptr;
+                    g_videoPlay = nullptr;
+                }
+                g_isSwitchingVideo = false;
+                // 切换到 wink 表情(检查 GetInstance 是否为空)
+                ThunderBirdModel* model = ThunderBirdModel::GetInstance();
+                if (model != nullptr) {
+                    model->SwitchToWinkingExpression();
+                } else {
+                    WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: ThunderBirdModel::GetInstance() is null, cannot switch to winking");
+                }
+                return false;
+            }
+            
             // 如果新播放器已开始播放,检查Surface是否有新的buffer(表示第一帧已输出)
             if (playerConfirmed)
             {
@@ -840,16 +886,18 @@ namespace OHOS
             // 静态图片会一直遮挡,直到这里才隐藏
             if (lastFrameView_ != nullptr && lastFrameView_->IsVisible())
             {
-                GraphicService::GetInstance()->PostGraphicEvent([this]() {
-                    lastFrameView_->SetVisible(false);
-                    lastFrameView_->Invalidate();
-                    WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: Hidden last frame view, new player first frame should be rendered");
-                });
+                lastFrameView_->SetVisible(false);
+                lastFrameView_->Invalidate();
+                WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: Hidden last frame view, new player first frame should be rendered");
             }
             
             // 释放最后一帧截图的内存(在隐藏静态图片后释放)
             if (hasLastFrame && lastFrameImage.data != nullptr)
             {
+                if (lastFrameView_ != nullptr)
+                {
+                    lastFrameView_->SetSrc(static_cast<const ImageInfo*>(nullptr));
+                }
                 ImageCacheFree(lastFrameImage);
                 WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: Released last frame image memory");
             }
index ef602c8..e714ec2 100644 (file)
@@ -2763,6 +2763,29 @@ extern "C" {
         
         return true; // 异步调用,返回 true 表示调用已提交
     }
+    
+    // 切换到 wink 表情(用于视频切换失败时的回退)
+    void ThunderBirdModel::SwitchToWinkingExpression()
+    {
+        // 检查 eyesShape_ 和 eyesColor_ 是否已初始化
+        if (eyesShape_.empty() || eyesColor_.empty()) {
+            WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdModel::SwitchToWinkingExpression: eyesShape_ or eyesColor_ is empty, cannot switch to winking");
+            return;
+        }
+        
+        std::string winkingExpressionPath = "/user/res/video/CloseEyes/" + eyesShape_ + "/" + eyesColor_ + ".mp4";
+        
+        // 检查文件是否存在
+        if (access(winkingExpressionPath.c_str(), F_OK) == 0) {
+            WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdModel::SwitchToWinkingExpression: fallback to winking expression: %s", winkingExpressionPath.c_str());
+            SwitchExpressionVideo(winkingExpressionPath.c_str());
+            // 更新当前表情为 winking
+            m_currentExpression = "winking";
+        } else {
+            WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdModel::SwitchToWinkingExpression: winking expression file does not exist: %s", winkingExpressionPath.c_str());
+        }
+    }
+    
     //切换发型
     bool ThunderBirdModel::SwitchHair(const char* hairShape, const char* hairColor)
      {
index 0dab7cd..edbb716 100644 (file)
@@ -129,33 +129,32 @@ namespace OHOS
         int16_t currentView = ThunderBirdMainView::GetInstance()->GetCurrentView();
         int16_t currentSubView = ThunderBirdMainView::GetInstance()->GetCurrentSubView();
         int screenState = uapi_get_screen_state();
-        int originalScreenState = screenState; // 保存原始屏幕状态,用于判断是否息屏
         
         // 按键事件日志
         WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "OnKeyAct: keyID=%d, keyState=%d, screenState=%d", keyID, keyState, screenState);
         
         // 息屏后,只有电源键操作有效,其他操作都直接忽略
         bool isPowerKey = (keyID == static_cast<uint16_t>(ZliteKeyCode::ZLITE_KEY_POWER));
-        if (originalScreenState != 0 && !isPowerKey) {
+        if (screenState == 1 && !isPowerKey) {
             // 息屏状态且不是电源键,直接忽略
             WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "OnKeyAct: screen is off and key is not power key, ignore keyID=%d", keyID);
             return true;
         }
         
         // 刷新息屏计时:任意按键按下时重置屏幕熄灭倒计时(只有电源键或已亮屏时才刷新)
-        if (keyState == InputDevice::STATE_PRESS && (isPowerKey || originalScreenState == 0)) {
+        if (keyState == InputDevice::STATE_PRESS && (isPowerKey || screenState == 0)) {
             uapi_set_screen_on_api();
-            // 修复:在调用uapi_set_screen_on_api()后重新获取屏幕状态,确保使用最新的状态
+            // 在调用uapi_set_screen_on_api()后重新获取屏幕状态,确保使用最新的状态
             screenState = uapi_get_screen_state();
-            WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "OnKeyAct: After uapi_set_screen_on_api(), screenState=%d (original=%d)", screenState, originalScreenState);
+            WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "OnKeyAct: After uapi_set_screen_on_api(), screenState=%d", screenState);
         }
 
         // 分发按键事件到对应的处理函数
         switch (keyID) {
             case static_cast<uint16_t>(ZliteKeyCode::ZLITE_KEY_POWER):
                 WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "HandlePowerKey: KEY_TYPE_POWER");
-                // ä¿®å¤\8dï¼\9a对äº\8eç\94µæº\90é\94®ï¼\8cä¼ é\80\92å\8e\9få§\8bå±\8få¹\95ç\8a¶æ\80\81ï¼\8c以便正确å\88¤æ\96­æ\98¯å\90¦ä»\8eæ\81¯å±\8fç\8a¶æ\80\81亮å±\8f
-                return HandlePowerKey(keyState, currentView, currentSubView, originalScreenState);
+                // ä½¿ç\94¨æ\9b´æ\96°å\90\8eç\9a\84å±\8få¹\95ç\8a¶æ\80\81
+                return HandlePowerKey(keyState, currentView, currentSubView, screenState);
                 
             case static_cast<uint16_t>(ZliteKeyCode::ZLITE_KEY_FUNC):
                 // 非电源键需要亮屏才能处理(已在上面检查过,这里确保亮屏)