From 560962e2cc8bc5d2db5f9631500e7d4d27d19ddf Mon Sep 17 00:00:00 2001 From: xue <2144439972@qq.com> Date: Thu, 11 Dec 2025 11:54:12 +0800 Subject: [PATCH] =?utf8?q?=E4=BF=AE=E5=A4=8D=E5=88=87=E6=8D=A2=E8=A1=A8?= =?utf8?q?=E6=83=85=E4=BC=9A=E9=97=AA=E9=BB=91=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../thunderbird/include/ThunderBirdMainView.h | 1 + .../thunderbird/src/ThunderBirdMainView.cpp | 223 ++++++++++++++++-- 2 files changed, 200 insertions(+), 24 deletions(-) diff --git a/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/include/ThunderBirdMainView.h b/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/include/ThunderBirdMainView.h index 969375e62..69d98619d 100644 --- a/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/include/ThunderBirdMainView.h +++ b/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/include/ThunderBirdMainView.h @@ -140,6 +140,7 @@ namespace OHOS DialVideoView *videoPlayer_ = nullptr; UILiteSurfaceView *surfaceView_ = nullptr; UIImageView *hairIcon{nullptr}; + UIImageView *lastFrameView_{nullptr}; // 用于显示最后一帧截图的临时视图 ColorType colorKey_; 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 52c065a28..aa975e09a 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 @@ -30,6 +30,8 @@ #include "custom_runtime.h" #include #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"); } - // 创建新播放器并启动 - videoPlay_ = new MediaVideoPlay(surface, nullptr, std::string(uri), true); - if (videoPlay_ == nullptr) + // 步骤3:创建新播放器 + 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(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) // 切换引导界面接口 -- 2.17.1