修复切换表情会闪黑的bug
authorxue <2144439972@qq.com>
Thu, 11 Dec 2025 03:54:12 +0000 (11:54 +0800)
committerxue <2144439972@qq.com>
Thu, 11 Dec 2025 03:54:12 +0000 (11:54 +0800)
code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/include/ThunderBirdMainView.h
code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/src/ThunderBirdMainView.cpp

index 969375e..69d9861 100644 (file)
@@ -140,6 +140,7 @@ namespace OHOS
         DialVideoView *videoPlayer_ = nullptr;
         UILiteSurfaceView *surfaceView_ = nullptr;
         UIImageView *hairIcon{nullptr};
+        UIImageView *lastFrameView_{nullptr};  // 用于显示最后一帧截图的临时视图
        
         ColorType colorKey_;
 
index 52c065a..aa975e0 100644 (file)
@@ -30,6 +30,8 @@
 #include "custom_runtime.h"
 #include <service/graphic_service.h>
 #include "ln_api.h"
+#include "common/screen.h"
+#include "common/lite_surface.h"
 namespace OHOS
 {
     static constexpr int16_t VIDEO_WIDTH = 360;
@@ -423,8 +425,10 @@ namespace OHOS
             return false;
         }
         
+        std::string targetPath = std::string(uri);
+        
         // 检查是否尝试切换到同一个文件
-        if (currentVideoPath_ == std::string(uri))
+        if (currentVideoPath_ == targetPath)
         {
             WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: Already playing the same video, skipping: %s", uri);
             return true; // 返回true表示"成功",因为不需要切换
@@ -455,48 +459,208 @@ namespace OHOS
             }
         }
         
-        // 停止并销毁旧播放器
-        if (videoPlay_ != nullptr)
+        Surface *surface = surfaceView_->GetSurface();
+        if (surface == nullptr)
         {
-            if (videoPlay_->isPlayed_)
+            WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: Failed to get surface from surfaceView_");
+            g_isSwitchingVideo = false;
+            return false;
+        }
+
+        MediaVideoPlay *oldPlayer = videoPlay_;
+        bool oldWasPlaying = (oldPlayer != nullptr && oldPlayer->isPlayed_);
+        bool oldWasEntered = (oldPlayer != nullptr && oldPlayer->isEntered_);
+        MediaVideoPlay *newPlayer = nullptr;
+        ImageInfo lastFrameImage = {{0}};
+        bool hasLastFrame = false;
+
+        // 步骤1:暂停旧播放器,保留最后一帧在屏幕上(如果正在播放)
+        if (oldPlayer != nullptr && oldWasPlaying)
+        {
+            oldPlayer->PauseVideoPlay(); // 暂停保留最后一帧,减少黑屏
+        }
+        
+        // 步骤1.5:截取最后一帧并显示,避免黑屏
+        if (oldPlayer != nullptr && oldWasPlaying && surfaceView_ != nullptr)
+        {
+            // 等待一小段时间确保暂停后的最后一帧已经显示
+            usleep(10000); // 10ms
+            
+            // 获取surfaceView_的位置和大小
+            Rect surfaceRect;
+            surfaceRect.SetX(surfaceView_->GetX());
+            surfaceRect.SetY(surfaceView_->GetY());
+            surfaceRect.SetWidth(surfaceView_->GetWidth());
+            surfaceRect.SetHeight(surfaceView_->GetHeight());
+            
+            // 截取surfaceView_区域的屏幕
+            if (Screen::GetInstance().GetScreenBitmapByRect(lastFrameImage, &surfaceRect))
             {
-                videoPlay_->PauseVideoPlay();
+                hasLastFrame = true;
+                WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: Captured last frame, size: %dx%d", 
+                             lastFrameImage.header.width, lastFrameImage.header.height);
+                
+                // 创建或更新临时视图显示最后一帧
+                if (lastFrameView_ == nullptr)
+                {
+                    lastFrameView_ = new UIImageView();
+                    if (lastFrameView_ != nullptr)
+                    {
+                        lastFrameView_->SetPosition(surfaceRect.GetX(), surfaceRect.GetY(), 
+                                                    surfaceRect.GetWidth(), surfaceRect.GetHeight());
+                        lastFrameView_->SetAutoEnable(false);
+                        lastFrameView_->SetResizeMode(UIImageView::COVER);
+                        // 将临时视图添加到surfaceView_的父容器中,并放在surfaceView_上方
+                        if (ScrollView_ != nullptr)
+                        {
+                            ScrollView_->Add(lastFrameView_);
+                        }
+                    }
+                }
+                
+                if (lastFrameView_ != nullptr)
+                {
+                    lastFrameView_->SetSrc(&lastFrameImage);
+                    lastFrameView_->SetVisible(true);
+                    lastFrameView_->Invalidate();
+                    WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: Last frame view displayed");
+                }
             }
