wenhongquan hai 1 día
pai
achega
9beaab9161

+ 11 - 2
CMakeLists.txt

@@ -27,7 +27,7 @@ include_directories(${AVUTIL_INCLUDE_DIR})
 include_directories(${SWSCALE_INCLUDE_DIR})
 
 # Source files for main application
-set(MAIN_SOURCES 
+file(GLOB MAIN_SOURCES 
     "src/config.cpp"
     "src/rtsp_client.cpp"
     "src/scheduler.cpp"
@@ -38,8 +38,13 @@ set(MAIN_SOURCES
 )
 
 # Source files for HTTP server
-set(HTTP_SERVER_SOURCES
+file(GLOB HTTP_SERVER_SOURCES
+    "src/config.cpp"
     "src/http_server.cpp"
+    "src/scheduler.cpp"
+    "src/rtsp_client.cpp"
+    "src/reporter.cpp"
+    "src/concurrent_calculator.cpp"
     "http_server_main.cpp"
 )
 
@@ -66,5 +71,9 @@ target_link_libraries(jtjai_http_server PRIVATE
     fmt::fmt
     Boost::json 
     Boost::system 
+    ${AVFORMAT_LIBRARY}
+    ${AVCODEC_LIBRARY}
+    ${AVUTIL_LIBRARY}
+    ${SWSCALE_LIBRARY}
     pthread
 )

+ 1349 - 77
HTTP_API_DOC.md

@@ -1,5 +1,18 @@
 # RTSP视频流管理HTTP服务器 API文档
 
+# RTSP视频流管理HTTP服务器 API文档
+
+## 目录
+
+- [概述](#概述)
+- [启动服务器](#启动服务器)
+- [API端点](#api端点)
+  - [视频管理API](#视频管理api)
+  - [配置管理API](#配置管理api)
+  - [轮询任务管理API](#轮询任务管理api)
+- [错误响应](#错误响应)
+- [使用示例](#使用示例)
+
 ## 概述
 
 这是一个用于管理RTSP视频流拉取结果的HTTP服务器,提供查询和删除视频文件的功能。
@@ -30,7 +43,9 @@ cmake --build cmake-build-debug --target jtjai_http_server
 
 ## API端点
 
-### 1. 列出所有时间戳目录
+### 视频管理API
+
+#### 1. 列出所有时间戳目录
 
 **请求**
 ```
@@ -208,125 +223,1382 @@ DELETE /api/timestamp/20251010_163000
 
 ---
 
-## 错误响应
+## 配置管理API
 
-所有错误响应都遵循以下格式:
+### 1. 获取完整配置
+
+**请求**
+```
+GET /api/config
+```
 
+**响应示例**
 ```json
 {
-  "error": "错误类型",
-  "message": "详细错误信息"
+  "global_config": {
+    "total_poll_duration_seconds": 60,
+    "max_concurrent_streams": 2,
+    "output_directory": "./output",
+    "report_filename": "rtsp_report.json",
+    "connection_timeout_seconds": 3,
+    "read_timeout_seconds": 5,
+    "poll_cycles": 3,
+    "cycle_interval_seconds": 30
+  },
+  "streams": [
+    {
+      "rtsp_url": "rtsp://example.com/stream1",
+      "duration_seconds": 15,
+      "weight": 1.0,
+      "output_filename": "test_stream1.mp4"
+    },
+    {
+      "rtsp_url": "rtsp://example.com/stream2",
+      "duration_seconds": 20,
+      "weight": 1.5,
+      "output_filename": "test_stream2.mp4"
+    }
+  ]
 }
 ```
 
-### 常见错误状态码
+**字段说明**
+- `total_poll_duration_seconds`: 每轮轮询的总时长(秒)
+- `max_concurrent_streams`: 最大并发流数
+- `output_directory`: 输出目录
+- `report_filename`: 报告文件名
+- `connection_timeout_seconds`: 连接超时(秒)
+- `read_timeout_seconds`: 读取超时(秒)
+- `poll_cycles`: 轮询次数(-1为无限循环)
+- `cycle_interval_seconds`: 轮询间隔(秒)
 
-- `400 Bad Request`: 请求参数错误
-- `403 Forbidden`: 无权访问或删除
-- `404 Not Found`: 请求的资源不存在
-- `500 Internal Server Error`: 服务器内部错误
+---
+
+### 2. 获取全局配置
+
+**请求**
+```
+GET /api/config/global
+```
+
+**响应示例**
+```json
+{
+  "total_poll_duration_seconds": 60,
+  "max_concurrent_streams": 2,
+  "output_directory": "./output",
+  "report_filename": "rtsp_report.json",
+  "connection_timeout_seconds": 3,
+  "read_timeout_seconds": 5,
+  "poll_cycles": 3,
+  "cycle_interval_seconds": 30
+}
+```
 
 ---
 
-## 使用示例
+### 3. 更新全局配置
 
-### 使用curl命令
+**请求**
+```
+PUT /api/config/global
+Content-Type: application/json
+```
 
-#### 列出所有时间戳目录
-```bash
-curl http://localhost:8080/api/timestamps
+**请求体示例**
+```json
+{
+  "max_concurrent_streams": 3,
+  "poll_cycles": 5,
+  "cycle_interval_seconds": 45
+}
 ```
 
-#### 列出所有视频
-```bash
-curl http://localhost:8080/api/videos
+**响应示例(成功)**
+```json
+{
+  "success": true,
+  "message": "全局配置更新成功",
+  "updated_config": {
+    "total_poll_duration_seconds": 60,
+    "max_concurrent_streams": 3,
+    "output_directory": "./output",
+    "report_filename": "rtsp_report.json",
+    "connection_timeout_seconds": 3,
+    "read_timeout_seconds": 5,
+    "poll_cycles": 5,
+    "cycle_interval_seconds": 45
+  }
+}
 ```
 
-#### 列出指定时间戳的视频
-```bash
-curl http://localhost:8080/api/videos/20251010_163000
+**响应示例(失败)**
+```json
+{
+  "error": "Bad Request",
+  "message": "配置验证失败: max_concurrent_streams 必须大于 0"
+}
 ```
 
-#### 删除单个视频
-```bash
-curl -X DELETE "http://localhost:8080/api/video?path=./output/20251010_163000/test_stream1.mp4"
+---
+
+### 4. 获取流配置列表
+
+**请求**
+```
+GET /api/config/streams
 ```
 
-#### 删除整个时间戳目录
-```bash
-curl -X DELETE http://localhost:8080/api/timestamp/20251010_163000
+**响应示例**
+```json
+{
+  "count": 2,
+  "streams": [
+    {
+      "index": 0,
+      "rtsp_url": "rtsp://example.com/stream1",
+      "duration_seconds": 15,
+      "weight": 1.0,
+      "output_filename": "test_stream1.mp4"
+    },
+    {
+      "index": 1,
+      "rtsp_url": "rtsp://example.com/stream2",
+      "duration_seconds": 20,
+      "weight": 1.5,
+      "output_filename": "test_stream2.mp4"
+    }
+  ]
+}
 ```
 
-### 使用JavaScript (浏览器)
+---
 
-```javascript
-// 列出所有视频
-fetch('http://localhost:8080/api/videos')
-  .then(response => response.json())
-  .then(data => console.log(data));
+### 5. 添加新的流配置
 
-// 删除视频
-fetch('http://localhost:8080/api/video?path=./output/20251010_163000/test_stream1.mp4', {
-  method: 'DELETE'
-})
-  .then(response => response.json())
-  .then(data => console.log(data));
+**请求**
+```
+POST /api/config/streams
+Content-Type: application/json
 ```
 
-### 使用Python
+**请求体示例**
+```json
+{
+  "rtsp_url": "rtsp://example.com/new_stream",
+  "duration_seconds": 25,
+  "weight": 1.8,
+  "output_filename": "new_stream.mp4"
+}
+```
 
-```python
-import requests
+**响应示例(成功)**
+```json
+{
+  "success": true,
+  "message": "流配置添加成功",
+  "stream_index": 2,
+  "added_stream": {
+    "rtsp_url": "rtsp://example.com/new_stream",
+    "duration_seconds": 25,
+    "weight": 1.8,
+    "output_filename": "new_stream.mp4"
+  }
+}
+```
 
-# 列出所有视频
-response = requests.get('http://localhost:8080/api/videos')
-print(response.json())
+---
 
-# 删除视频
-response = requests.delete('http://localhost:8080/api/video', 
-                          params={'path': './output/20251010_163000/test_stream1.mp4'})
-print(response.json())
+### 6. 更新流配置
+
+**请求**
+```
+PUT /api/config/streams/{index}
+Content-Type: application/json
+```
+
+**路径参数**
+- `index`: 流索引(从0开始)
+
+**请求体示例**
+```json
+{
+  "duration_seconds": 30,
+  "weight": 2.0
+}
+```
+
+**响应示例(成功)**
+```json
+{
+  "success": true,
+  "message": "流配置更新成功",
+  "stream_index": 0,
+  "updated_stream": {
+    "rtsp_url": "rtsp://example.com/stream1",
+    "duration_seconds": 30,
+    "weight": 2.0,
+    "output_filename": "test_stream1.mp4"
+  }
+}
 ```
 
 ---
 
-## CORS支持
+### 7. 删除流配置
 
-服务器支持跨域请求(CORS),可以从任何域的网页中调用API。
+**请求**
+```
+DELETE /api/config/streams/{index}
+```
+
+**路径参数**
+- `index`: 流索引(从0开始)
+
+**响应示例(成功)**
+```json
+{
+  "success": true,
+  "message": "流配置删除成功",
+  "deleted_stream_index": 1
+}
+```
+
+**响应示例(失败)**
+```json
+{
+  "error": "Not Found",
+  "message": "无效的流索引: 5"
+}
+```
 
 ---
 
-## 注意事项
+### 8. 保存配置
 
-1. **安全性**: 当前版本没有身份验证机制,请勿在公网环境中使用
-2. **文件路径**: 删除操作只能针对输出目录内的文件,防止误删系统文件
-3. **并发**: 服务器支持并发请求处理
-4. **停止服务器**: 按 Ctrl+C 可以优雅地停止服务器
+**请求**
+```
+POST /api/config/save
+```
+
+**响应示例(成功)**
+```json
+{
+  "success": true,
+  "message": "配置保存成功",
+  "saved_to": "config.json"
+}
+```
 
 ---
 
-## 目录结构
+### 9. 重新加载配置
 
+**请求**
 ```
-output/
-├── 20251010_163000/        # 第1轮的输出
-│   ├── test_stream1.mp4
-│   ├── test_stream2.mp4
-│   ├── rtsp_report.json
-│   ├── report.txt
-│   └── streams.csv
-├── 20251010_163100/        # 第2轮的输出
-│   ├── test_stream1.mp4
-│   ├── test_stream2.mp4
-│   ├── rtsp_report.json
-│   ├── report.txt
-│   └── streams.csv
-└── 20251010_163200/        # 第3轮的输出
-    ├── test_stream1.mp4
-    ├── test_stream2.mp4
-    ├── rtsp_report.json
-    ├── report.txt
-    └── streams.csv
+POST /api/config/reload
 ```
 
-服务器会扫描输出目录下所有的时间戳子目录,并提供统一的查询和管理接口。
+**响应示例(成功)**
+```json
+{
+  "success": true,
+  "message": "配置重新加载成功",
+  "loaded_from": "config.json"
+}
+```
+
+---
+
+## 轮询任务管理API
+
+### 1. 获取任务列表
+
+**请求**
+```
+GET /api/tasks
+```
+
+**响应示例**
+```json
+{
+  "scheduler_running": true,
+  "total_tasks": 2,
+  "completed_tasks": 1,
+  "failed_tasks": 0,
+  "cancelled_tasks": 0,
+  "max_concurrent_used": 2,
+  "completion_rate": 0.5,
+  "tasks": [
+    {
+      "stream_index": 0,
+      "rtsp_url": "rtsp://example.com/stream1",
+      "output_file": "./output/20251011_144708/test_stream1.mp4",
+      "status_code": 4,
+      "status_name": "完成",
+      "start_time": "2025-10-11 14:47:08",
+      "end_time": "2025-10-11 14:47:23",
+      "progress": 15,
+      "bytes_received": 1024000,
+      "frames_received": 375,
+      "error_message": ""
+    },
+    {
+      "stream_index": 1,
+      "rtsp_url": "rtsp://example.com/stream2",
+      "output_file": "./output/20251011_144708/test_stream2.mp4",
+      "status_code": 3,
+      "status_name": "录制中",
+      "start_time": "2025-10-11 14:47:08",
+      "end_time": "1970-01-01 08:00:00",
+      "progress": 20,
+      "bytes_received": 512000,
+      "frames_received": 150,
+      "error_message": ""
+    }
+  ]
+}
+```
+
+**状态码说明**
+- `0`: 空闲
+- `1`: 连接中
+- `2`: 已连接
+- `3`: 录制中
+- `4`: 完成
+- `5`: 连接错误
+- `6`: 录制错误
+- `7`: 超时
+- `8`: 已取消
+- `9`: 流不可用
+
+---
+
+### 2. 获取实时任务状态
+
+**请求**
+```
+GET /api/tasks/status
+```
+
+**响应示例**
+```json
+{
+  "scheduler_running": true,
+  "total_tasks": 2,
+  "completed_tasks": 2,
+  "failed_tasks": 0,
+  "cancelled_tasks": 0,
+  "max_concurrent_used": 2,
+  "completion_rate": 1.0,
+  "tasks": [
+    {
+      "stream_index": 0,
+      "rtsp_url": "rtsp://example.com/stream1",
+      "output_file": "./output/20251011_144708/test_stream1.mp4",
+      "status_code": 4,
+      "status_name": "完成",
+      "start_time": "2025-10-11 14:47:08",
+      "end_time": "2025-10-11 14:47:23",
+      "progress": 15,
+      "bytes_received": 1024000,
+      "frames_received": 375,
+      "error_message": ""
+    },
+    {
+      "stream_index": 1,
+      "rtsp_url": "rtsp://example.com/stream2",
+      "output_file": "./output/20251011_144708/test_stream2.mp4",
+      "status_code": 4,
+      "status_name": "完成",
+      "start_time": "2025-10-11 14:47:08",
+      "end_time": "2025-10-11 14:47:28",
+      "progress": 20,
+      "bytes_received": 2048000,
+      "frames_received": 600,
+      "error_message": ""
+    }
+  ]
+}
+```
+
+**说明**
+- 与`/api/tasks`的响应格式相同,但提供实时状态更新
+- 适合用于实时监控和状态追踪
+
+---
+
+### 3. 获取调度器状态
+
+**请求**
+```
+GET /api/scheduler/status
+```
+
+**响应示例**
+```json
+{
+  "is_running": true,
+  "start_time": "2025-10-11 14:47:08",
+  "end_time": "1970-01-01 08:00:00",
+  "total_tasks": 2,
+  "completed_tasks": 2,
+  "failed_tasks": 0,
+  "cancelled_tasks": 0,
+  "max_concurrent_used": 2,
+  "completion_rate": 1.0
+}
+```
+
+**字段说明**
+- `is_running`: 调度器是否正在运行
+- `start_time`: 调度器启动时间
+- `end_time`: 调度器结束时间(未结束时为1970-01-01)
+- `completion_rate`: 完成率(0.0-1.0)
+
+---
+
+### 4. 获取当前周期状态
+
+**请求**
+```
+GET /api/scheduler/cycle
+```
+
+**响应示例**
+```json
+{
+  "scheduler_running": true,
+  "cycle_info": {
+    "start_time": "2025-10-11 14:47:08",
+    "elapsed_seconds": 25,
+    "total_duration": 60,
+    "progress_percentage": 41.67
+  },
+  "scheduler_stats": {
+    "total_tasks": 2,
+    "completed_tasks": 1,
+    "failed_tasks": 0,
+    "cancelled_tasks": 0,
+    "max_concurrent_used": 2,
+    "completion_rate": 0.5
+  },
+  "active_tasks": [
+    {
+      "stream_index": 1,
+      "status": 3,
+      "progress_seconds": 12,
+      "target_duration": 20
+    }
+  ]
+}
+```
+
+**说明**
+- 提供当前轮询周期的详细进度信息
+- `active_tasks`: 当前正在执行的任务列表
+- 适合显示周期进度和活跃任务
+
+---
+
+### 5. 启动调度器
+
+**请求**
+```
+POST /api/scheduler/start
+```
+
+**响应示例(成功)**
+```json
+{
+  "success": true,
+  "message": "调度器启动成功",
+  "is_running": true
+}
+```
+
+**响应示例(已在运行)**
+```json
+{
+  "error": "Conflict",
+  "message": "调度器已经在运行中"
+}
+```
+
+---
+
+### 6. 停止调度器
+
+**请求**
+```
+POST /api/scheduler/stop
+```
+
+**响应示例(成功)**
+```json
+{
+  "success": true,
+  "message": "调度器停止成功",
+  "is_running": false
+}
+```
+
+---
+
+## 错误响应
+
+所有错误响应都遵循以下格式:
+
+```json
+{
+  "error": "错误类型",
+  "message": "详细错误信息"
+}
+```
+
+### 常见错误状态码
+
+- `400 Bad Request`: 请求参数错误
+- `403 Forbidden`: 无权访问或删除
+- `404 Not Found`: 请求的资源不存在
+- `500 Internal Server Error`: 服务器内部错误
+
+---
+
+## 使用示例
+
+### 使用curl命令
+
+#### 列出所有时间戳目录
+```bash
+curl http://localhost:8080/api/timestamps
+```
+
+#### 列出所有视频
+```bash
+curl http://localhost:8080/api/videos
+```
+
+#### 列出指定时间戳的视频
+```bash
+curl http://localhost:8080/api/videos/20251010_163000
+```
+
+#### 删除单个视频
+```bash
+curl -X DELETE "http://localhost:8080/api/video?path=./output/20251010_163000/test_stream1.mp4"
+```
+
+#### 删除整个时间戳目录
+```bash
+curl -X DELETE http://localhost:8080/api/timestamp/20251010_163000
+```
+
+#### 配置管理API示例
+
+**获取完整配置**
+```bash
+curl http://localhost:8080/api/config
+```
+
+**获取全局配置**
+```bash
+curl http://localhost:8080/api/config/global
+```
+
+**更新全局配置**
+```bash
+curl -X PUT -H "Content-Type: application/json" -d '{
+  "max_concurrent_streams": 3,
+  "poll_cycles": 5
+}' http://localhost:8080/api/config/global
+```
+
+**获取流配置列表**
+```bash
+curl http://localhost:8080/api/config/streams
+```
+
+**添加新的流配置**
+```bash
+curl -X POST -H "Content-Type: application/json" -d '{
+  "rtsp_url": "rtsp://example.com/new_stream",
+  "duration_seconds": 25,
+  "weight": 1.8,
+  "output_filename": "new_stream.mp4"
+}' http://localhost:8080/api/config/streams
+```
+
+**更新流配置**
+```bash
+curl -X PUT -H "Content-Type: application/json" -d '{
+  "duration_seconds": 30,
+  "weight": 2.0
+}' http://localhost:8080/api/config/streams/0
+```
+
+**删除流配置**
+```bash
+curl -X DELETE http://localhost:8080/api/config/streams/1
+```
+
+**保存配置**
+```bash
+curl -X POST http://localhost:8080/api/config/save
+```
+
+**重新加载配置**
+```bash
+curl -X POST http://localhost:8080/api/config/reload
+```
+
+#### 轮询任务管理API示例
+
+**获取任务列表**
+```bash
+curl http://localhost:8080/api/tasks
+```
+
+**获取实时任务状态**
+```bash
+curl http://localhost:8080/api/tasks/status
+```
+
+**获取调度器状态**
+```bash
+curl http://localhost:8080/api/scheduler/status
+```
+
+**获取当前周期状态**
+```bash
+curl http://localhost:8080/api/scheduler/cycle
+```
+
+**启动调度器**
+```bash
+curl -X POST http://localhost:8080/api/scheduler/start
+```
+
+**停止调度器**
+```bash
+curl -X POST http://localhost:8080/api/scheduler/stop
+```
+
+### 使用JavaScript (浏览器)
+
+```javascript
+// 列出所有视频
+fetch('http://localhost:8080/api/videos')
+  .then(response => response.json())
+  .then(data => console.log(data));
+
+// 删除视频
+fetch('http://localhost:8080/api/video?path=./output/20251010_163000/test_stream1.mp4', {
+  method: 'DELETE'
+})
+  .then(response => response.json())
+  .then(data => console.log(data));
+
+// 获取配置
+fetch('http://localhost:8080/api/config')
+  .then(response => response.json())
+  .then(data => console.log(data));
+
+// 更新全局配置
+fetch('http://localhost:8080/api/config/global', {
+  method: 'PUT',
+  headers: {
+    'Content-Type': 'application/json'
+  },
+  body: JSON.stringify({
+    max_concurrent_streams: 3,
+    poll_cycles: 5
+  })
+})
+  .then(response => response.json())
+  .then(data => console.log(data));
+
+// 添加新流配置
+fetch('http://localhost:8080/api/config/streams', {
+  method: 'POST',
+  headers: {
+    'Content-Type': 'application/json'
+  },
+  body: JSON.stringify({
+    rtsp_url: 'rtsp://example.com/stream',
+    duration_seconds: 30,
+    weight: 2.0,
+    output_filename: 'new_stream.mp4'
+  })
+})
+  .then(response => response.json())
+  .then(data => console.log(data));
+
+// 获取任务列表
+fetch('http://localhost:8080/api/tasks')
+  .then(response => response.json())
+  .then(data => console.log(data));
+
+// 获取实时任务状态
+fetch('http://localhost:8080/api/tasks/status')
+  .then(response => response.json())
+  .then(data => console.log(data));
+
+// 获取调度器状态
+fetch('http://localhost:8080/api/scheduler/status')
+  .then(response => response.json())
+  .then(data => console.log(data));
+
+// 启动调度器
+fetch('http://localhost:8080/api/scheduler/start', {
+  method: 'POST'
+})
+  .then(response => response.json())
+  .then(data => console.log(data));
+
+// 停止调度器
+fetch('http://localhost:8080/api/scheduler/stop', {
+  method: 'POST'
+})
+  .then(response => response.json())
+  .then(data => console.log(data));
+```
+
+### 使用Python
+
+```python
+import requests
+import json
+
+# 列出所有视频
+response = requests.get('http://localhost:8080/api/videos')
+print(response.json())
+
+# 删除视频
+response = requests.delete('http://localhost:8080/api/video', 
+                          params={'path': './output/20251010_163000/test_stream1.mp4'})
+print(response.json())
+
+# 获取配置
+response = requests.get('http://localhost:8080/api/config')
+print(response.json())
+
+# 更新全局配置
+config_update = {
+    'max_concurrent_streams': 3,
+    'poll_cycles': 5
+}
+response = requests.put('http://localhost:8080/api/config/global',
+                       json=config_update)
+print(response.json())
+
+# 添加新流配置
+new_stream = {
+    'rtsp_url': 'rtsp://example.com/stream',
+    'duration_seconds': 30,
+    'weight': 2.0,
+    'output_filename': 'new_stream.mp4'
+}
+response = requests.post('http://localhost:8080/api/config/streams',
+                        json=new_stream)
+print(response.json())
+
+# 更新流配置
+stream_update = {
+    'duration_seconds': 25,
+    'weight': 1.8
+}
+response = requests.put('http://localhost:8080/api/config/streams/0',
+                       json=stream_update)
+print(response.json())
+
+# 删除流配置
+response = requests.delete('http://localhost:8080/api/config/streams/1')
+print(response.json())
+
+# 保存配置
+response = requests.post('http://localhost:8080/api/config/save')
+print(response.json())
+
+# 获取任务列表
+response = requests.get('http://localhost:8080/api/tasks')
+print(response.json())
+
+# 获取实时任务状态
+response = requests.get('http://localhost:8080/api/tasks/status')
+print(response.json())
+
+# 获取调度器状态
+response = requests.get('http://localhost:8080/api/scheduler/status')
+print(response.json())
+
+# 获取当前周期状态
+response = requests.get('http://localhost:8080/api/scheduler/cycle')
+print(response.json())
+
+# 启动调度器
+response = requests.post('http://localhost:8080/api/scheduler/start')
+print(response.json())
+
+# 停止调度器
+response = requests.post('http://localhost:8080/api/scheduler/stop')
+print(response.json())
+```
+
+---
+
+## CORS支持
+
+服务器支持跨域请求(CORS),可以从任何域的网页中调用API。
+
+---
+
+## 注意事项
+
+1. **安全性**: 当前版本没有身份验证机制,请勿在公网环境中使用
+2. **文件路径**: 删除操作只能针对输出目录内的文件,防止误删系统文件
+3. **并发**: 服务器支持并发请求处理
+4. **停止服务器**: 按 Ctrl+C 可以优雅地停止服务器
+
+---
+
+## 目录结构
+
+```
+output/
+├── 20251010_163000/        # 第1轮的输出
+│   ├── test_stream1.mp4
+│   ├── test_stream2.mp4
+│   ├── rtsp_report.json
+│   ├── report.txt
+│   └── streams.csv
+├── 20251010_163100/        # 第2轮的输出
+│   ├── test_stream1.mp4
+│   ├── test_stream2.mp4
+│   ├── rtsp_report.json
+│   ├── report.txt
+│   └── streams.csv
+└── 20251010_163200/        # 第3轮的输出
+    ├── test_stream1.mp4
+    ├── test_stream2.mp4
+    ├── rtsp_report.json
+    ├── report.txt
+    └── streams.csv
+```
+
+服务器会扫描输出目录下所有的时间戳子目录,并提供统一的查询和管理接口。
+
+---
+
+### 配置管理API
+
+#### 1. 获取完整配置
+
+**请求**
+```
+GET /api/config
+```
+
+**响应示例**
+```json
+{
+  "global_config": {
+    "total_poll_duration_seconds": 60,
+    "max_concurrent_streams": 2,
+    "output_directory": "./output",
+    "report_filename": "rtsp_report.json",
+    "connection_timeout_seconds": 10,
+    "read_timeout_seconds": 15,
+    "poll_cycles": -1,
+    "cycle_interval_seconds": 30
+  },
+  "streams": [
+    {
+      "rtsp_url": "rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001",
+      "duration_seconds": 15,
+      "weight": 1.0,
+      "output_filename": "test_stream1.mp4"
+    },
+    {
+      "rtsp_url": "rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001",
+      "duration_seconds": 20,
+      "weight": 1.5,
+      "output_filename": "test_stream2.mp4"
+    }
+  ]
+}
+```
+
+---
+
+#### 2. 更新完整配置
+
+**请求**
+```
+PUT /api/config
+Content-Type: application/json
+
+{
+  "global_config": {
+    "total_poll_duration_seconds": 120,
+    "max_concurrent_streams": 3,
+    "output_directory": "./output",
+    "report_filename": "rtsp_report.json",
+    "connection_timeout_seconds": 15,
+    "read_timeout_seconds": 30,
+    "poll_cycles": 5,
+    "cycle_interval_seconds": 60
+  },
+  "streams": [
+    {
+      "rtsp_url": "rtsp://example.com/stream1",
+      "duration_seconds": 30,
+      "weight": 2.0,
+      "output_filename": "new_stream1.mp4"
+    }
+  ]
+}
+```
+
+**响应示例(成功)**
+```json
+{
+  "success": true,
+  "message": "配置更新成功"
+}
+```
+
+---
+
+#### 3. 获取全局配置
+
+**请求**
+```
+GET /api/config/global
+```
+
+**响应示例**
+```json
+{
+  "total_poll_duration_seconds": 60,
+  "max_concurrent_streams": 2,
+  "output_directory": "./output",
+  "report_filename": "rtsp_report.json",
+  "connection_timeout_seconds": 10,
+  "read_timeout_seconds": 15,
+  "poll_cycles": -1,
+  "cycle_interval_seconds": 30
+}
+```
+
+---
+
+#### 4. 更新全局配置
+
+**请求**
+```
+PUT /api/config/global
+Content-Type: application/json
+
+{
+  "total_poll_duration_seconds": 120,
+  "max_concurrent_streams": 3,
+  "poll_cycles": 5
+}
+```
+
+**响应示例(成功)**
+```json
+{
+  "success": true,
+  "message": "全局配置更新成功"
+}
+```
+
+**说明**
+- 只需提供需要修改的字段,其他字段保持原值
+- 支持部分更新
+
+---
+
+#### 5. 获取流配置列表
+
+**请求**
+```
+GET /api/config/streams
+```
+
+**响应示例**
+```json
+{
+  "count": 2,
+  "streams": [
+    {
+      "index": 0,
+      "rtsp_url": "rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001",
+      "duration_seconds": 15,
+      "weight": 1.0,
+      "output_filename": "test_stream1.mp4"
+    },
+    {
+      "index": 1,
+      "rtsp_url": "rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001",
+      "duration_seconds": 20,
+      "weight": 1.5,
+      "output_filename": "test_stream2.mp4"
+    }
+  ]
+}
+```
+
+---
+
+#### 6. 添加新的流配置
+
+**请求**
+```
+POST /api/config/streams
+Content-Type: application/json
+
+{
+  "rtsp_url": "rtsp://example.com/new_stream",
+  "duration_seconds": 25,
+  "weight": 1.8,
+  "output_filename": "new_stream.mp4"
+}
+```
+
+**响应示例(成功)**
+```json
+{
+  "success": true,
+  "message": "流配置添加成功",
+  "index": 2
+}
+```
+
+---
+
+#### 7. 更新指定索引的流配置
+
+**请求**
+```
+PUT /api/config/streams/0
+Content-Type: application/json
+
+{
+  "duration_seconds": 30,
+  "weight": 2.0
+}
+```
+
+**响应示例(成功)**
+```json
+{
+  "success": true,
+  "message": "流配置更新成功",
+  "index": 0
+}
+```
+
+**说明**
+- 只需提供需要修改的字段
+- 索引从0开始
+
+---
+
+#### 8. 删除指定索引的流配置
+
+**请求**
+```
+DELETE /api/config/streams/1
+```
+
+**响应示例(成功)**
+```json
+{
+  "success": true,
+  "message": "流配置删除成功",
+  "deleted_index": 1
+}
+```
+
+**注意**
+- 删除后,后面的流配置索引会自动前移
+- 删除后必须至少保留一个流配置
+
+---
+
+#### 9. 保存配置到文件
+
+**请求**
+```
+POST /api/config/save
+Content-Type: application/json
+
+{
+  "file_path": "config.json"  // 可选,默认使用原文件路径
+}
+```
+
+**响应示例(成功)**
+```json
+{
+  "success": true,
+  "message": "配置保存成功",
+  "file_path": "config.json"
+}
+```
+
+---
+
+#### 10. 从文件重新加载配置
+
+**请求**
+```
+POST /api/config/reload
+```
+
+**响应示例(成功)**
+```json
+{
+  "success": true,
+  "message": "配置重新加载成功",
+  "file_path": "config.json"
+}
+```
+
+---
+
+### 配置管理错误响应
+
+#### 常见错误状态码
+
+- `400 Bad Request`: 请求参数错误或配置验证失败
+- `404 Not Found`: 流配置索引不存在
+- `503 Service Unavailable`: 配置管理器未初始化
+- `500 Internal Server Error`: 服务器内部错误
+
+#### 错误响应示例
+
+```json
+{
+  "error": "Bad Request",
+  "message": "流配置参数不完整或不合法"
+}
+```
+
+---
+
+### 轮询任务管理API
+
+#### 1. 获取轮询任务列表
+
+**请求**
+```
+GET /api/tasks
+```
+
+**响应示例**
+```json
+{
+  "count": 2,
+  "tasks": [
+    {
+      "task_id": 0,
+      "stream_index": 0,
+      "rtsp_url": "rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001",
+      "output_file": "./output/test_stream1.mp4",
+      "duration_seconds": 15,
+      "status": 4,
+      "status_text": "已完成",
+      "start_time": "2025-10-11 16:30:05",
+      "end_time": "2025-10-11 16:30:20",
+      "error_message": "",
+      "bytes_received": 2048576,
+      "frames_received": 450
+    },
+    {
+      "task_id": 1,
+      "stream_index": 1,
+      "rtsp_url": "rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001",
+      "output_file": "./output/test_stream2.mp4",
+      "duration_seconds": 20,
+      "status": 3,
+      "status_text": "录制中",
+      "start_time": "2025-10-11 16:30:10",
+      "end_time": "",
+      "error_message": "",
+      "bytes_received": 1024000,
+      "frames_received": 300
+    }
+  ],
+  "schedule_info": "调度方案: 2个任务\n任务 0: 0秒开始, 持续 15秒\n任务 1: 5秒开始, 持续 20秒"
+}
+```
+
+**字段说明**
+- `task_id`: 任务ID
+- `stream_index`: 流索引
+- `status`: 任务状态码(0=等待中, 1=连接中, 2=已连接, 3=录制中, 4=已完成, 5=连接错误, 6=录制错误, 7=超时, 8=已取消, 9=流不可用)
+- `status_text`: 状态文本描述
+
+---
+
+#### 2. 获取实时任务状态
+
+**请求**
+```
+GET /api/tasks/status
+```
+
+**响应示例**
+```json
+{
+  "scheduler_running": true,
+  "total_tasks": 2,
+  "completed_tasks": 1,
+  "failed_tasks": 0,
+  "cancelled_tasks": 0,
+  "max_concurrent_used": 2,
+  "completion_rate": 0.5,
+  "tasks": [
+    {
+      "task_id": 0,
+      "stream_index": 0,
+      "status": 4,
+      "status_text": "已完成",
+      "progress": 15,
+      "bytes_received": 2048576,
+      "frames_received": 450,
+      "error_message": ""
+    },
+    {
+      "task_id": 1,
+      "stream_index": 1,
+      "status": 3,
+      "status_text": "录制中",
+      "progress": 12,
+      "bytes_received": 1536000,
+      "frames_received": 360,
+      "error_message": ""
+    }
+  ]
+}
+```
+
+**说明**
+- 这个API提供实时的任务执行状态
+- `progress`: 当前任务的进度(秒)
+- 适合用于前端轮询显示实时进度
+
+---
+
+#### 3. 获取调度器状态
+
+**请求**
+```
+GET /api/scheduler/status
+```
+
+**响应示例**
+```json
+{
+  "is_running": true,
+  "total_tasks": 2,
+  "completed_tasks": 1,
+  "failed_tasks": 0,
+  "cancelled_tasks": 0,
+  "max_concurrent_used": 2,
+  "completion_rate": 0.5,
+  "start_time": "2025-10-11 16:30:00",
+  "end_time": ""
+}
+```
+
+---
+
+#### 4. 获取当前周期状态
+
+**请求**
+```
+GET /api/scheduler/cycle
+```
+
+**响应示例**
+```json
+{
+  "is_running": true,
+  "current_cycle": 1,
+  "cycle_progress": {
+    "elapsed_seconds": 25,
+    "total_seconds": 60,
+    "progress_percentage": 41.67
+  },
+  "scheduler_stats": {
+    "total_tasks": 2,
+    "completed_tasks": 1,
+    "failed_tasks": 0,
+    "cancelled_tasks": 0,
+    "max_concurrent_used": 2,
+    "completion_rate": 0.5
+  },
+  "active_tasks": [
+    {
+      "stream_index": 1,
+      "status": 3,
+      "progress_seconds": 12,
+      "target_duration": 20
+    }
+  ]
+}
+```
+
+**说明**
+- 提供当前轮询周期的详细进度信息
+- `active_tasks`: 当前正在执行的任务列表
+- 适合显示周期进度和活跃任务
+
+---
+
+#### 5. 启动调度器
+
+**请求**
+```
+POST /api/scheduler/start
+```
+
+**响应示例(成功)**
+```json
+{
+  "success": true,
+  "message": "调度器启动成功",
+  "is_running": true
+}
+```
+
+**响应示例(已在运行)**
+```json
+{
+  "error": "Conflict",
+  "message": "调度器已经在运行中"
+}
+```
+
+---
+
+#### 6. 停止调度器
+
+**请求**
+```
+POST /api/scheduler/stop
+```
+
+**响应示例(成功)**
+```json
+{
+  "success": true,
+  "message": "调度器停止成功",
+  "is_running": false
+}
+```

+ 0 - 1
build/.cmake/api/v1/query/client-vscode/query.json

@@ -1 +0,0 @@
-{"requests":[{"kind":"cache","version":2},{"kind":"codemodel","version":2},{"kind":"toolchains","version":1},{"kind":"cmakeFiles","version":1}]}

+ 0 - 14
build/.cmake/api/v1/reply/directory-.-f5ebdc15457944623624.json

@@ -1,14 +0,0 @@
-{
-	"backtraceGraph" : 
-	{
-		"commands" : [],
-		"files" : [],
-		"nodes" : []
-	},
-	"installers" : [],
-	"paths" : 
-	{
-		"build" : ".",
-		"source" : "."
-	}
-}

+ 0 - 97
build/.cmake/api/v1/reply/toolchains-v1-5b4d9f0541f3c23ab27b.json

@@ -1,97 +0,0 @@
-{
-	"kind" : "toolchains",
-	"toolchains" : 
-	[
-		{
-			"compiler" : 
-			{
-				"id" : "AppleClang",
-				"implicit" : 
-				{
-					"includeDirectories" : 
-					[
-						"/usr/local/include",
-						"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/17/include",
-						"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include",
-						"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include"
-					],
-					"linkDirectories" : 
-					[
-						"/opt/homebrew/opt/ruby/lib",
-						"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib",
-						"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib/swift"
-					],
-					"linkFrameworkDirectories" : 
-					[
-						"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks"
-					],
-					"linkLibraries" : []
-				},
-				"path" : "/usr/bin/cc",
-				"version" : "17.0.0.17000319"
-			},
-			"language" : "C",
-			"sourceFileExtensions" : 
-			[
-				"c",
-				"m"
-			]
-		},
-		{
-			"compiler" : 
-			{
-				"id" : "AppleClang",
-				"implicit" : 
-				{
-					"includeDirectories" : 
-					[
-						"/usr/local/include",
-						"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1",
-						"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/17/include",
-						"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include",
-						"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include"
-					],
-					"linkDirectories" : 
-					[
-						"/opt/homebrew/opt/ruby/lib",
-						"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib",
-						"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib/swift"
-					],
-					"linkFrameworkDirectories" : 
-					[
-						"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks"
-					],
-					"linkLibraries" : 
-					[
-						"c++"
-					]
-				},
-				"path" : "/usr/bin/c++",
-				"version" : "17.0.0.17000319"
-			},
-			"language" : "CXX",
-			"sourceFileExtensions" : 
-			[
-				"C",
-				"M",
-				"c++",
-				"cc",
-				"cpp",
-				"cxx",
-				"mm",
-				"mpp",
-				"CPP",
-				"ixx",
-				"cppm",
-				"ccm",
-				"cxxm",
-				"c++m"
-			]
-		}
-	],
-	"version" : 
-	{
-		"major" : 1,
-		"minor" : 0
-	}
-}

+ 26 - 227
build/CMakeCache.txt

@@ -33,18 +33,14 @@ AVUTIL_INCLUDE_DIR:PATH=/opt/homebrew/include
 AVUTIL_LIBRARY:FILEPATH=/opt/homebrew/lib/libavutil.dylib
 
 //The directory containing a CMake configuration file for Boost.
-Boost_DIR:PATH=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/share/boost
+Boost_DIR:PATH=/opt/homebrew/lib/cmake/Boost-1.86.0
 
 //Path to a file.
-Boost_INCLUDE_DIR:PATH=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/include
+Boost_INCLUDE_DIR:PATH=/opt/homebrew/include
 
-Boost_JSON_LIBRARY_DEBUG:STRING=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/debug/lib/libboost_json.a
+Boost_JSON_LIBRARY_RELEASE:STRING=/opt/homebrew/lib/libboost_json-mt.dylib
 
-Boost_JSON_LIBRARY_RELEASE:STRING=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/lib/libboost_json.a
-
-Boost_SYSTEM_LIBRARY_DEBUG:STRING=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/debug/lib/libboost_system.a
-
-Boost_SYSTEM_LIBRARY_RELEASE:STRING=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/lib/libboost_system.a
+Boost_SYSTEM_LIBRARY_RELEASE:STRING=/opt/homebrew/lib/libboost_system-mt.dylib
 
 //Path to a program.
 CMAKE_ADDR2LINE:FILEPATH=CMAKE_ADDR2LINE-NOTFOUND
@@ -56,6 +52,9 @@ CMAKE_AR:FILEPATH=/usr/bin/ar
 // MinSizeRel ...
 CMAKE_BUILD_TYPE:STRING=
 
+//Enable/Disable color output during build.
+CMAKE_COLOR_MAKEFILE:BOOL=ON
+
 //CXX compiler
 CMAKE_CXX_COMPILER:FILEPATH=/usr/bin/c++
 
@@ -110,9 +109,6 @@ CMAKE_EXE_LINKER_FLAGS_RELEASE:STRING=
 //Flags used by the linker during RELWITHDEBINFO builds.
 CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO:STRING=
 
-//Enable/Disable output of build database during the build.
-CMAKE_EXPORT_BUILD_DATABASE:BOOL=
-
 //Enable/Disable output of compile commands during generation.
 CMAKE_EXPORT_COMPILE_COMMANDS:BOOL=
 
@@ -128,8 +124,8 @@ CMAKE_INSTALL_PREFIX:PATH=/usr/local
 //Path to a program.
 CMAKE_LINKER:FILEPATH=/usr/bin/ld
 
-//Program used to build from build.ninja files.
-CMAKE_MAKE_PROGRAM:FILEPATH=/Users/wenhongquan/miniforge3/bin/ninja
+//Path to a program.
+CMAKE_MAKE_PROGRAM:FILEPATH=/opt/homebrew/bin/gmake
 
 //Flags used by the linker during the creation of modules during
 // all build types.
@@ -257,138 +253,32 @@ CMAKE_STRIP:FILEPATH=/usr/bin/strip
 //Path to a program.
 CMAKE_TAPI:FILEPATH=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/tapi
 
-//The CMake toolchain file
-CMAKE_TOOLCHAIN_FILE:FILEPATH=/Users/wenhongquan/gitdata/vcpkg/scripts/buildsystems/vcpkg.cmake
-
 //If this value is on, makefiles will be generated without the
 // .SILENT directive, and all commands will be echoed to the console
 // during the make.  This is useful for debugging only. With Visual
 // Studio IDE projects all commands are done without /nologo.
 CMAKE_VERBOSE_MAKEFILE:BOOL=FALSE
 
-//Arguments to supply to pkg-config
-PKG_CONFIG_ARGN:STRING=
-
-//pkg-config executable
-PKG_CONFIG_EXECUTABLE:FILEPATH=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/tools/pkgconf/pkgconf
-
 //Path to a file.
 SWSCALE_INCLUDE_DIR:PATH=/opt/homebrew/include
 
 //Path to a library.
 SWSCALE_LIBRARY:FILEPATH=/opt/homebrew/lib/libswscale.dylib
 
-//Automatically copy dependencies into the output directory for
-// executables.
-VCPKG_APPLOCAL_DEPS:BOOL=ON
-
-//The directory which contains the installed libraries for each
-// triplet
-VCPKG_INSTALLED_DIR:PATH=/Users/wenhongquan/gitdata/vcpkg/installed
-
-//The path to the vcpkg manifest directory.
-VCPKG_MANIFEST_DIR:PATH=
-
-//Use manifest mode, as opposed to classic mode.
-VCPKG_MANIFEST_MODE:BOOL=OFF
-
-//Appends the vcpkg paths to CMAKE_PREFIX_PATH, CMAKE_LIBRARY_PATH
-// and CMAKE_FIND_ROOT_PATH so that vcpkg libraries/packages are
-// found after toolchain/system libraries/packages.
-VCPKG_PREFER_SYSTEM_LIBS:BOOL=OFF
-
-//Enable the setup of CMAKE_PROGRAM_PATH to vcpkg paths
-VCPKG_SETUP_CMAKE_PROGRAM_PATH:BOOL=ON
-
-//Vcpkg target triplet (ex. x86-windows)
-VCPKG_TARGET_TRIPLET:STRING=arm64-osx
-
-//Trace calls to find_package()
-VCPKG_TRACE_FIND_PACKAGE:BOOL=OFF
-
-//Enables messages from the VCPKG toolchain for debugging purposes.
-VCPKG_VERBOSE:BOOL=OFF
-
-//(experimental) Automatically copy dependencies into the install
-// target directory for executables. Requires CMake 3.14.
-X_VCPKG_APPLOCAL_DEPS_INSTALL:BOOL=OFF
-
-//(experimental) Add USES_TERMINAL to VCPKG_APPLOCAL_DEPS to force
-// serialization.
-X_VCPKG_APPLOCAL_DEPS_SERIALIZED:BOOL=OFF
-
-//Path to a program.
-Z_VCPKG_CL:FILEPATH=Z_VCPKG_CL-NOTFOUND
-
-//The directory which contains the installed libraries for each
-// triplet
-_VCPKG_INSTALLED_DIR:PATH=/Users/wenhongquan/gitdata/vcpkg/installed
-
-//The directory containing a CMake configuration file for boost_align.
-boost_align_DIR:PATH=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/share/boost_align
-
-//The directory containing a CMake configuration file for boost_assert.
-boost_assert_DIR:PATH=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/share/boost_assert
-
-//The directory containing a CMake configuration file for boost_config.
-boost_config_DIR:PATH=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/share/boost_config
-
 //The directory containing a CMake configuration file for boost_container.
-boost_container_DIR:PATH=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/share/boost_container
-
-//The directory containing a CMake configuration file for boost_container_hash.
-boost_container_hash_DIR:PATH=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/share/boost_container_hash
-
-//The directory containing a CMake configuration file for boost_core.
-boost_core_DIR:PATH=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/share/boost_core
-
-//The directory containing a CMake configuration file for boost_describe.
-boost_describe_DIR:PATH=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/share/boost_describe
-
-//The directory containing a CMake configuration file for boost_endian.
-boost_endian_DIR:PATH=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/share/boost_endian
-
-//The directory containing a CMake configuration file for boost_filesystem.
-boost_filesystem_DIR:PATH=boost_filesystem_DIR-NOTFOUND
+boost_container_DIR:PATH=/opt/homebrew/lib/cmake/boost_container-1.86.0
 
 //The directory containing a CMake configuration file for boost_headers.
-boost_headers_DIR:PATH=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/share/boost_headers
-
-//The directory containing a CMake configuration file for boost_intrusive.
-boost_intrusive_DIR:PATH=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/share/boost_intrusive
+boost_headers_DIR:PATH=/opt/homebrew/lib/cmake/boost_headers-1.86.0
 
 //The directory containing a CMake configuration file for boost_json.
-boost_json_DIR:PATH=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/share/boost_json
-
-//The directory containing a CMake configuration file for boost_move.
-boost_move_DIR:PATH=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/share/boost_move
-
-//The directory containing a CMake configuration file for boost_mp11.
-boost_mp11_DIR:PATH=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/share/boost_mp11
-
-//The directory containing a CMake configuration file for boost_predef.
-boost_predef_DIR:PATH=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/share/boost_predef
-
-//The directory containing a CMake configuration file for boost_static_assert.
-boost_static_assert_DIR:PATH=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/share/boost_static_assert
+boost_json_DIR:PATH=/opt/homebrew/lib/cmake/boost_json-1.86.0
 
 //The directory containing a CMake configuration file for boost_system.
-boost_system_DIR:PATH=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/share/boost_system
-
-//The directory containing a CMake configuration file for boost_thread.
-boost_thread_DIR:PATH=boost_thread_DIR-NOTFOUND
-
-//The directory containing a CMake configuration file for boost_throw_exception.
-boost_throw_exception_DIR:PATH=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/share/boost_throw_exception
-
-//The directory containing a CMake configuration file for boost_variant2.
-boost_variant2_DIR:PATH=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/share/boost_variant2
-
-//The directory containing a CMake configuration file for boost_winapi.
-boost_winapi_DIR:PATH=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/share/boost_winapi
+boost_system_DIR:PATH=/opt/homebrew/lib/cmake/boost_system-1.86.0
 
 //The directory containing a CMake configuration file for fmt.
-fmt_DIR:PATH=/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/share/fmt
+fmt_DIR:PATH=/opt/homebrew/lib/cmake/fmt
 
 //Value Computed by CMake
 jtjai_media_BINARY_DIR:STATIC=/Users/wenhongquan/CLionProjects/jtjai_media/build
@@ -399,21 +289,6 @@ jtjai_media_IS_TOP_LEVEL:STATIC=ON
 //Value Computed by CMake
 jtjai_media_SOURCE_DIR:STATIC=/Users/wenhongquan/CLionProjects/jtjai_media
 
-//Path to a library.
-pkgcfg_lib_FFMPEG_avcodec:FILEPATH=/opt/homebrew/Cellar/ffmpeg/7.1.1_3/lib/libavcodec.dylib
-
-//Path to a library.
-pkgcfg_lib_FFMPEG_avformat:FILEPATH=/opt/homebrew/Cellar/ffmpeg/7.1.1_3/lib/libavformat.dylib
-
-//Path to a library.
-pkgcfg_lib_FFMPEG_avutil:FILEPATH=/opt/homebrew/Cellar/ffmpeg/7.1.1_3/lib/libavutil.dylib
-
-//Path to a library.
-pkgcfg_lib_FFMPEG_swscale:FILEPATH=/opt/homebrew/Cellar/ffmpeg/7.1.1_3/lib/libswscale.dylib
-
-//The directory containing a CMake configuration file for zlmediakit.
-zlmediakit_DIR:PATH=zlmediakit_DIR-NOTFOUND
-
 
 ########################
 # INTERNAL cache entries
@@ -433,6 +308,8 @@ CMAKE_CACHE_MAJOR_VERSION:INTERNAL=4
 CMAKE_CACHE_MINOR_VERSION:INTERNAL=1
 //Patch version of cmake used to create the current loaded cache
 CMAKE_CACHE_PATCH_VERSION:INTERNAL=1
+//ADVANCED property for variable: CMAKE_COLOR_MAKEFILE
+CMAKE_COLOR_MAKEFILE-ADVANCED:INTERNAL=1
 //Path to CMake executable.
 CMAKE_COMMAND:INTERNAL=/opt/homebrew/bin/cmake
 //Path to cpack program executable.
@@ -479,22 +356,18 @@ CMAKE_EXE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
 CMAKE_EXE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
 //ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO
 CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
-//ADVANCED property for variable: CMAKE_EXPORT_BUILD_DATABASE
-CMAKE_EXPORT_BUILD_DATABASE-ADVANCED:INTERNAL=1
 //ADVANCED property for variable: CMAKE_EXPORT_COMPILE_COMMANDS
 CMAKE_EXPORT_COMPILE_COMMANDS-ADVANCED:INTERNAL=1
 //Name of external makefile project generator.
 CMAKE_EXTRA_GENERATOR:INTERNAL=
 //Name of generator.
-CMAKE_GENERATOR:INTERNAL=Ninja
+CMAKE_GENERATOR:INTERNAL=Unix Makefiles
 //Generator instance identifier.
 CMAKE_GENERATOR_INSTANCE:INTERNAL=
 //Name of generator platform.
 CMAKE_GENERATOR_PLATFORM:INTERNAL=
 //Name of generator toolset.
 CMAKE_GENERATOR_TOOLSET:INTERNAL=
-//Test CMAKE_HAVE_LIBC_PTHREAD
-CMAKE_HAVE_LIBC_PTHREAD:INTERNAL=1
 //Source directory with the top level CMakeLists.txt file for this
 // project
 CMAKE_HOME_DIRECTORY:INTERNAL=/Users/wenhongquan/CLionProjects/jtjai_media
@@ -560,92 +433,18 @@ CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
 CMAKE_STRIP-ADVANCED:INTERNAL=1
 //ADVANCED property for variable: CMAKE_TAPI
 CMAKE_TAPI-ADVANCED:INTERNAL=1
-//ADVANCED property for variable: CMAKE_TOOLCHAIN_FILE
-CMAKE_TOOLCHAIN_FILE-ADVANCED:INTERNAL=1
 //uname command
 CMAKE_UNAME:INTERNAL=/usr/bin/uname
 //ADVANCED property for variable: CMAKE_VERBOSE_MAKEFILE
 CMAKE_VERBOSE_MAKEFILE-ADVANCED:INTERNAL=1
-FFMPEG_CFLAGS:INTERNAL=-I/opt/homebrew/Cellar/ffmpeg/7.1.1_3/include
-FFMPEG_CFLAGS_I:INTERNAL=
-FFMPEG_CFLAGS_OTHER:INTERNAL=
-FFMPEG_FOUND:INTERNAL=1
-FFMPEG_INCLUDEDIR:INTERNAL=
-FFMPEG_INCLUDE_DIRS:INTERNAL=/opt/homebrew/Cellar/ffmpeg/7.1.1_3/include
-FFMPEG_LDFLAGS:INTERNAL=-L/opt/homebrew/Cellar/ffmpeg/7.1.1_3/lib;-lavformat;-lavcodec;-lswscale;-lavutil
-FFMPEG_LDFLAGS_OTHER:INTERNAL=
-FFMPEG_LIBDIR:INTERNAL=
-FFMPEG_LIBRARIES:INTERNAL=avformat;avcodec;swscale;avutil
-FFMPEG_LIBRARY_DIRS:INTERNAL=/opt/homebrew/Cellar/ffmpeg/7.1.1_3/lib
-FFMPEG_LIBS:INTERNAL=
-FFMPEG_LIBS_L:INTERNAL=
-FFMPEG_LIBS_OTHER:INTERNAL=
-FFMPEG_LIBS_PATHS:INTERNAL=
-FFMPEG_MODULE_NAME:INTERNAL=
-FFMPEG_PREFIX:INTERNAL=
-FFMPEG_STATIC_CFLAGS:INTERNAL=-I/opt/homebrew/Cellar/ffmpeg/7.1.1_3/include
-FFMPEG_STATIC_CFLAGS_I:INTERNAL=
-FFMPEG_STATIC_CFLAGS_OTHER:INTERNAL=
-FFMPEG_STATIC_INCLUDE_DIRS:INTERNAL=/opt/homebrew/Cellar/ffmpeg/7.1.1_3/include
-FFMPEG_STATIC_LDFLAGS:INTERNAL=-L/opt/homebrew/Cellar/ffmpeg/7.1.1_3/lib;-lavformat;-lm;-lxml2;-lbz2;-L/opt/homebrew/Cellar/libbluray/1.3.4/lib;-lbluray;-lz;-L/opt/homebrew/Cellar/gnutls/3.8.9/lib;-lgnutls;-L/opt/homebrew/Cellar/librist/0.2.11/lib;-lrist;-L/opt/homebrew/Cellar/srt/1.5.4/lib;-lsrt;-L/opt/homebrew/Cellar/libssh/0.11.1/lib;-lssh;-L/opt/homebrew/Cellar/zeromq/4.3.5_1/lib;-lzmq;-lavcodec;-L/opt/homebrew/Cellar/libvpx/1.15.1/lib;-lvpx;-lm;-L/opt/homebrew/Cellar/libvpx/1.15.1/lib;-lvpx;-lm;-L/opt/homebrew/Cellar/libvpx/1.15.1/lib;-lvpx;-lm;-L/opt/homebrew/Cellar/libvpx/1.15.1/lib;-lvpx;-lm;-L/opt/homebrew/opt/webp/lib;-lwebpmux;-liconv;-lm;-llzma;-L/opt/homebrew/Cellar/aribb24/1.0.4/lib;-laribb24;-L/opt/homebrew/Cellar/dav1d/1.5.1/lib;-ldav1d;-lopencore-amrwb;-lsnappy;-lstdc++;-framework;AudioToolbox;-L/opt/homebrew/Cellar/aom/3.12.1/lib;-laom;-L/opt/homebrew/Cellar/libvmaf/3.0.0/lib;-lvmaf;-L/opt/homebrew/opt/jpeg-xl/lib;-ljxl;-L/opt/homebrew/opt/jpeg-xl/lib;-ljxl_threads;-lmp3lame;-lm;-lopencore-amrnb;-L/opt/homebrew/Cellar/openjpeg/2.5.3/lib;-lopenjp2;-L/opt/homebrew/Cellar/opus/1.5.2/lib;-lopus;-L/opt/homebrew/Cellar/rav1e/0.8.0/lib;-lrav1e;-L/opt/homebrew/Cellar/speex/1.2.1/lib;-lspeex;-L/opt/homebrew/Cellar/svt-av1/3.0.2/lib;-lSvtAv1Enc;-ltheoraenc;-ltheoradec;-logg;-L/opt/homebrew/Cellar/libvorbis/1.3.7/lib;-lvorbis;-L/opt/homebrew/Cellar/libvorbis/1.3.7/lib;-lvorbisenc;-L/opt/homebrew/opt/webp/lib;-lwebp;-L/opt/homebrew/Cellar/x264/r3108/lib;-lx264;-L/opt/homebrew/Cellar/x265/4.1/lib;-lx265;-lxvidcore;-pthread;-lz;-framework;VideoToolbox;-framework;CoreFoundation;-framework;CoreMedia;-framework;CoreVideo;-framework;CoreServices;-lswresample;-lm;-lsoxr;-lswscale;-lm;-lavutil;-pthread;-lm;-framework;VideoToolbox;-L/opt/homebrew/Cellar/libx11/1.8.12/lib;-lX11;-framework;CoreFoundation;-framework;CoreMedia;-framework;CoreVideo;-framework;CoreServices
-FFMPEG_STATIC_LDFLAGS_OTHER:INTERNAL=-framework;AudioToolbox;-L/opt/homebrew/Cellar/aom/3.12.1/lib;-laom;-L/opt/homebrew/Cellar/libvmaf/3.0.0/lib;-lvmaf;-L/opt/homebrew/opt/jpeg-xl/lib;-ljxl;-L/opt/homebrew/opt/jpeg-xl/lib;-ljxl_threads;-lmp3lame;-lm;-lopencore-amrnb;-L/opt/homebrew/Cellar/openjpeg/2.5.3/lib;-lopenjp2;-L/opt/homebrew/Cellar/opus/1.5.2/lib;-lopus;-L/opt/homebrew/Cellar/rav1e/0.8.0/lib;-lrav1e;-L/opt/homebrew/Cellar/speex/1.2.1/lib;-lspeex;-L/opt/homebrew/Cellar/svt-av1/3.0.2/lib;-lSvtAv1Enc;-ltheoraenc;-ltheoradec;-logg;-L/opt/homebrew/Cellar/libvorbis/1.3.7/lib;-lvorbis;-L/opt/homebrew/Cellar/libvorbis/1.3.7/lib;-lvorbisenc;-L/opt/homebrew/opt/webp/lib;-lwebp;-L/opt/homebrew/Cellar/x264/r3108/lib;-lx264;-L/opt/homebrew/Cellar/x265/4.1/lib;-lx265;-lxvidcore;-pthread;-lz;-framework;VideoToolbox;-framework;CoreFoundation;-framework;CoreMedia;-framework;CoreVideo;-framework;CoreServices;-pthread;-framework;VideoToolbox;-L/opt/homebrew/Cellar/libx11/1.8.12/lib;-lX11;-framework;CoreFoundation;-framework;CoreMedia;-framework;CoreVideo;-framework;CoreServices
-FFMPEG_STATIC_LIBDIR:INTERNAL=
-FFMPEG_STATIC_LIBRARIES:INTERNAL=avformat;m;xml2;bz2;bluray;z;gnutls;rist;srt;ssh;zmq;avcodec;vpx;m;vpx;m;vpx;m;vpx;m;webpmux;iconv;m;lzma;aribb24;dav1d;opencore-amrwb;snappy;stdc++;swresample;m;soxr;swscale;m;avutil;m
-FFMPEG_STATIC_LIBRARY_DIRS:INTERNAL=/opt/homebrew/Cellar/ffmpeg/7.1.1_3/lib;/opt/homebrew/Cellar/libbluray/1.3.4/lib;/opt/homebrew/Cellar/gnutls/3.8.9/lib;/opt/homebrew/Cellar/librist/0.2.11/lib;/opt/homebrew/Cellar/srt/1.5.4/lib;/opt/homebrew/Cellar/libssh/0.11.1/lib;/opt/homebrew/Cellar/zeromq/4.3.5_1/lib;/opt/homebrew/Cellar/libvpx/1.15.1/lib;/opt/homebrew/Cellar/libvpx/1.15.1/lib;/opt/homebrew/Cellar/libvpx/1.15.1/lib;/opt/homebrew/Cellar/libvpx/1.15.1/lib;/opt/homebrew/opt/webp/lib;/opt/homebrew/Cellar/aribb24/1.0.4/lib;/opt/homebrew/Cellar/dav1d/1.5.1/lib
-FFMPEG_STATIC_LIBS:INTERNAL=
-FFMPEG_STATIC_LIBS_L:INTERNAL=
-FFMPEG_STATIC_LIBS_OTHER:INTERNAL=
-FFMPEG_STATIC_LIBS_PATHS:INTERNAL=
-FFMPEG_VERSION:INTERNAL=
-FFMPEG_libavcodec_INCLUDEDIR:INTERNAL=/opt/homebrew/Cellar/ffmpeg/7.1.1_3/include
-FFMPEG_libavcodec_LIBDIR:INTERNAL=/opt/homebrew/Cellar/ffmpeg/7.1.1_3/lib
-FFMPEG_libavcodec_MODULE_NAME:INTERNAL=libavcodec
-FFMPEG_libavcodec_PREFIX:INTERNAL=/opt/homebrew/Cellar/ffmpeg/7.1.1_3
-FFMPEG_libavcodec_VERSION:INTERNAL=61.19.101
-FFMPEG_libavformat_INCLUDEDIR:INTERNAL=/opt/homebrew/Cellar/ffmpeg/7.1.1_3/include
-FFMPEG_libavformat_LIBDIR:INTERNAL=/opt/homebrew/Cellar/ffmpeg/7.1.1_3/lib
-FFMPEG_libavformat_MODULE_NAME:INTERNAL=libavformat
-FFMPEG_libavformat_PREFIX:INTERNAL=/opt/homebrew/Cellar/ffmpeg/7.1.1_3
-FFMPEG_libavformat_VERSION:INTERNAL=61.7.100
-FFMPEG_libavutil_INCLUDEDIR:INTERNAL=/opt/homebrew/Cellar/ffmpeg/7.1.1_3/include
-FFMPEG_libavutil_LIBDIR:INTERNAL=/opt/homebrew/Cellar/ffmpeg/7.1.1_3/lib
-FFMPEG_libavutil_MODULE_NAME:INTERNAL=libavutil
-FFMPEG_libavutil_PREFIX:INTERNAL=/opt/homebrew/Cellar/ffmpeg/7.1.1_3
-FFMPEG_libavutil_VERSION:INTERNAL=59.39.100
-FFMPEG_libswscale_INCLUDEDIR:INTERNAL=/opt/homebrew/Cellar/ffmpeg/7.1.1_3/include
-FFMPEG_libswscale_LIBDIR:INTERNAL=/opt/homebrew/Cellar/ffmpeg/7.1.1_3/lib
-FFMPEG_libswscale_MODULE_NAME:INTERNAL=libswscale
-FFMPEG_libswscale_PREFIX:INTERNAL=/opt/homebrew/Cellar/ffmpeg/7.1.1_3
-FFMPEG_libswscale_VERSION:INTERNAL=8.3.100
 //Details about finding Boost
-FIND_PACKAGE_MESSAGE_DETAILS_Boost:INTERNAL=[/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/share/boost/BoostConfig.cmake][found components: json system ][v1.88.0()]
-//Details about finding PkgConfig
-FIND_PACKAGE_MESSAGE_DETAILS_PkgConfig:INTERNAL=[/Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/tools/pkgconf/pkgconf][v2.5.1()]
-//ADVANCED property for variable: PKG_CONFIG_ARGN
-PKG_CONFIG_ARGN-ADVANCED:INTERNAL=1
-//ADVANCED property for variable: PKG_CONFIG_EXECUTABLE
-PKG_CONFIG_EXECUTABLE-ADVANCED:INTERNAL=1
-//Install the dependencies listed in your manifest:
-//\n    If this is off, you will have to manually install your dependencies.
-//\n    See https://github.com/microsoft/vcpkg/tree/master/docs/specifications/manifests.md
-// for more info.
-//\n
-VCPKG_MANIFEST_INSTALL:INTERNAL=OFF
-//ADVANCED property for variable: VCPKG_VERBOSE
-VCPKG_VERBOSE-ADVANCED:INTERNAL=1
-//Making sure VCPKG_MANIFEST_MODE doesn't change
-Z_VCPKG_CHECK_MANIFEST_MODE:INTERNAL=OFF
-//Vcpkg root directory
-Z_VCPKG_ROOT_DIR:INTERNAL=/Users/wenhongquan/gitdata/vcpkg
-__pkg_config_arguments_FFMPEG:INTERNAL=REQUIRED;IMPORTED_TARGET;libavformat;libavcodec;libavutil;libswscale
-__pkg_config_checked_FFMPEG:INTERNAL=1
-//ADVANCED property for variable: pkgcfg_lib_FFMPEG_avcodec
-pkgcfg_lib_FFMPEG_avcodec-ADVANCED:INTERNAL=1
-//ADVANCED property for variable: pkgcfg_lib_FFMPEG_avformat
-pkgcfg_lib_FFMPEG_avformat-ADVANCED:INTERNAL=1
-//ADVANCED property for variable: pkgcfg_lib_FFMPEG_avutil
-pkgcfg_lib_FFMPEG_avutil-ADVANCED:INTERNAL=1
-//ADVANCED property for variable: pkgcfg_lib_FFMPEG_swscale
-pkgcfg_lib_FFMPEG_swscale-ADVANCED:INTERNAL=1
-prefix_result:INTERNAL=/opt/homebrew/Cellar/ffmpeg/7.1.1_3/lib
+FIND_PACKAGE_MESSAGE_DETAILS_Boost:INTERNAL=[/opt/homebrew/lib/cmake/Boost-1.86.0/BoostConfig.cmake][found components: json system ][v1.86.0()]
+//ADVANCED property for variable: boost_container_DIR
+boost_container_DIR-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: boost_headers_DIR
+boost_headers_DIR-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: boost_json_DIR
+boost_json_DIR-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: boost_system_DIR
+boost_system_DIR-ADVANCED:INTERNAL=1
 

+ 1 - 1
build/CMakeFiles/4.1.1/CMakeCXXCompiler.cmake

@@ -98,7 +98,7 @@ set(CMAKE_CXX_COMPILER_CLANG_RESOURCE_DIR "")
 
 set(CMAKE_CXX_COMPILER_IMPORT_STD "")
 ### Imported target for C++23 standard library
-set(CMAKE_CXX23_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE "Toolchain does not support discovering `import std` support")
+set(CMAKE_CXX23_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE "Unsupported generator: Unix Makefiles")
 
 
 

BIN=BIN
build/CMakeFiles/4.1.1/CMakeDetermineCompilerABI_C.bin


BIN=BIN
build/CMakeFiles/4.1.1/CMakeDetermineCompilerABI_CXX.bin


+ 1 - 1
build/CMakeFiles/4.1.1/CMakeSystem.cmake

@@ -3,7 +3,7 @@ set(CMAKE_HOST_SYSTEM_NAME "Darwin")
 set(CMAKE_HOST_SYSTEM_VERSION "25.0.0")
 set(CMAKE_HOST_SYSTEM_PROCESSOR "arm64")
 
-include("/Users/wenhongquan/gitdata/vcpkg/scripts/buildsystems/vcpkg.cmake")
+
 
 set(CMAKE_SYSTEM "Darwin-25.0.0")
 set(CMAKE_SYSTEM_NAME "Darwin")

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 54 - 826
build/CMakeFiles/CMakeConfigureLog.yaml


+ 0 - 83
build/CMakeFiles/rules.ninja

@@ -1,83 +0,0 @@
-# CMAKE generated file: DO NOT EDIT!
-# Generated by "Ninja" Generator, CMake Version 4.1
-
-# This file contains all the rules used to get the outputs files
-# built from the input files.
-# It is included in the main 'build.ninja'.
-
-# =============================================================================
-# Project: jtjai_media
-# Configurations: 
-# =============================================================================
-# =============================================================================
-
-#############################################
-# Rule for compiling CXX files.
-
-rule CXX_COMPILER__jtjai_media_unscanned_
-  depfile = $DEP_FILE
-  deps = gcc
-  command = ${LAUNCHER}${CODE_CHECK}/usr/bin/c++ $DEFINES $INCLUDES $FLAGS -MD -MT $out -MF $DEP_FILE -o $out -c $in
-  description = Building CXX object $out
-
-
-#############################################
-# Rule for linking CXX executable.
-
-rule CXX_EXECUTABLE_LINKER__jtjai_media_
-  command = $PRE_LINK && /usr/bin/c++ $FLAGS $LINK_FLAGS $in -o $TARGET_FILE $LINK_PATH $LINK_LIBRARIES && $POST_BUILD
-  description = Linking CXX executable $TARGET_FILE
-  restat = $RESTAT
-
-
-#############################################
-# Rule for compiling CXX files.
-
-rule CXX_COMPILER__jtjai_http_server_unscanned_
-  depfile = $DEP_FILE
-  deps = gcc
-  command = ${LAUNCHER}${CODE_CHECK}/usr/bin/c++ $DEFINES $INCLUDES $FLAGS -MD -MT $out -MF $DEP_FILE -o $out -c $in
-  description = Building CXX object $out
-
-
-#############################################
-# Rule for linking CXX executable.
-
-rule CXX_EXECUTABLE_LINKER__jtjai_http_server_
-  command = $PRE_LINK && /usr/bin/c++ $FLAGS $LINK_FLAGS $in -o $TARGET_FILE $LINK_PATH $LINK_LIBRARIES && $POST_BUILD
-  description = Linking CXX executable $TARGET_FILE
-  restat = $RESTAT
-
-
-#############################################
-# Rule for running custom commands.
-
-rule CUSTOM_COMMAND
-  command = $COMMAND
-  description = $DESC
-
-
-#############################################
-# Rule for re-running cmake.
-
-rule RERUN_CMAKE
-  command = /opt/homebrew/bin/cmake --regenerate-during-build -S/Users/wenhongquan/CLionProjects/jtjai_media -B/Users/wenhongquan/CLionProjects/jtjai_media/build
-  description = Re-running CMake...
-  generator = 1
-
-
-#############################################
-# Rule for cleaning all built files.
-
-rule CLEAN
-  command = /Users/wenhongquan/miniforge3/bin/ninja $FILE_ARG -t clean $TARGETS
-  description = Cleaning all built files...
-
-
-#############################################
-# Rule for printing all primary targets available.
-
-rule HELP
-  command = /Users/wenhongquan/miniforge3/bin/ninja -t targets
-  description = All primary targets available:
-

+ 221 - 8
build/Makefile

@@ -92,13 +92,6 @@ all: cmake_check_build_system
 	$(CMAKE_COMMAND) -E cmake_progress_start /Users/wenhongquan/CLionProjects/jtjai_media/build/CMakeFiles 0
 .PHONY : all
 
-# The main codegen target
-codegen: cmake_check_build_system
-	$(CMAKE_COMMAND) -E cmake_progress_start /Users/wenhongquan/CLionProjects/jtjai_media/build/CMakeFiles /Users/wenhongquan/CLionProjects/jtjai_media/build//CMakeFiles/progress.marks
-	$(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 codegen
-	$(CMAKE_COMMAND) -E cmake_progress_start /Users/wenhongquan/CLionProjects/jtjai_media/build/CMakeFiles 0
-.PHONY : codegen
-
 # The main clean target
 clean:
 	$(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 clean
@@ -136,6 +129,43 @@ jtjai_media/fast:
 	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_media.dir/build.make CMakeFiles/jtjai_media.dir/build
 .PHONY : jtjai_media/fast
 
+#=============================================================================
+# Target rules for targets named jtjai_http_server
+
+# Build rule for target.
+jtjai_http_server: cmake_check_build_system
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 jtjai_http_server
+.PHONY : jtjai_http_server
+
+# fast build rule for target.
+jtjai_http_server/fast:
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_http_server.dir/build.make CMakeFiles/jtjai_http_server.dir/build
+.PHONY : jtjai_http_server/fast
+
+http_server_main.o: http_server_main.cpp.o
+.PHONY : http_server_main.o
+
+# target to build an object file
+http_server_main.cpp.o:
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_http_server.dir/build.make CMakeFiles/jtjai_http_server.dir/http_server_main.cpp.o
+.PHONY : http_server_main.cpp.o
+
+http_server_main.i: http_server_main.cpp.i
+.PHONY : http_server_main.i
+
+# target to preprocess a source file
+http_server_main.cpp.i:
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_http_server.dir/build.make CMakeFiles/jtjai_http_server.dir/http_server_main.cpp.i
+.PHONY : http_server_main.cpp.i
+
+http_server_main.s: http_server_main.cpp.s
+.PHONY : http_server_main.s
+
+# target to generate assembly for a file
+http_server_main.cpp.s:
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_http_server.dir/build.make CMakeFiles/jtjai_http_server.dir/http_server_main.cpp.s
+.PHONY : http_server_main.cpp.s
+
 main.o: main.cpp.o
 .PHONY : main.o
 
@@ -160,19 +190,202 @@ main.cpp.s:
 	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_media.dir/build.make CMakeFiles/jtjai_media.dir/main.cpp.s
 .PHONY : main.cpp.s
 
+src/concurrent_calculator.o: src/concurrent_calculator.cpp.o
+.PHONY : src/concurrent_calculator.o
+
+# target to build an object file
+src/concurrent_calculator.cpp.o:
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_media.dir/build.make CMakeFiles/jtjai_media.dir/src/concurrent_calculator.cpp.o
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_http_server.dir/build.make CMakeFiles/jtjai_http_server.dir/src/concurrent_calculator.cpp.o
+.PHONY : src/concurrent_calculator.cpp.o
+
+src/concurrent_calculator.i: src/concurrent_calculator.cpp.i
+.PHONY : src/concurrent_calculator.i
+
+# target to preprocess a source file
+src/concurrent_calculator.cpp.i:
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_media.dir/build.make CMakeFiles/jtjai_media.dir/src/concurrent_calculator.cpp.i
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_http_server.dir/build.make CMakeFiles/jtjai_http_server.dir/src/concurrent_calculator.cpp.i
+.PHONY : src/concurrent_calculator.cpp.i
+
+src/concurrent_calculator.s: src/concurrent_calculator.cpp.s
+.PHONY : src/concurrent_calculator.s
+
+# target to generate assembly for a file
+src/concurrent_calculator.cpp.s:
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_media.dir/build.make CMakeFiles/jtjai_media.dir/src/concurrent_calculator.cpp.s
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_http_server.dir/build.make CMakeFiles/jtjai_http_server.dir/src/concurrent_calculator.cpp.s
+.PHONY : src/concurrent_calculator.cpp.s
+
+src/config.o: src/config.cpp.o
+.PHONY : src/config.o
+
+# target to build an object file
+src/config.cpp.o:
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_media.dir/build.make CMakeFiles/jtjai_media.dir/src/config.cpp.o
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_http_server.dir/build.make CMakeFiles/jtjai_http_server.dir/src/config.cpp.o
+.PHONY : src/config.cpp.o
+
+src/config.i: src/config.cpp.i
+.PHONY : src/config.i
+
+# target to preprocess a source file
+src/config.cpp.i:
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_media.dir/build.make CMakeFiles/jtjai_media.dir/src/config.cpp.i
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_http_server.dir/build.make CMakeFiles/jtjai_http_server.dir/src/config.cpp.i
+.PHONY : src/config.cpp.i
+
+src/config.s: src/config.cpp.s
+.PHONY : src/config.s
+
+# target to generate assembly for a file
+src/config.cpp.s:
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_media.dir/build.make CMakeFiles/jtjai_media.dir/src/config.cpp.s
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_http_server.dir/build.make CMakeFiles/jtjai_http_server.dir/src/config.cpp.s
+.PHONY : src/config.cpp.s
+
+src/http_server.o: src/http_server.cpp.o
+.PHONY : src/http_server.o
+
+# target to build an object file
+src/http_server.cpp.o:
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_media.dir/build.make CMakeFiles/jtjai_media.dir/src/http_server.cpp.o
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_http_server.dir/build.make CMakeFiles/jtjai_http_server.dir/src/http_server.cpp.o
+.PHONY : src/http_server.cpp.o
+
+src/http_server.i: src/http_server.cpp.i
+.PHONY : src/http_server.i
+
+# target to preprocess a source file
+src/http_server.cpp.i:
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_media.dir/build.make CMakeFiles/jtjai_media.dir/src/http_server.cpp.i
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_http_server.dir/build.make CMakeFiles/jtjai_http_server.dir/src/http_server.cpp.i
+.PHONY : src/http_server.cpp.i
+
+src/http_server.s: src/http_server.cpp.s
+.PHONY : src/http_server.s
+
+# target to generate assembly for a file
+src/http_server.cpp.s:
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_media.dir/build.make CMakeFiles/jtjai_media.dir/src/http_server.cpp.s
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_http_server.dir/build.make CMakeFiles/jtjai_http_server.dir/src/http_server.cpp.s
+.PHONY : src/http_server.cpp.s
+
+src/reporter.o: src/reporter.cpp.o
+.PHONY : src/reporter.o
+
+# target to build an object file
+src/reporter.cpp.o:
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_media.dir/build.make CMakeFiles/jtjai_media.dir/src/reporter.cpp.o
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_http_server.dir/build.make CMakeFiles/jtjai_http_server.dir/src/reporter.cpp.o
+.PHONY : src/reporter.cpp.o
+
+src/reporter.i: src/reporter.cpp.i
+.PHONY : src/reporter.i
+
+# target to preprocess a source file
+src/reporter.cpp.i:
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_media.dir/build.make CMakeFiles/jtjai_media.dir/src/reporter.cpp.i
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_http_server.dir/build.make CMakeFiles/jtjai_http_server.dir/src/reporter.cpp.i
+.PHONY : src/reporter.cpp.i
+
+src/reporter.s: src/reporter.cpp.s
+.PHONY : src/reporter.s
+
+# target to generate assembly for a file
+src/reporter.cpp.s:
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_media.dir/build.make CMakeFiles/jtjai_media.dir/src/reporter.cpp.s
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_http_server.dir/build.make CMakeFiles/jtjai_http_server.dir/src/reporter.cpp.s
+.PHONY : src/reporter.cpp.s
+
+src/rtsp_client.o: src/rtsp_client.cpp.o
+.PHONY : src/rtsp_client.o
+
+# target to build an object file
+src/rtsp_client.cpp.o:
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_media.dir/build.make CMakeFiles/jtjai_media.dir/src/rtsp_client.cpp.o
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_http_server.dir/build.make CMakeFiles/jtjai_http_server.dir/src/rtsp_client.cpp.o
+.PHONY : src/rtsp_client.cpp.o
+
+src/rtsp_client.i: src/rtsp_client.cpp.i
+.PHONY : src/rtsp_client.i
+
+# target to preprocess a source file
+src/rtsp_client.cpp.i:
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_media.dir/build.make CMakeFiles/jtjai_media.dir/src/rtsp_client.cpp.i
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_http_server.dir/build.make CMakeFiles/jtjai_http_server.dir/src/rtsp_client.cpp.i
+.PHONY : src/rtsp_client.cpp.i
+
+src/rtsp_client.s: src/rtsp_client.cpp.s
+.PHONY : src/rtsp_client.s
+
+# target to generate assembly for a file
+src/rtsp_client.cpp.s:
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_media.dir/build.make CMakeFiles/jtjai_media.dir/src/rtsp_client.cpp.s
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_http_server.dir/build.make CMakeFiles/jtjai_http_server.dir/src/rtsp_client.cpp.s
+.PHONY : src/rtsp_client.cpp.s
+
+src/scheduler.o: src/scheduler.cpp.o
+.PHONY : src/scheduler.o
+
+# target to build an object file
+src/scheduler.cpp.o:
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_media.dir/build.make CMakeFiles/jtjai_media.dir/src/scheduler.cpp.o
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_http_server.dir/build.make CMakeFiles/jtjai_http_server.dir/src/scheduler.cpp.o
+.PHONY : src/scheduler.cpp.o
+
+src/scheduler.i: src/scheduler.cpp.i
+.PHONY : src/scheduler.i
+
+# target to preprocess a source file
+src/scheduler.cpp.i:
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_media.dir/build.make CMakeFiles/jtjai_media.dir/src/scheduler.cpp.i
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_http_server.dir/build.make CMakeFiles/jtjai_http_server.dir/src/scheduler.cpp.i
+.PHONY : src/scheduler.cpp.i
+
+src/scheduler.s: src/scheduler.cpp.s
+.PHONY : src/scheduler.s
+
+# target to generate assembly for a file
+src/scheduler.cpp.s:
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_media.dir/build.make CMakeFiles/jtjai_media.dir/src/scheduler.cpp.s
+	$(MAKE) $(MAKESILENT) -f CMakeFiles/jtjai_http_server.dir/build.make CMakeFiles/jtjai_http_server.dir/src/scheduler.cpp.s
+.PHONY : src/scheduler.cpp.s
+
 # Help Target
 help:
 	@echo "The following are some of the valid targets for this Makefile:"
 	@echo "... all (the default if no target is provided)"
 	@echo "... clean"
 	@echo "... depend"
-	@echo "... codegen"
 	@echo "... edit_cache"
 	@echo "... rebuild_cache"
+	@echo "... jtjai_http_server"
 	@echo "... jtjai_media"
+	@echo "... http_server_main.o"
+	@echo "... http_server_main.i"
+	@echo "... http_server_main.s"
 	@echo "... main.o"
 	@echo "... main.i"
 	@echo "... main.s"
+	@echo "... src/concurrent_calculator.o"
+	@echo "... src/concurrent_calculator.i"
+	@echo "... src/concurrent_calculator.s"
+	@echo "... src/config.o"
+	@echo "... src/config.i"
+	@echo "... src/config.s"
+	@echo "... src/http_server.o"
+	@echo "... src/http_server.i"
+	@echo "... src/http_server.s"
+	@echo "... src/reporter.o"
+	@echo "... src/reporter.i"
+	@echo "... src/reporter.s"
+	@echo "... src/rtsp_client.o"
+	@echo "... src/rtsp_client.i"
+	@echo "... src/rtsp_client.s"
+	@echo "... src/scheduler.o"
+	@echo "... src/scheduler.i"
+	@echo "... src/scheduler.s"
 .PHONY : help
 
 

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 207
build/build.ninja


+ 0 - 8
build/compile_commands.json

@@ -1,8 +0,0 @@
-[
-{
-  "directory": "/Users/wenhongquan/CLionProjects/jtjai_media/build",
-  "command": "/usr/bin/c++  -isystem /Users/wenhongquan/gitdata/vcpkg/installed/arm64-osx/include -g -std=gnu++11 -arch arm64 -o CMakeFiles/jtjai_media.dir/main.cpp.o -c /Users/wenhongquan/CLionProjects/jtjai_media/main.cpp",
-  "file": "/Users/wenhongquan/CLionProjects/jtjai_media/main.cpp",
-  "output": "CMakeFiles/jtjai_media.dir/main.cpp.o"
-}
-]

+ 0 - 36
build/config.json

@@ -1,36 +0,0 @@
-{
-  "global_config": {
-    "total_poll_duration_seconds": 300,
-    "max_concurrent_streams": 8,
-    "output_directory": "./output",
-    "report_filename": "rtsp_report.json",
-    "connection_timeout_seconds": 10,
-    "read_timeout_seconds": 30
-  },
-  "streams": [
-    {
-      "rtsp_url": "rtsp://example1.com:554/live/stream1",
-      "duration_seconds": 60,
-      "weight": 1.0,
-      "output_filename": "stream1.mp4"
-    },
-    {
-      "rtsp_url": "rtsp://example2.com:554/live/stream2", 
-      "duration_seconds": 90,
-      "weight": 1.5,
-      "output_filename": "stream2.mp4"
-    },
-    {
-      "rtsp_url": "rtsp://example3.com:554/live/stream3",
-      "duration_seconds": 45,
-      "weight": 0.8,
-      "output_filename": "stream3.mp4"
-    },
-    {
-      "rtsp_url": "rtsp://example4.com:554/live/stream4",
-      "duration_seconds": 120,
-      "weight": 2.0,
-      "output_filename": "stream4.mp4"
-    }
-  ]
-}

BIN=BIN
build/jtjai_media


+ 16 - 0
include/config.h

@@ -61,6 +61,21 @@ public:
     void add_stream_config(const StreamConfig& config) { stream_configs_.push_back(config); }
     void clear_stream_configs() { stream_configs_.clear(); }
     
+    // 流配置管理
+    bool update_stream_config(size_t index, const StreamConfig& config);
+    bool remove_stream_config(size_t index);
+    size_t get_stream_count() const { return stream_configs_.size(); }
+    
+    // 配置文件路径管理
+    void set_config_file_path(const std::string& path) { config_file_path_ = path; }
+    const std::string& get_config_file_path() const { return config_file_path_; }
+    
+    // 重新加载配置
+    bool reload_from_file();
+    
+    // JSON格式输出
+    std::string to_json_string() const;
+    
     // 验证配置有效性
     bool validate() const;
     
@@ -70,6 +85,7 @@ public:
 private:
     GlobalConfig global_config_;
     std::vector<StreamConfig> stream_configs_;
+    std::string config_file_path_;  // 记住当前加载的配置文件路径
     
     // JSON转换辅助函数
     boost::json::object stream_config_to_json(const StreamConfig& config) const;

+ 35 - 0
include/http_server.h

@@ -10,6 +10,8 @@
 #include <atomic>
 #include <boost/asio.hpp>
 #include <boost/json.hpp>
+#include "config.h"
+#include "scheduler.h"
 
 namespace jtjai_media {
 
@@ -47,6 +49,18 @@ struct HttpResponse {
 class HttpServer {
 public:
     explicit HttpServer(const std::string& output_directory, int port = 8080);
+    
+    // 构造函数重载,支持配置管理
+    explicit HttpServer(const std::string& output_directory, 
+                       std::shared_ptr<ConfigManager> config_manager, 
+                       int port = 8080);
+    
+    // 构造函数重载,支持配置管理和调度器
+    explicit HttpServer(const std::string& output_directory, 
+                       std::shared_ptr<ConfigManager> config_manager,
+                       std::shared_ptr<StreamScheduler> scheduler,
+                       int port = 8080);
+    
     ~HttpServer();
     
     // 启动服务器
@@ -66,6 +80,8 @@ private:
     int port_;                       // 监听端口
     std::atomic<bool> is_running_;   // 运行状态
     std::unique_ptr<std::thread> server_thread_;  // 服务器线程
+    std::shared_ptr<ConfigManager> config_manager_;  // 配置管理器
+    std::shared_ptr<StreamScheduler> scheduler_;     // 调度器
     
     // 服务器主循环
     void server_main_loop();
@@ -93,6 +109,25 @@ private:
     HttpResponse handle_get_report(const HttpRequest& request);
     HttpResponse handle_list_reports(const HttpRequest& request);
     
+    // 配置管理API处理函数
+    HttpResponse handle_get_config(const HttpRequest& request);
+    HttpResponse handle_update_config(const HttpRequest& request);
+    HttpResponse handle_get_global_config(const HttpRequest& request);
+    HttpResponse handle_update_global_config(const HttpRequest& request);
+    HttpResponse handle_get_stream_configs(const HttpRequest& request);
+    HttpResponse handle_add_stream_config(const HttpRequest& request);
+    HttpResponse handle_update_stream_config(const HttpRequest& request);
+    HttpResponse handle_delete_stream_config(const HttpRequest& request);
+    HttpResponse handle_save_config(const HttpRequest& request);
+    HttpResponse handle_reload_config(const HttpRequest& request);
+    // 轮询任务管理API处理函数
+    HttpResponse handle_get_task_list(const HttpRequest& request);
+    HttpResponse handle_get_task_status(const HttpRequest& request);
+    HttpResponse handle_get_scheduler_status(const HttpRequest& request);
+    HttpResponse handle_start_scheduler(const HttpRequest& request);
+    HttpResponse handle_stop_scheduler(const HttpRequest& request);
+    HttpResponse handle_get_current_cycle_status(const HttpRequest& request);
+    
     // 工具函数
     std::vector<std::string> list_timestamp_directories();
     std::vector<VideoFileInfo> list_videos_in_directory(const std::string& dir_path);

+ 1 - 0
include/rtsp_client.h

@@ -114,6 +114,7 @@ private:
     void cleanup();
     bool process_stream(int duration_seconds);
     void update_status(RTSPClientStatus status, const std::string& error_msg = "");
+    void update_final_file_stats();
     
     // FFmpeg日志回调
     static void ffmpeg_log_callback(void* ptr, int level, const char* fmt, va_list vl);

+ 62 - 1
src/config.cpp

@@ -18,7 +18,11 @@ bool ConfigManager::load_from_file(const std::string& config_file) {
         buffer << file.rdbuf();
         file.close();
         
-        return load_from_json(buffer.str());
+        bool result = load_from_json(buffer.str());
+        if (result) {
+            config_file_path_ = config_file;  // 记住配置文件路径
+        }
+        return result;
     } catch (const std::exception& e) {
         std::cerr << "加载配置文件时发生异常: " << e.what() << std::endl;
         return false;
@@ -237,4 +241,61 @@ GlobalConfig ConfigManager::global_config_from_json(const boost::json::object& j
     return config;
 }
 
+bool ConfigManager::update_stream_config(size_t index, const StreamConfig& config) {
+    if (index >= stream_configs_.size()) {
+        std::cerr << "流配置索引超出范围: " << index << std::endl;
+        return false;
+    }
+    
+    // 验证新配置
+    if (config.rtsp_url.empty() || config.duration_seconds <= 0 || 
+        config.weight <= 0 || config.output_filename.empty()) {
+        std::cerr << "新流配置参数不合法" << std::endl;
+        return false;
+    }
+    
+    stream_configs_[index] = config;
+    return true;
+}
+
+bool ConfigManager::remove_stream_config(size_t index) {
+    if (index >= stream_configs_.size()) {
+        std::cerr << "流配置索引超出范围: " << index << std::endl;
+        return false;
+    }
+    
+    stream_configs_.erase(stream_configs_.begin() + index);
+    return true;
+}
+
+bool ConfigManager::reload_from_file() {
+    if (config_file_path_.empty()) {
+        std::cerr << "未设置配置文件路径" << std::endl;
+        return false;
+    }
+    
+    return load_from_file(config_file_path_);
+}
+
+std::string ConfigManager::to_json_string() const {
+    try {
+        boost::json::object root;
+        
+        // 添加全局配置
+        root["global_config"] = global_config_to_json(global_config_);
+        
+        // 添加流配置
+        boost::json::array streams_array;
+        for (const auto& stream : stream_configs_) {
+            streams_array.push_back(stream_config_to_json(stream));
+        }
+        root["streams"] = streams_array;
+        
+        return boost::json::serialize(root);
+    } catch (const std::exception& e) {
+        std::cerr << "生成JSON字符串时发生异常: " << e.what() << std::endl;
+        return "{}";
+    }
+}
+
 } // namespace jtjai_media

+ 1098 - 12
src/http_server.cpp

@@ -12,7 +12,29 @@ namespace jtjai_media {
 HttpServer::HttpServer(const std::string& output_directory, int port)
     : output_directory_(output_directory)
     , port_(port)
-    , is_running_(false) {
+    , is_running_(false)
+    , config_manager_(nullptr) {
+}
+
+HttpServer::HttpServer(const std::string& output_directory, 
+                       std::shared_ptr<ConfigManager> config_manager, 
+                       int port)
+    : output_directory_(output_directory)
+    , port_(port)
+    , is_running_(false)
+    , config_manager_(config_manager)
+    , scheduler_(nullptr) {
+}
+
+HttpServer::HttpServer(const std::string& output_directory, 
+                       std::shared_ptr<ConfigManager> config_manager,
+                       std::shared_ptr<StreamScheduler> scheduler, 
+                       int port)
+    : output_directory_(output_directory)
+    , port_(port)
+    , is_running_(false)
+    , config_manager_(config_manager)
+    , scheduler_(scheduler) {
 }
 
 HttpServer::~HttpServer() {
@@ -37,6 +59,31 @@ bool HttpServer::start() {
     std::cout << "  GET  /api/report/{timestamp} - 获取指定时间戳的轮询报告" << std::endl;
     std::cout << "  DELETE /api/video?path={path} - 删除指定视频文件" << std::endl;
     std::cout << "  DELETE /api/timestamp/{timestamp} - 删除指定时间戳目录及其所有文件" << std::endl;
+    
+    if (config_manager_) {
+        std::cout << "配置管理API:" << std::endl;
+        std::cout << "  GET  /api/config - 获取完整配置" << std::endl;
+        std::cout << "  PUT  /api/config - 更新完整配置" << std::endl;
+        std::cout << "  GET  /api/config/global - 获取全局配置" << std::endl;
+        std::cout << "  PUT  /api/config/global - 更新全局配置" << std::endl;
+        std::cout << "  GET  /api/config/streams - 获取流配置列表" << std::endl;
+        std::cout << "  POST /api/config/streams - 添加新的流配置" << std::endl;
+        std::cout << "  PUT  /api/config/streams/{index} - 更新指定索引的流配置" << std::endl;
+        std::cout << "  DELETE /api/config/streams/{index} - 删除指定索引的流配置" << std::endl;
+        std::cout << "  POST /api/config/save - 保存配置到文件" << std::endl;
+        std::cout << "  POST /api/config/reload - 从文件重新加载配置" << std::endl;
+    }
+    
+    if (scheduler_) {
+        std::cout << "轮询任务管理API:" << std::endl;
+        std::cout << "  GET  /api/tasks - 获取轮询任务列表" << std::endl;
+        std::cout << "  GET  /api/tasks/status - 获取实时任务状态" << std::endl;
+        std::cout << "  GET  /api/scheduler/status - 获取调度器状态" << std::endl;
+        std::cout << "  GET  /api/scheduler/cycle - 获取当前周期状态" << std::endl;
+        std::cout << "  POST /api/scheduler/start - 启动调度器" << std::endl;
+        std::cout << "  POST /api/scheduler/stop - 停止调度器" << std::endl;
+    }
+    
     std::cout << "Web界面:" << std::endl;
     std::cout << "  GET  / - 主页" << std::endl;
     std::cout << "  GET  /manager - 视频管理界面" << std::endl;
@@ -174,6 +221,19 @@ HttpRequest HttpServer::parse_request(const std::string& request_data) {
         }
     }
     
+    // 解析请求体(对于POST/PUT请求)
+    if (request.method == "POST" || request.method == "PUT") {
+        std::string remaining_content;
+        std::string body_line;
+        while (std::getline(stream, body_line)) {
+            if (!remaining_content.empty()) {
+                remaining_content += "\n";
+            }
+            remaining_content += body_line;
+        }
+        request.body = remaining_content;
+    }
+    
     return request;
 }
 
@@ -218,6 +278,20 @@ HttpResponse HttpServer::route_request(const HttpRequest& request) {
             return handle_list_timestamps(request);
         } else if (request.path == "/api/reports") {
             return handle_list_reports(request);
+        } else if (request.path == "/api/config") {
+            return handle_get_config(request);
+        } else if (request.path == "/api/config/global") {
+            return handle_get_global_config(request);
+        } else if (request.path == "/api/config/streams") {
+            return handle_get_stream_configs(request);
+        } else if (request.path == "/api/tasks") {
+            return handle_get_task_list(request);
+        } else if (request.path == "/api/tasks/status") {
+            return handle_get_task_status(request);
+        } else if (request.path == "/api/scheduler/status") {
+            return handle_get_scheduler_status(request);
+        } else if (request.path == "/api/scheduler/cycle") {
+            return handle_get_current_cycle_status(request);
         } else if (request.path.find("/api/report/") == 0) {
             return handle_get_report(request);
         } else if (request.path.find("/api/videos/") == 0) {
@@ -234,11 +308,33 @@ HttpResponse HttpServer::route_request(const HttpRequest& request) {
             // 处理静态文件访问(HTML、CSS、JS等)
             return handle_static_file(request);
         }
+    } else if (request.method == "PUT") {
+        if (request.path == "/api/config") {
+            return handle_update_config(request);
+        } else if (request.path == "/api/config/global") {
+            return handle_update_global_config(request);
+        } else if (request.path.find("/api/config/streams/") == 0) {
+            return handle_update_stream_config(request);
+        }
+    } else if (request.method == "POST") {
+        if (request.path == "/api/config/streams") {
+            return handle_add_stream_config(request);
+        } else if (request.path == "/api/config/save") {
+            return handle_save_config(request);
+        } else if (request.path == "/api/config/reload") {
+            return handle_reload_config(request);
+        } else if (request.path == "/api/scheduler/start") {
+            return handle_start_scheduler(request);
+        } else if (request.path == "/api/scheduler/stop") {
+            return handle_stop_scheduler(request);
+        }
     } else if (request.method == "DELETE") {
         if (request.path == "/api/video") {
             return handle_delete_video(request);
         } else if (request.path.find("/api/timestamp/") == 0) {
             return handle_delete_timestamp(request);
+        } else if (request.path.find("/api/config/streams/") == 0) {
+            return handle_delete_stream_config(request);
         }
     }
     
@@ -954,6 +1050,106 @@ HttpResponse HttpServer::generate_index_page() {
 }
 
 HttpResponse HttpServer::generate_api_doc() {
+    std::string config_section = "";
+    if (config_manager_) {
+        config_section = R"(
+        <div class="section" id="config-api">
+            <h2>⚙️ 配置管理API</h2>
+            <div class="status-codes">
+                <h4>🔧 核心功能:</h4>
+                <p>• 实时修改系统参数 • 热更新配置 • 流管理 • 持久化保存</p>
+            </div>
+            <div class="endpoint">
+                <h3><span class="method get">GET</span>/api/config</h3>
+                <p>获取完整配置</p>
+            </div>
+            <div class="endpoint">
+                <h3><span class="method get">GET</span>/api/config/global</h3>
+                <p>获取全局配置</p>
+            </div>
+            <div class="endpoint">
+                <h3><span class="method put">PUT</span>/api/config/global</h3>
+                <p>更新全局配置</p>
+            </div>
+            <div class="endpoint">
+                <h3><span class="method get">GET</span>/api/config/streams</h3>
+                <p>获取流配置列表</p>
+            </div>
+            <div class="endpoint">
+                <h3><span class="method post">POST</span>/api/config/streams</h3>
+                <p>添加新的流配置</p>
+            </div>
+            <div class="endpoint">
+                <h3><span class="method put">PUT</span>/api/config/streams/{index}</h3>
+                <p>更新指定索引的流配置</p>
+            </div>
+            <div class="endpoint">
+                <h3><span class="method delete">DELETE</span>/api/config/streams/{index}</h3>
+                <p>删除指定索引的流配置</p>
+            </div>
+            <div class="endpoint">
+                <h3><span class="method post">POST</span>/api/config/save</h3>
+                <p>保存配置到文件</p>
+            </div>
+            <div class="endpoint">
+                <h3><span class="method post">POST</span>/api/config/reload</h3>
+                <p>从文件重新加载配置</p>
+            </div>
+        </div>)";
+    }
+    
+    std::string task_section = "";
+    if (scheduler_) {
+        task_section = R"(
+        <div class="section" id="task-api">
+            <h2>🔄 轮询任务管理API</h2>
+            <div class="status-codes">
+                <h4>🔍 状态码说明:</h4>
+                <p>• 0=空闲 • 1=连接中 • 2=已连接 • 3=录制中 • 4=完成</p>
+                <p>• 5=连接错误 • 6=录制错误 • 7=超时 • 8=已取消 • 9=流不可用</p>
+            </div>
+            <div class="endpoint">
+                <h3><span class="method get">GET</span>/api/tasks</h3>
+                <p>获取任务列表和调度状态</p>
+            </div>
+            <div class="endpoint">
+                <h3><span class="method get">GET</span>/api/tasks/status</h3>
+                <p>获取实时任务状态</p>
+            </div>
+            <div class="endpoint">
+                <h3><span class="method get">GET</span>/api/scheduler/status</h3>
+                <p>获取调度器状态</p>
+            </div>
+            <div class="endpoint">
+                <h3><span class="method get">GET</span>/api/scheduler/cycle</h3>
+                <p>获取当前周期状态</p>
+            </div>
+            <div class="endpoint">
+                <h3><span class="method post">POST</span>/api/scheduler/start</h3>
+                <p>启动调度器</p>
+            </div>
+            <div class="endpoint">
+                <h3><span class="method post">POST</span>/api/scheduler/stop</h3>
+                <p>停止调度器</p>
+            </div>
+        </div>)";
+    }
+    
+    std::string nav_config = config_manager_ ? R"(<a href="#config-api">⚙️ 配置管理</a>)" : "";
+    std::string nav_task = scheduler_ ? R"(<a href="#task-api">🔄 轮询任务</a>)" : "";
+    
+    std::string example_config = config_manager_ ? R"(
+            <div class="endpoint">
+                <h3>示例: 获取配置</h3>
+                <p><code>curl http://localhost:8080/api/config</code></p>
+            </div>)" : "";
+    
+    std::string example_task = scheduler_ ? R"(
+            <div class="endpoint">
+                <h3>示例: 获取任务状态</h3>
+                <p><code>curl http://localhost:8080/api/tasks</code></p>
+            </div>)" : "";
+    
     std::string html = R"(
 <!DOCTYPE html>
 <html lang="zh-CN">
@@ -962,28 +1158,102 @@ HttpResponse HttpServer::generate_api_doc() {
     <title>API文档</title>
     <style>
         body { font-family: sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
-        .container { max-width: 1000px; margin: 0 auto; background: white; padding: 40px; border-radius: 10px; }
-        .endpoint { background: #f8f9fa; padding: 20px; margin: 20px 0; border-radius: 8px; }
-        .method { display: inline-block; padding: 4px 8px; border-radius: 4px; color: white; font-weight: bold; margin-right: 10px; }
+        .container { max-width: 1000px; margin: 0 auto; background: white; padding: 40px; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); }
+        .endpoint { background: #f8f9fa; padding: 20px; margin: 15px 0; border-radius: 8px; border-left: 4px solid #007bff; }
+        .method { display: inline-block; padding: 4px 8px; border-radius: 4px; color: white; font-weight: bold; margin-right: 10px; font-size: 12px; }
         .get { background: #28a745; }
+        .post { background: #007bff; }
+        .put { background: #ffc107; color: #212529; }
         .delete { background: #dc3545; }
+        .section { margin: 30px 0; }
+        .section h2 { color: #333; border-bottom: 2px solid #007bff; padding-bottom: 10px; margin-bottom: 20px; }
+        .nav-links { background: #e9ecef; padding: 15px; border-radius: 8px; margin-bottom: 30px; }
+        .nav-links a { display: inline-block; margin-right: 15px; color: #007bff; text-decoration: none; }
+        .nav-links a:hover { text-decoration: underline; }
+        .status-codes { background: #fff3cd; padding: 15px; border-radius: 8px; margin: 20px 0; }
+        .status-codes h4 { margin-top: 0; color: #856404; }
+        .endpoint p { margin: 5px 0; color: #6c757d; }
+        .highlight { background: #d4edda; padding: 10px; border-radius: 4px; margin: 10px 0; }
     </style>
 </head>
 <body>
     <div class="container">
         <h1>📊 API文档</h1>
-        <div class="endpoint">
-            <h3><span class="method get">GET</span>/api/videos</h3>
-            <p>列出所有视频文件</p>
+        
+        <div class="nav-links">
+            <strong>📦 快速导航:</strong>
+            <a href="#video-api">🎥 视频管理</a>)" + nav_config + nav_task + R"(
+            <a href="/manager">🖥️ Web管理界面</a>
         </div>
-        <div class="endpoint">
-            <h3><span class="method delete">DELETE</span>/api/video</h3>
-            <p>删除指定视频文件</p>
+        
+        <div class="highlight">
+            <strong>🎆 系统功能:</strong> RTSP视频流管理系统提供完整的视频文件管理、动态配置和实时任务监控能力
+        </div>
+        
+        <div class="section" id="video-api">
+            <h2>🎥 视频管理API</h2>
+            <div class="endpoint">
+                <h3><span class="method get">GET</span>/api/videos</h3>
+                <p>列出所有视频文件</p>
+            </div>
+            <div class="endpoint">
+                <h3><span class="method get">GET</span>/api/timestamps</h3>
+                <p>列出所有时间戳目录</p>
+            </div>
+            <div class="endpoint">
+                <h3><span class="method get">GET</span>/api/videos/{timestamp}</h3>
+                <p>列出指定时间戳目录的视频</p>
+            </div>
+            <div class="endpoint">
+                <h3><span class="method get">GET</span>/api/reports</h3>
+                <p>列出所有轮询报告</p>
+            </div>
+            <div class="endpoint">
+                <h3><span class="method get">GET</span>/api/report/{timestamp}</h3>
+                <p>获取指定时间戳的轮询报告</p>
+            </div>
+            <div class="endpoint">
+                <h3><span class="method delete">DELETE</span>/api/video</h3>
+                <p>删除指定视频文件 (参数: path)</p>
+            </div>
+            <div class="endpoint">
+                <h3><span class="method delete">DELETE</span>/api/timestamp/{timestamp}</h3>
+                <p>删除整个时间戳目录</p>
+            </div>
+        </div>)" + 
+        config_section + 
+        task_section + R"(
+        
+        <div class="section">
+            <h2>🔍 状态码说明</h2>
+            <div class="status-codes">
+                <h4>HTTP 状态码:</h4>
+                <p>• <strong>200 OK</strong>: 请求成功</p>
+                <p>• <strong>400 Bad Request</strong>: 请求参数错误</p>
+                <p>• <strong>404 Not Found</strong>: 资源不存在</p>
+                <p>• <strong>500 Internal Server Error</strong>: 服务器内部错误</p>
+            </div>
+        </div>
+        
+        <div class="section">
+            <h2>🚀 快速开始</h2>
+            <div class="endpoint">
+                <h3>示例: 获取所有视频</h3>
+                <p><code>curl http://localhost:8080/api/videos</code></p>
+            </div>)" + example_config + example_task + R"(
+        </div>
+        
+        <div class="section">
+            <h2>🔗 相关链接</h2>
+            <div class="nav-links">
+                <a href="/">🏠 系统主页</a>
+                <a href="/manager">🖥️ Web管理界面</a>
+                <a href="https://github.com/your-repo">📚 文档仓库</a>
+            </div>
         </div>
     </div>
 </body>
-</html>
-    )";
+</html>)";
     
     HttpResponse response;
     response.status_code = 200;
@@ -991,6 +1261,822 @@ HttpResponse HttpServer::generate_api_doc() {
     response.content_type = "text/html";
     response.body = html;
     return response;
+
+}
+
+// ===================== 配置管理API实现 =====================
+
+HttpResponse HttpServer::handle_get_config(const HttpRequest& request) {
+    try {
+        if (!config_manager_) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Service Unavailable";
+            error_obj["message"] = "配置管理器未初始化";
+            return HttpResponse(503, "Service Unavailable", boost::json::serialize(error_obj));
+        }
+        
+        std::string config_json = config_manager_->to_json_string();
+        return HttpResponse(200, "OK", config_json);
+        
+    } catch (const std::exception& e) {
+        boost::json::object error_obj;
+        error_obj["error"] = "Internal Server Error";
+        error_obj["message"] = e.what();
+        return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
+    }
+}
+
+HttpResponse HttpServer::handle_update_config(const HttpRequest& request) {
+    try {
+        if (!config_manager_) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Service Unavailable";
+            error_obj["message"] = "配置管理器未初始化";
+            return HttpResponse(503, "Service Unavailable", boost::json::serialize(error_obj));
+        }
+        
+        if (request.body.empty()) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Bad Request";
+            error_obj["message"] = "请求体不能为空";
+            return HttpResponse(400, "Bad Request", boost::json::serialize(error_obj));
+        }
+        
+        // 尝试解析新配置
+        if (!config_manager_->load_from_json(request.body)) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Bad Request";
+            error_obj["message"] = "配置格式错误或验证失败";
+            return HttpResponse(400, "Bad Request", boost::json::serialize(error_obj));
+        }
+        
+        boost::json::object result;
+        result["success"] = true;
+        result["message"] = "配置更新成功";
+        return HttpResponse(200, "OK", boost::json::serialize(result));
+        
+    } catch (const std::exception& e) {
+        boost::json::object error_obj;
+        error_obj["error"] = "Internal Server Error";
+        error_obj["message"] = e.what();
+        return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
+    }
+}
+
+HttpResponse HttpServer::handle_get_global_config(const HttpRequest& request) {
+    try {
+        if (!config_manager_) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Service Unavailable";
+            error_obj["message"] = "配置管理器未初始化";
+            return HttpResponse(503, "Service Unavailable", boost::json::serialize(error_obj));
+        }
+        
+        const auto& global_config = config_manager_->get_global_config();
+        
+        boost::json::object config_obj;
+        config_obj["total_poll_duration_seconds"] = global_config.total_poll_duration_seconds;
+        config_obj["max_concurrent_streams"] = global_config.max_concurrent_streams;
+        config_obj["output_directory"] = global_config.output_directory;
+        config_obj["report_filename"] = global_config.report_filename;
+        config_obj["connection_timeout_seconds"] = global_config.connection_timeout_seconds;
+        config_obj["read_timeout_seconds"] = global_config.read_timeout_seconds;
+        config_obj["poll_cycles"] = global_config.poll_cycles;
+        config_obj["cycle_interval_seconds"] = global_config.cycle_interval_seconds;
+        
+        return HttpResponse(200, "OK", boost::json::serialize(config_obj));
+        
+    } catch (const std::exception& e) {
+        boost::json::object error_obj;
+        error_obj["error"] = "Internal Server Error";
+        error_obj["message"] = e.what();
+        return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
+    }
+}
+
+HttpResponse HttpServer::handle_update_global_config(const HttpRequest& request) {
+    try {
+        if (!config_manager_) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Service Unavailable";
+            error_obj["message"] = "配置管理器未初始化";
+            return HttpResponse(503, "Service Unavailable", boost::json::serialize(error_obj));
+        }
+        
+        if (request.body.empty()) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Bad Request";
+            error_obj["message"] = "请求体不能为空";
+            return HttpResponse(400, "Bad Request", boost::json::serialize(error_obj));
+        }
+        
+        // 解析JSON
+        auto json_value = boost::json::parse(request.body);
+        auto json_obj = json_value.as_object();
+        
+        // 获取当前全局配置
+        GlobalConfig global_config = config_manager_->get_global_config();
+        
+        // 更新各个字段(如果存在)
+        if (json_obj.contains("total_poll_duration_seconds")) {
+            global_config.total_poll_duration_seconds = static_cast<int>(json_obj.at("total_poll_duration_seconds").as_int64());
+        }
+        if (json_obj.contains("max_concurrent_streams")) {
+            global_config.max_concurrent_streams = static_cast<int>(json_obj.at("max_concurrent_streams").as_int64());
+        }
+        if (json_obj.contains("output_directory")) {
+            global_config.output_directory = json_obj.at("output_directory").as_string().c_str();
+        }
+        if (json_obj.contains("report_filename")) {
+            global_config.report_filename = json_obj.at("report_filename").as_string().c_str();
+        }
+        if (json_obj.contains("connection_timeout_seconds")) {
+            global_config.connection_timeout_seconds = static_cast<int>(json_obj.at("connection_timeout_seconds").as_int64());
+        }
+        if (json_obj.contains("read_timeout_seconds")) {
+            global_config.read_timeout_seconds = static_cast<int>(json_obj.at("read_timeout_seconds").as_int64());
+        }
+        if (json_obj.contains("poll_cycles")) {
+            global_config.poll_cycles = static_cast<int>(json_obj.at("poll_cycles").as_int64());
+        }
+        if (json_obj.contains("cycle_interval_seconds")) {
+            global_config.cycle_interval_seconds = static_cast<int>(json_obj.at("cycle_interval_seconds").as_int64());
+        }
+        
+        // 设置新配置
+        config_manager_->set_global_config(global_config);
+        
+        // 验证配置
+        if (!config_manager_->validate()) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Bad Request";
+            error_obj["message"] = "全局配置验证失败";
+            return HttpResponse(400, "Bad Request", boost::json::serialize(error_obj));
+        }
+        
+        boost::json::object result;
+        result["success"] = true;
+        result["message"] = "全局配置更新成功";
+        return HttpResponse(200, "OK", boost::json::serialize(result));
+        
+    } catch (const std::exception& e) {
+        boost::json::object error_obj;
+        error_obj["error"] = "Internal Server Error";
+        error_obj["message"] = e.what();
+        return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
+    }
+}
+
+HttpResponse HttpServer::handle_get_stream_configs(const HttpRequest& request) {
+    try {
+        if (!config_manager_) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Service Unavailable";
+            error_obj["message"] = "配置管理器未初始化";
+            return HttpResponse(503, "Service Unavailable", boost::json::serialize(error_obj));
+        }
+        
+        const auto& stream_configs = config_manager_->get_stream_configs();
+        
+        boost::json::array streams_array;
+        for (size_t i = 0; i < stream_configs.size(); ++i) {
+            const auto& stream = stream_configs[i];
+            boost::json::object stream_obj;
+            stream_obj["index"] = i;
+            stream_obj["rtsp_url"] = stream.rtsp_url;
+            stream_obj["duration_seconds"] = stream.duration_seconds;
+            stream_obj["weight"] = stream.weight;
+            stream_obj["output_filename"] = stream.output_filename;
+            streams_array.push_back(stream_obj);
+        }
+        
+        boost::json::object result;
+        result["count"] = stream_configs.size();
+        result["streams"] = streams_array;
+        
+        return HttpResponse(200, "OK", boost::json::serialize(result));
+        
+    } catch (const std::exception& e) {
+        boost::json::object error_obj;
+        error_obj["error"] = "Internal Server Error";
+        error_obj["message"] = e.what();
+        return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
+    }
+}
+
+HttpResponse HttpServer::handle_add_stream_config(const HttpRequest& request) {
+    try {
+        if (!config_manager_) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Service Unavailable";
+            error_obj["message"] = "配置管理器未初始化";
+            return HttpResponse(503, "Service Unavailable", boost::json::serialize(error_obj));
+        }
+        
+        if (request.body.empty()) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Bad Request";
+            error_obj["message"] = "请求体不能为空";
+            return HttpResponse(400, "Bad Request", boost::json::serialize(error_obj));
+        }
+        
+        // 解析JSON
+        auto json_value = boost::json::parse(request.body);
+        auto json_obj = json_value.as_object();
+        
+        // 创建新的流配置
+        StreamConfig new_stream;
+        
+        if (json_obj.contains("rtsp_url")) {
+            new_stream.rtsp_url = json_obj.at("rtsp_url").as_string().c_str();
+        }
+        if (json_obj.contains("duration_seconds")) {
+            new_stream.duration_seconds = static_cast<int>(json_obj.at("duration_seconds").as_int64());
+        }
+        if (json_obj.contains("weight")) {
+            new_stream.weight = json_obj.at("weight").as_double();
+        }
+        if (json_obj.contains("output_filename")) {
+            new_stream.output_filename = json_obj.at("output_filename").as_string().c_str();
+        }
+        
+        // 验证新流配置
+        if (new_stream.rtsp_url.empty() || new_stream.duration_seconds <= 0 || 
+            new_stream.weight <= 0 || new_stream.output_filename.empty()) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Bad Request";
+            error_obj["message"] = "流配置参数不完整或不合法";
+            return HttpResponse(400, "Bad Request", boost::json::serialize(error_obj));
+        }
+        
+        // 添加新流配置
+        config_manager_->add_stream_config(new_stream);
+        
+        // 验证整体配置
+        if (!config_manager_->validate()) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Bad Request";
+            error_obj["message"] = "流配置验证失败";
+            return HttpResponse(400, "Bad Request", boost::json::serialize(error_obj));
+        }
+        
+        boost::json::object result;
+        result["success"] = true;
+        result["message"] = "流配置添加成功";
+        result["index"] = config_manager_->get_stream_count() - 1;
+        
+        return HttpResponse(201, "Created", boost::json::serialize(result));
+        
+    } catch (const std::exception& e) {
+        boost::json::object error_obj;
+        error_obj["error"] = "Internal Server Error";
+        error_obj["message"] = e.what();
+        return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
+    }
+}
+
+HttpResponse HttpServer::handle_update_stream_config(const HttpRequest& request) {
+    try {
+        if (!config_manager_) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Service Unavailable";
+            error_obj["message"] = "配置管理器未初始化";
+            return HttpResponse(503, "Service Unavailable", boost::json::serialize(error_obj));
+        }
+        
+        // 从路径中提取索引 /api/config/streams/{index}
+        std::string path = request.path;
+        size_t pos = path.find_last_of('/');
+        if (pos == std::string::npos) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Bad Request";
+            error_obj["message"] = "无效的请求路径";
+            return HttpResponse(400, "Bad Request", boost::json::serialize(error_obj));
+        }
+        
+        std::string index_str = path.substr(pos + 1);
+        size_t index;
+        try {
+            index = std::stoull(index_str);
+        } catch (...) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Bad Request";
+            error_obj["message"] = "无效的流索引";
+            return HttpResponse(400, "Bad Request", boost::json::serialize(error_obj));
+        }
+        
+        if (request.body.empty()) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Bad Request";
+            error_obj["message"] = "请求体不能为空";
+            return HttpResponse(400, "Bad Request", boost::json::serialize(error_obj));
+        }
+        
+        // 解析JSON
+        auto json_value = boost::json::parse(request.body);
+        auto json_obj = json_value.as_object();
+        
+        // 获取当前流配置
+        const auto& stream_configs = config_manager_->get_stream_configs();
+        if (index >= stream_configs.size()) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Not Found";
+            error_obj["message"] = "流配置索引不存在";
+            return HttpResponse(404, "Not Found", boost::json::serialize(error_obj));
+        }
+        
+        StreamConfig updated_stream = stream_configs[index];
+        
+        // 更新各个字段(如果存在)
+        if (json_obj.contains("rtsp_url")) {
+            updated_stream.rtsp_url = json_obj.at("rtsp_url").as_string().c_str();
+        }
+        if (json_obj.contains("duration_seconds")) {
+            updated_stream.duration_seconds = static_cast<int>(json_obj.at("duration_seconds").as_int64());
+        }
+        if (json_obj.contains("weight")) {
+            updated_stream.weight = json_obj.at("weight").as_double();
+        }
+        if (json_obj.contains("output_filename")) {
+            updated_stream.output_filename = json_obj.at("output_filename").as_string().c_str();
+        }
+        
+        // 更新流配置
+        if (!config_manager_->update_stream_config(index, updated_stream)) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Bad Request";
+            error_obj["message"] = "流配置更新失败";
+            return HttpResponse(400, "Bad Request", boost::json::serialize(error_obj));
+        }
+        
+        boost::json::object result;
+        result["success"] = true;
+        result["message"] = "流配置更新成功";
+        result["index"] = index;
+        
+        return HttpResponse(200, "OK", boost::json::serialize(result));
+        
+    } catch (const std::exception& e) {
+        boost::json::object error_obj;
+        error_obj["error"] = "Internal Server Error";
+        error_obj["message"] = e.what();
+        return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
+    }
+}
+
+HttpResponse HttpServer::handle_delete_stream_config(const HttpRequest& request) {
+    try {
+        if (!config_manager_) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Service Unavailable";
+            error_obj["message"] = "配置管理器未初始化";
+            return HttpResponse(503, "Service Unavailable", boost::json::serialize(error_obj));
+        }
+        
+        // 从路径中提取索引 /api/config/streams/{index}
+        std::string path = request.path;
+        size_t pos = path.find_last_of('/');
+        if (pos == std::string::npos) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Bad Request";
+            error_obj["message"] = "无效的请求路径";
+            return HttpResponse(400, "Bad Request", boost::json::serialize(error_obj));
+        }
+        
+        std::string index_str = path.substr(pos + 1);
+        size_t index;
+        try {
+            index = std::stoull(index_str);
+        } catch (...) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Bad Request";
+            error_obj["message"] = "无效的流索引";
+            return HttpResponse(400, "Bad Request", boost::json::serialize(error_obj));
+        }
+        
+        // 删除流配置
+        if (!config_manager_->remove_stream_config(index)) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Not Found";
+            error_obj["message"] = "流配置索引不存在";
+            return HttpResponse(404, "Not Found", boost::json::serialize(error_obj));
+        }
+        
+        // 验证整体配置
+        if (!config_manager_->validate()) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Bad Request";
+            error_obj["message"] = "删除后配置验证失败";
+            return HttpResponse(400, "Bad Request", boost::json::serialize(error_obj));
+        }
+        
+        boost::json::object result;
+        result["success"] = true;
+        result["message"] = "流配置删除成功";
+        result["deleted_index"] = index;
+        
+        return HttpResponse(200, "OK", boost::json::serialize(result));
+        
+    } catch (const std::exception& e) {
+        boost::json::object error_obj;
+        error_obj["error"] = "Internal Server Error";
+        error_obj["message"] = e.what();
+        return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
+    }
+}
+
+HttpResponse HttpServer::handle_save_config(const HttpRequest& request) {
+    try {
+        if (!config_manager_) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Service Unavailable";
+            error_obj["message"] = "配置管理器未初始化";
+            return HttpResponse(503, "Service Unavailable", boost::json::serialize(error_obj));
+        }
+        
+        std::string config_file = config_manager_->get_config_file_path();
+        if (config_file.empty()) {
+            // 如果没有指定文件路径,使用默认路径
+            config_file = "config.json";
+        }
+        
+        // 从请求体中获取文件路径(可选)
+        if (!request.body.empty()) {
+            try {
+                auto json_value = boost::json::parse(request.body);
+                auto json_obj = json_value.as_object();
+                if (json_obj.contains("file_path")) {
+                    config_file = json_obj.at("file_path").as_string().c_str();
+                }
+            } catch (...) {
+                // 如果JSON解析失败,使用默认路径
+            }
+        }
+        
+        // 保存配置
+        if (!config_manager_->save_to_file(config_file)) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Internal Server Error";
+            error_obj["message"] = "保存配置文件失败";
+            return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
+        }
+        
+        boost::json::object result;
+        result["success"] = true;
+        result["message"] = "配置保存成功";
+        result["file_path"] = config_file;
+        
+        return HttpResponse(200, "OK", boost::json::serialize(result));
+        
+    } catch (const std::exception& e) {
+        boost::json::object error_obj;
+        error_obj["error"] = "Internal Server Error";
+        error_obj["message"] = e.what();
+        return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
+    }
+}
+
+HttpResponse HttpServer::handle_reload_config(const HttpRequest& request) {
+    try {
+        if (!config_manager_) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Service Unavailable";
+            error_obj["message"] = "配置管理器未初始化";
+            return HttpResponse(503, "Service Unavailable", boost::json::serialize(error_obj));
+        }
+        
+        // 重新加载配置
+        if (!config_manager_->reload_from_file()) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Internal Server Error";
+            error_obj["message"] = "重新加载配置失败";
+            return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
+        }
+        
+        boost::json::object result;
+        result["success"] = true;
+        result["message"] = "配置重新加载成功";
+        result["file_path"] = config_manager_->get_config_file_path();
+        
+        return HttpResponse(200, "OK", boost::json::serialize(result));
+        
+    } catch (const std::exception& e) {
+        boost::json::object error_obj;
+        error_obj["error"] = "Internal Server Error";
+        error_obj["message"] = e.what();
+        return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
+    }
+}
+
+// ===================== 轮询任务管理API实现 =====================
+
+HttpResponse HttpServer::handle_get_task_list(const HttpRequest& request) {
+    try {
+        if (!scheduler_) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Service Unavailable";
+            error_obj["message"] = "调度器未初始化";
+            return HttpResponse(503, "Service Unavailable", boost::json::serialize(error_obj));
+        }
+        
+        // 获取调度方案信息
+        std::string schedule_info = scheduler_->get_schedule_info();
+        
+        // 获取所有客户端统计信息
+        auto client_stats = scheduler_->get_all_stats();
+        
+        boost::json::array tasks_array;
+        for (const auto& stats : client_stats) {
+            boost::json::object task_obj;
+            task_obj["task_id"] = stats.stream_index;
+            task_obj["stream_index"] = stats.stream_index;
+            task_obj["rtsp_url"] = stats.rtsp_url;
+            task_obj["output_file"] = stats.output_file;
+            task_obj["duration_seconds"] = stats.duration_seconds;
+            task_obj["status"] = static_cast<int>(stats.status);
+            task_obj["status_text"] = [&stats]() {
+                switch (stats.status) {
+                    case RTSPClientStatus::IDLE: return "等待中";
+                    case RTSPClientStatus::CONNECTING: return "连接中";
+                    case RTSPClientStatus::CONNECTED: return "已连接";
+                    case RTSPClientStatus::RECORDING: return "录制中";
+                    case RTSPClientStatus::FINISHED: return "已完成";
+                    case RTSPClientStatus::ERROR_CONNECT: return "连接错误";
+                    case RTSPClientStatus::ERROR_RECORD: return "录制错误";
+                    case RTSPClientStatus::TIMEOUT: return "超时";
+                    case RTSPClientStatus::CANCELLED: return "已取消";
+                    case RTSPClientStatus::STREAM_UNAVAILABLE: return "流不可用";
+                    default: return "未知状态";
+                }
+            }();
+            // 转换时间点为字符串
+            auto start_time_t = std::chrono::system_clock::to_time_t(stats.start_time);
+            auto end_time_t = std::chrono::system_clock::to_time_t(stats.end_time);
+            
+            std::ostringstream start_ss, end_ss;
+            start_ss << std::put_time(std::localtime(&start_time_t), "%Y-%m-%d %H:%M:%S");
+            end_ss << std::put_time(std::localtime(&end_time_t), "%Y-%m-%d %H:%M:%S");
+            
+            task_obj["start_time"] = start_ss.str();
+            task_obj["end_time"] = end_ss.str();
+            task_obj["error_message"] = stats.error_message;
+            task_obj["bytes_received"] = stats.bytes_received;
+            task_obj["frames_received"] = stats.frames_received;
+            
+            tasks_array.push_back(task_obj);
+        }
+        
+        boost::json::object result;
+        result["count"] = tasks_array.size();
+        result["tasks"] = tasks_array;
+        result["schedule_info"] = schedule_info;
+        
+        return HttpResponse(200, "OK", boost::json::serialize(result));
+        
+    } catch (const std::exception& e) {
+        boost::json::object error_obj;
+        error_obj["error"] = "Internal Server Error";
+        error_obj["message"] = e.what();
+        return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
+    }
+}
+
+HttpResponse HttpServer::handle_get_task_status(const HttpRequest& request) {
+    try {
+        if (!scheduler_) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Service Unavailable";
+            error_obj["message"] = "调度器未初始化";
+            return HttpResponse(503, "Service Unavailable", boost::json::serialize(error_obj));
+        }
+        
+        // 获取实时任务状态
+        auto client_stats = scheduler_->get_all_stats();
+        auto scheduler_stats = scheduler_->get_scheduler_stats();
+        
+        boost::json::array tasks_array;
+        for (const auto& stats : client_stats) {
+            boost::json::object task_obj;
+            task_obj["task_id"] = stats.stream_index;
+            task_obj["stream_index"] = stats.stream_index;
+            task_obj["status"] = static_cast<int>(stats.status);
+            task_obj["status_text"] = [&stats]() {
+                switch (stats.status) {
+                    case RTSPClientStatus::IDLE: return "等待中";
+                    case RTSPClientStatus::CONNECTING: return "连接中";
+                    case RTSPClientStatus::CONNECTED: return "已连接";
+                    case RTSPClientStatus::RECORDING: return "录制中";
+                    case RTSPClientStatus::FINISHED: return "已完成";
+                    case RTSPClientStatus::ERROR_CONNECT: return "连接错误";
+                    case RTSPClientStatus::ERROR_RECORD: return "录制错误";
+                    case RTSPClientStatus::TIMEOUT: return "超时";
+                    case RTSPClientStatus::CANCELLED: return "已取消";
+                    case RTSPClientStatus::STREAM_UNAVAILABLE: return "流不可用";
+                    default: return "未知状态";
+                }
+            }();
+            task_obj["progress"] = stats.duration_seconds;
+            task_obj["bytes_received"] = stats.bytes_received;
+            task_obj["frames_received"] = stats.frames_received;
+            task_obj["error_message"] = stats.error_message;
+            
+            tasks_array.push_back(task_obj);
+        }
+        
+        boost::json::object result;
+        result["scheduler_running"] = scheduler_->is_running();
+        result["total_tasks"] = scheduler_stats.total_tasks;
+        result["completed_tasks"] = scheduler_stats.completed_tasks;
+        result["failed_tasks"] = scheduler_stats.failed_tasks;
+        result["cancelled_tasks"] = scheduler_stats.cancelled_tasks;
+        result["max_concurrent_used"] = scheduler_stats.max_concurrent_used;
+        result["completion_rate"] = scheduler_stats.completion_rate;
+        result["tasks"] = tasks_array;
+        
+        return HttpResponse(200, "OK", boost::json::serialize(result));
+        
+    } catch (const std::exception& e) {
+        boost::json::object error_obj;
+        error_obj["error"] = "Internal Server Error";
+        error_obj["message"] = e.what();
+        return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
+    }
+}
+
+HttpResponse HttpServer::handle_get_scheduler_status(const HttpRequest& request) {
+    try {
+        if (!scheduler_) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Service Unavailable";
+            error_obj["message"] = "调度器未初始化";
+            return HttpResponse(503, "Service Unavailable", boost::json::serialize(error_obj));
+        }
+        
+        auto scheduler_stats = scheduler_->get_scheduler_stats();
+        
+        boost::json::object result;
+        result["is_running"] = scheduler_->is_running();
+        result["total_tasks"] = scheduler_stats.total_tasks;
+        result["completed_tasks"] = scheduler_stats.completed_tasks;
+        result["failed_tasks"] = scheduler_stats.failed_tasks;
+        result["cancelled_tasks"] = scheduler_stats.cancelled_tasks;
+        result["max_concurrent_used"] = scheduler_stats.max_concurrent_used;
+        result["completion_rate"] = scheduler_stats.completion_rate;
+        
+        // 时间信息
+        auto start_time = std::chrono::system_clock::to_time_t(scheduler_stats.start_time);
+        auto end_time = std::chrono::system_clock::to_time_t(scheduler_stats.end_time);
+        
+        std::ostringstream start_ss, end_ss;
+        start_ss << std::put_time(std::localtime(&start_time), "%Y-%m-%d %H:%M:%S");
+        
+        result["start_time"] = start_ss.str();
+        if (scheduler_stats.end_time != std::chrono::system_clock::time_point{}) {
+            end_ss << std::put_time(std::localtime(&end_time), "%Y-%m-%d %H:%M:%S");
+            result["end_time"] = end_ss.str();
+        } else {
+            result["end_time"] = "";
+        }
+        
+        return HttpResponse(200, "OK", boost::json::serialize(result));
+        
+    } catch (const std::exception& e) {
+        boost::json::object error_obj;
+        error_obj["error"] = "Internal Server Error";
+        error_obj["message"] = e.what();
+        return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
+    }
+}
+
+HttpResponse HttpServer::handle_start_scheduler(const HttpRequest& request) {
+    try {
+        if (!scheduler_) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Service Unavailable";
+            error_obj["message"] = "调度器未初始化";
+            return HttpResponse(503, "Service Unavailable", boost::json::serialize(error_obj));
+        }
+        
+        if (scheduler_->is_running()) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Conflict";
+            error_obj["message"] = "调度器已经在运行中";
+            return HttpResponse(409, "Conflict", boost::json::serialize(error_obj));
+        }
+        
+        bool success = scheduler_->start_execution();
+        
+        boost::json::object result;
+        result["success"] = success;
+        result["message"] = success ? "调度器启动成功" : "调度器启动失败";
+        result["is_running"] = scheduler_->is_running();
+        
+        return HttpResponse(success ? 200 : 500, success ? "OK" : "Internal Server Error", boost::json::serialize(result));
+        
+    } catch (const std::exception& e) {
+        boost::json::object error_obj;
+        error_obj["error"] = "Internal Server Error";
+        error_obj["message"] = e.what();
+        return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
+    }
+}
+
+HttpResponse HttpServer::handle_stop_scheduler(const HttpRequest& request) {
+    try {
+        if (!scheduler_) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Service Unavailable";
+            error_obj["message"] = "调度器未初始化";
+            return HttpResponse(503, "Service Unavailable", boost::json::serialize(error_obj));
+        }
+        
+        if (!scheduler_->is_running()) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Conflict";
+            error_obj["message"] = "调度器未在运行";
+            return HttpResponse(409, "Conflict", boost::json::serialize(error_obj));
+        }
+        
+        scheduler_->stop_execution();
+        
+        boost::json::object result;
+        result["success"] = true;
+        result["message"] = "调度器停止成功";
+        result["is_running"] = scheduler_->is_running();
+        
+        return HttpResponse(200, "OK", boost::json::serialize(result));
+        
+    } catch (const std::exception& e) {
+        boost::json::object error_obj;
+        error_obj["error"] = "Internal Server Error";
+        error_obj["message"] = e.what();
+        return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
+    }
+}
+
+HttpResponse HttpServer::handle_get_current_cycle_status(const HttpRequest& request) {
+    try {
+        if (!scheduler_) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Service Unavailable";
+            error_obj["message"] = "调度器未初始化";
+            return HttpResponse(503, "Service Unavailable", boost::json::serialize(error_obj));
+        }
+        
+        auto scheduler_stats = scheduler_->get_scheduler_stats();
+        auto client_stats = scheduler_->get_all_stats();
+        
+        // 计算当前周期的进度
+        auto now = std::chrono::system_clock::now();
+        auto elapsed_seconds = std::chrono::duration_cast<std::chrono::seconds>(
+            now - scheduler_stats.start_time).count();
+        
+        // 获取配置信息
+        int total_duration = 60; // 默认值
+        if (config_manager_) {
+            total_duration = config_manager_->get_global_config().total_poll_duration_seconds;
+        }
+        
+        boost::json::object result;
+        result["is_running"] = scheduler_->is_running();
+        result["current_cycle"] = 1; // 这里可以根据实际情况获取当前周期数
+        result["cycle_progress"] = {
+            {"elapsed_seconds", elapsed_seconds},
+            {"total_seconds", total_duration},
+            {"progress_percentage", std::min(100.0, (elapsed_seconds * 100.0) / total_duration)}
+        };
+        result["scheduler_stats"] = {
+            {"total_tasks", scheduler_stats.total_tasks},
+            {"completed_tasks", scheduler_stats.completed_tasks},
+            {"failed_tasks", scheduler_stats.failed_tasks},
+            {"cancelled_tasks", scheduler_stats.cancelled_tasks},
+            {"max_concurrent_used", scheduler_stats.max_concurrent_used},
+            {"completion_rate", scheduler_stats.completion_rate}
+        };
+        
+        boost::json::array active_tasks;
+        for (const auto& stats : client_stats) {
+            if (stats.status == RTSPClientStatus::RECORDING || 
+                stats.status == RTSPClientStatus::CONNECTING ||
+                stats.status == RTSPClientStatus::CONNECTED) {
+                boost::json::object task_obj;
+                task_obj["stream_index"] = stats.stream_index;
+                task_obj["status"] = static_cast<int>(stats.status);
+                task_obj["progress_seconds"] = stats.duration_seconds;
+                task_obj["target_duration"] = stats.duration_seconds;
+                active_tasks.push_back(task_obj);
+            }
+        }
+        result["active_tasks"] = active_tasks;
+        
+        return HttpResponse(200, "OK", boost::json::serialize(result));
+        
+    } catch (const std::exception& e) {
+        boost::json::object error_obj;
+        error_obj["error"] = "Internal Server Error";
+        error_obj["message"] = e.what();
+        return HttpResponse(500, "Internal Server Error", boost::json::serialize(error_obj));
+    }
 }
 
 } // namespace jtjai_media

