13 KiB
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) │
└─────────────────────────────────────────────────────────────────┘
数据流说明
- 推流流程: 主播 → RTMP → SRS → HTTP Callback → API Server → 更新房间状态
- 观看流程: 观众 → React App → 获取房间信息 → flv.js/hls.js → SRS HTTP-FLV/HLS
- 管理流程: 用户 → React App → REST API → API Server → 房间 CRUD
Components and Interfaces
1. SRS 流媒体服务器
职责: 接收 RTMP 推流,转换并分发 HTTP-FLV 和 HLS 流
配置接口:
# 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
// 接口定义
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
// 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 组件接口
interface LivePlayerProps {
room: Room;
preferFlv?: boolean; // 优先使用 FLV(低延迟)
}
// 播放器内部逻辑
// 1. 检测浏览器是否支持 flv.js (需要 MSE 支持)
// 2. 支持则使用 HTTP-FLV,否则降级到 HLS
// 3. 监听播放错误,自动重连
Data Models
Room 数据模型
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 响应格式
// 成功响应
interface SuccessResponse<T> {
success: true;
data: T;
}
// 错误响应
interface ErrorResponse {
success: false;
error: {
code: string;
message: string;
};
}
// SRS 回调响应
interface SRSCallbackResponse {
code: number; // 0 表示成功
}
流地址格式
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 |
错误处理策略
- API 层: 统一错误响应格式,包含错误码和消息
- SRS 回调: 返回
{ code: 0 }表示成功,非零表示失败 - 前端: 显示友好的错误提示,支持重试操作
- 播放器: 自动重连机制,最多重试 3 次
Testing Strategy
单元测试
使用 Jest 进行单元测试,覆盖以下模块:
- Room Service: 房间 CRUD 操作
- Validation: 输入验证逻辑
- URL Generator: 流地址生成
属性测试 (Property-Based Testing)
使用 fast-check 库进行属性测试:
- Property 1: 房间创建往返一致性
- Property 2: 新房间初始状态
- Property 3: 空标题拒绝
- Property 4: 回调状态转换
- Property 5: 流地址格式
- Property 6: 房间列表完整性
测试配置
// jest.config.js
module.exports = {
testEnvironment: 'node',
testMatch: ['**/*.test.js', '**/*.property.test.js'],
collectCoverageFrom: ['server/**/*.js'],
coverageThreshold: {
global: { branches: 80, functions: 80, lines: 80 }
}
};
属性测试示例
// 每个属性测试运行 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 }
);
});