-            if (videoPlay_->isEntered_)
+            else
             {
-                videoPlay_->StopVideoPlay();
+                WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: Failed to capture last frame");
             }
-            delete videoPlay_;
-            videoPlay_ = nullptr;
         }
 
-        Surface *surface = surfaceView_->GetSurface();
-        if (surface == nullptr)
+        // 步骤2:在创建新播放器前,停止并销毁旧播放器
+        // 静态图片会遮挡整个过程,所以可以安全地销毁旧播放器
+        if (oldPlayer != nullptr)
         {
-            WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: Failed to get surface from surfaceView_");
-            g_isSwitchingVideo = false; // 清除标志
-            return false;
+            // 先停止播放
+            if (oldWasEntered || oldWasPlaying)
+            {
+                oldPlayer->StopVideoPlay();
+            }
+            // 等待旧播放器完全停止
+            usleep(50000); // 50ms,确保旧播放器完全停止
+            delete oldPlayer;
+            oldPlayer = nullptr;
+            videoPlay_ = nullptr; // 清除指针
+            g_videoPlay = nullptr;
+            WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: Old player stopped and destroyed before creating new player");
         }
 
-        // å\88\9b建æ\96°æ\92­æ\94¾å\99¨å¹¶å\90¯å\8a¨
-        videoPlay_ = new MediaVideoPlay(surface, nullptr, std::string(uri), true);
-        if (videoPlay_ == nullptr)
+        // æ­¥éª¤3ï¼\9aå\88\9b建æ\96°æ\92­æ\94¾å\99¨
+        newPlayer = new MediaVideoPlay(surface, nullptr, targetPath, true);
+        if (newPlayer == nullptr)
         {
             WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: Failed to create MediaVideoPlay");
-            g_isSwitchingVideo = false; // 清除标志
+            g_isSwitchingVideo = false;
             return false;
         }
-        g_videoPlay = videoPlay_;
-        int32_t ret = videoPlay_->StartVideoPlay();
+
+        // 步骤4:立即启动新播放器
+        int32_t ret = newPlayer->StartVideoPlay();
         if (ret != 0)
         {
             WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: Failed to start video play, ret=%d", ret);
-            g_isSwitchingVideo = false; // 清除标志
+            delete newPlayer;
+            g_isSwitchingVideo = false;
             return false;
         }
+
+        // 步骤5:立即切换指针到新播放器
+        videoPlay_ = newPlayer;
+        g_videoPlay = videoPlay_;
+        
+        // 步骤5.5:同步等待新播放器渲染第一帧后再隐藏最后一帧视图
+        // 静态图片会一直遮挡,直到新播放器的第一帧真正渲染到屏幕
+        // 通过检查Surface的buffer状态来判断第一帧是否已渲染
+        if (lastFrameView_ != nullptr && lastFrameView_->IsVisible() && surface != nullptr)
+        {
+            // 等待新播放器开始播放
+            int checkCount = 0;
+            const int maxCheckCount = 50; // 50 * 20ms = 1000ms
+            bool playerConfirmed = false;
+            
+            while (checkCount < maxCheckCount)
+            {
+                // 检查新播放器是否真正开始播放
+                if (newPlayer != nullptr && newPlayer->isEntered_ && newPlayer->isPlayed_ && !newPlayer->isPause_)
+                {
+                    playerConfirmed = true;
+                    WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: New player confirmed playing after %d ms", checkCount * 20);
+                    break;
+                }
+                usleep(20000); // 等待20ms
+                checkCount++;
+            }
+            
+            // 如果新播放器已开始播放,检查Surface是否有新的buffer(表示第一帧已输出)
+            if (playerConfirmed)
+            {
+                // 检查Surface是否有新的buffer被flush(表示第一帧已经输出到Surface)
+                // 通过检查GetBackBuf()来判断是否有新的buffer
+                LiteSurface* liteSurface = dynamic_cast<LiteSurface*>(surface);
+                if (liteSurface != nullptr)
+                {
+                    int bufferCheckCount = 0;
+                    const int maxBufferCheckCount = 30; // 30 * 50ms = 1500ms
+                    bool firstFrameReady = false;
+                    
+                    while (bufferCheckCount < maxBufferCheckCount)
+                    {
+                        // 检查Surface是否有新的buffer(GetBackBuf返回非null表示有新的buffer)
+                        SurfaceBuffer* backBuf = liteSurface->GetBackBuf();
+                        if (backBuf != nullptr)
+                        {
+                            // 有新的buffer,说明第一帧已经输出到Surface
+                            firstFrameReady = true;
+                            WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: First frame buffer ready after %d ms", bufferCheckCount * 50);
+                            // 再等待一小段时间确保第一帧已经渲染到屏幕
+                            usleep(50000); // 50ms,确保第一帧已渲染到屏幕
+                            break;
+                        }
+                        usleep(50000); // 等待50ms
+                        bufferCheckCount++;
+                    }
+                    
+                    if (!firstFrameReady)
+                    {
+                        WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: First frame buffer not ready after %d ms, proceeding anyway", maxBufferCheckCount * 50);
+                    }
+                }
+                else
+                {
+                    // 如果不是LiteSurface,使用固定等待时间
+                    usleep(300000); // 300ms
+                }
+            }
+            
+            // 隐藏最后一帧视图(此时新播放器的第一帧应该已经渲染到屏幕)
+            // 静态图片会一直遮挡,直到这里才隐藏
+            if (lastFrameView_ != nullptr && lastFrameView_->IsVisible())
+            {
+                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)
+            {
+                ImageCacheFree(lastFrameImage);
+                WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "ThunderBirdMainView::SwitchVideo: Released last frame image memory");
+            }
+        }
+        else if (hasLastFrame && lastFrameImage.data != nullptr)
+        {
+            // 如果没有静态图片视图,直接释放截图内存
+            ImageCacheFree(lastFrameImage);
+        }
         
         // 更新当前播放的视频路径
-        currentVideoPath_ = std::string(uri);
+        currentVideoPath_ = targetPath;
         
         // 清除切换视频标志
         g_isSwitchingVideo = false;
@@ -521,6 +685,18 @@ namespace OHOS
             delete videoPlay_;
             videoPlay_ = nullptr;
         }
+        
+        // 清理最后一帧视图
+        if (lastFrameView_ != nullptr)
+        {
+            if (ScrollView_ != nullptr)
+            {
+                ScrollView_->Remove(lastFrameView_);
+            }
+            delete lastFrameView_;
+            lastFrameView_ = nullptr;
+        }
+        
         g_videoPlay = nullptr;
         g_lastPlayed = false;
         // 注销屏幕状态通知
@@ -537,8 +713,7 @@ namespace OHOS
             curFace = nullptr;
         }
     }
-  
-
+    
     // OTA界面已独立为slice,不再需要此函数
 
     bool ThunderBirdMainView::ChangeThunderBirdGuideView(int16_t thisView) // 切换引导界面接口