diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 005c3aa..e3e8f69 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -18,6 +18,12 @@ config STORY_API_URL help 故事播放API接口地址,设备会附加 ?mac_address=XX:XX:XX:XX:XX:XX 参数请求 +config MUSIC_API_URL + string "Music API URL" + default "http://192.168.124.8:8000/api/v1/devices/music/" + help + 音乐播放API接口地址,设备会附加 ?mac_address=XX:XX:XX:XX:XX:XX 参数请求 + choice prompt "语言选择" default LANGUAGE_ZH_CN diff --git a/main/application.cc b/main/application.cc index a556bb8..558c02e 100644 --- a/main/application.cc +++ b/main/application.cc @@ -917,7 +917,7 @@ void Application::Start() { } } } - // 讲故事功能:支持HTTPS下载或WebSocket两种方式 + // 讲故事功能:支持指定URL或故事API两种HTTPS方式 else if (strcmp(name->valuestring, "obtain_story") == 0) { ESP_LOGI(TAG, "收到obtain_story工具调用"); cJSON* sn = cJSON_GetObjectItem(args_obj, "story_name"); @@ -933,8 +933,8 @@ void Application::Start() { HttpsPlaybackFromUrl(url_item->valuestring); msg = "正在通过HTTPS为你播放故事"; } else { - // WebSocket方式:通过服务器推送 - ESP_LOGI(TAG, "[WS播放] 使用WebSocket方式请求音频"); + // HTTPS故事API方式:通过蓝牙MAC请求故事 + ESP_LOGI(TAG, "[HTTPS播放] 使用故事API方式请求音频"); SendStoryRequest(); msg = "正在为你获取故事"; } @@ -944,6 +944,31 @@ void Application::Start() { protocol_->SendFunctionResult(call_id, msg); } } + // 播放音乐功能:支持指定URL或音乐API两种HTTPS方式 + else if (strcmp(name->valuestring, "obtain_music") == 0) { + ESP_LOGI(TAG, "收到obtain_music工具调用"); + cJSON* mn = cJSON_GetObjectItem(args_obj, "music"); + const char* music = (mn && cJSON_IsString(mn) && mn->valuestring) ? mn->valuestring : "random"; + cJSON* url_item = cJSON_GetObjectItem(args_obj, "music_url"); + ESP_LOGI(TAG, "音乐名称: %s", music); + + AbortSpeaking(kAbortReasonNone); + std::string msg; + if (url_item && cJSON_IsString(url_item) && url_item->valuestring && strlen(url_item->valuestring) > 0) { + ESP_LOGI(TAG, "[HTTPS播放] 使用URL方式: %s", url_item->valuestring); + HttpsPlaybackFromUrl(url_item->valuestring); + msg = "正在通过HTTPS为你播放音乐"; + } else { + ESP_LOGI(TAG, "[HTTPS播放] 使用音乐API方式请求音频"); + SendMusicRequest(); + msg = "正在为你获取音乐"; + } + cJSON* call_id_item = cJSON_GetObjectItem(call, "id"); + const char* call_id = (call_id_item && cJSON_IsString(call_id_item) && call_id_item->valuestring) ? call_id_item->valuestring : ""; + if (protocol_ && call_id && call_id[0] != '\0') { + protocol_->SendFunctionResult(call_id, msg); + } + } // // 添加天气查询功能处理 get_weather_aihub // else if (strcmp(name->valuestring, "get_weather_aihub") == 0) { // ESP_LOGI(TAG, "[WeatherAPI] ===== 收到get_weather_aihub工具调用 ====="); @@ -1139,7 +1164,7 @@ void Application::Start() { } } } - // 讲故事功能:支持HTTPS下载或WebSocket两种方式 + // 讲故事功能:支持指定URL或故事API两种HTTPS方式 else if (strcmp(name->valuestring, "obtain_story") == 0) { ESP_LOGI(TAG, "收到obtain_story工具调用"); cJSON* url_item = cJSON_GetObjectItem(args_obj, "story_url"); @@ -1150,7 +1175,8 @@ void Application::Start() { HttpsPlaybackFromUrl(url_item->valuestring); msg = "正在通过HTTPS为你播放故事"; } else { - ESP_LOGI(TAG, "[WS播放] 使用WebSocket方式请求音频"); + // HTTPS故事API方式:通过蓝牙MAC请求故事 + ESP_LOGI(TAG, "[HTTPS播放] 使用故事API方式请求音频"); SendStoryRequest(); msg = "正在为你获取故事"; } @@ -1160,6 +1186,27 @@ void Application::Start() { protocol_->SendFunctionResult(call_id, msg); } } + // 播放音乐功能:支持指定URL或音乐API两种HTTPS方式 + else if (strcmp(name->valuestring, "obtain_music") == 0) { + ESP_LOGI(TAG, "收到obtain_music工具调用"); + cJSON* url_item = cJSON_GetObjectItem(args_obj, "music_url"); + AbortSpeaking(kAbortReasonNone); + std::string msg; + if (url_item && cJSON_IsString(url_item) && url_item->valuestring && strlen(url_item->valuestring) > 0) { + ESP_LOGI(TAG, "[HTTPS播放] 使用URL方式: %s", url_item->valuestring); + HttpsPlaybackFromUrl(url_item->valuestring); + msg = "正在通过HTTPS为你播放音乐"; + } else { + ESP_LOGI(TAG, "[HTTPS播放] 使用音乐API方式请求音频"); + SendMusicRequest(); + msg = "正在为你获取音乐"; + } + cJSON* call_id_item = cJSON_GetObjectItem(call, "id"); + const char* call_id = (call_id_item && cJSON_IsString(call_id_item) && call_id_item->valuestring) ? call_id_item->valuestring : ""; + if (protocol_ && call_id && call_id[0] != '\0') { + protocol_->SendFunctionResult(call_id, msg); + } + } } } } @@ -1311,7 +1358,7 @@ void Application::Start() { } } } - // 讲故事功能:支持HTTPS下载或WebSocket两种方式 + // 讲故事功能:支持指定URL或故事API两种HTTPS方式 else if (strcmp(name->valuestring, "obtain_story") == 0) { ESP_LOGI(TAG, "收到obtain_story工具调用"); cJSON* url_item = cJSON_GetObjectItem(args_obj, "story_url"); @@ -1322,7 +1369,8 @@ void Application::Start() { HttpsPlaybackFromUrl(url_item->valuestring); msg = "正在通过HTTPS为你播放故事"; } else { - ESP_LOGI(TAG, "[WS播放] 使用WebSocket方式请求音频"); + // HTTPS故事API方式:通过蓝牙MAC请求故事 + ESP_LOGI(TAG, "[HTTPS播放] 使用故事API方式请求音频"); SendStoryRequest(); msg = "正在为你获取故事"; } @@ -1332,6 +1380,27 @@ void Application::Start() { protocol_->SendFunctionResult(call_id, msg); } } + // 播放音乐功能:支持指定URL或音乐API两种HTTPS方式 + else if (strcmp(name->valuestring, "obtain_music") == 0) { + ESP_LOGI(TAG, "收到obtain_music工具调用"); + cJSON* url_item = cJSON_GetObjectItem(args_obj, "music_url"); + AbortSpeaking(kAbortReasonNone); + std::string msg; + if (url_item && cJSON_IsString(url_item) && url_item->valuestring && strlen(url_item->valuestring) > 0) { + ESP_LOGI(TAG, "[HTTPS播放] 使用URL方式: %s", url_item->valuestring); + HttpsPlaybackFromUrl(url_item->valuestring); + msg = "正在通过HTTPS为你播放音乐"; + } else { + ESP_LOGI(TAG, "[HTTPS播放] 使用音乐API方式请求音频"); + SendMusicRequest(); + msg = "正在为你获取音乐"; + } + cJSON* call_id_item = cJSON_GetObjectItem(root, "call_id"); + const char* call_id = (call_id_item && cJSON_IsString(call_id_item) && call_id_item->valuestring) ? call_id_item->valuestring : ""; + if (protocol_ && call_id && call_id[0] != '\0') { + protocol_->SendFunctionResult(call_id, msg); + } + } cJSON_Delete(args_obj); } else { ESP_LOGI(TAG, "工具调用: name=%s arguments=%s", name->valuestring, args_str); @@ -1384,7 +1453,7 @@ void Application::Start() { } } } - // 讲故事功能:支持HTTPS下载或WebSocket两种方式 + // 讲故事功能:支持指定URL或故事API两种HTTPS方式 else if (strcmp(name->valuestring, "obtain_story") == 0) { ESP_LOGI(TAG, "收到obtain_story工具调用"); cJSON* args_parsed = nullptr; @@ -1399,7 +1468,8 @@ void Application::Start() { HttpsPlaybackFromUrl(url_item->valuestring); msg = "正在通过HTTPS为你播放故事"; } else { - ESP_LOGI(TAG, "[WS播放] 使用WebSocket方式请求音频"); + // HTTPS故事API方式:通过蓝牙MAC请求故事 + ESP_LOGI(TAG, "[HTTPS播放] 使用故事API方式请求音频"); SendStoryRequest(); msg = "正在为你获取故事"; } @@ -1410,6 +1480,32 @@ void Application::Start() { protocol_->SendFunctionResult(call_id, msg); } } + // 播放音乐功能:支持指定URL或音乐API两种HTTPS方式 + else if (strcmp(name->valuestring, "obtain_music") == 0) { + ESP_LOGI(TAG, "收到obtain_music工具调用"); + cJSON* args_parsed2 = nullptr; + if (arguments && cJSON_IsString(arguments) && arguments->valuestring) { + args_parsed2 = cJSON_Parse(arguments->valuestring); + } + cJSON* url_item = args_parsed2 ? cJSON_GetObjectItem(args_parsed2, "music_url") : nullptr; + AbortSpeaking(kAbortReasonNone); + std::string msg; + if (url_item && cJSON_IsString(url_item) && url_item->valuestring && strlen(url_item->valuestring) > 0) { + ESP_LOGI(TAG, "[HTTPS播放] 使用URL方式: %s", url_item->valuestring); + HttpsPlaybackFromUrl(url_item->valuestring); + msg = "正在通过HTTPS为你播放音乐"; + } else { + ESP_LOGI(TAG, "[HTTPS播放] 使用音乐API方式请求音频"); + SendMusicRequest(); + msg = "正在为你获取音乐"; + } + if (args_parsed2) cJSON_Delete(args_parsed2); + cJSON* call_id_item = cJSON_GetObjectItem(root, "call_id"); + const char* call_id = (call_id_item && cJSON_IsString(call_id_item) && call_id_item->valuestring) ? call_id_item->valuestring : ""; + if (protocol_ && call_id && call_id[0] != '\0') { + protocol_->SendFunctionResult(call_id, msg); + } + } } } // 新增代码(小程序控制 暂停/继续播放 音频) @@ -2386,15 +2482,36 @@ void Application::AbortHttpsPlayback(const char* reason) { }, "dma_flush", 4096, NULL, 10, NULL); } -// 通过故事API请求并播放故事(intro标题 + body正文无缝衔接) +// 通过HTTPS故事API请求并播放故事 void Application::SendStoryRequest() { - // 防止重复启动:opus_playback_active_ 在任务启动时设置,覆盖HTTP请求阶段 + HttpsApiPlayback(CONFIG_STORY_API_URL, "故事API", "story_play"); +} + +// 通过HTTPS音乐API请求并播放音乐 +void Application::SendMusicRequest() { + HttpsApiPlayback(CONFIG_MUSIC_API_URL, "音乐API", "music_play"); +} + +// HTTPS API音频播放通用实现(intro标题 + body正文无缝衔接) +// api_url: API基础地址(自动附加 ?mac_address=),tag: 日志标签,task_name: FreeRTOS任务名 +struct HttpsApiParams { + const char* api_url; + const char* tag; +}; + +void Application::HttpsApiPlayback(const char* api_url_base, const char* tag, const char* task_name) { if (https_playback_active_.load() || https_playback_abort_.load() || opus_playback_active_.load()) { - ESP_LOGW(TAG, "[故事API] 已有音频正在播放或退出中,忽略本次请求"); + ESP_LOGW(TAG, "[%s] 已有音频正在播放或退出中,忽略本次请求", tag); return; } + auto* params = new HttpsApiParams{api_url_base, tag}; xTaskCreate([](void* arg) { + auto* p = static_cast(arg); + const char* api_url_base = p->api_url; + const char* tag = p->tag; + delete p; + auto& app = Application::GetInstance(); // 先设置opus和abort标志(用于重复启动守卫和OnIncomingAudio阻断RTC PCM) // 注意:https_playback_active_ 延迟到intro音频入队前设置, @@ -2413,19 +2530,18 @@ void Application::SendStoryRequest() { b64_inited = true; } - // ========== 步骤1: 请求故事API ========== + // ========== 步骤1: 请求API ========== std::string mac = SystemInfo::GetBleMacAddress(); - // 转大写 for (auto& c : mac) { if (c >= 'a' && c <= 'f') c -= 32; } char api_url[256]; snprintf(api_url, sizeof(api_url), "%s?mac_address=%s", - CONFIG_STORY_API_URL, mac.c_str()); + api_url_base, mac.c_str()); - ESP_LOGI(TAG, "[故事API] 请求: %s", api_url); - ESP_LOGI(TAG, "[故事API] 空闲堆: %lu", (unsigned long)esp_get_free_heap_size()); + ESP_LOGI(TAG, "[%s] 请求: %s", tag, api_url); + ESP_LOGI(TAG, "[%s] 空闲堆: %lu", tag, (unsigned long)esp_get_free_heap_size()); esp_http_client_config_t api_config = {}; api_config.url = api_url; @@ -2436,7 +2552,7 @@ void Application::SendStoryRequest() { esp_http_client_handle_t api_client = esp_http_client_init(&api_config); if (!api_client) { - ESP_LOGE(TAG, "[故事API] HTTP客户端初始化失败"); + ESP_LOGE(TAG, "[%s] HTTP客户端初始化失败", tag); app.https_playback_active_.store(false); app.opus_playback_active_.store(false); vTaskDelete(NULL); @@ -2445,7 +2561,7 @@ void Application::SendStoryRequest() { esp_err_t err = esp_http_client_open(api_client, 0); if (err != ESP_OK) { - ESP_LOGE(TAG, "[故事API] 连接失败: %s", esp_err_to_name(err)); + ESP_LOGE(TAG, "[%s] 连接失败: %s", tag, esp_err_to_name(err)); esp_http_client_cleanup(api_client); app.https_playback_active_.store(false); app.opus_playback_active_.store(false); @@ -2456,7 +2572,7 @@ void Application::SendStoryRequest() { esp_http_client_fetch_headers(api_client); int api_status = esp_http_client_get_status_code(api_client); if (api_status != 200) { - ESP_LOGE(TAG, "[故事API] 请求失败,状态码: %d", api_status); + ESP_LOGE(TAG, "[%s] 请求失败,状态码: %d", tag, api_status); esp_http_client_close(api_client); esp_http_client_cleanup(api_client); app.https_playback_active_.store(false); @@ -2475,13 +2591,13 @@ void Application::SendStoryRequest() { esp_http_client_close(api_client); esp_http_client_cleanup(api_client); - ESP_LOGI(TAG, "[故事API] 响应: %d 字节", (int)api_response.size()); + ESP_LOGI(TAG, "[%s] 响应: %d 字节", tag, (int)api_response.size()); if (app.https_playback_abort_.load()) { app.https_playback_active_.store(false); app.https_playback_abort_.store(false); app.opus_playback_active_.store(false); - ESP_LOGI(TAG, "[故事API] HTTP请求阶段被中止"); + ESP_LOGI(TAG, "[%s] HTTP请求阶段被中止", tag); vTaskDelete(NULL); return; } @@ -2492,7 +2608,7 @@ void Application::SendStoryRequest() { api_response.shrink_to_fit(); if (!root) { - ESP_LOGE(TAG, "[故事API] JSON解析失败"); + ESP_LOGE(TAG, "[%s] JSON解析失败", tag); app.https_playback_active_.store(false); app.opus_playback_active_.store(false); vTaskDelete(NULL); @@ -2502,7 +2618,7 @@ void Application::SendStoryRequest() { cJSON* code_item = cJSON_GetObjectItem(root, "code"); if (!code_item || code_item->valueint != 0) { cJSON* msg_item = cJSON_GetObjectItem(root, "message"); - ESP_LOGE(TAG, "[故事API] 服务端错误: %s", + ESP_LOGE(TAG, "[%s] 服务端错误: %s", tag, (msg_item && msg_item->valuestring) ? msg_item->valuestring : "unknown"); cJSON_Delete(root); app.https_playback_active_.store(false); @@ -2518,7 +2634,7 @@ void Application::SendStoryRequest() { if (!intro_str || !cJSON_IsString(intro_str) || !intro_str->valuestring || !opus_url_item || !cJSON_IsString(opus_url_item) || !opus_url_item->valuestring) { - ESP_LOGE(TAG, "[故事API] 缺少intro_opus_data或opus_url字段"); + ESP_LOGE(TAG, "[%s] 缺少intro_opus_data或opus_url字段", tag); cJSON_Delete(root); app.https_playback_active_.store(false); app.opus_playback_active_.store(false); @@ -2526,7 +2642,7 @@ void Application::SendStoryRequest() { return; } - ESP_LOGI(TAG, "[故事API] 标题: %s", + ESP_LOGI(TAG, "[%s] 标题: %s", tag, (title_item && title_item->valuestring) ? title_item->valuestring : "未知"); // 提取字符串后释放外层JSON @@ -2540,7 +2656,7 @@ void Application::SendStoryRequest() { intro_json_str.shrink_to_fit(); if (!intro_root) { - ESP_LOGE(TAG, "[故事API] intro JSON解析失败"); + ESP_LOGE(TAG, "[%s] intro JSON解析失败", tag); app.https_playback_active_.store(false); app.opus_playback_active_.store(false); vTaskDelete(NULL); @@ -2552,7 +2668,7 @@ void Application::SendStoryRequest() { cJSON* intro_frames = cJSON_GetObjectItem(intro_root, "frames"); if (!intro_frames || !cJSON_IsArray(intro_frames)) { - ESP_LOGE(TAG, "[故事API] intro缺少frames数组"); + ESP_LOGE(TAG, "[%s] intro缺少frames数组", tag); cJSON_Delete(intro_root); app.https_playback_active_.store(false); app.opus_playback_active_.store(false); @@ -2564,7 +2680,7 @@ void Application::SendStoryRequest() { int frame_duration = (intro_fd && cJSON_IsNumber(intro_fd)) ? intro_fd->valueint : 60; int intro_count = cJSON_GetArraySize(intro_frames); - ESP_LOGI(TAG, "[故事API] intro: 采样率=%d, 帧时长=%dms, 帧数=%d (%.1f秒)", + ESP_LOGI(TAG, "[%s] intro: 采样率=%d, 帧时长=%dms, 帧数=%d (%.1f秒)", tag, sample_rate, frame_duration, intro_count, intro_count * frame_duration / 1000.0f); @@ -2620,10 +2736,10 @@ void Application::SendStoryRequest() { } cJSON_Delete(intro_root); - ESP_LOGI(TAG, "[故事API] intro入队完成: %d帧, 错误: %d", enqueued, errors); + ESP_LOGI(TAG, "[%s] intro入队完成: %d帧, 错误: %d", tag, enqueued, errors); if (app.https_playback_abort_.load()) { - ESP_LOGI(TAG, "[故事API] intro阶段被中止"); + ESP_LOGI(TAG, "[%s] intro阶段被中止", tag); app.https_playback_active_.store(false); app.https_playback_abort_.store(false); app.opus_playback_active_.store(false); @@ -2632,7 +2748,7 @@ void Application::SendStoryRequest() { } // ========== 步骤4: 下载 opus_url 正文 ========== - ESP_LOGI(TAG, "[故事API] 开始下载正文: %s", opus_url.c_str()); + ESP_LOGI(TAG, "[%s] 开始下载正文: %s", tag, opus_url.c_str()); esp_http_client_config_t opus_config = {}; opus_config.url = opus_url.c_str(); @@ -2647,7 +2763,7 @@ void Application::SendStoryRequest() { esp_http_client_handle_t opus_client = esp_http_client_init(&opus_config); if (!opus_client) { - ESP_LOGE(TAG, "[故事API] opus HTTP初始化失败"); + ESP_LOGE(TAG, "[%s] opus HTTP初始化失败", tag); app.https_playback_active_.store(false); app.https_playback_abort_.store(false); app.opus_playback_active_.store(false); @@ -2657,7 +2773,7 @@ void Application::SendStoryRequest() { err = esp_http_client_open(opus_client, 0); if (err != ESP_OK) { - ESP_LOGE(TAG, "[故事API] opus连接失败: %s", esp_err_to_name(err)); + ESP_LOGE(TAG, "[%s] opus连接失败: %s", tag, esp_err_to_name(err)); esp_http_client_cleanup(opus_client); app.https_playback_active_.store(false); app.https_playback_abort_.store(false); @@ -2668,10 +2784,10 @@ void Application::SendStoryRequest() { int64_t opus_content_len = esp_http_client_fetch_headers(opus_client); int opus_status = esp_http_client_get_status_code(opus_client); - ESP_LOGI(TAG, "[故事API] opus状态码: %d, 长度: %lld", opus_status, (long long)opus_content_len); + ESP_LOGI(TAG, "[%s] opus状态码: %d, 长度: %lld", tag, opus_status, (long long)opus_content_len); if (opus_status != 200) { - ESP_LOGE(TAG, "[故事API] opus请求失败,状态码: %d", opus_status); + ESP_LOGE(TAG, "[%s] opus请求失败,状态码: %d", tag, opus_status); esp_http_client_close(opus_client); esp_http_client_cleanup(opus_client); app.https_playback_active_.store(false); @@ -2692,11 +2808,11 @@ void Application::SendStoryRequest() { esp_http_client_close(opus_client); esp_http_client_cleanup(opus_client); - ESP_LOGI(TAG, "[故事API] opus下载完成: %d 字节, 堆: %lu", + ESP_LOGI(TAG, "[%s] opus下载完成: %d 字节, 堆: %lu", tag, total_read, (unsigned long)esp_get_free_heap_size()); if (app.https_playback_abort_.load()) { - ESP_LOGI(TAG, "[故事API] opus下载被中止"); + ESP_LOGI(TAG, "[%s] opus下载被中止", tag); app.https_playback_active_.store(false); app.https_playback_abort_.store(false); app.opus_playback_active_.store(false); @@ -2708,10 +2824,10 @@ void Application::SendStoryRequest() { cJSON* opus_root = cJSON_Parse(opus_json.c_str()); opus_json.clear(); opus_json.shrink_to_fit(); - ESP_LOGI(TAG, "[故事API] opus JSON已释放, 堆: %lu", (unsigned long)esp_get_free_heap_size()); + ESP_LOGI(TAG, "[%s] opus JSON已释放, 堆: %lu", tag, (unsigned long)esp_get_free_heap_size()); if (!opus_root) { - ESP_LOGE(TAG, "[故事API] opus JSON解析失败"); + ESP_LOGE(TAG, "[%s] opus JSON解析失败", tag); app.https_playback_active_.store(false); app.https_playback_abort_.store(false); app.opus_playback_active_.store(false); @@ -2721,7 +2837,7 @@ void Application::SendStoryRequest() { cJSON* body_frames = cJSON_GetObjectItem(opus_root, "frames"); if (!body_frames || !cJSON_IsArray(body_frames)) { - ESP_LOGE(TAG, "[故事API] opus缺少frames数组"); + ESP_LOGE(TAG, "[%s] opus缺少frames数组", tag); cJSON_Delete(opus_root); app.https_playback_active_.store(false); app.https_playback_abort_.store(false); @@ -2737,7 +2853,7 @@ void Application::SendStoryRequest() { int body_frame_duration = (body_fd && cJSON_IsNumber(body_fd)) ? body_fd->valueint : frame_duration; int body_count = cJSON_GetArraySize(body_frames); - ESP_LOGI(TAG, "[故事API] body: 采样率=%d, 帧时长=%dms, 帧数=%d (%.1f秒)", + ESP_LOGI(TAG, "[%s] body: 采样率=%d, 帧时长=%dms, 帧数=%d (%.1f秒)", tag, body_sample_rate, body_frame_duration, body_count, body_count * body_frame_duration / 1000.0f); @@ -2750,7 +2866,7 @@ void Application::SendStoryRequest() { for (int i = 0; i < body_count; i++) { if (app.https_playback_abort_.load()) { - ESP_LOGI(TAG, "[故事API] body入队中止: %d/%d", body_enqueued, body_count); + ESP_LOGI(TAG, "[%s] body入队中止: %d/%d", tag, body_enqueued, body_count); break; } @@ -2796,7 +2912,7 @@ void Application::SendStoryRequest() { if (body_enqueued % 100 == 0) { size_t qs; { std::lock_guard lock(app.mutex_); qs = app.audio_decode_queue_.size(); } - ESP_LOGI(TAG, "[故事API] body进度: %d/%d (%.0f%%), 队列: %zu, 堆: %lu", + ESP_LOGI(TAG, "[%s] body进度: %d/%d (%.0f%%), 队列: %zu, 堆: %lu", tag, body_enqueued, body_count, body_enqueued * 100.0f / body_count, qs, (unsigned long)esp_get_free_heap_size()); @@ -2804,11 +2920,11 @@ void Application::SendStoryRequest() { } cJSON_Delete(opus_root); - ESP_LOGI(TAG, "[故事API] body入队完成: %d帧, 错误: %d", body_enqueued, body_errors); + ESP_LOGI(TAG, "[%s] body入队完成: %d帧, 错误: %d", tag, body_enqueued, body_errors); // ========== 步骤6: 等待播放完毕 ========== if (!app.https_playback_abort_.load()) { - ESP_LOGI(TAG, "[故事API] 全部入队完成,等待播放完毕..."); + ESP_LOGI(TAG, "[%s] 全部入队完成,等待播放完毕...", tag); while (!app.https_playback_abort_.load()) { size_t qs; { std::lock_guard lock(app.mutex_); qs = app.audio_decode_queue_.size(); } @@ -2821,11 +2937,11 @@ void Application::SendStoryRequest() { app.https_playback_active_.store(false); app.https_playback_abort_.store(false); app.opus_playback_active_.store(false); - ESP_LOGI(TAG, "[故事API] 播放结束, aborted=%d, 堆: %lu", + ESP_LOGI(TAG, "[%s] 播放结束, aborted=%d, 堆: %lu", tag, was_aborted, (unsigned long)esp_get_free_heap_size()); vTaskDelete(NULL); - }, "story_play", 10240, NULL, 5, NULL); + }, task_name, 10240, params, 5, NULL); } // 通过HTTPS下载JSON并流式播放音频(故事/歌曲等) diff --git a/main/application.h b/main/application.h index 0c95c86..6b3fb70 100644 --- a/main/application.h +++ b/main/application.h @@ -71,7 +71,8 @@ public: void DismissAlert();// 关闭警报 void AbortSpeaking(AbortReason reason);// 打断语音播报 void AbortHttpsPlayback(const char* reason);// 中止HTTPS音频播放并清空DMA - void SendStoryRequest(); // 发送讲故事 请求(WebSocket方式) + void SendStoryRequest(); // 通过HTTPS故事API请求并播放故事 + void SendMusicRequest(); // 通过HTTPS音乐API请求并播放音乐 void HttpsPlaybackFromUrl(const std::string& url); // 通过HTTPS下载JSON并播放音频(故事/歌曲等) void ToggleChatState();// 切换聊天状态 void ToggleListeningState();// 切换监听状态 @@ -115,6 +116,7 @@ public: // void HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON* data, BleJsonService& service); private: + void HttpsApiPlayback(const char* api_url, const char* tag, const char* task_name);// HTTPS API音频播放通用实现 Application();// 构造函数 ~Application();// 析构函数