Merge #3102 into ln from refs/changes/3102/head 3103/target/1
authorsnickersun@163.com <snickersun@163.com>
Sat, 17 Jan 2026 04:29:17 +0000 (04:29 +0000)
committersnickersun@163.com <snickersun@163.com>
Sat, 17 Jan 2026 04:29:17 +0000 (04:29 +0000)
Code review from snickersun@163.com

* refs/changes/3102/head: (2 commits squashed)

  - 避免暂停时video申请buffer,造成黑脸

  - 退出对话时保证资源销毁后重连websocket。减少死机风险。

Signed-off-by: snickersun@163.com <snickersun@163.com>
Reviewed-by: snickersun@163.com <snickersun@163.com>
Merged-by: snickersun@163.com <snickersun@163.com>
link: https://codeup.aliyun.com/64191456b9581d62e8f81ac0/FD230/change/3102
code/sdk/middleware/services/media/foundation/player/frameworks/player_control/player/liteplayer.cpp
code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c

index a914912..e0dcdba 100644 (file)
@@ -1316,6 +1316,12 @@ int32_t PlayerControl::QueueOutputBufferToVideoDecoder(void)
     CHECK_NULL_RETURN(sinkManager_, HI_FAILURE, "sink manager is null");
     CHECK_NULL_RETURN(videoDecoder_, HI_FAILURE, "video decoder is null");
 
+    // 0. 如果播放器已暂停,跳过请求缓冲区,避免屏幕关闭时连续失败
+    if (paused_) {
+        MEDIA_DEBUG_LOG("player is paused, skip requesting surface buffer");
+        return HI_SUCCESS;
+    }
+
     // 1. if video sink have cache, means have remain buffer not send to surface, don't need request buffer from surface
     bool haveCache = sinkManager_->HaveCacheFrame(false);
     if (haveCache) {
@@ -1330,6 +1336,11 @@ int32_t PlayerControl::QueueOutputBufferToVideoDecoder(void)
     surfaceBuf_ = LvRequestBuffer(reinterpret_cast<lv_obj_t *>(surface_), 0);
 #endif
     if (surfaceBuf_ == nullptr) {
+        // 如果播放器已暂停,不增加失败计数,直接返回成功
+        if (paused_) {
+            MEDIA_DEBUG_LOG("surface buffer request failed but player is paused, skip error count");
+            return HI_SUCCESS;
+        }
         if (continousRequestSurfaceBufferFailCount_ == MAX_CONTINOUS_REQUEST_SURFACE_BUFFER_FAIL_COUNT) {
             PalayControlEventItem item = { EVNET_VIDEO_RUNNING_ERR };
             eventQueue_.push_back(item);
index 1a98f6f..e9e9071 100644 (file)
@@ -1851,6 +1851,9 @@ int ln_ws_send_voice_chat_interrupt(const char *reason)
         return -1;
     }
     
+    // 检查是否是退出对话场景
+    bool is_exit_dialogue = (strcmp(reason, "退出对话") == 0);
+    
     // 检查是否需要在1秒内有数据时触发重连
     bool need_reconnect = ln_ws_check_binary_data_interrupt();
     
@@ -1899,24 +1902,64 @@ int ln_ws_send_voice_chat_interrupt(const char *reason)
     LN_WS_DEBUG("Sending VoiceChatInterrupt: %s\n", json_string);
     
     // 发送消息
+    int ret = uapi_ws_send_text(json_string);
+    if (ret == 0) {
+        LN_WS_DEBUG("VoiceChatInterrupt sent successfully\n");
+    }
     
     // 清理资源
-    int ret=0;
-    if (need_reconnect ||  (g_audio_chat_ctx.finish_state == LN_CHAT_SENT_END_NO_REPLY)) 
-    {
-        ret = uapi_ws_send_text(json_string);
-        if (ret == 0) {
-            LN_WS_DEBUG("VoiceChatInterrupt sent successfully\n");
-        }
-        // 如果检测到频繁数据接收,直接重连
-        LN_WS_INFO("Triggering reconnection due to frequent binary data reception\n");
-        if (ln_ws_reconnect() != 0) {
-            LN_WS_ERROR("Failed to reconnect after frequent data reception\n");
+    // 如果需要重连
+    if (need_reconnect || (g_audio_chat_ctx.finish_state == LN_CHAT_SENT_END_NO_REPLY)) {
+        if (is_exit_dialogue) {
+            // 只有退出对话场景,才等待资源清理完成后再重连
+            // 资源清理(AUDIO_DEINIT)通过消息队列异步执行,需要等待清理完成
+            LN_WS_INFO("Exit dialogue mode, waiting for resource cleanup before reconnection\n");
+            
+            // 轮询检查资源清理完成标志,最多等待500ms
+            const uint32_t max_wait_ms = 500;
+            const uint32_t check_interval_ms = 20; // 每20ms检查一次
+            uint32_t waited_ms = 0;
+            bool cleanup_complete = false;
+            
+            while (waited_ms < max_wait_ms) {
+                // 检查音频是否已反初始化(资源清理完成的标志)
+                if (!ln_ws_is_audio_initialized()) {
+                    cleanup_complete = true;
+                    LN_WS_INFO("Resource cleanup completed after %u ms\n", waited_ms);
+                    break;
+                }
+                LOS_Msleep(check_interval_ms);
+                waited_ms += check_interval_ms;
+            }
+            
+            if (!cleanup_complete) {
+                LN_WS_WARN("Resource cleanup timeout after %u ms, proceeding with reconnection anyway\n", max_wait_ms);
+            }
+            
+            // 再次检查连接状态,确保在等待期间连接状态未改变
+            if (g_ws_connected == WS_STATE_CONNECTED) {
+                LN_WS_INFO("Triggering reconnection after exit dialogue (resource cleanup %s)\n", 
+                          cleanup_complete ? "completed" : "timeout");
+                if (ln_ws_reconnect() != 0) {
+                    LN_WS_ERROR("Failed to reconnect after exit dialogue\n");
+                }
+            } else {
+                LN_WS_WARN("Connection state changed during wait, skip reconnection: %d\n", g_ws_connected);
+            }
+        } else {
+            // 非退出对话场景(如二进制数据间隔检查触发的重连),直接重连
+            // WebSocket重连是网络层操作,与音频资源无关
+            if (g_ws_connected == WS_STATE_CONNECTED) {
+                LN_WS_INFO("Triggering reconnection %s\n", 
+                          need_reconnect ? "due to binary data interrupt" : "due to chat finish state");
+                if (ln_ws_reconnect() != 0) {
+                    LN_WS_ERROR("Failed to reconnect\n");
+                }
+            } else {
+                LN_WS_WARN("Connection state changed, skip reconnection: %d\n", g_ws_connected);
+            }
         }
     }
-    else {
-        LN_WS_INFO("no to send VoiceChatInterrupt\n");
-    }
     cJSON_free(json_string);
     cJSON_Delete(json);
 
@@ -2409,6 +2452,16 @@ static int ln_ws_parse_data_message(const char *json_str, ws_data_msg_t *data_ms
     if (data_msg->command == WS_CMD_RANDOM_STORY_RESPONSE) {
         cJSON *data_obj = cJSON_GetObjectItem(root, "data");
         if (data_obj && cJSON_IsObject(data_obj)) {
+            // 根据协议,先检查errorCode字段(在data对象中,不在data.data中)
+            cJSON *error_code = cJSON_GetObjectItem(data_obj, "errorCode");
+            if (error_code && !cJSON_IsNull(error_code)) {
+                // 如果errorCode不为null,表示发生了错误
+                // 根据协议,错误响应中会包含data字段,其中包含错误信息和音频URL
+                LN_WS_WARN("RandomStoryResponse error: errorCode=%s\n", 
+                       cJSON_IsString(error_code) ? error_code->valuestring : "unknown");
+                // 错误响应中仍然会包含data字段,继续解析以便播放错误音频
+            }
+            
             // 解析finished字段(先解析到data_msg,但不更新全局上下文)
             cJSON *finished = cJSON_GetObjectItem(data_obj, "finished");
             if (finished && cJSON_IsBool(finished)) {
@@ -2437,7 +2490,7 @@ static int ln_ws_parse_data_message(const char *json_str, ws_data_msg_t *data_ms
                 }
             }
             
-            // 解析data.data字段
+            // 解析data.data字段(根据协议,即使有错误,data字段也可能存在,包含错误信息和音频URL)
             cJSON *data_data = cJSON_GetObjectItem(data_obj, "data");
             if (data_data && cJSON_IsObject(data_data)) {
                 // 解析storyTheme字段,存储到text_content中