# Design Document ## Overview 本设计文档描述了基于 SRS 的个人直播系统的技术架构和实现方案。系统采用前后端分离架构,使用 SRS 作为核心流媒体服务器处理视频流的接收和分发,Node.js 后端提供业务 API,React 前端提供用户界面。 ### 技术栈选型 | 组件 | 技术选择 | 理由 | |------|----------|------| | 流媒体服务器 | SRS 5.0 | 高性能、低延迟、支持多协议、Docker 部署简单 | | 后端框架 | Node.js + Express | 轻量级、异步处理能力强、生态丰富 | | 前端框架 | React 18 | 组件化开发、生态成熟、性能优秀 | | 视频播放器 | flv.js + hls.js | flv.js 支持 HTTP-FLV 低延迟播放,hls.js 提供 HLS 兼容性 | | 容器化 | Docker + Docker Compose | 简化部署、环境一致性 | ## Architecture ``` ┌─────────────────────────────────────────────────────────────────┐ │ 用户层 (Users) │ ├─────────────────────────────────────────────────────────────────┤ │ ┌─────────────┐ ┌─────────────────┐ │ │ │ 主播 │ │ 观众 │ │ │ │ (OBS/FFmpeg)│ │ (浏览器) │ │ │ └──────┬──────┘ └────────┬────────┘ │ │ │ RTMP 推流 │ HTTP 请求 │ │ │ rtmp://host:1935/live/{streamKey} │ │ └─────────┼─────────────────────────────────────────┼────────────┘ │ │ ┌─────────┼─────────────────────────────────────────┼────────────┐ │ ▼ ▼ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ SRS Server │ │ React Frontend │ │ │ │ (Docker) │ │ (Port 3000) │ │ │ │ │ │ │ │ │ │ RTMP: 1935 │◄───HTTP-FLV/HLS───│ flv.js/hls.js │ │ │ │ HTTP: 8080 │ │ │ │ │ └────────┬────────┘ └────────┬────────┘ │ │ │ │ │ │ │ HTTP Callback │ REST API │ │ │ (on_publish/on_unpublish) │ │ │ ▼ ▼ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Node.js API Server │ │ │ │ (Port 3001) │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │ │ │ │ │ Room API │ │ Callback │ │ Room Store │ │ │ │ │ │ Controller │ │ Handler │ │ (In-Memory) │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────────┘ │ │ │ └─────────────────────────────────────────────────────────┘ │ │ 服务层 (Services) │ └─────────────────────────────────────────────────────────────────┘ ``` ### 数据流说明 1. **推流流程**: 主播 → RTMP → SRS → HTTP Callback → API Server → 更新房间状态 2. **观看流程**: 观众 → React App → 获取房间信息 → flv.js/hls.js → SRS HTTP-FLV/HLS 3. **管理流程**: 用户 → React App → REST API → API Server → 房间 CRUD ## Components and Interfaces ### 1. SRS 流媒体服务器 **职责**: 接收 RTMP 推流,转换并分发 HTTP-FLV 和 HLS 流 **配置接口**: ```conf # srs.conf listen 1935; max_connections 1000; daemon off; srs_log_tank console; http_server { enabled on; listen 8080; dir ./objs/nginx/html; } vhost __defaultVhost__ { hls { enabled on; hls_fragment 2; hls_window 10; hls_path ./objs/nginx/html; hls_m3u8_file [app]/[stream].m3u8; hls_ts_file [app]/[stream]-[seq].ts; } http_remux { enabled on; mount [vhost]/[app]/[stream].flv; } http_hooks { enabled on; on_publish http://api-server:3001/api/srs/on_publish; on_unpublish http://api-server:3001/api/srs/on_unpublish; } } ``` ### 2. API Server (Node.js + Express) **职责**: 提供业务 API,处理 SRS 回调,管理房间数据 #### 2.1 Room API Controller ```typescript // 接口定义 interface RoomController { // 获取所有房间 GET /api/rooms -> Room[] // 创建房间 POST /api/rooms { title: string, streamerName: string } -> Room // 获取单个房间 GET /api/rooms/:id -> Room // 删除房间 DELETE /api/rooms/:id -> { success: boolean } } ``` #### 2.2 SRS Callback Handler ```typescript // SRS 回调接口 interface SRSCallbackHandler { // 推流开始回调 POST /api/srs/on_publish { action: 'on_publish', app: string, // 应用名,如 'live' stream: string // 流名称,即 streamKey } -> { code: 0 } // 推流结束回调 POST /api/srs/on_unpublish { action: 'on_unpublish', app: string, stream: string } -> { code: 0 } } ``` ### 3. React Frontend **职责**: 提供用户界面,包括房间列表、直播播放、房间创建 #### 3.1 组件结构 ``` src/ ├── components/ │ ├── RoomList.jsx # 房间列表组件 │ ├── RoomCard.jsx # 房间卡片组件 │ ├── LivePlayer.jsx # 直播播放器组件 │ ├── CreateRoom.jsx # 创建房间弹窗 │ └── StreamInfo.jsx # 推流信息展示 ├── pages/ │ ├── HomePage.jsx # 首页(房间列表) │ └── RoomPage.jsx # 房间详情页 ├── services/ │ └── api.js # API 调用封装 └── App.jsx # 应用入口 ``` #### 3.2 LivePlayer 组件接口 ```typescript interface LivePlayerProps { room: Room; preferFlv?: boolean; // 优先使用 FLV(低延迟) } // 播放器内部逻辑 // 1. 检测浏览器是否支持 flv.js (需要 MSE 支持) // 2. 支持则使用 HTTP-FLV,否则降级到 HLS // 3. 监听播放错误,自动重连 ``` ## Data Models ### Room 数据模型 ```typescript interface Room { id: string; // 房间唯一标识 (UUID) title: string; // 房间标题 streamerName: string; // 主播名称 streamKey: string; // 推流密钥 (与 id 相同) isLive: boolean; // 是否正在直播 viewerCount: number; // 观看人数 createdAt: string; // 创建时间 (ISO 8601) startedAt?: string; // 开播时间 (ISO 8601) } ``` ### API 响应格式 ```typescript // 成功响应 interface SuccessResponse { success: true; data: T; } // 错误响应 interface ErrorResponse { success: false; error: { code: string; message: string; }; } // SRS 回调响应 interface SRSCallbackResponse { code: number; // 0 表示成功 } ``` ### 流地址格式 ```typescript interface StreamUrls { rtmp: string; // rtmp://host:1935/live/{streamKey} flv: string; // http://host:8080/live/{streamKey}.flv hls: string; // http://host:8080/live/{streamKey}.m3u8 } ``` ## Correctness Properties *A property is a characteristic or behavior that should hold true across all valid executions of a system-essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.* ### Property 1: Room Creation Round-Trip Consistency *For any* valid room creation request with non-empty title and streamer name, creating the room and then retrieving it by ID SHALL return a room with matching title, streamer name, a unique stream key, and a valid creation timestamp. **Validates: Requirements 1.1, 1.4** ### Property 2: New Rooms Start Offline *For any* newly created room, the initial `isLive` status SHALL be `false`. **Validates: Requirements 1.2** ### Property 3: Empty Title Rejection *For any* room creation request where the title is empty or consists only of whitespace characters, the system SHALL reject the request and return a validation error without creating a room. **Validates: Requirements 1.3** ### Property 4: Publish Callback Status Transition *For any* room that exists in the system, when a publish callback is received with that room's stream key, the room's `isLive` status SHALL transition to `true`. When an unpublish callback is received, the status SHALL transition to `false`. **Validates: Requirements 2.3, 2.5** ### Property 5: Stream URLs Contain Required Formats *For any* room returned by the API, the stream URLs SHALL include both HTTP-FLV (`.flv`) and HLS (`.m3u8`) format URLs containing the room's stream key. **Validates: Requirements 3.1** ### Property 6: Room List Completeness *For any* set of created rooms, requesting the room list SHALL return all rooms with their current status, title, and streamer name intact. **Validates: Requirements 4.1, 4.2** ## Error Handling ### API Error Codes | 错误码 | 描述 | HTTP 状态码 | |--------|------|-------------| | `VALIDATION_ERROR` | 请求参数验证失败 | 400 | | `ROOM_NOT_FOUND` | 房间不存在 | 404 | | `STREAM_KEY_INVALID` | 推流密钥无效 | 401 | | `INTERNAL_ERROR` | 服务器内部错误 | 500 | ### 错误处理策略 1. **API 层**: 统一错误响应格式,包含错误码和消息 2. **SRS 回调**: 返回 `{ code: 0 }` 表示成功,非零表示失败 3. **前端**: 显示友好的错误提示,支持重试操作 4. **播放器**: 自动重连机制,最多重试 3 次 ## Testing Strategy ### 单元测试 使用 Jest 进行单元测试,覆盖以下模块: 1. **Room Service**: 房间 CRUD 操作 2. **Validation**: 输入验证逻辑 3. **URL Generator**: 流地址生成 ### 属性测试 (Property-Based Testing) 使用 fast-check 库进行属性测试: 1. **Property 1**: 房间创建往返一致性 2. **Property 2**: 新房间初始状态 3. **Property 3**: 空标题拒绝 4. **Property 4**: 回调状态转换 5. **Property 5**: 流地址格式 6. **Property 6**: 房间列表完整性 ### 测试配置 ```javascript // jest.config.js module.exports = { testEnvironment: 'node', testMatch: ['**/*.test.js', '**/*.property.test.js'], collectCoverageFrom: ['server/**/*.js'], coverageThreshold: { global: { branches: 80, functions: 80, lines: 80 } } }; ``` ### 属性测试示例 ```javascript // 每个属性测试运行 100 次迭代 // 标注格式: **Feature: live-streaming-system, Property {number}: {property_text}** import fc from 'fast-check'; // **Feature: live-streaming-system, Property 2: New Rooms Start Offline** test('newly created rooms should have isLive = false', () => { fc.assert( fc.property( fc.string({ minLength: 1 }), // title fc.string({ minLength: 1 }), // streamerName (title, streamerName) => { const room = createRoom({ title, streamerName }); return room.isLive === false; } ), { numRuns: 100 } ); }); ```