wenhongquan 2 일 전
부모
커밋
a348127335
59개의 변경된 파일2181개의 추가작업 그리고 0개의 파일을 삭제
  1. 29 0
      .dockerignore
  2. 311 0
      API_DOCUMENTATION.md
  3. 401 0
      DOCKER_FILES_README.md
  4. 111 0
      DOCKER_PLATFORM_FIX.md
  5. 444 0
      DOCKER_README.md
  6. 86 0
      Dockerfile
  7. 271 0
      FEATURES_SUMMARY.md
  8. BIN
      build/jtjai_media
  9. 36 0
      docker-compose.yml
  10. 169 0
      docker-start.sh
  11. 66 0
      docker-stop.sh
  12. 2 0
      include/http_server.h
  13. 1 0
      output/20251010_180758/report.txt
  14. 1 0
      output/20251010_180758/rtsp_report.json
  15. 1 0
      output/20251010_180758/streams.csv
  16. BIN
      output/20251010_180758/test_stream1.mp4
  17. BIN
      output/20251010_180758/test_stream2.mp4
  18. 1 0
      output/20251010_180858/report.txt
  19. 1 0
      output/20251010_180858/rtsp_report.json
  20. 1 0
      output/20251010_180858/streams.csv
  21. BIN
      output/20251010_180858/test_stream1.mp4
  22. BIN
      output/20251010_180858/test_stream2.mp4
  23. 1 0
      output/20251010_181521/report.txt
  24. 1 0
      output/20251010_181521/rtsp_report.json
  25. 1 0
      output/20251010_181521/streams.csv
  26. BIN
      output/20251010_181521/test_stream1.mp4
  27. BIN
      output/20251010_181521/test_stream2.mp4
  28. 1 0
      output/20251010_181622/report.txt
  29. 1 0
      output/20251010_181622/rtsp_report.json
  30. 1 0
      output/20251010_181622/streams.csv
  31. BIN
      output/20251010_181622/test_stream1.mp4
  32. BIN
      output/20251010_181622/test_stream2.mp4
  33. 1 0
      output/20251010_181722/report.txt
  34. 1 0
      output/20251010_181722/rtsp_report.json
  35. 1 0
      output/20251010_181722/streams.csv
  36. BIN
      output/20251010_181722/test_stream1.mp4
  37. BIN
      output/20251010_181722/test_stream2.mp4
  38. 1 0
      output/20251011_082130/report.txt
  39. 1 0
      output/20251011_082130/rtsp_report.json
  40. 1 0
      output/20251011_082130/streams.csv
  41. BIN
      output/20251011_082130/test_stream1.mp4
  42. BIN
      output/20251011_082130/test_stream2.mp4
  43. 1 0
      output/20251011_082233/report.txt
  44. 1 0
      output/20251011_082233/rtsp_report.json
  45. 1 0
      output/20251011_082233/streams.csv
  46. BIN
      output/20251011_082233/test_stream1.mp4
  47. BIN
      output/20251011_082233/test_stream2.mp4
  48. 1 0
      output/20251011_082333/report.txt
  49. 1 0
      output/20251011_082333/rtsp_report.json
  50. 1 0
      output/20251011_082333/streams.csv
  51. BIN
      output/20251011_082333/test_stream1.mp4
  52. BIN
      output/20251011_082333/test_stream2.mp4
  53. 1 0
      output/20251011_082434/report.txt
  54. 1 0
      output/20251011_082434/rtsp_report.json
  55. 1 0
      output/20251011_082434/streams.csv
  56. BIN
      output/20251011_082434/test_stream1.mp4
  57. BIN
      output/20251011_082434/test_stream2.mp4
  58. 121 0
      src/http_server.cpp
  59. 107 0
      video_manager.html

+ 29 - 0
.dockerignore

@@ -0,0 +1,29 @@
+# 构建相关
+build/
+cmake-build-*/
+.cmake/
+
+# 输出文件
+output/
+*.mp4
+
+# IDE相关
+.idea/
+.vscode/
+.DS_Store
+*.swp
+*.swo
+
+# Git相关
+.git/
+.gitignore
+
+# 文档
+*.md
+API_DOCUMENTATION.md
+FEATURES_SUMMARY.md
+README.md
+
+# 临时文件
+*.log
+nohup.out

+ 311 - 0
API_DOCUMENTATION.md

@@ -0,0 +1,311 @@
+# RTSP视频流管理系统 API 文档
+
+## 基础信息
+- **基础URL**: `http://localhost:8080`
+- **数据格式**: JSON
+- **编码**: UTF-8
+
+## API 端点
+
+### 1. 视频管理
+
+#### 1.1 列出所有视频
+```
+GET /api/videos
+```
+
+**响应示例**:
+```json
+{
+  "count": 10,
+  "videos": [
+    {
+      "filename": "test_stream1.mp4",
+      "full_path": "./output/20251011_082130/test_stream1.mp4",
+      "timestamp_dir": "20251011_082130",
+      "file_size": 787456,
+      "created_time": "2025-10-11 08:21:45",
+      "stream_index": "1"
+    }
+  ]
+}
+```
+
+#### 1.2 列出指定时间戳目录的视频
+```
+GET /api/videos/{timestamp}
+```
+
+**路径参数**:
+- `timestamp`: 时间戳目录名,例如 `20251011_082130`
+
+---
+
+### 2. 时间戳目录管理
+
+#### 2.1 列出所有时间戳目录
+```
+GET /api/timestamps
+```
+
+**响应示例**:
+```json
+{
+  "count": 5,
+  "timestamps": [
+    {
+      "timestamp": "20251011_082130",
+      "video_count": 2,
+      "total_size": 5748992
+    }
+  ]
+}
+```
+
+#### 2.2 删除时间戳目录
+```
+DELETE /api/timestamp/{timestamp}
+```
+
+**路径参数**:
+- `timestamp`: 要删除的时间戳目录名
+
+**响应示例**:
+```json
+{
+  "success": true,
+  "path": "./output/20251011_082130",
+  "message": "目录删除成功"
+}
+```
+
+---
+
+### 3. 轮询报告管理 🆕
+
+#### 3.1 列出所有轮询报告
+```
+GET /api/reports
+```
+
+**响应示例**:
+```json
+{
+  "count": 8,
+  "reports": [
+    {
+      "timestamp": "20251011_082130",
+      "has_report": true,
+      "summary": {
+        "total_streams": 2,
+        "successful_streams": 2,
+        "failed_streams": 0,
+        "cancelled_streams": 0,
+        "total_bytes": 0,
+        "total_frames": 950,
+        "success_rate": 1.0,
+        "average_duration": 18.0,
+        "earliest_start": "2025-10-11 08:21:30",
+        "latest_end": "2025-10-11 08:21:50"
+      },
+      "generated_at": "2025-10-11 08:21:50"
+    }
+  ]
+}
+```
+
+**字段说明**:
+- `total_streams`: 总流数
+- `successful_streams`: 成功拉取的流数量
+- `failed_streams`: 失败的流数量
+- `cancelled_streams`: 取消的流数量
+- `success_rate`: 成功率 (0.0-1.0)
+- `total_frames`: 总接收帧数
+- `average_duration`: 平均持续时间(秒)
+
+#### 3.2 获取指定时间戳的轮询报告
+```
+GET /api/report/{timestamp}
+```
+
+**路径参数**:
+- `timestamp`: 时间戳目录名
+
+**响应示例**:
+```json
+{
+  "report_generated_at": "2025-10-11 08:21:50",
+  "config": {
+    "global_config": {
+      "total_poll_duration_seconds": 60,
+      "max_concurrent_streams": 2,
+      "output_directory": "./output/20251011_082130"
+    },
+    "streams": [...]
+  },
+  "scheduler_stats": {
+    "total_tasks": 2,
+    "completed_tasks": 2,
+    "failed_tasks": 0,
+    "cancelled_tasks": 0
+  },
+  "streams": [
+    {
+      "stream_index": 1,
+      "rtsp_url": "rtsp://example.com/stream1",
+      "output_file": "./output/20251011_082130/test_stream2.mp4",
+      "status": "完成",
+      "start_time": "2025-10-11 08:21:30",
+      "end_time": "2025-10-11 08:21:50",
+      "bytes_received": 0,
+      "frames_received": 549,
+      "duration_seconds": 20,
+      "error_message": "",
+      "actual_duration_seconds": 20
+    }
+  ],
+  "summary": {
+    "total_streams": 2,
+    "successful_streams": 2,
+    "failed_streams": 0,
+    "total_frames": 950,
+    "success_rate": 1.0
+  }
+}
+```
+
+**流状态说明**:
+- `完成`: 流成功拉取
+- `失败`: 流拉取失败(会有error_message)
+- `取消`: 流被取消
+
+---
+
+### 4. 视频文件操作
+
+#### 4.1 删除视频文件
+```
+DELETE /api/video?path={path}
+```
+
+**查询参数**:
+- `path`: 视频文件的完整路径
+
+**响应示例**:
+```json
+{
+  "success": true,
+  "path": "./output/20251011_082130/test_stream1.mp4",
+  "message": "文件删除成功"
+}
+```
+
+#### 4.2 播放/下载视频
+```
+GET /videos/{timestamp}/{filename}
+```
+
+**路径参数**:
+- `timestamp`: 时间戳目录名
+- `filename`: 视频文件名
+
+**功能**: 直接返回视频文件流,支持浏览器播放和下载
+
+---
+
+## Web 界面
+
+### 主页
+```
+GET /
+```
+系统主页,提供快速导航
+
+### 视频管理界面
+```
+GET /manager
+```
+完整的视频和报告管理界面,包括:
+- 时间戳目录浏览
+- 所有视频列表
+- **轮询报告查看** 🆕
+- 视频播放功能
+- 文件删除功能
+
+---
+
+## 错误响应
+
+所有错误响应遵循以下格式:
+
+```json
+{
+  "error": "错误类型",
+  "message": "详细错误信息"
+}
+```
+
+**常见HTTP状态码**:
+- `200 OK`: 请求成功
+- `400 Bad Request`: 请求参数错误
+- `403 Forbidden`: 权限不足
+- `404 Not Found`: 资源不存在
+- `500 Internal Server Error`: 服务器内部错误
+
+---
+
+## 使用示例
+
+### curl 示例
+
+#### 获取所有轮询报告
+```bash
+curl http://localhost:8080/api/reports
+```
+
+#### 获取特定报告详情
+```bash
+curl http://localhost:8080/api/report/20251011_082130
+```
+
+#### 删除视频文件
+```bash
+curl -X DELETE "http://localhost:8080/api/video?path=./output/20251011_082130/test_stream1.mp4"
+```
+
+### JavaScript 示例
+
+```javascript
+// 获取所有报告
+async function loadReports() {
+  const response = await fetch('http://localhost:8080/api/reports');
+  const data = await response.json();
+  console.log(`共有 ${data.count} 个报告`);
+  return data.reports;
+}
+
+// 获取特定报告
+async function getReport(timestamp) {
+  const response = await fetch(`http://localhost:8080/api/report/${timestamp}`);
+  const report = await response.json();
+  return report;
+}
+```
+
+---
+
+## 注意事项
+
+1. **CORS支持**: API支持跨域请求
+2. **文件路径安全**: 删除操作会检查文件路径,只能删除output目录下的文件
+3. **视频播放**: 支持HTTP Range请求,可以拖动播放进度
+4. **轮询报告**: 每次任务执行都会生成独立的JSON报告文件
+5. **失败流展示**: 报告中包含所有流的状态,包括失败和未拉取成功的流
+
+## 更新日志
+
+### v1.1.0 (2025-10-11)
+- ✨ 新增轮询报告API (`/api/reports`, `/api/report/{timestamp}`)
+- ✨ Web界面新增"轮询报告"选项卡
+- ✨ 报告展示包含成功和失败的流信息
+- 🔧 优化API文档和错误处理

+ 401 - 0
DOCKER_FILES_README.md

@@ -0,0 +1,401 @@
+# Docker 部署文件说明
+
+本目录包含了在AMD64平台上使用Docker部署RTSP视频流管理系统所需的所有文件。
+
+## 📁 文件列表
+
+| 文件名 | 说明 | 必需 |
+|--------|------|------|
+| `Dockerfile` | Docker镜像构建文件 | ✅ 是 |
+| `docker-compose.yml` | Docker Compose配置文件 | 推荐 |
+| `.dockerignore` | Docker构建忽略文件 | 推荐 |
+| `docker-start.sh` | 快速启动脚本 | 可选 |
+| `docker-stop.sh` | 快速停止脚本 | 可选 |
+| `DOCKER_README.md` | 详细使用文档 | 推荐阅读 |
+
+## 🚀 快速开始
+
+### 最简单的方式
+
+```bash
+# 1. 给脚本添加执行权限(仅首次)
+chmod +x docker-start.sh docker-stop.sh
+
+# 2. 启动服务
+./docker-start.sh
+
+# 3. 访问
+# 浏览器打开: http://localhost:8080/manager
+
+# 4. 停止服务
+./docker-stop.sh
+```
+
+### 使用Docker Compose
+
+```bash
+# 启动
+docker-compose up -d
+
+# 查看日志
+docker-compose logs -f
+
+# 停止
+docker-compose down
+```
+
+### 使用Docker命令
+
+```bash
+# 构建镜像
+docker build --platform linux/amd64 -t jtjai_media:latest .
+
+# 运行容器
+docker run -d \
+  --name jtjai_media \
+  --platform linux/amd64 \
+  -p 8080:8080 \
+  -v $(pwd)/output:/app/output \
+  -v $(pwd)/config.json:/app/config.json:ro \
+  jtjai_media:latest
+
+# 查看日志
+docker logs -f jtjai_media
+
+# 停止容器
+docker stop jtjai_media
+```
+
+## 📋 Dockerfile 说明
+
+### 多阶段构建
+
+```dockerfile
+# 阶段1: 构建环境 (builder)
+FROM ubuntu:22.04 AS builder
+# - 安装构建工具
+# - 编译C++项目
+
+# 阶段2: 运行环境
+FROM ubuntu:22.04
+# - 仅安装运行时依赖
+# - 复制可执行文件
+# - 配置启动命令
+```
+
+### 优点
+- ✅ 镜像体积小(最终镜像不包含构建工具)
+- ✅ 构建速度快(利用Docker缓存)
+- ✅ 安全性好(减少攻击面)
+
+### 安装的依赖
+
+**构建阶段**:
+- build-essential (GCC/G++)
+- cmake
+- git
+- wget
+- pkg-config
+- FFmpeg开发库
+- Boost开发库
+
+**运行阶段**:
+- FFmpeg运行库
+- Boost运行库
+
+## 🔧 Docker Compose 配置
+
+### 主要配置项
+
+```yaml
+services:
+  jtjai_media:
+    # 平台指定
+    platforms:
+      - linux/amd64
+    
+    # 端口映射
+    ports:
+      - "8080:8080"
+    
+    # 数据卷挂载
+    volumes:
+      - ./config.json:/app/config.json:ro
+      - ./output:/app/output
+    
+    # 资源限制
+    deploy:
+      resources:
+        limits:
+          cpus: '2.0'
+          memory: 2G
+    
+    # 健康检查
+    healthcheck:
+      test: ["CMD", "curl", "-f", "http://localhost:8080/"]
+      interval: 30s
+```
+
+## 📦 镜像大小优化
+
+### 当前镜像分层
+
+```
+构建阶段 (临时):
+- Ubuntu 22.04: ~77MB
+- 构建工具: ~500MB
+- 源代码编译: ~100MB
+总计: ~677MB (临时,不保留)
+
+运行阶段 (最终):
+- Ubuntu 22.04: ~77MB
+- 运行时库: ~150MB
+- 可执行文件: ~5MB
+- 配置文件: <1MB
+最终镜像: ~235MB
+```
+
+### 进一步优化建议
+
+如需更小的镜像,可以考虑:
+
+1. **使用Alpine Linux**
+   ```dockerfile
+   FROM alpine:3.18 AS builder
+   # 需要安装musl-dev等依赖
+   ```
+   最终镜像可减小到 ~100MB
+
+2. **使用Distroless**
+   ```dockerfile
+   FROM gcr.io/distroless/cc-debian11
+   # 无Shell,更安全
+   ```
+   最终镜像可减小到 ~50MB
+
+## 🔒 安全建议
+
+### 1. 最小权限原则
+
+在Dockerfile中添加非root用户:
+
+```dockerfile
+# 创建应用用户
+RUN groupadd -r appuser && useradd -r -g appuser appuser
+RUN chown -R appuser:appuser /app
+USER appuser
+```
+
+### 2. 只读文件系统
+
+在docker-compose.yml中:
+
+```yaml
+security_opt:
+  - no-new-privileges:true
+read_only: true
+tmpfs:
+  - /tmp
+```
+
+### 3. 网络隔离
+
+```yaml
+networks:
+  rtsp_network:
+    driver: bridge
+    internal: true  # 内部网络,不能访问外网
+```
+
+## 🐛 故障排查
+
+### 常见问题
+
+1. **构建失败: 找不到依赖**
+   ```bash
+   # 清除缓存重新构建
+   docker build --no-cache -t jtjai_media:latest .
+   ```
+
+2. **容器启动失败**
+   ```bash
+   # 查看详细日志
+   docker logs jtjai_media
+   
+   # 交互式运行
+   docker run -it --rm jtjai_media:latest bash
+   ```
+
+3. **端口冲突**
+   ```bash
+   # 检查端口占用
+   lsof -i :8080
+   
+   # 修改端口映射
+   docker run -p 9090:8080 jtjai_media:latest
+   ```
+
+4. **权限问题**
+   ```bash
+   # 检查output目录权限
+   ls -la output/
+   
+   # 修复权限
+   chmod 777 output/
+   ```
+
+## 📊 监控和日志
+
+### 查看日志
+
+```bash
+# 实时日志
+docker logs -f jtjai_media
+
+# 最近100行
+docker logs --tail 100 jtjai_media
+
+# 带时间戳
+docker logs -t jtjai_media
+```
+
+### 资源监控
+
+```bash
+# 查看资源使用
+docker stats jtjai_media
+
+# 查看详细信息
+docker inspect jtjai_media
+```
+
+## 🔄 更新和维护
+
+### 更新镜像
+
+```bash
+# 1. 拉取最新代码
+git pull
+
+# 2. 停止旧容器
+docker-compose down
+
+# 3. 重新构建
+docker-compose build --no-cache
+
+# 4. 启动新容器
+docker-compose up -d
+```
+
+### 清理旧镜像
+
+```bash
+# 删除未使用的镜像
+docker image prune -a
+
+# 删除所有停止的容器
+docker container prune
+
+# 完整清理
+docker system prune -a --volumes
+```
+
+## 📈 性能优化
+
+### 1. 构建缓存
+
+利用Docker层缓存加速构建:
+
+```dockerfile
+# 先复制依赖相关文件
+COPY CMakeLists.txt ./
+RUN cmake ...
+
+# 再复制源代码
+COPY src/ ./src/
+RUN make
+```
+
+### 2. 并行编译
+
+```dockerfile
+RUN make -j$(nproc)
+```
+
+### 3. 共享内存
+
+```yaml
+shm_size: '2gb'
+```
+
+## 🌐 生产环境部署
+
+### 推荐配置
+
+```yaml
+version: '3.8'
+
+services:
+  jtjai_media:
+    image: jtjai_media:latest
+    restart: always
+    ports:
+      - "8080:8080"
+    volumes:
+      - ./config.json:/app/config.json:ro
+      - ./output:/app/output
+      - /etc/localtime:/etc/localtime:ro
+    environment:
+      - TZ=Asia/Shanghai
+    deploy:
+      resources:
+        limits:
+          cpus: '4.0'
+          memory: 4G
+    healthcheck:
+      test: ["CMD", "curl", "-f", "http://localhost:8080/"]
+      interval: 30s
+      timeout: 10s
+      retries: 3
+    logging:
+      driver: "json-file"
+      options:
+        max-size: "10m"
+        max-file: "3"
+```
+
+## 📞 技术支持
+
+- 详细文档: `DOCKER_README.md`
+- API文档: `API_DOCUMENTATION.md`
+- 功能说明: `FEATURES_SUMMARY.md`
+
+## 📝 版本信息
+
+- **Dockerfile版本**: 1.0
+- **平台**: linux/amd64
+- **基础镜像**: Ubuntu 22.04
+- **构建方式**: 多阶段构建
+- **最后更新**: 2025-10-11
+
+---
+
+**快速命令参考**:
+
+```bash
+# 启动
+./docker-start.sh
+# 或
+docker-compose up -d
+
+# 停止
+./docker-stop.sh
+# 或
+docker-compose down
+
+# 日志
+docker logs -f jtjai_media
+
+# 重启
+docker restart jtjai_media
+```

+ 111 - 0
DOCKER_PLATFORM_FIX.md

@@ -0,0 +1,111 @@
+# Docker平台配置修复说明
+
+## 问题描述
+
+Docker语言服务器报告警告:
+```
+FROM --platform flag should not use a constant value (FROM --platform flag should not use constant value "linux/amd64")
+```
+
+## 问题原因
+
+1. **硬编码平台限制**:在Dockerfile中直接使用 `--platform=linux/amd64` 硬编码了平台架构
+2. **缺乏灵活性**:无法支持多架构构建
+3. **不符合最佳实践**:Docker推荐使用构建参数来动态指定平台
+
+## 修复方案
+
+### 1. Dockerfile修改
+
+**修改前:**
+```dockerfile
+FROM --platform=linux/amd64 ubuntu:22.04 AS builder
+...
+FROM --platform=linux/amd64 ubuntu:22.04
+```
+
+**修改后:**
+```dockerfile
+# 构建参数定义平台架构,默认为linux/amd64
+ARG BUILDPLATFORM=linux/amd64
+ARG TARGETPLATFORM=linux/amd64
+
+FROM --platform=$BUILDPLATFORM ubuntu:22.04 AS builder
+...
+FROM --platform=$TARGETPLATFORM ubuntu:22.04
+```
+
+### 2. 构建脚本更新
+
+**docker-start.sh修改:**
+```bash
+# 修改前
+docker build --platform linux/amd64 -t jtjai_media:latest .
+
+# 修改后
+docker build --build-arg BUILDPLATFORM=linux/amd64 --build-arg TARGETPLATFORM=linux/amd64 -t jtjai_media:latest .
+```
+
+## 使用方式
+
+### 1. 默认AMD64构建
+```bash
+# 使用默认参数构建(AMD64)
+docker build -t jtjai_media:latest .
+
+# 或使用启动脚本
+./docker-start.sh
+```
+
+### 2. 指定平台构建
+```bash
+# 构建ARM64版本
+docker build --build-arg BUILDPLATFORM=linux/arm64 --build-arg TARGETPLATFORM=linux/arm64 -t jtjai_media:arm64 .
+
+# 多平台构建
+docker buildx build --platform linux/amd64,linux/arm64 -t jtjai_media:multi .
+```
+
+### 3. Docker Compose部署
+```bash
+# docker-compose.yml已配置platform: linux/amd64
+docker-compose up -d
+```
+
+## 优势
+
+1. **消除警告**:解决Docker语言服务器的平台常量警告
+2. **提高灵活性**:支持通过构建参数指定不同平台
+3. **保持兼容性**:默认仍为AMD64平台,确保现有部署不受影响
+4. **支持多架构**:为将来支持ARM等架构打下基础
+5. **符合最佳实践**:遵循Docker官方推荐的构建参数使用方式
+
+## 验证
+
+1. **构建测试**:
+   ```bash
+   docker build -t jtjai_media:test .
+   ```
+
+2. **运行测试**:
+   ```bash
+   docker run --rm -p 8080:8080 jtjai_media:test
+   ```
+
+3. **平台检查**:
+   ```bash
+   docker inspect jtjai_media:test | grep Architecture
+   ```
+
+## 注意事项
+
+1. 默认平台仍为 `linux/amd64`,确保与现有部署兼容
+2. 如需构建其他架构版本,需要相应的基础镜像支持
+3. 多架构构建需要使用 `docker buildx` 命令
+4. ARM架构构建可能需要额外的依赖库配置
+
+## 相关文件
+
+- `Dockerfile` - 主要修改文件
+- `docker-start.sh` - 启动脚本更新
+- `docker-compose.yml` - 已符合规范,无需修改

+ 444 - 0
DOCKER_README.md

@@ -0,0 +1,444 @@
+# Docker 部署指南
+
+本文档介绍如何使用Docker在AMD64平台上部署RTSP视频流管理系统。
+
+## 📋 前置要求
+
+- Docker Engine 20.10+
+- Docker Compose 1.29+ (可选)
+- AMD64架构的Linux主机
+
+## 🚀 快速开始
+
+### 方法1: 使用Docker Compose(推荐)
+
+```bash
+# 1. 构建并启动服务
+docker-compose up -d
+
+# 2. 查看日志
+docker-compose logs -f
+
+# 3. 停止服务
+docker-compose down
+
+# 4. 停止并删除数据卷
+docker-compose down -v
+```
+
+### 方法2: 使用Docker命令
+
+```bash
+# 1. 构建镜像
+docker build --platform linux/amd64 -t jtjai_media:latest .
+
+# 2. 运行容器
+docker run -d \
+  --name jtjai_media \
+  --platform linux/amd64 \
+  -p 8080:8080 \
+  -v $(pwd)/output:/app/output \
+  -v $(pwd)/config.json:/app/config.json:ro \
+  jtjai_media:latest
+
+# 3. 查看日志
+docker logs -f jtjai_media
+
+# 4. 停止容器
+docker stop jtjai_media
+
+# 5. 删除容器
+docker rm jtjai_media
+```
+
+## 📦 镜像说明
+
+### 多阶段构建
+
+Dockerfile使用多阶段构建来优化镜像大小:
+
+1. **构建阶段(builder)**
+   - 基于 Ubuntu 22.04
+   - 安装完整的构建工具链
+   - 编译C++项目
+   - 生成可执行文件
+
+2. **运行阶段**
+   - 基于 Ubuntu 22.04
+   - 仅安装运行时依赖
+   - 复制可执行文件
+   - 最终镜像大小约 300-400MB
+
+### 平台支持
+
+镜像明确指定为 **linux/amd64** 平台,确保在AMD64架构上运行。
+
+## 🔧 配置管理
+
+### 修改配置文件
+
+有两种方式修改配置:
+
+#### 方式1: 挂载配置文件(推荐)
+
+```bash
+# 1. 修改宿主机的config.json
+vim config.json
+
+# 2. 重启容器使配置生效
+docker-compose restart
+# 或
+docker restart jtjai_media
+```
+
+#### 方式2: 进入容器修改
+
+```bash
+# 1. 进入容器
+docker exec -it jtjai_media bash
+
+# 2. 修改配置
+vi /app/config.json
+
+# 3. 退出容器并重启
+exit
+docker restart jtjai_media
+```
+
+### 配置示例
+
+```json
+{
+  "global_config": {
+    "total_poll_duration_seconds": 60,
+    "max_concurrent_streams": 2,
+    "output_directory": "./output",
+    "poll_cycles": 3
+  },
+  "streams": [
+    {
+      "rtsp_url": "rtsp://your-rtsp-url",
+      "duration_seconds": 15,
+      "weight": 1.0,
+      "output_filename": "stream1.mp4"
+    }
+  ]
+}
+```
+
+## 💾 数据持久化
+
+### 输出目录挂载
+
+视频文件默认保存在容器的 `/app/output` 目录,建议挂载到宿主机:
+
+```yaml
+volumes:
+  - ./output:/app/output
+```
+
+这样即使容器删除,视频文件也会保留在宿主机的 `./output` 目录中。
+
+### 目录结构
+
+```
+output/
+├── 20251011_082130/
+│   ├── test_stream1.mp4
+│   ├── test_stream2.mp4
+│   ├── rtsp_report.json
+│   ├── report.txt
+│   └── streams.csv
+├── 20251011_082230/
+│   └── ...
+└── ...
+```
+
+## 🌐 访问服务
+
+容器启动后,可以通过以下地址访问:
+
+- **Web管理界面**: http://localhost:8080/manager
+- **主页**: http://localhost:8080/
+- **API文档**: http://localhost:8080/api
+
+### 端口映射
+
+默认映射 `8080:8080`,如需修改宿主机端口:
+
+```yaml
+ports:
+  - "9090:8080"  # 宿主机9090端口映射到容器8080
+```
+
+## 🔍 调试与监控
+
+### 查看实时日志
+
+```bash
+# Docker Compose
+docker-compose logs -f
+
+# Docker
+docker logs -f jtjai_media
+
+# 只看最近100行
+docker logs --tail 100 jtjai_media
+```
+
+### 进入容器调试
+
+```bash
+# 进入bash
+docker exec -it jtjai_media bash
+
+# 查看进程
+docker exec jtjai_media ps aux
+
+# 查看输出文件
+docker exec jtjai_media ls -lh /app/output
+```
+
+### 健康检查
+
+容器配置了健康检查,可以查看状态:
+
+```bash
+# 查看健康状态
+docker inspect --format='{{.State.Health.Status}}' jtjai_media
+
+# 查看健康检查日志
+docker inspect --format='{{range .State.Health.Log}}{{.Output}}{{end}}' jtjai_media
+```
+
+## 🎯 高级用法
+
+### 自定义资源限制
+
+在 `docker-compose.yml` 中调整:
+
+```yaml
+deploy:
+  resources:
+    limits:
+      cpus: '4.0'      # 最多使用4个CPU核心
+      memory: 4G       # 最多使用4GB内存
+    reservations:
+      cpus: '2.0'      # 预留2个CPU核心
+      memory: 1G       # 预留1GB内存
+```
+
+### 使用环境变量
+
+```yaml
+environment:
+  - TZ=Asia/Shanghai           # 时区
+  - LOG_LEVEL=DEBUG            # 日志级别(需要代码支持)
+```
+
+### 网络配置
+
+```yaml
+networks:
+  rtsp_network:
+    driver: bridge
+    ipam:
+      config:
+        - subnet: 172.20.0.0/16
+```
+
+### 多实例部署
+
+运行多个实例(不同端口):
+
+```bash
+# 实例1
+docker run -d --name jtjai_media_1 -p 8081:8080 jtjai_media:latest
+
+# 实例2
+docker run -d --name jtjai_media_2 -p 8082:8080 jtjai_media:latest
+```
+
+## 🛠️ 故障排查
+
+### 容器无法启动
+
+```bash
+# 1. 查看详细日志
+docker logs jtjai_media
+
+# 2. 检查配置文件
+docker run --rm -v $(pwd)/config.json:/config.json ubuntu:22.04 cat /config.json
+
+# 3. 检查端口占用
+netstat -tuln | grep 8080
+```
+
+### 视频无法保存
+
+```bash
+# 1. 检查目录权限
+ls -la output/
+
+# 2. 进入容器检查
+docker exec -it jtjai_media bash
+cd /app/output
+ls -la
+
+# 3. 检查磁盘空间
+df -h
+```
+
+### 无法访问Web界面
+
+```bash
+# 1. 确认容器运行
+docker ps | grep jtjai_media
+
+# 2. 测试端口
+curl http://localhost:8080/
+
+# 3. 检查防火墙
+sudo ufw status
+```
+
+## 📊 性能优化
+
+### 构建优化
+
+```bash
+# 使用构建缓存
+docker build --cache-from jtjai_media:latest -t jtjai_media:latest .
+
+# 并行构建
+docker build --build-arg MAKEFLAGS="-j$(nproc)" .
+```
+
+### 运行优化
+
+```yaml
+# 在docker-compose.yml中
+services:
+  jtjai_media:
+    # 使用主机网络模式(更好的性能,但失去网络隔离)
+    network_mode: "host"
+    
+    # 共享内存大小
+    shm_size: '2gb'
+```
+
+## 🔐 安全建议
+
+1. **只读挂载配置文件**
+   ```yaml
+   volumes:
+     - ./config.json:/app/config.json:ro
+   ```
+
+2. **限制容器权限**
+   ```yaml
+   security_opt:
+     - no-new-privileges:true
+   read_only: true
+   tmpfs:
+     - /tmp
+   ```
+
+3. **使用非root用户**(需要修改Dockerfile)
+   ```dockerfile
+   RUN useradd -m -u 1000 appuser
+   USER appuser
+   ```
+
+## 📝 完整示例
+
+### 生产环境部署
+
+```yaml
+version: '3.8'
+
+services:
+  jtjai_media:
+    build:
+      context: .
+      dockerfile: Dockerfile
+      platforms:
+        - linux/amd64
+    image: jtjai_media:latest
+    container_name: jtjai_media_prod
+    restart: always
+    ports:
+      - "8080:8080"
+    volumes:
+      - ./config.json:/app/config.json:ro
+      - ./output:/app/output
+      - /etc/localtime:/etc/localtime:ro
+    environment:
+      - TZ=Asia/Shanghai
+    deploy:
+      resources:
+        limits:
+          cpus: '4.0'
+          memory: 4G
+        reservations:
+          cpus: '2.0'
+          memory: 1G
+    healthcheck:
+      test: ["CMD", "curl", "-f", "http://localhost:8080/"]
+      interval: 30s
+      timeout: 10s
+      retries: 3
+      start_period: 40s
+    logging:
+      driver: "json-file"
+      options:
+        max-size: "10m"
+        max-file: "3"
+    networks:
+      - rtsp_network
+
+networks:
+  rtsp_network:
+    driver: bridge
+```
+
+## 🎓 常用命令速查
+
+```bash
+# 构建
+docker-compose build
+docker-compose build --no-cache  # 不使用缓存
+
+# 启动
+docker-compose up -d
+docker-compose up -d --force-recreate  # 强制重建容器
+
+# 停止
+docker-compose stop
+docker-compose down
+
+# 日志
+docker-compose logs -f
+docker-compose logs -f --tail=100
+
+# 重启
+docker-compose restart
+
+# 清理
+docker-compose down -v  # 删除容器和卷
+docker system prune -a  # 清理所有未使用的Docker对象
+```
+
+## 📞 获取帮助
+
+如遇到问题,请检查:
+1. Docker版本是否满足要求
+2. 配置文件格式是否正确
+3. 网络连接是否正常
+4. RTSP流地址是否可访问
+
+---
+
+**最后更新**: 2025-10-11
+**Docker版本**: 20.10+
+**平台**: linux/amd64

+ 86 - 0
Dockerfile

@@ -0,0 +1,86 @@
+# 多阶段构建Dockerfile for AMD64平台
+# RTSP视频流并发拉取系统
+
+# 构建参数定义平台架构,默认为linux/amd64
+ARG BUILDPLATFORM=linux/amd64
+ARG TARGETPLATFORM=linux/amd64
+
+# ============================================
+# 阶段1: 构建环境
+# ============================================
+FROM --platform=$BUILDPLATFORM ubuntu:22.04 AS builder
+
+# 设置环境变量避免交互式提示
+ENV DEBIAN_FRONTEND=noninteractive
+ENV TZ=Asia/Shanghai
+
+# 安装构建依赖
+RUN apt-get update && apt-get install -y \
+    build-essential \
+    cmake \
+    git \
+    wget \
+    pkg-config \
+    libavcodec-dev \
+    libavformat-dev \
+    libavutil-dev \
+    libswscale-dev \
+    libboost-all-dev \
+    && rm -rf /var/lib/apt/lists/*
+
+# 创建工作目录
+WORKDIR /app
+
+# 复制项目文件
+COPY CMakeLists.txt ./
+COPY include/ ./include/
+COPY src/ ./src/
+COPY main.cpp ./
+COPY config.json ./
+COPY video_manager.html ./
+
+# 创建构建目录并编译
+RUN mkdir -p build && \
+    cd build && \
+    cmake .. && \
+    make -j$(nproc)
+
+# ============================================
+# 阶段2: 运行环境
+# ============================================
+FROM --platform=$TARGETPLATFORM ubuntu:22.04
+
+# 设置环境变量
+ENV DEBIAN_FRONTEND=noninteractive
+ENV TZ=Asia/Shanghai
+
+# 安装运行时依赖
+RUN apt-get update && apt-get install -y \
+    libavcodec59 \
+    libavformat59 \
+    libavutil57 \
+    libswscale6 \
+    libboost-system1.74.0 \
+    libboost-thread1.74.0 \
+    libboost-json1.74.0 \
+    libboost-filesystem1.74.0 \
+    && rm -rf /var/lib/apt/lists/*
+
+# 创建应用目录
+WORKDIR /app
+
+# 从构建阶段复制编译好的可执行文件
+COPY --from=builder /app/build/jtjai_media /app/jtjai_media
+
+# 复制配置文件和Web资源
+COPY --from=builder /app/config.json /app/config.json
+COPY --from=builder /app/video_manager.html /app/video_manager.html
+
+# 创建输出目录
+RUN mkdir -p /app/output
+
+# 暴露HTTP服务端口
+EXPOSE 8080
+
+# 设置启动命令
+CMD ["/app/jtjai_media", "/app/config.json"]

+ 271 - 0
FEATURES_SUMMARY.md

@@ -0,0 +1,271 @@
+# RTSP视频流管理系统 - 功能总结
+
+## ✅ 已实现的功能
+
+### 1. 轮询报告展示系统 🆕
+
+#### 1.1 后端API
+- ✅ **GET /api/reports** - 列出所有轮询报告
+  - 返回所有时间戳目录的报告摘要
+  - 包含成功率、总流数、成功/失败数量等统计信息
+  
+- ✅ **GET /api/report/{timestamp}** - 获取指定轮询的详细报告
+  - 返回完整的报告JSON数据
+  - 包含每个流的详细状态(成功/失败)
+  - 显示错误信息(如果有)
+  - 包含配置信息和调度统计
+
+#### 1.2 前端界面
+- ✅ **轮询报告选项卡**
+  - 在视频管理界面新增"轮询报告"标签页
+  - 列表展示所有轮询报告
+  - 显示关键指标:成功率、成功数、失败数、总帧数
+  
+- ✅ **报告详情展示**
+  - 点击"查看详情"按钮展开/收起详细信息
+  - 卡片式展示每个流的状态
+  - 使用颜色区分成功(绿色✅)和失败(红色❌)
+  - 显示完整的流信息:
+    - RTSP URL
+    - 状态(完成/失败/取消)
+    - 输出文件路径
+    - 开始/结束时间
+    - 实际持续时间 vs 计划持续时间
+    - 接收帧数和字节数
+    - 错误信息(如果失败)
+
+### 2. 定时任务执行系统
+
+#### 2.1 严格定时执行
+- ✅ 按照 `total_poll_duration_seconds` 严格间隔执行
+- ✅ 不等待前一轮任务完成
+- ✅ 到时间自动开始新一轮
+
+#### 2.2 超时控制
+- ✅ 强制超时机制
+- ✅ 超时时自动终止当前任务
+- ✅ 在报告中标记超时状态
+
+#### 2.3 时间戳管理
+- ✅ 每轮任务独立的时间戳目录
+- ✅ 避免文件冲突
+- ✅ 易于追溯历史记录
+
+### 3. HTTP服务与主程序融合
+
+#### 3.1 统一启动
+- ✅ HTTP服务器与主程序一起启动
+- ✅ 共享同一进程
+- ✅ 统一的配置管理
+
+#### 3.2 Web管理界面
+- ✅ 主页导航
+- ✅ 视频管理界面
+- ✅ API文档界面
+- ✅ 轮询报告界面 🆕
+
+### 4. 视频播放功能
+
+#### 4.1 在线播放
+- ✅ 浏览器内视频播放
+- ✅ 支持播放控制(暂停、快进、音量)
+- ✅ 支持进度拖动(Range请求)
+
+#### 4.2 视频信息展示
+- ✅ 文件名、大小、创建时间
+- ✅ 所属时间戳目录
+- ✅ 流索引信息
+
+### 5. 文件管理功能
+
+#### 5.1 时间戳目录管理
+- ✅ 列出所有时间戳目录
+- ✅ 显示每个目录的视频数量和总大小
+- ✅ 展开/收起目录内容
+- ✅ 删除整个时间戳目录
+
+#### 5.2 视频文件管理
+- ✅ 列出所有视频文件
+- ✅ 按时间戳目录分组
+- ✅ 删除单个视频文件
+- ✅ 实时统计更新
+
+## 📊 数据展示特性
+
+### 1. 统计卡片
+- 时间戳目录总数
+- 视频文件总数
+- 存储空间总大小
+
+### 2. 报告展示 🆕
+每个轮询报告包含:
+
+#### 汇总信息
+- 生成时间
+- 成功率百分比
+- 成功流数量
+- 失败流数量
+- 总接收帧数
+
+#### 详细流信息
+对于**每一个流**(包括失败的流):
+- ✅ 流索引
+- ✅ RTSP URL
+- ✅ 状态(完成/失败/取消)
+- ✅ 开始和结束时间
+- ✅ 计划时长 vs 实际时长
+- ✅ 接收帧数和字节数
+- ✅ 输出文件路径
+- ✅ 错误信息(如果有)
+
+### 3. 视觉化设计
+- ✅ 状态使用emoji标识(✅成功 / ❌失败)
+- ✅ 颜色编码(绿色=成功,红色=失败)
+- ✅ 响应式卡片布局
+- ✅ 渐变背景设计
+- ✅ 平滑动画效果
+
+## 🔌 API端点总览
+
+### 视频相关
+| 方法 | 端点 | 功能 |
+|------|------|------|
+| GET | `/api/videos` | 列出所有视频 |
+| GET | `/api/videos/{timestamp}` | 列出指定时间戳的视频 |
+| DELETE | `/api/video?path={path}` | 删除视频文件 |
+| GET | `/videos/{timestamp}/{filename}` | 播放/下载视频 |
+
+### 时间戳相关
+| 方法 | 端点 | 功能 |
+|------|------|------|
+| GET | `/api/timestamps` | 列出所有时间戳目录 |
+| DELETE | `/api/timestamp/{timestamp}` | 删除时间戳目录 |
+
+### 报告相关 🆕
+| 方法 | 端点 | 功能 |
+|------|------|------|
+| GET | `/api/reports` | 列出所有轮询报告 |
+| GET | `/api/report/{timestamp}` | 获取详细报告 |
+
+### Web界面
+| 方法 | 端点 | 功能 |
+|------|------|------|
+| GET | `/` | 主页 |
+| GET | `/manager` | 管理界面 |
+| GET | `/api` | API文档 |
+
+## 🎯 使用场景
+
+### 场景1: 监控流拉取状态
+1. 打开管理界面的"轮询报告"选项卡
+2. 查看所有轮询的成功率
+3. 点击查看详情,检查每个流的状态
+4. 对于失败的流,查看错误信息
+
+### 场景2: 定期任务执行
+1. 配置 `total_poll_duration_seconds` 设置轮询间隔
+2. 配置 `poll_cycles` 设置执行轮数
+3. 系统自动按时间间隔执行任务
+4. 每轮生成独立的报告
+
+### 场景3: 视频文件管理
+1. 在"时间戳目录"选项卡查看所有录制批次
+2. 展开目录查看视频文件
+3. 点击播放按钮在线预览
+4. 删除不需要的文件或整个目录
+
+### 场景4: API集成
+1. 通过 `/api/reports` 获取所有报告列表
+2. 通过 `/api/report/{timestamp}` 获取详细数据
+3. 解析JSON数据进行自定义分析
+4. 集成到其他监控系统
+
+## 🔍 报告内容示例
+
+### 成功的流
+```json
+{
+  "stream_index": 1,
+  "rtsp_url": "rtsp://example.com/stream1",
+  "status": "完成",
+  "frames_received": 549,
+  "actual_duration_seconds": 20,
+  "error_message": ""
+}
+```
+
+### 失败的流
+```json
+{
+  "stream_index": 2,
+  "rtsp_url": "rtsp://invalid.com/stream2",
+  "status": "失败",
+  "frames_received": 0,
+  "actual_duration_seconds": 3,
+  "error_message": "连接超时"
+}
+```
+
+## 📝 技术特性
+
+### 后端
+- C++ 实现
+- Boost.Asio 异步网络库
+- Boost.JSON JSON处理
+- FFmpeg 视频处理
+- 多线程并发调度
+
+### 前端
+- 原生JavaScript(无框架依赖)
+- 响应式设计
+- Fetch API异步请求
+- HTML5 Video播放器
+- CSS Grid/Flexbox布局
+
+### 数据存储
+- JSON格式报告文件
+- 文本格式报告(可读性)
+- CSV格式流数据(便于分析)
+
+## 🚀 下一步可能的增强
+
+1. **实时监控**
+   - WebSocket实时推送任务状态
+   - 进度条实时更新
+
+2. **数据可视化**
+   - 成功率趋势图表
+   - 流性能对比图
+
+3. **高级筛选**
+   - 按状态筛选流
+   - 按时间范围筛选报告
+
+4. **导出功能**
+   - 导出报告为PDF
+   - 批量下载视频
+
+5. **告警系统**
+   - 失败率超过阈值时告警
+   - 邮件/短信通知
+
+## 📌 注意事项
+
+1. **端口占用**: HTTP服务默认使用8080端口
+2. **文件权限**: 确保对output目录有读写权限
+3. **RTSP流**: 确保RTSP流可访问
+4. **超时设置**: 根据网络情况调整超时参数
+5. **存储空间**: 定期清理旧的视频文件
+
+## ✨ 总结
+
+当前系统已经实现了完整的RTSP流拉取、管理和监控功能:
+
+- ✅ **严格的定时任务执行机制**
+- ✅ **完整的轮询报告系统**(包括成功和失败的流)
+- ✅ **友好的Web管理界面**
+- ✅ **RESTful API接口**
+- ✅ **在线视频播放**
+- ✅ **文件管理功能**
+
+系统可以通过Web界面或API轻松查看每次轮询的结果,包括所有流的详细状态,无论成功还是失败!

BIN
build/jtjai_media


+ 36 - 0
docker-compose.yml

@@ -0,0 +1,36 @@
+version: '3.8'
+
+services:
+  jtjai_media:
+    build:
+      context: .
+      dockerfile: Dockerfile
+    platform: linux/amd64
+    image: k8s.device.wenhq.top:8583/docker_r/jtjai_media:latest
+    container_name: jtjai_media
+    ports:
+      - "8080:8080"
+    volumes:
+      # 挂载配置文件(可选,如果需要修改配置)
+      - ./config.json:/app/config.json:ro
+      # 挂载输出目录到宿主机,持久化视频文件
+      - ./output:/app/output
+    restart: unless-stopped
+    environment:
+      - TZ=Asia/Shanghai
+    # 资源限制(可选)
+    deploy:
+      resources:
+        limits:
+          cpus: '2.0'
+          memory: 2G
+        reservations:
+          cpus: '1.0'
+          memory: 512M
+    # 健康检查
+    healthcheck:
+      test: ["CMD", "curl", "-f", "http://localhost:8080/"]
+      interval: 30s
+      timeout: 10s
+      retries: 3
+      start_period: 40s

+ 169 - 0
docker-start.sh

@@ -0,0 +1,169 @@
+#!/bin/bash
+
+# RTSP视频流管理系统 - Docker快速启动脚本
+# 适用于AMD64平台
+
+set -e
+
+# 颜色定义
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m' # No Color
+
+# 打印带颜色的消息
+print_info() {
+    echo -e "${GREEN}[INFO]${NC} $1"
+}
+
+print_warn() {
+    echo -e "${YELLOW}[WARN]${NC} $1"
+}
+
+print_error() {
+    echo -e "${RED}[ERROR]${NC} $1"
+}
+
+# 检查Docker是否安装
+check_docker() {
+    if ! command -v docker &> /dev/null; then
+        print_error "Docker未安装,请先安装Docker"
+        exit 1
+    fi
+    print_info "Docker版本: $(docker --version)"
+}
+
+# 检查Docker Compose是否安装
+check_docker_compose() {
+    if command -v docker-compose &> /dev/null; then
+        print_info "Docker Compose版本: $(docker-compose --version)"
+        return 0
+    elif docker compose version &> /dev/null; then
+        print_info "Docker Compose (plugin)版本: $(docker compose version)"
+        return 0
+    else
+        print_warn "Docker Compose未安装,将使用docker命令"
+        return 1
+    fi
+}
+
+# 创建必要的目录
+create_directories() {
+    print_info "创建输出目录..."
+    mkdir -p output
+    print_info "输出目录已创建: $(pwd)/output"
+}
+
+# 使用Docker Compose启动
+start_with_compose() {
+    print_info "使用Docker Compose启动服务..."
+    
+    if command -v docker-compose &> /dev/null; then
+        docker-compose up -d
+    else
+        docker compose up -d
+    fi
+    
+    print_info "服务已启动"
+}
+
+# 使用Docker命令启动
+start_with_docker() {
+    print_info "使用Docker命令启动服务..."
+    
+    # 检查容器是否已存在
+    if docker ps -a | grep -q jtjai_media; then
+        print_warn "检测到已存在的容器,正在删除..."
+        docker rm -f jtjai_media
+    fi
+    
+    # 构建镜像
+    print_info "构建Docker镜像..."
+    docker build --build-arg BUILDPLATFORM=linux/amd64 --build-arg TARGETPLATFORM=linux/amd64 -t jtjai_media:latest .
+    
+    # 运行容器
+    print_info "启动Docker容器..."
+    docker run -d \
+        --name jtjai_media \
+        --platform linux/amd64 \
+        -p 8080:8080 \
+        -v "$(pwd)/output:/app/output" \
+        -v "$(pwd)/config.json:/app/config.json:ro" \
+        --restart unless-stopped \
+        jtjai_media:latest
+    
+    print_info "服务已启动"
+}
+
+# 显示访问信息
+show_access_info() {
+    echo ""
+    print_info "=========================================="
+    print_info "RTSP视频流管理系统已成功启动!"
+    print_info "=========================================="
+    echo ""
+    print_info "访问地址:"
+    echo "  🌐 Web管理界面: http://localhost:8080/manager"
+    echo "  🏠 主页: http://localhost:8080/"
+    echo "  📊 API文档: http://localhost:8080/api"
+    echo ""
+    print_info "管理命令:"
+    echo "  查看日志: docker logs -f jtjai_media"
+    echo "  停止服务: docker stop jtjai_media"
+    echo "  启动服务: docker start jtjai_media"
+    echo "  删除容器: docker rm -f jtjai_media"
+    echo ""
+    print_info "输出目录: $(pwd)/output"
+    print_info "=========================================="
+}
+
+# 主函数
+main() {
+    echo ""
+    print_info "=========================================="
+    print_info "RTSP视频流管理系统 - Docker部署"
+    print_info "平台: AMD64"
+    print_info "=========================================="
+    echo ""
+    
+    # 检查Docker
+    check_docker
+    
+    # 创建目录
+    create_directories
+    
+    # 检查并启动
+    if check_docker_compose; then
+        start_with_compose
+    else
+        start_with_docker
+    fi
+    
+    # 等待服务启动
+    print_info "等待服务启动..."
+    sleep 3
+    
+    # 检查服务状态
+    if docker ps | grep -q jtjai_media; then
+        print_info "✅ 容器运行正常"
+        
+        # 显示访问信息
+        show_access_info
+        
+        # 询问是否查看日志
+        echo ""
+        read -p "是否查看实时日志? (y/n) " -n 1 -r
+        echo ""
+        if [[ $REPLY =~ ^[Yy]$ ]]; then
+            docker logs -f jtjai_media
+        fi
+    else
+        print_error "❌ 容器启动失败"
+        print_info "查看错误日志:"
+        docker logs jtjai_media
+        exit 1
+    fi
+}
+
+# 执行主函数
+main

+ 66 - 0
docker-stop.sh

@@ -0,0 +1,66 @@
+#!/bin/bash
+
+# RTSP视频流管理系统 - Docker停止脚本
+
+set -e
+
+# 颜色定义
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m' # No Color
+
+print_info() {
+    echo -e "${GREEN}[INFO]${NC} $1"
+}
+
+print_warn() {
+    echo -e "${YELLOW}[WARN]${NC} $1"
+}
+
+print_error() {
+    echo -e "${RED}[ERROR]${NC} $1"
+}
+
+echo ""
+print_info "=========================================="
+print_info "停止RTSP视频流管理系统"
+print_info "=========================================="
+echo ""
+
+# 检查Docker Compose
+if command -v docker-compose &> /dev/null || docker compose version &> /dev/null 2>&1; then
+    print_info "使用Docker Compose停止服务..."
+    
+    if command -v docker-compose &> /dev/null; then
+        docker-compose down
+    else
+        docker compose down
+    fi
+    
+    print_info "✅ 服务已停止"
+else
+    print_info "使用Docker命令停止服务..."
+    
+    # 检查容器是否存在
+    if docker ps -a | grep -q jtjai_media; then
+        docker stop jtjai_media
+        print_info "✅ 容器已停止"
+        
+        # 询问是否删除容器
+        read -p "是否删除容器? (y/n) " -n 1 -r
+        echo ""
+        if [[ $REPLY =~ ^[Yy]$ ]]; then
+            docker rm jtjai_media
+            print_info "✅ 容器已删除"
+        fi
+    else
+        print_warn "未找到运行的容器"
+    fi
+fi
+
+echo ""
+print_info "=========================================="
+print_info "服务已停止"
+print_info "=========================================="
+echo ""

+ 2 - 0
include/http_server.h

@@ -90,6 +90,8 @@ private:
     HttpResponse handle_get_video_info(const HttpRequest& request);
     HttpResponse handle_video_file(const HttpRequest& request);
     HttpResponse handle_static_file(const HttpRequest& request);
+    HttpResponse handle_get_report(const HttpRequest& request);
+    HttpResponse handle_list_reports(const HttpRequest& request);
     
     // 工具函数
     std::vector<std::string> list_timestamp_directories();

+ 1 - 0
output/20251010_180758/report.txt

@@ -0,0 +1 @@
+========================================\nRTSP视频流拉取报告\n========================================\n生成时间: 2025-10-10 18:08:18\n\n配置信息:\n--------\n总轮询时长: 60 秒\n最大并发流数: 2\n输出目录: ./output/20251010_180758\n配置的流数量: 2\n\n调度器统计:\n----------\n总任务数: 2\n已完成: 0\n失败: 0\n取消: 0\n最大并发数: 0\n完成率: 0.00%\n调度开始时间: 2025-10-10 18:07:58\n调度结束时间: N/A\n总调度时长: N/A\n\n汇总统计:\n--------\n成功流数: 2 / 2\n成功率: 100.00%\n总接收字节: 0.00 B\n总接收帧数: 970\n平均持续时间: 18.0 秒\n\n详细流信息:\n==========\n流 1:\n  RTSP地址: rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001\n  输出文件: ./output/20251010_180758/test_stream2.mp4\n  状态: 完成\n  开始时间: 2025-10-10 18:07:58\n  结束时间: 2025-10-10 18:08:18\n  持续时间: 20 秒\n  计划持续时间: 20 秒\n  接收字节: 0.00 B\n  接收帧数: 567\n\n流 0:\n  RTSP地址: rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001\n  输出文件: ./output/20251010_180758/test_stream1.mp4\n  状态: 完成\n  开始时间: 2025-10-10 18:07:58\n  结束时间: 2025-10-10 18:08:14\n  持续时间: 16 秒\n  计划持续时间: 15 秒\n  接收字节: 0.00 B\n  接收帧数: 403\n\n

+ 1 - 0
output/20251010_180758/rtsp_report.json

@@ -0,0 +1 @@
+{"report_generated_at":"2025-10-10 18:08:18","config":{"global_config":{"total_poll_duration_seconds":60,"max_concurrent_streams":2,"output_directory":"./output/20251010_180758","report_filename":"rtsp_report.json","connection_timeout_seconds":3,"read_timeout_seconds":5},"streams":[{"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001","duration_seconds":15,"weight":1E0,"output_filename":"test_stream1.mp4"},{"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001","duration_seconds":20,"weight":1.5E0,"output_filename":"test_stream2.mp4"}]},"scheduler_stats":{"total_tasks":2,"completed_tasks":0,"failed_tasks":0,"cancelled_tasks":0,"start_time":"2025-10-10 18:07:58","end_time":"N/A","max_concurrent_used":0,"completion_rate":0E0},"streams":[{"stream_index":1,"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001","output_file":"./output/20251010_180758/test_stream2.mp4","status":"完成","start_time":"2025-10-10 18:07:58","end_time":"2025-10-10 18:08:18","bytes_received":0,"frames_received":567,"duration_seconds":20,"error_message":"","actual_duration_seconds":20},{"stream_index":0,"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001","output_file":"./output/20251010_180758/test_stream1.mp4","status":"完成","start_time":"2025-10-10 18:07:58","end_time":"2025-10-10 18:08:14","bytes_received":0,"frames_received":403,"duration_seconds":15,"error_message":"","actual_duration_seconds":16}],"summary":{"total_streams":2,"successful_streams":2,"failed_streams":0,"cancelled_streams":0,"total_bytes":0,"total_frames":970,"success_rate":1E0,"average_duration":1.8E1,"earliest_start":"2025-10-10 18:07:58","latest_end":"2025-10-10 18:08:18"}}

+ 1 - 0
output/20251010_180758/streams.csv

@@ -0,0 +1 @@
+Stream Index,RTSP URL,Output File,Status,Start Time,End Time,Duration (s),Planned Duration (s),Bytes Received,Frames Received,Error Message\n1,\ << stats.rtsp_url << ,\ << stats.output_file << ,\ << status_to_string(stats.status) << ,\ << format_timestamp(stats.start_time) << ,\ << format_timestamp(stats.end_time) << ,20,20,0,567,\ << stats.error_message << \n0,\ << stats.rtsp_url << ,\ << stats.output_file << ,\ << status_to_string(stats.status) << ,\ << format_timestamp(stats.start_time) << ,\ << format_timestamp(stats.end_time) << ,16,15,0,403,\ << stats.error_message << \n

BIN
output/20251010_180758/test_stream1.mp4


BIN
output/20251010_180758/test_stream2.mp4


+ 1 - 0
output/20251010_180858/report.txt

@@ -0,0 +1 @@
+========================================\nRTSP视频流拉取报告\n========================================\n生成时间: 2025-10-10 18:09:16\n\n配置信息:\n--------\n总轮询时长: 60 秒\n最大并发流数: 2\n输出目录: ./output/20251010_180858\n配置的流数量: 2\n\n调度器统计:\n----------\n总任务数: 2\n已完成: 0\n失败: 0\n取消: 0\n最大并发数: 0\n完成率: 0.00%\n调度开始时间: 2025-10-10 18:08:58\n调度结束时间: N/A\n总调度时长: N/A\n\n汇总统计:\n--------\n成功流数: 1 / 2\n成功率: 50.00%\n总接收字节: 0.00 B\n总接收帧数: 928\n平均持续时间: 17.0 秒\n\n详细流信息:\n==========\n流 1:\n  RTSP地址: rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001\n  输出文件: ./output/20251010_180858/test_stream2.mp4\n  状态: 已取消\n  开始时间: 2025-10-10 18:08:58\n  结束时间: 2025-10-10 18:09:16\n  持续时间: 18 秒\n  计划持续时间: 20 秒\n  接收字节: 0.00 B\n  接收帧数: 525\n\n流 0:\n  RTSP地址: rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001\n  输出文件: ./output/20251010_180858/test_stream1.mp4\n  状态: 完成\n  开始时间: 2025-10-10 18:08:58\n  结束时间: 2025-10-10 18:09:15\n  持续时间: 16 秒\n  计划持续时间: 15 秒\n  接收字节: 0.00 B\n  接收帧数: 403\n\n

+ 1 - 0
output/20251010_180858/rtsp_report.json

@@ -0,0 +1 @@
+{"report_generated_at":"2025-10-10 18:09:16","config":{"global_config":{"total_poll_duration_seconds":60,"max_concurrent_streams":2,"output_directory":"./output/20251010_180858","report_filename":"rtsp_report.json","connection_timeout_seconds":3,"read_timeout_seconds":5},"streams":[{"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001","duration_seconds":15,"weight":1E0,"output_filename":"test_stream1.mp4"},{"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001","duration_seconds":20,"weight":1.5E0,"output_filename":"test_stream2.mp4"}]},"scheduler_stats":{"total_tasks":2,"completed_tasks":0,"failed_tasks":0,"cancelled_tasks":0,"start_time":"2025-10-10 18:08:58","end_time":"N/A","max_concurrent_used":0,"completion_rate":0E0},"streams":[{"stream_index":1,"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001","output_file":"./output/20251010_180858/test_stream2.mp4","status":"已取消","start_time":"2025-10-10 18:08:58","end_time":"2025-10-10 18:09:16","bytes_received":0,"frames_received":525,"duration_seconds":20,"error_message":"","actual_duration_seconds":18},{"stream_index":0,"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001","output_file":"./output/20251010_180858/test_stream1.mp4","status":"完成","start_time":"2025-10-10 18:08:58","end_time":"2025-10-10 18:09:15","bytes_received":0,"frames_received":403,"duration_seconds":15,"error_message":"","actual_duration_seconds":16}],"summary":{"total_streams":2,"successful_streams":1,"failed_streams":0,"cancelled_streams":1,"total_bytes":0,"total_frames":928,"success_rate":5E-1,"average_duration":1.7E1,"earliest_start":"2025-10-10 18:08:58","latest_end":"2025-10-10 18:09:16"}}

+ 1 - 0
output/20251010_180858/streams.csv

@@ -0,0 +1 @@
+Stream Index,RTSP URL,Output File,Status,Start Time,End Time,Duration (s),Planned Duration (s),Bytes Received,Frames Received,Error Message\n1,\ << stats.rtsp_url << ,\ << stats.output_file << ,\ << status_to_string(stats.status) << ,\ << format_timestamp(stats.start_time) << ,\ << format_timestamp(stats.end_time) << ,18,20,0,525,\ << stats.error_message << \n0,\ << stats.rtsp_url << ,\ << stats.output_file << ,\ << status_to_string(stats.status) << ,\ << format_timestamp(stats.start_time) << ,\ << format_timestamp(stats.end_time) << ,16,15,0,403,\ << stats.error_message << \n

BIN
output/20251010_180858/test_stream1.mp4


BIN
output/20251010_180858/test_stream2.mp4


+ 1 - 0
output/20251010_181521/report.txt

@@ -0,0 +1 @@
+========================================\nRTSP视频流拉取报告\n========================================\n生成时间: 2025-10-10 18:15:42\n\n配置信息:\n--------\n总轮询时长: 60 秒\n最大并发流数: 2\n输出目录: ./output/20251010_181521\n配置的流数量: 2\n\n调度器统计:\n----------\n总任务数: 2\n已完成: 0\n失败: 0\n取消: 0\n最大并发数: 0\n完成率: 0.00%\n调度开始时间: 2025-10-10 18:15:21\n调度结束时间: N/A\n总调度时长: N/A\n\n汇总统计:\n--------\n成功流数: 2 / 2\n成功率: 100.00%\n总接收字节: 0.00 B\n总接收帧数: 1062\n平均持续时间: 17.5 秒\n\n详细流信息:\n==========\n流 1:\n  RTSP地址: rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001\n  输出文件: ./output/20251010_181521/test_stream2.mp4\n  状态: 完成\n  开始时间: 2025-10-10 18:15:21\n  结束时间: 2025-10-10 18:15:42\n  持续时间: 20 秒\n  计划持续时间: 20 秒\n  接收字节: 0.00 B\n  接收帧数: 589\n\n流 0:\n  RTSP地址: rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001\n  输出文件: ./output/20251010_181521/test_stream1.mp4\n  状态: 完成\n  开始时间: 2025-10-10 18:15:21\n  结束时间: 2025-10-10 18:15:37\n  持续时间: 15 秒\n  计划持续时间: 15 秒\n  接收字节: 0.00 B\n  接收帧数: 473\n\n

+ 1 - 0
output/20251010_181521/rtsp_report.json

@@ -0,0 +1 @@
+{"report_generated_at":"2025-10-10 18:15:42","config":{"global_config":{"total_poll_duration_seconds":60,"max_concurrent_streams":2,"output_directory":"./output/20251010_181521","report_filename":"rtsp_report.json","connection_timeout_seconds":3,"read_timeout_seconds":5},"streams":[{"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001","duration_seconds":15,"weight":1E0,"output_filename":"test_stream1.mp4"},{"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001","duration_seconds":20,"weight":1.5E0,"output_filename":"test_stream2.mp4"}]},"scheduler_stats":{"total_tasks":2,"completed_tasks":0,"failed_tasks":0,"cancelled_tasks":0,"start_time":"2025-10-10 18:15:21","end_time":"N/A","max_concurrent_used":0,"completion_rate":0E0},"streams":[{"stream_index":1,"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001","output_file":"./output/20251010_181521/test_stream2.mp4","status":"完成","start_time":"2025-10-10 18:15:21","end_time":"2025-10-10 18:15:42","bytes_received":0,"frames_received":589,"duration_seconds":20,"error_message":"","actual_duration_seconds":20},{"stream_index":0,"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001","output_file":"./output/20251010_181521/test_stream1.mp4","status":"完成","start_time":"2025-10-10 18:15:21","end_time":"2025-10-10 18:15:37","bytes_received":0,"frames_received":473,"duration_seconds":15,"error_message":"","actual_duration_seconds":15}],"summary":{"total_streams":2,"successful_streams":2,"failed_streams":0,"cancelled_streams":0,"total_bytes":0,"total_frames":1062,"success_rate":1E0,"average_duration":1.75E1,"earliest_start":"2025-10-10 18:15:21","latest_end":"2025-10-10 18:15:42"}}

+ 1 - 0
output/20251010_181521/streams.csv

@@ -0,0 +1 @@
+Stream Index,RTSP URL,Output File,Status,Start Time,End Time,Duration (s),Planned Duration (s),Bytes Received,Frames Received,Error Message\n1,\ << stats.rtsp_url << ,\ << stats.output_file << ,\ << status_to_string(stats.status) << ,\ << format_timestamp(stats.start_time) << ,\ << format_timestamp(stats.end_time) << ,20,20,0,589,\ << stats.error_message << \n0,\ << stats.rtsp_url << ,\ << stats.output_file << ,\ << status_to_string(stats.status) << ,\ << format_timestamp(stats.start_time) << ,\ << format_timestamp(stats.end_time) << ,15,15,0,473,\ << stats.error_message << \n

BIN
output/20251010_181521/test_stream1.mp4


BIN
output/20251010_181521/test_stream2.mp4


+ 1 - 0
output/20251010_181622/report.txt

@@ -0,0 +1 @@
+========================================\nRTSP视频流拉取报告\n========================================\n生成时间: 2025-10-10 18:16:42\n\n配置信息:\n--------\n总轮询时长: 60 秒\n最大并发流数: 2\n输出目录: ./output/20251010_181622\n配置的流数量: 2\n\n调度器统计:\n----------\n总任务数: 2\n已完成: 0\n失败: 0\n取消: 0\n最大并发数: 0\n完成率: 0.00%\n调度开始时间: 2025-10-10 18:16:22\n调度结束时间: N/A\n总调度时长: N/A\n\n汇总统计:\n--------\n成功流数: 2 / 2\n成功率: 100.00%\n总接收字节: 0.00 B\n总接收帧数: 999\n平均持续时间: 18.0 秒\n\n详细流信息:\n==========\n流 1:\n  RTSP地址: rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001\n  输出文件: ./output/20251010_181622/test_stream2.mp4\n  状态: 完成\n  开始时间: 2025-10-10 18:16:22\n  结束时间: 2025-10-10 18:16:42\n  持续时间: 20 秒\n  计划持续时间: 20 秒\n  接收字节: 0.00 B\n  接收帧数: 595\n\n流 0:\n  RTSP地址: rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001\n  输出文件: ./output/20251010_181622/test_stream1.mp4\n  状态: 完成\n  开始时间: 2025-10-10 18:16:22\n  结束时间: 2025-10-10 18:16:38\n  持续时间: 16 秒\n  计划持续时间: 15 秒\n  接收字节: 0.00 B\n  接收帧数: 404\n\n

+ 1 - 0
output/20251010_181622/rtsp_report.json

@@ -0,0 +1 @@
+{"report_generated_at":"2025-10-10 18:16:42","config":{"global_config":{"total_poll_duration_seconds":60,"max_concurrent_streams":2,"output_directory":"./output/20251010_181622","report_filename":"rtsp_report.json","connection_timeout_seconds":3,"read_timeout_seconds":5},"streams":[{"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001","duration_seconds":15,"weight":1E0,"output_filename":"test_stream1.mp4"},{"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001","duration_seconds":20,"weight":1.5E0,"output_filename":"test_stream2.mp4"}]},"scheduler_stats":{"total_tasks":2,"completed_tasks":0,"failed_tasks":0,"cancelled_tasks":0,"start_time":"2025-10-10 18:16:22","end_time":"N/A","max_concurrent_used":0,"completion_rate":0E0},"streams":[{"stream_index":1,"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001","output_file":"./output/20251010_181622/test_stream2.mp4","status":"完成","start_time":"2025-10-10 18:16:22","end_time":"2025-10-10 18:16:42","bytes_received":0,"frames_received":595,"duration_seconds":20,"error_message":"","actual_duration_seconds":20},{"stream_index":0,"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001","output_file":"./output/20251010_181622/test_stream1.mp4","status":"完成","start_time":"2025-10-10 18:16:22","end_time":"2025-10-10 18:16:38","bytes_received":0,"frames_received":404,"duration_seconds":15,"error_message":"","actual_duration_seconds":16}],"summary":{"total_streams":2,"successful_streams":2,"failed_streams":0,"cancelled_streams":0,"total_bytes":0,"total_frames":999,"success_rate":1E0,"average_duration":1.8E1,"earliest_start":"2025-10-10 18:16:22","latest_end":"2025-10-10 18:16:42"}}

+ 1 - 0
output/20251010_181622/streams.csv

@@ -0,0 +1 @@
+Stream Index,RTSP URL,Output File,Status,Start Time,End Time,Duration (s),Planned Duration (s),Bytes Received,Frames Received,Error Message\n1,\ << stats.rtsp_url << ,\ << stats.output_file << ,\ << status_to_string(stats.status) << ,\ << format_timestamp(stats.start_time) << ,\ << format_timestamp(stats.end_time) << ,20,20,0,595,\ << stats.error_message << \n0,\ << stats.rtsp_url << ,\ << stats.output_file << ,\ << status_to_string(stats.status) << ,\ << format_timestamp(stats.start_time) << ,\ << format_timestamp(stats.end_time) << ,16,15,0,404,\ << stats.error_message << \n

BIN
output/20251010_181622/test_stream1.mp4


BIN
output/20251010_181622/test_stream2.mp4


+ 1 - 0
output/20251010_181722/report.txt

@@ -0,0 +1 @@
+========================================\nRTSP视频流拉取报告\n========================================\n生成时间: 2025-10-10 18:17:42\n\n配置信息:\n--------\n总轮询时长: 60 秒\n最大并发流数: 2\n输出目录: ./output/20251010_181722\n配置的流数量: 2\n\n调度器统计:\n----------\n总任务数: 2\n已完成: 0\n失败: 0\n取消: 0\n最大并发数: 0\n完成率: 0.00%\n调度开始时间: 2025-10-10 18:17:22\n调度结束时间: N/A\n总调度时长: N/A\n\n汇总统计:\n--------\n成功流数: 2 / 2\n成功率: 100.00%\n总接收字节: 0.00 B\n总接收帧数: 1002\n平均持续时间: 18.0 秒\n\n详细流信息:\n==========\n流 1:\n  RTSP地址: rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001\n  输出文件: ./output/20251010_181722/test_stream2.mp4\n  状态: 完成\n  开始时间: 2025-10-10 18:17:22\n  结束时间: 2025-10-10 18:17:42\n  持续时间: 20 秒\n  计划持续时间: 20 秒\n  接收字节: 0.00 B\n  接收帧数: 599\n\n流 0:\n  RTSP地址: rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001\n  输出文件: ./output/20251010_181722/test_stream1.mp4\n  状态: 完成\n  开始时间: 2025-10-10 18:17:22\n  结束时间: 2025-10-10 18:17:38\n  持续时间: 16 秒\n  计划持续时间: 15 秒\n  接收字节: 0.00 B\n  接收帧数: 403\n\n

+ 1 - 0
output/20251010_181722/rtsp_report.json

@@ -0,0 +1 @@
+{"report_generated_at":"2025-10-10 18:17:42","config":{"global_config":{"total_poll_duration_seconds":60,"max_concurrent_streams":2,"output_directory":"./output/20251010_181722","report_filename":"rtsp_report.json","connection_timeout_seconds":3,"read_timeout_seconds":5},"streams":[{"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001","duration_seconds":15,"weight":1E0,"output_filename":"test_stream1.mp4"},{"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001","duration_seconds":20,"weight":1.5E0,"output_filename":"test_stream2.mp4"}]},"scheduler_stats":{"total_tasks":2,"completed_tasks":0,"failed_tasks":0,"cancelled_tasks":0,"start_time":"2025-10-10 18:17:22","end_time":"N/A","max_concurrent_used":0,"completion_rate":0E0},"streams":[{"stream_index":1,"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001","output_file":"./output/20251010_181722/test_stream2.mp4","status":"完成","start_time":"2025-10-10 18:17:22","end_time":"2025-10-10 18:17:42","bytes_received":0,"frames_received":599,"duration_seconds":20,"error_message":"","actual_duration_seconds":20},{"stream_index":0,"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001","output_file":"./output/20251010_181722/test_stream1.mp4","status":"完成","start_time":"2025-10-10 18:17:22","end_time":"2025-10-10 18:17:38","bytes_received":0,"frames_received":403,"duration_seconds":15,"error_message":"","actual_duration_seconds":16}],"summary":{"total_streams":2,"successful_streams":2,"failed_streams":0,"cancelled_streams":0,"total_bytes":0,"total_frames":1002,"success_rate":1E0,"average_duration":1.8E1,"earliest_start":"2025-10-10 18:17:22","latest_end":"2025-10-10 18:17:42"}}

+ 1 - 0
output/20251010_181722/streams.csv

@@ -0,0 +1 @@
+Stream Index,RTSP URL,Output File,Status,Start Time,End Time,Duration (s),Planned Duration (s),Bytes Received,Frames Received,Error Message\n1,\ << stats.rtsp_url << ,\ << stats.output_file << ,\ << status_to_string(stats.status) << ,\ << format_timestamp(stats.start_time) << ,\ << format_timestamp(stats.end_time) << ,20,20,0,599,\ << stats.error_message << \n0,\ << stats.rtsp_url << ,\ << stats.output_file << ,\ << status_to_string(stats.status) << ,\ << format_timestamp(stats.start_time) << ,\ << format_timestamp(stats.end_time) << ,16,15,0,403,\ << stats.error_message << \n

BIN
output/20251010_181722/test_stream1.mp4


BIN
output/20251010_181722/test_stream2.mp4


+ 1 - 0
output/20251011_082130/report.txt

@@ -0,0 +1 @@
+========================================\nRTSP视频流拉取报告\n========================================\n生成时间: 2025-10-11 08:21:50\n\n配置信息:\n--------\n总轮询时长: 60 秒\n最大并发流数: 2\n输出目录: ./output/20251011_082130\n配置的流数量: 2\n\n调度器统计:\n----------\n总任务数: 2\n已完成: 0\n失败: 0\n取消: 0\n最大并发数: 0\n完成率: 0.00%\n调度开始时间: 2025-10-11 08:21:30\n调度结束时间: N/A\n总调度时长: N/A\n\n汇总统计:\n--------\n成功流数: 2 / 2\n成功率: 100.00%\n总接收字节: 0.00 B\n总接收帧数: 950\n平均持续时间: 18.0 秒\n\n详细流信息:\n==========\n流 1:\n  RTSP地址: rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001\n  输出文件: ./output/20251011_082130/test_stream2.mp4\n  状态: 完成\n  开始时间: 2025-10-11 08:21:30\n  结束时间: 2025-10-11 08:21:50\n  持续时间: 20 秒\n  计划持续时间: 20 秒\n  接收字节: 0.00 B\n  接收帧数: 549\n\n流 0:\n  RTSP地址: rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001\n  输出文件: ./output/20251011_082130/test_stream1.mp4\n  状态: 完成\n  开始时间: 2025-10-11 08:21:30\n  结束时间: 2025-10-11 08:21:46\n  持续时间: 16 秒\n  计划持续时间: 15 秒\n  接收字节: 0.00 B\n  接收帧数: 401\n\n

+ 1 - 0
output/20251011_082130/rtsp_report.json

@@ -0,0 +1 @@
+{"report_generated_at":"2025-10-11 08:21:50","config":{"global_config":{"total_poll_duration_seconds":60,"max_concurrent_streams":2,"output_directory":"./output/20251011_082130","report_filename":"rtsp_report.json","connection_timeout_seconds":3,"read_timeout_seconds":5},"streams":[{"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001","duration_seconds":15,"weight":1E0,"output_filename":"test_stream1.mp4"},{"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001","duration_seconds":20,"weight":1.5E0,"output_filename":"test_stream2.mp4"}]},"scheduler_stats":{"total_tasks":2,"completed_tasks":0,"failed_tasks":0,"cancelled_tasks":0,"start_time":"2025-10-11 08:21:30","end_time":"N/A","max_concurrent_used":0,"completion_rate":0E0},"streams":[{"stream_index":1,"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001","output_file":"./output/20251011_082130/test_stream2.mp4","status":"完成","start_time":"2025-10-11 08:21:30","end_time":"2025-10-11 08:21:50","bytes_received":0,"frames_received":549,"duration_seconds":20,"error_message":"","actual_duration_seconds":20},{"stream_index":0,"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001","output_file":"./output/20251011_082130/test_stream1.mp4","status":"完成","start_time":"2025-10-11 08:21:30","end_time":"2025-10-11 08:21:46","bytes_received":0,"frames_received":401,"duration_seconds":15,"error_message":"","actual_duration_seconds":16}],"summary":{"total_streams":2,"successful_streams":2,"failed_streams":0,"cancelled_streams":0,"total_bytes":0,"total_frames":950,"success_rate":1E0,"average_duration":1.8E1,"earliest_start":"2025-10-11 08:21:30","latest_end":"2025-10-11 08:21:50"}}

+ 1 - 0
output/20251011_082130/streams.csv

@@ -0,0 +1 @@
+Stream Index,RTSP URL,Output File,Status,Start Time,End Time,Duration (s),Planned Duration (s),Bytes Received,Frames Received,Error Message\n1,\ << stats.rtsp_url << ,\ << stats.output_file << ,\ << status_to_string(stats.status) << ,\ << format_timestamp(stats.start_time) << ,\ << format_timestamp(stats.end_time) << ,20,20,0,549,\ << stats.error_message << \n0,\ << stats.rtsp_url << ,\ << stats.output_file << ,\ << status_to_string(stats.status) << ,\ << format_timestamp(stats.start_time) << ,\ << format_timestamp(stats.end_time) << ,16,15,0,401,\ << stats.error_message << \n

BIN
output/20251011_082130/test_stream1.mp4


BIN
output/20251011_082130/test_stream2.mp4


+ 1 - 0
output/20251011_082233/report.txt

@@ -0,0 +1 @@
+========================================\nRTSP视频流拉取报告\n========================================\n生成时间: 2025-10-11 08:22:53\n\n配置信息:\n--------\n总轮询时长: 60 秒\n最大并发流数: 2\n输出目录: ./output/20251011_082233\n配置的流数量: 2\n\n调度器统计:\n----------\n总任务数: 2\n已完成: 0\n失败: 0\n取消: 0\n最大并发数: 0\n完成率: 0.00%\n调度开始时间: 2025-10-11 08:22:33\n调度结束时间: N/A\n总调度时长: N/A\n\n汇总统计:\n--------\n成功流数: 2 / 2\n成功率: 100.00%\n总接收字节: 0.00 B\n总接收帧数: 933\n平均持续时间: 18.0 秒\n\n详细流信息:\n==========\n流 1:\n  RTSP地址: rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001\n  输出文件: ./output/20251011_082233/test_stream2.mp4\n  状态: 完成\n  开始时间: 2025-10-11 08:22:33\n  结束时间: 2025-10-11 08:22:53\n  持续时间: 20 秒\n  计划持续时间: 20 秒\n  接收字节: 0.00 B\n  接收帧数: 530\n\n流 0:\n  RTSP地址: rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001\n  输出文件: ./output/20251011_082233/test_stream1.mp4\n  状态: 完成\n  开始时间: 2025-10-11 08:22:33\n  结束时间: 2025-10-11 08:22:50\n  持续时间: 16 秒\n  计划持续时间: 15 秒\n  接收字节: 0.00 B\n  接收帧数: 403\n\n

+ 1 - 0
output/20251011_082233/rtsp_report.json

@@ -0,0 +1 @@
+{"report_generated_at":"2025-10-11 08:22:53","config":{"global_config":{"total_poll_duration_seconds":60,"max_concurrent_streams":2,"output_directory":"./output/20251011_082233","report_filename":"rtsp_report.json","connection_timeout_seconds":3,"read_timeout_seconds":5},"streams":[{"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001","duration_seconds":15,"weight":1E0,"output_filename":"test_stream1.mp4"},{"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001","duration_seconds":20,"weight":1.5E0,"output_filename":"test_stream2.mp4"}]},"scheduler_stats":{"total_tasks":2,"completed_tasks":0,"failed_tasks":0,"cancelled_tasks":0,"start_time":"2025-10-11 08:22:33","end_time":"N/A","max_concurrent_used":0,"completion_rate":0E0},"streams":[{"stream_index":1,"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001","output_file":"./output/20251011_082233/test_stream2.mp4","status":"完成","start_time":"2025-10-11 08:22:33","end_time":"2025-10-11 08:22:53","bytes_received":0,"frames_received":530,"duration_seconds":20,"error_message":"","actual_duration_seconds":20},{"stream_index":0,"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001","output_file":"./output/20251011_082233/test_stream1.mp4","status":"完成","start_time":"2025-10-11 08:22:33","end_time":"2025-10-11 08:22:50","bytes_received":0,"frames_received":403,"duration_seconds":15,"error_message":"","actual_duration_seconds":16}],"summary":{"total_streams":2,"successful_streams":2,"failed_streams":0,"cancelled_streams":0,"total_bytes":0,"total_frames":933,"success_rate":1E0,"average_duration":1.8E1,"earliest_start":"2025-10-11 08:22:33","latest_end":"2025-10-11 08:22:53"}}

+ 1 - 0
output/20251011_082233/streams.csv

@@ -0,0 +1 @@
+Stream Index,RTSP URL,Output File,Status,Start Time,End Time,Duration (s),Planned Duration (s),Bytes Received,Frames Received,Error Message\n1,\ << stats.rtsp_url << ,\ << stats.output_file << ,\ << status_to_string(stats.status) << ,\ << format_timestamp(stats.start_time) << ,\ << format_timestamp(stats.end_time) << ,20,20,0,530,\ << stats.error_message << \n0,\ << stats.rtsp_url << ,\ << stats.output_file << ,\ << status_to_string(stats.status) << ,\ << format_timestamp(stats.start_time) << ,\ << format_timestamp(stats.end_time) << ,16,15,0,403,\ << stats.error_message << \n

BIN
output/20251011_082233/test_stream1.mp4


BIN
output/20251011_082233/test_stream2.mp4


+ 1 - 0
output/20251011_082333/report.txt

@@ -0,0 +1 @@
+========================================\nRTSP视频流拉取报告\n========================================\n生成时间: 2025-10-11 08:23:54\n\n配置信息:\n--------\n总轮询时长: 60 秒\n最大并发流数: 2\n输出目录: ./output/20251011_082333\n配置的流数量: 2\n\n调度器统计:\n----------\n总任务数: 2\n已完成: 0\n失败: 0\n取消: 0\n最大并发数: 0\n完成率: 0.00%\n调度开始时间: 2025-10-11 08:23:33\n调度结束时间: N/A\n总调度时长: N/A\n\n汇总统计:\n--------\n成功流数: 2 / 2\n成功率: 100.00%\n总接收字节: 0.00 B\n总接收帧数: 942\n平均持续时间: 18.0 秒\n\n详细流信息:\n==========\n流 1:\n  RTSP地址: rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001\n  输出文件: ./output/20251011_082333/test_stream2.mp4\n  状态: 完成\n  开始时间: 2025-10-11 08:23:33\n  结束时间: 2025-10-11 08:23:54\n  持续时间: 20 秒\n  计划持续时间: 20 秒\n  接收字节: 0.00 B\n  接收帧数: 538\n\n流 0:\n  RTSP地址: rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001\n  输出文件: ./output/20251011_082333/test_stream1.mp4\n  状态: 完成\n  开始时间: 2025-10-11 08:23:33\n  结束时间: 2025-10-11 08:23:50\n  持续时间: 16 秒\n  计划持续时间: 15 秒\n  接收字节: 0.00 B\n  接收帧数: 404\n\n

+ 1 - 0
output/20251011_082333/rtsp_report.json

@@ -0,0 +1 @@
+{"report_generated_at":"2025-10-11 08:23:54","config":{"global_config":{"total_poll_duration_seconds":60,"max_concurrent_streams":2,"output_directory":"./output/20251011_082333","report_filename":"rtsp_report.json","connection_timeout_seconds":3,"read_timeout_seconds":5},"streams":[{"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001","duration_seconds":15,"weight":1E0,"output_filename":"test_stream1.mp4"},{"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001","duration_seconds":20,"weight":1.5E0,"output_filename":"test_stream2.mp4"}]},"scheduler_stats":{"total_tasks":2,"completed_tasks":0,"failed_tasks":0,"cancelled_tasks":0,"start_time":"2025-10-11 08:23:33","end_time":"N/A","max_concurrent_used":0,"completion_rate":0E0},"streams":[{"stream_index":1,"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001","output_file":"./output/20251011_082333/test_stream2.mp4","status":"完成","start_time":"2025-10-11 08:23:33","end_time":"2025-10-11 08:23:54","bytes_received":0,"frames_received":538,"duration_seconds":20,"error_message":"","actual_duration_seconds":20},{"stream_index":0,"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001","output_file":"./output/20251011_082333/test_stream1.mp4","status":"完成","start_time":"2025-10-11 08:23:33","end_time":"2025-10-11 08:23:50","bytes_received":0,"frames_received":404,"duration_seconds":15,"error_message":"","actual_duration_seconds":16}],"summary":{"total_streams":2,"successful_streams":2,"failed_streams":0,"cancelled_streams":0,"total_bytes":0,"total_frames":942,"success_rate":1E0,"average_duration":1.8E1,"earliest_start":"2025-10-11 08:23:33","latest_end":"2025-10-11 08:23:54"}}

+ 1 - 0
output/20251011_082333/streams.csv

@@ -0,0 +1 @@
+Stream Index,RTSP URL,Output File,Status,Start Time,End Time,Duration (s),Planned Duration (s),Bytes Received,Frames Received,Error Message\n1,\ << stats.rtsp_url << ,\ << stats.output_file << ,\ << status_to_string(stats.status) << ,\ << format_timestamp(stats.start_time) << ,\ << format_timestamp(stats.end_time) << ,20,20,0,538,\ << stats.error_message << \n0,\ << stats.rtsp_url << ,\ << stats.output_file << ,\ << status_to_string(stats.status) << ,\ << format_timestamp(stats.start_time) << ,\ << format_timestamp(stats.end_time) << ,16,15,0,404,\ << stats.error_message << \n

BIN
output/20251011_082333/test_stream1.mp4


BIN
output/20251011_082333/test_stream2.mp4


+ 1 - 0
output/20251011_082434/report.txt

@@ -0,0 +1 @@
+========================================\nRTSP视频流拉取报告\n========================================\n生成时间: 2025-10-11 08:24:54\n\n配置信息:\n--------\n总轮询时长: 60 秒\n最大并发流数: 2\n输出目录: ./output/20251011_082434\n配置的流数量: 2\n\n调度器统计:\n----------\n总任务数: 2\n已完成: 0\n失败: 0\n取消: 0\n最大并发数: 0\n完成率: 0.00%\n调度开始时间: 2025-10-11 08:24:34\n调度结束时间: N/A\n总调度时长: N/A\n\n汇总统计:\n--------\n成功流数: 2 / 2\n成功率: 100.00%\n总接收字节: 0.00 B\n总接收帧数: 944\n平均持续时间: 18.0 秒\n\n详细流信息:\n==========\n流 1:\n  RTSP地址: rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001\n  输出文件: ./output/20251011_082434/test_stream2.mp4\n  状态: 完成\n  开始时间: 2025-10-11 08:24:34\n  结束时间: 2025-10-11 08:24:54\n  持续时间: 20 秒\n  计划持续时间: 20 秒\n  接收字节: 0.00 B\n  接收帧数: 541\n\n流 0:\n  RTSP地址: rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001\n  输出文件: ./output/20251011_082434/test_stream1.mp4\n  状态: 完成\n  开始时间: 2025-10-11 08:24:34\n  结束时间: 2025-10-11 08:24:50\n  持续时间: 16 秒\n  计划持续时间: 15 秒\n  接收字节: 0.00 B\n  接收帧数: 403\n\n

+ 1 - 0
output/20251011_082434/rtsp_report.json

@@ -0,0 +1 @@
+{"report_generated_at":"2025-10-11 08:24:54","config":{"global_config":{"total_poll_duration_seconds":60,"max_concurrent_streams":2,"output_directory":"./output/20251011_082434","report_filename":"rtsp_report.json","connection_timeout_seconds":3,"read_timeout_seconds":5},"streams":[{"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001","duration_seconds":15,"weight":1E0,"output_filename":"test_stream1.mp4"},{"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001","duration_seconds":20,"weight":1.5E0,"output_filename":"test_stream2.mp4"}]},"scheduler_stats":{"total_tasks":2,"completed_tasks":0,"failed_tasks":0,"cancelled_tasks":0,"start_time":"2025-10-11 08:24:34","end_time":"N/A","max_concurrent_used":0,"completion_rate":0E0},"streams":[{"stream_index":1,"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000164_34020000001320000001","output_file":"./output/20251011_082434/test_stream2.mp4","status":"完成","start_time":"2025-10-11 08:24:34","end_time":"2025-10-11 08:24:54","bytes_received":0,"frames_received":541,"duration_seconds":20,"error_message":"","actual_duration_seconds":20},{"stream_index":0,"rtsp_url":"rtsp://218.94.57.146:40007/rtp/44010200492000000074_34020000001320000001","output_file":"./output/20251011_082434/test_stream1.mp4","status":"完成","start_time":"2025-10-11 08:24:34","end_time":"2025-10-11 08:24:50","bytes_received":0,"frames_received":403,"duration_seconds":15,"error_message":"","actual_duration_seconds":16}],"summary":{"total_streams":2,"successful_streams":2,"failed_streams":0,"cancelled_streams":0,"total_bytes":0,"total_frames":944,"success_rate":1E0,"average_duration":1.8E1,"earliest_start":"2025-10-11 08:24:34","latest_end":"2025-10-11 08:24:54"}}

+ 1 - 0
output/20251011_082434/streams.csv

@@ -0,0 +1 @@
+Stream Index,RTSP URL,Output File,Status,Start Time,End Time,Duration (s),Planned Duration (s),Bytes Received,Frames Received,Error Message\n1,\ << stats.rtsp_url << ,\ << stats.output_file << ,\ << status_to_string(stats.status) << ,\ << format_timestamp(stats.start_time) << ,\ << format_timestamp(stats.end_time) << ,20,20,0,541,\ << stats.error_message << \n0,\ << stats.rtsp_url << ,\ << stats.output_file << ,\ << status_to_string(stats.status) << ,\ << format_timestamp(stats.start_time) << ,\ << format_timestamp(stats.end_time) << ,16,15,0,403,\ << stats.error_message << \n

BIN
output/20251011_082434/test_stream1.mp4


BIN
output/20251011_082434/test_stream2.mp4


+ 121 - 0
src/http_server.cpp

@@ -33,6 +33,8 @@ bool HttpServer::start() {
     std::cout << "  GET  /api/videos - 列出所有视频" << std::endl;
     std::cout << "  GET  /api/timestamps - 列出所有时间戳目录" << std::endl;
     std::cout << "  GET  /api/videos/{timestamp} - 列出指定时间戳目录的视频" << std::endl;
+    std::cout << "  GET  /api/reports - 列出所有轮询报告" << std::endl;
+    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;
     std::cout << "Web界面:" << std::endl;
@@ -214,6 +216,10 @@ HttpResponse HttpServer::route_request(const HttpRequest& request) {
             return handle_list_videos(request);
         } else if (request.path == "/api/timestamps") {
             return handle_list_timestamps(request);
+        } else if (request.path == "/api/reports") {
+            return handle_list_reports(request);
+        } else if (request.path.find("/api/report/") == 0) {
+            return handle_get_report(request);
         } else if (request.path.find("/api/videos/") == 0) {
             return handle_get_video_info(request);
         } else if (request.path.find("/videos/") == 0) {
@@ -794,6 +800,121 @@ HttpResponse HttpServer::handle_static_file(const HttpRequest& request) {
     }
 }
 
+HttpResponse HttpServer::handle_list_reports(const HttpRequest& request) {
+    try {
+        auto timestamps = list_timestamp_directories();
+        
+        // 构建JSON响应
+        boost::json::array reports_array;
+        for (const auto& timestamp : timestamps) {
+            std::string report_path = output_directory_ + "/" + timestamp + "/rtsp_report.json";
+            
+            // 检查报告文件是否存在
+            if (std::filesystem::exists(report_path)) {
+                // 读取报告文件
+                std::ifstream file(report_path);
+                if (file.is_open()) {
+                    std::string content((std::istreambuf_iterator<char>(file)),
+                                       std::istreambuf_iterator<char>());
+                    file.close();
+                    
+                    try {
+                        // 解析JSON
+                        auto report = boost::json::parse(content);
+                        boost::json::object report_info;
+                        report_info["timestamp"] = timestamp;
+                        report_info["has_report"] = true;
+                        
+                        // 提取摘要信息
+                        if (report.is_object()) {
+                            auto& obj = report.as_object();
+                            if (obj.contains("summary")) {
+                                report_info["summary"] = obj.at("summary");
+                            }
+                            if (obj.contains("report_generated_at")) {
+                                report_info["generated_at"] = obj.at("report_generated_at");
+                            }
+                        }
+                        
+                        reports_array.push_back(report_info);
+                    } catch (...) {
+                        // JSON解析失败,跳过
+                    }
+                }
+            }
+        }
+        
+        boost::json::object result;
+        result["count"] = reports_array.size();
+        result["reports"] = reports_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_report(const HttpRequest& request) {
+    try {
+        // 从路径中提取时间戳 /api/report/{timestamp}
+        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 timestamp = path.substr(pos + 1);
+        std::string report_path = output_directory_ + "/" + timestamp + "/rtsp_report.json";
+        
+        // 检查文件是否存在
+        if (!std::filesystem::exists(report_path)) {
+            boost::json::object error_obj;
+            error_obj["error"] = "Not Found";
+            error_obj["message"] = "报告文件不存在";
+            error_obj["timestamp"] = timestamp;
+            return HttpResponse(404, "Not Found", boost::json::serialize(error_obj));
+        }
+        
+        // 读取报告文件
+        std::ifstream file(report_path);
+        if (!file.is_open()) {
+            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));
+        }
+        
+        std::string content((std::istreambuf_iterator<char>(file)),
+                           std::istreambuf_iterator<char>());
+        file.close();
+        
+        // 验证JSON格式
+        try {
+            boost::json::parse(content);
+        } catch (const std::exception& e) {
+            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));
+        }
+        
+        return HttpResponse(200, "OK", content);
+        
+    } 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::generate_index_page() {
     std::string html = R"(
 <!DOCTYPE html>

+ 107 - 0
video_manager.html

@@ -359,6 +359,7 @@
             <div class="tabs">
                 <button class="tab active" onclick="switchTab('timestamps')">时间戳目录</button>
                 <button class="tab" onclick="switchTab('videos')">所有视频</button>
+                <button class="tab" onclick="switchTab('reports')">轮询报告</button>
             </div>
 
             <div id="timestamps-tab" class="tab-content active">
@@ -368,6 +369,10 @@
             <div id="videos-tab" class="tab-content">
                 <div class="loading">加载中...</div>
             </div>
+
+            <div id="reports-tab" class="tab-content">
+                <div class="loading">加载中...</div>
+            </div>
         </div>
     </div>
 
@@ -674,10 +679,112 @@
             }
         }
 
+        async function loadReports() {
+            const container = document.getElementById('reports-tab');
+            container.innerHTML = '<div class="loading">加载中...</div>';
+
+            try {
+                const response = await fetch(`${API_BASE}/api/reports`);
+                const data = await response.json();
+
+                if (data.count === 0) {
+                    container.innerHTML = '<div class="loading">暂无轮询报告</div>';
+                    return;
+                }
+
+                let html = '<div class="timestamp-list">';
+                for (const report of data.reports) {
+                    const summary = report.summary || {};
+                    const successRate = (summary.success_rate * 100 || 0).toFixed(1);
+                    
+                    html += `
+                        <div class="timestamp-item">
+                            <div class="timestamp-header">
+                                <div>
+                                    <div class="timestamp-name">📊 ${report.timestamp}</div>
+                                    <div class="timestamp-info">
+                                        生成时间: ${report.generated_at || 'N/A'}
+                                    </div>
+                                    <div class="timestamp-info">
+                                        成功率: ${successRate}% | 
+                                        成功: ${summary.successful_streams || 0} | 
+                                        失败: ${summary.failed_streams || 0} | 
+                                        总帧数: ${summary.total_frames || 0}
+                                    </div>
+                                </div>
+                                <div>
+                                    <button class="btn btn-primary" onclick="viewReport('${report.timestamp}')"查看详情</button>
+                                </div>
+                            </div>
+                            <div id="report-${report.timestamp}"></div>
+                        </div>
+                    `;
+                }
+                html += '</div>';
+                container.innerHTML = html;
+            } catch (error) {
+                container.innerHTML = `<div class="error">加载失败: ${error.message}</div>`;
+            }
+        }
+
+        async function viewReport(timestamp) {
+            const container = document.getElementById(`report-${timestamp}`);
+            
+            if (container.innerHTML) {
+                container.innerHTML = '';
+                return;
+            }
+
+            container.innerHTML = '<div class="loading">加载中...</div>';
+
+            try {
+                const response = await fetch(`${API_BASE}/api/report/${timestamp}`);
+                const report = await response.json();
+
+                let html = '<div style="margin-top: 15px; padding: 15px; background: white; border-radius: 8px;">';
+                
+                // 显示流详细信息
+                if (report.streams && report.streams.length > 0) {
+                    html += '<h3 style="margin-bottom: 15px;">流详细信息</h3>';
+                    html += '<div class="video-grid">';
+                    
+                    for (const stream of report.streams) {
+                        const statusEmoji = stream.status === '完成' ? '✅' : '❌';
+                        const statusClass = stream.status === '完成' ? 'success' : 'error';
+                        
+                        html += `
+                            <div class="video-card">
+                                <div class="video-name">${statusEmoji} 流 ${stream.stream_index}</div>
+                                <div class="video-info">📹 RTSP: ${stream.rtsp_url}</div>
+                                <div class="video-info">🎬 状态: <span style="color: ${stream.status === '完成' ? 'green' : 'red'}; font-weight: bold;">${stream.status}</span></div>
+                                <div class="video-info">💾 输出: ${stream.output_file || 'N/A'}</div>
+                                <div class="video-info">⏱️ 开始: ${stream.start_time || 'N/A'}</div>
+                                <div class="video-info">⏹️ 结束: ${stream.end_time || 'N/A'}</div>
+                                <div class="video-info">🕒 持续: ${stream.actual_duration_seconds || 0}秒 / ${stream.duration_seconds || 0}秒</div>
+                                <div class="video-info">🎬 帧数: ${stream.frames_received || 0}</div>
+                                <div class="video-info">📊 大小: ${formatBytes(stream.bytes_received || 0)}</div>
+                                ${stream.error_message ? `<div class="video-info" style="color: red;">❌ 错误: ${stream.error_message}</div>` : ''}
+                            </div>
+                        `;
+                    }
+                    
+                    html += '</div>';
+                } else {
+                    html += '<p>暂无流信息</p>';
+                }
+                
+                html += '</div>';
+                container.innerHTML = html;
+            } catch (error) {
+                container.innerHTML = `<div class="error">加载失败: ${error.message}</div>`;
+            }
+        }
+
         function loadData() {
             loadStats();
             loadTimestamps();
             loadAllVideos();
+            loadReports();
         }
 
         // 页面加载时初始化