+ 21 - 0
src/rtsp_client.cpp

@@ -437,6 +437,10 @@ bool RTSPClient::process_stream(int duration_seconds) {
     }
     
     av_packet_free(&packet);
+        
+    // 在录制完成后更新实际文件大小统计
+    update_final_file_stats();
+        
     return true;
 }
 
@@ -468,6 +472,23 @@ void RTSPClient::update_status(RTSPClientStatus status, const std::string& error
     }
 }
 
+void RTSPClient::update_final_file_stats() {
+    // 更新实际文件大小统计
+    try {
+        if (std::filesystem::exists(full_output_path_)) {
+            auto file_size = std::filesystem::file_size(full_output_path_);
+            std::lock_guard<std::mutex> lock(stats_mutex_);
+            // 如果实际文件大小大于已统计的bytes_received,使用实际文件大小
+            if (file_size > static_cast<size_t>(stats_.bytes_received)) {
+                stats_.bytes_received = file_size;
+            }
+            std::cout << "文件 " << full_output_path_ << " 最终大小: " << file_size << " 字节" << std::endl;
+        }
+    } catch (const std::exception& e) {
+        std::cerr << "获取文件大小失败: " << e.what() << std::endl;
+    }
+}
+
 void RTSPClient::ffmpeg_log_callback(void* ptr, int level, const char* fmt, va_list vl) {
     if (level > AV_LOG_WARNING) {
         return;

+ 44 - 0
test_api_doc_update.sh

@@ -0,0 +1,44 @@
+#!/bin/bash
+
+echo "测试网页API文档更新"
+
+# 启动HTTP服务器(仅基础功能)
+cd /Users/wenhongquan/CLionProjects/jtjai_media/build
+./jtjai_http_server ../output 8082 > /dev/null 2>&1 &
+SERVER_PID=$!
+
+# 等待服务器启动
+sleep 3
+
+echo "测试基础HTTP服务器的API文档页面..."
+RESPONSE=$(curl -s "http://localhost:8082/api")
+
+if echo "$RESPONSE" | grep -q "🎥 视频管理API"; then
+    echo "✅ 基础视频管理API文档正常显示"
+else
+    echo "❌ 基础视频管理API文档缺失"
+fi
+
+if echo "$RESPONSE" | grep -q "配置管理API"; then
+    echo "⚠️  基础服务器不应该显示配置管理API(正常)"
+else
+    echo "✅ 基础服务器正确隐藏配置管理API"
+fi
+
+if echo "$RESPONSE" | grep -q "轮询任务管理API"; then
+    echo "⚠️  基础服务器不应该显示轮询任务管理API(正常)"
+else
+    echo "✅ 基础服务器正确隐藏轮询任务管理API"
+fi
+
+echo "API文档页面包含以下主要章节:"
+echo "$RESPONSE" | grep -E "<h2>" | sed 's/<[^>]*>//g' | sed 's/^[[:space:]]*/• /'
+
+# 停止服务器
+kill $SERVER_PID 2>/dev/null
+
+echo ""
+echo "网页API文档更新测试完成!"
+echo "✅ 基础功能:只显示视频管理API"
+echo "✅ 动态功能:根据服务器配置显示相应API章节"
+echo "✅ 完整功能:主程序会显示所有API章节(配置管理+轮询任务+视频管理)"

+ 89 - 0
test_config_api.sh

@@ -0,0 +1,89 @@
+#!/bin/bash
+
+# 配置管理API测试脚本
+API_BASE="http://localhost:8080/api"
+
+echo "🔧 配置管理API测试开始"
+echo "========================"
+
+# 测试获取完整配置
+echo "1. 获取完整配置:"
+curl -s "$API_BASE/config" | jq '.' || echo "JSON解析失败"
+echo -e "\n"
+
+# 测试获取全局配置
+echo "2. 获取全局配置:"
+curl -s "$API_BASE/config/global" | jq '.' || echo "JSON解析失败"
+echo -e "\n"
+
+# 测试获取流配置列表
+echo "3. 获取流配置列表:"
+curl -s "$API_BASE/config/streams" | jq '.' || echo "JSON解析失败"
+echo -e "\n"
+
+# 测试更新全局配置
+echo "4. 更新全局配置(增加并发流数):"
+curl -s -X PUT -H "Content-Type: application/json" \
+  -d '{"max_concurrent_streams": 3, "poll_cycles": 5}' \
+  "$API_BASE/config/global" | jq '.' || echo "JSON解析失败"
+echo -e "\n"
+
+# 验证更新结果
+echo "5. 验证全局配置更新结果:"
+curl -s "$API_BASE/config/global" | jq '.' || echo "JSON解析失败"
+echo -e "\n"
+
+# 测试添加新的流配置
+echo "6. 添加新的流配置:"
+curl -s -X POST -H "Content-Type: application/json" \
+  -d '{
+    "rtsp_url": "rtsp://test.example.com/stream",
+    "duration_seconds": 25,
+    "weight": 1.8,
+    "output_filename": "test_new_stream.mp4"
+  }' \
+  "$API_BASE/config/streams" | jq '.' || echo "JSON解析失败"
+echo -e "\n"
+
+# 验证流配置添加结果
+echo "7. 验证流配置添加结果:"
+curl -s "$API_BASE/config/streams" | jq '.' || echo "JSON解析失败"
+echo -e "\n"
+
+# 测试更新第一个流配置
+echo "8. 更新第一个流配置:"
+curl -s -X PUT -H "Content-Type: application/json" \
+  -d '{"duration_seconds": 30, "weight": 2.0}' \
+  "$API_BASE/config/streams/0" | jq '.' || echo "JSON解析失败"
+echo -e "\n"
+
+# 测试保存配置
+echo "9. 保存配置到文件:"
+curl -s -X POST -H "Content-Type: application/json" \
+  -d '{"file_path": "config_backup.json"}' \
+  "$API_BASE/config/save" | jq '.' || echo "JSON解析失败"
+echo -e "\n"
+
+# 测试重新加载配置
+echo "10. 重新加载配置:"
+curl -s -X POST "$API_BASE/config/reload" | jq '.' || echo "JSON解析失败"
+echo -e "\n"
+
+# 测试删除最后一个流配置(如果有多个)
+STREAM_COUNT=$(curl -s "$API_BASE/config/streams" | jq '.count' 2>/dev/null)
+if [ "$STREAM_COUNT" -gt 1 ] 2>/dev/null; then
+    LAST_INDEX=$((STREAM_COUNT - 1))
+    echo "11. 删除最后一个流配置(索引 $LAST_INDEX):"
+    curl -s -X DELETE "$API_BASE/config/streams/$LAST_INDEX" | jq '.' || echo "JSON解析失败"
+    echo -e "\n"
+    
+    echo "12. 验证删除结果:"
+    curl -s "$API_BASE/config/streams" | jq '.' || echo "JSON解析失败"
+    echo -e "\n"
+else
+    echo "11. 跳过删除测试(只有一个流配置,不能删除)"
+    echo -e "\n"
+fi
+
+echo "========================"
+echo "🎉 配置管理API测试完成"

+ 51 - 0
test_file_size_fix.cpp

@@ -0,0 +1,51 @@
+#include <iostream>
+#include "include/rtsp_client.h"
+#include "include/config.h"
+
+using namespace jtjai_media;
+
+int main() {
+    std::cout << "测试文件大小统计修复" << std::endl;
+    
+    // 创建测试配置
+    StreamConfig test_config;
+    test_config.rtsp_url = "rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001";
+    test_config.duration_seconds = 5;  // 短时间测试
+    test_config.weight = 1.0;
+    test_config.output_filename = "test_file_size.mp4";
+    
+    // 创建输出目录
+    std::string output_dir = "./test_output";
+    std::filesystem::create_directories(output_dir);
+    
+    // 创建RTSP客户端
+    RTSPClient client(0, test_config, output_dir);
+    
+    std::cout << "开始录制测试..." << std::endl;
+    bool success = client.start_recording(5);
+    
+    // 获取统计信息
+    auto stats = client.get_stats();
+    
+    std::cout << "\n=== 录制结果 ===" << std::endl;
+    std::cout << "状态: " << static_cast<int>(stats.status) << std::endl;
+    std::cout << "输出文件: " << stats.output_file << std::endl;
+    std::cout << "接收字节数: " << stats.bytes_received << std::endl;
+    std::cout << "接收帧数: " << stats.frames_received << std::endl;
+    
+    // 验证文件是否存在并检查实际大小
+    if (std::filesystem::exists(stats.output_file)) {
+        auto actual_file_size = std::filesystem::file_size(stats.output_file);
+        std::cout << "实际文件大小: " << actual_file_size << " 字节" << std::endl;
+        
+        if (stats.bytes_received > 0) {
+            std::cout << "✅ 文件大小统计正常!" << std::endl;
+        } else {
+            std::cout << "❌ 文件大小统计仍为0" << std::endl;
+        }
+    } else {
+        std::cout << "⚠️ 文件未创建" << std::endl;
+    }
+    
+    return 0;
+}

+ 98 - 0
test_full_api_doc.sh

@@ -0,0 +1,98 @@
+#!/bin/bash
+
+echo "测试完整版本的API文档(配置管理+轮询任务管理)"
+
+# 创建测试配置
+cat > /tmp/test_config.json << 'EOF'
+{
+  "global_config": {
+    "total_poll_duration_seconds": 30,
+    "max_concurrent_streams": 1,
+    "output_directory": "./test_output",
+    "report_filename": "test_report.json",
+    "connection_timeout_seconds": 3,
+    "read_timeout_seconds": 5,
+    "poll_cycles": 1,
+    "cycle_interval_seconds": 10
+  },
+  "streams": [
+    {
+      "rtsp_url": "rtsp://test.example.com/stream1",
+      "duration_seconds": 10,
+      "weight": 1.0,
+      "output_filename": "test_stream.mp4"
+    }
+  ]
+}
+EOF
+
+echo "启动带配置管理和调度器的完整服务器..."
+cd /Users/wenhongquan/CLionProjects/jtjai_media/build
+
+# 启动完整主程序(会失败连接但HTTP服务器应该能启动)
+timeout 10 ./jtjai_media /tmp/test_config.json > /dev/null 2>&1 &
+MAIN_PID=$!
+
+# 等待服务器启动
+sleep 5
+
+echo "测试完整版本的API文档..."
+
+# 尝试8080端口,如果不行就用8083
+if curl -s "http://localhost:8080/api" > /dev/null 2>&1; then
+    PORT=8080
+else
+    # 启动备用服务器
+    timeout 10 ./jtjai_http_server ../output 8083 > /dev/null 2>&1 &
+    BACKUP_PID=$!
+    sleep 3
+    PORT=8083
+fi
+
+RESPONSE=$(curl -s "http://localhost:$PORT/api")
+
+echo "检查API文档内容..."
+
+if echo "$RESPONSE" | grep -q "🎥 视频管理API"; then
+    echo "✅ 视频管理API文档正常显示"
+else
+    echo "❌ 视频管理API文档缺失"
+fi
+
+if echo "$RESPONSE" | grep -q "⚙️ 配置管理API"; then
+    echo "✅ 配置管理API文档正常显示"
+else
+    echo "❌ 配置管理API文档缺失"
+fi
+
+if echo "$RESPONSE" | grep -q "🔄 轮询任务管理API"; then
+    echo "✅ 轮询任务管理API文档正常显示"
+else
+    echo "❌ 轮询任务管理API文档缺失"
+fi
+
+echo ""
+echo "API文档页面包含的所有章节:"
+echo "$RESPONSE" | grep -E "<h2>" | sed 's/<[^>]*>//g' | sed 's/^[[:space:]]*/• /'
+
+echo ""
+echo "检查具体API端点..."
+if echo "$RESPONSE" | grep -q "/api/config"; then
+    echo "✅ 配置管理端点文档存在"
+fi
+
+if echo "$RESPONSE" | grep -q "/api/tasks"; then
+    echo "✅ 轮询任务端点文档存在"
+fi
+
+if echo "$RESPONSE" | grep -q "状态码说明"; then
+    echo "✅ 状态码说明章节存在"
+fi
+
+# 清理
+kill $MAIN_PID 2>/dev/null
+kill $BACKUP_PID 2>/dev/null
+rm -f /tmp/test_config.json
+
+echo ""
+echo "完整版本API文档测试完成!"

+ 64 - 0
test_task_api.sh

@@ -0,0 +1,64 @@
+#!/bin/bash
+
+# 轮询任务管理API测试脚本
+API_BASE="http://localhost:8080/api"
+
+echo "🔄 轮询任务管理API测试开始"
+echo "========================"
+
+# 测试获取任务列表
+echo "1. 获取轮询任务列表:"
+curl -s "$API_BASE/tasks" | jq '.' || echo "JSON解析失败"
+echo -e "\n"
+
+# 测试获取实时任务状态
+echo "2. 获取实时任务状态:"
+curl -s "$API_BASE/tasks/status" | jq '.' || echo "JSON解析失败"
+echo -e "\n"
+
+# 测试获取调度器状态
+echo "3. 获取调度器状态:"
+curl -s "$API_BASE/scheduler/status" | jq '.' || echo "JSON解析失败"
+echo -e "\n"
+
+# 测试获取当前周期状态
+echo "4. 获取当前周期状态:"
+curl -s "$API_BASE/scheduler/cycle" | jq '.' || echo "JSON解析失败"
+echo -e "\n"
+
+# 检查调度器是否在运行
+SCHEDULER_RUNNING=$(curl -s "$API_BASE/scheduler/status" | jq -r '.is_running' 2>/dev/null)
+
+if [ "$SCHEDULER_RUNNING" = "true" ]; then
+    echo "5. 调度器正在运行,测试停止调度器:"
+    curl -s -X POST "$API_BASE/scheduler/stop" | jq '.' || echo "JSON解析失败"
+    echo -e "\n"
+    
+    # 等待一秒后再次检查状态
+    sleep 1
+    echo "6. 验证调度器停止状态:"
+    curl -s "$API_BASE/scheduler/status" | jq '.' || echo "JSON解析失败"
+    echo -e "\n"
+else
+    echo "5. 调度器未在运行,测试启动调度器:"
+    curl -s -X POST "$API_BASE/scheduler/start" | jq '.' || echo "JSON解析失败"
+    echo -e "\n"
+    
+    # 等待一秒后再次检查状态
+    sleep 1
+    echo "6. 验证调度器启动状态:"
+    curl -s "$API_BASE/scheduler/status" | jq '.' || echo "JSON解析失败"
+    echo -e "\n"
+fi
+
+# 实时监控演示(可选)
+echo "7. 实时监控演示(5秒):"
+for i in {1..5}; do
+    echo "  -> 第 $i 秒状态:"
+    curl -s "$API_BASE/tasks/status" | jq '.scheduler_running, .completed_tasks, .total_tasks' || echo "  获取状态失败"
+    sleep 1
+done
+echo -e "\n"
+
+echo "========================"
+echo "🎉 轮询任务管理API测试完成"

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio