本文目录导读:

开发一个PHP直播后台系统,主要涉及推流管理、直播流调度、用户鉴权、录制/转码以及播放器对接等核心功能。
由于PHP本身不擅长直接处理底层的RTMP/RTSP协议(这类工作通常由Nginx、SRS、FFmpeg等工具完成),PHP在直播后台中的角色通常是业务逻辑层(API服务)和控制管理后台。
以下是使用PHP开发直播后台的完整技术方案和核心代码思路:
核心技术栈选型
| 功能模块 | 推荐技术/工具 | 说明 |
|---|---|---|
| 流媒体服务器 | SRS、Nginx-RTMP、ZLMediaKit | 核心推流/拉流,PHP不直接实现,通过API控制 |
| 视频处理 | FFmpeg | 转码、截图、录制 |
| PHP框架 | Laravel、ThinkPHP 6/8、Webman | 快速开发API和后台管理 |
| 协议支持 | HLS、HTTP-FLV、WebRTC(转) | 前端播放,PHP生成鉴权URL |
| 消息队列 | Redis Stream、RabbitMQ | 异步处理录制、转码、回调 |
| 实时通信 | WebSocket + PHP (Workerman/Swoole) | 实时推送在线人数、弹幕、状态变更 |
核心业务流程与PHP代码实现
直播流创建与推流地址生成(鉴权)
用户发起直播时,后台需要生成唯一的推流地址(rtmp://domain/live/{stream_key}),并验证用户身份。
// 使用Laravel框架示例
namespace App\Services;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Cache;
use App\Models\LiveRoom;
class LiveStreamService
{
/**
* 创建一场直播并生成推流地址
* @param int $userId 主播用户ID
* @return array
*/
public function createStream(int $userId): array
{
// 1. 生成唯一的流密钥(Stream Key)
$streamKey = Str::uuid()->toString();
// 2. 生成推流鉴权签名(预防盗推)
$secret = env('PUSH_SECRET_KEY'); // 后台配置的密钥
$expire = time() + 3600; // 1小时有效
$sign = md5("/live/{$streamKey}-{$expire}-{$secret}");
$pushUrl = "rtmp://push.yourdomain.com/live/{$streamKey}?txSecret={$sign}&txTime={$expire}";
// 3. 写入数据库(状态为待推流)
LiveRoom::create([
'user_id' => $userId,
'stream_key' => $streamKey,
'status' => 'waiting',
'created_at' => now()
]);
return [
'push_url' => $pushUrl,
'play_urls' => [
'hls' => "https://play.yourdomain.com/live/{$streamKey}.m3u8",
'flv' => "https://play.yourdomain.com/live/{$streamKey}.flv"
]
];
}
}
回调处理(PHP接收流媒体服务器通知)
流媒体服务器(如SRS)在推流开始、结束、录制完成时会POST请求你PHP服务器的回调地址。
// routes/api.php
Route::post('/callback/streaming', [StreamCallbackController::class, 'handle']);
// StreamCallbackController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\LiveRoom;
class StreamCallbackController extends Controller
{
public function handle(Request $request)
{
// 验证签名(防止伪造回调)
// 此处省略验证逻辑
$event = $request->input('action'); // publish, unpublish, record_mp4等
$streamKey = $request->input('stream_id');
$clientIp = $request->input('client_ip');
$room = LiveRoom::where('stream_key', $streamKey)->first();
if (!$room) {
return response()->json(['code' => 404]);
}
switch ($event) {
case 'publish': // 推流开始
$room->update([
'status' => 'live',
'stream_start_time' => now(),
'push_ip' => $clientIp
]);
// 可以发送WebSocket通知前端主播已开播
break;
case 'unpublish': // 推流结束
$room->update([
'status' => 'end',
'stream_end_time' => now(),
'duration' => $room->stream_start_time->diffInSeconds(now())
]);
// 触发录制文件转存逻辑
break;
case 'record_mp4': // 录制文件生成完成
$recordFile = $request->input('file_path');
// 异步转换为点播视频(可投递到队列)
break;
}
return response()->json(['code' => 0]);
}
}
播放鉴权与防盗链
播放地址同样需要签名,防止被任意盗用。
public function generatePlayUrl(string $streamKey): string
{
$secret = env('PLAY_SECRET_KEY');
$expire = time() + 3600;
$sign = md5("/live/{$streamKey}-{$expire}-{$secret}");
return "https://play.yourdomain.com/live/{$streamKey}.flv?sign={$sign}&expire={$expire}";
}
// 在Nginx层验证播放签名(使用Lua或Nginx Secure Link模块)
// 或者PHP写一个中间件验证(不推荐,性能较差)
在线人数与实时消息(WebSocket + PHP)
推荐使用Workerman(纯PHP)或Swoole WebSocket服务器处理实时推送。
// 基于Workerman的WebSocket服务(需要独立运行)
use Workerman\Worker;
use Workerman\Lib\Timer;
$ws_worker = new Worker('websocket://0.0.0.0:8282');
$ws_worker->count = 4;
// 存储所有连接的客户端(按直播间分组)
$connections = [];
$ws_worker->onConnect = function ($connection) {
// 客户端连接时,等待发送join房间消息
};
$ws_worker->onMessage = function ($connection, $data) use (&$connections) {
$msg = json_decode($data, true);
if ($msg['type'] == 'join') {
$roomId = $msg['room_id'];
$connections[$roomId][] = $connection;
$connection->roomId = $roomId;
// 广播在线人数
$count = count($connections[$roomId]);
$msg = json_encode(['type' => 'online_count', 'count' => $count]);
foreach ($connections[$roomId] as $conn) {
$conn->send($msg);
}
}
};
$ws_worker->onClose = function ($connection) use (&$connections) {
if (isset($connection->roomId, $connections[$connection->roomId])) {
// 移除断开的客户端
$connections[$connection->roomId] = array_filter($connections[$connection->roomId], function($c) use ($connection) {
return $c != $connection;
});
}
};
Worker::runAll();
PHP后台可以通过Redis Pub/Sub 发送消息到WebSocket服务器,实现弹幕、礼物通知等。
异步任务处理(录制转码、截图等)
使用队列(Laravel Job 或 ThinkPHP Queue)异步执行耗时操作。
// 触发转码任务
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class TranscodeVideoJob implements ShouldQueue
{
use InteractsWithQueue, Queueable, SerializesModels;
protected $inputPath;
protected $outputPath;
public function handle()
{
// 调用FFmpeg
$command = "ffmpeg -i {$this->inputPath} -c:v libx264 -c:a aac -hls_time 10 -hls_list_size 0 -f hls {$this->outputPath}";
exec($command, $output, $returnCode);
// 更新数据库状态
}
}
关键架构图
sequenceDiagram
participant 主播
participant 推流端(OBS)
participant PHP后端
participant 流媒体服务器(SRS)
participant 观众前端
participant WebSocket服务器
主播->>PHP后端: 创建直播 (生成推流地址)
PHP后端-->>主播: 返回推流URL
主播->>推流端: 输入RTMP地址
推流端->>流媒体服务器: 推流
流媒体服务器->>PHP后端: 回调 (on_publish)
PHP后端->>数据库: 更新状态为直播中
PHP后端->>WebSocket服务器: 发布开播事件
WebSocket服务器-->>观众前端: 推送主播开播通知
观众前端->>PHP后端: 请求播放地址 (带鉴权)
PHP后端-->>观众前端: 返回签名后的FLV/HLS地址
观众前端->>流媒体服务器: 拉流播放
WebSocket服务器-->>观众前端: 实时推送聊天/礼物/人数
注意事项与性能优化
- 不要在PHP中直接处理RTMP数据包,PHP的IO模型不适合,所有流媒体工作交给Nginx-rtmp、SRS或M7s。
- 回调验证:务必验证流媒体服务器回调的
ip和token,防止恶意伪造请求。 - 数据库设计:直播流表要有
stream_key、status(live/end/replay)、start_time、end_time索引。 - 大并发:PHP-FPM不擅长长连接,在线人数、聊天请务必使用Swoole/Workerman维护长连接。
- 录制存储:录制文件建议直接写到OSS(阿里云/腾讯云/COS),并用PHP生成临时访问URL。
- 转码:如果只有PHP开发能力,腾讯云/阿里云直播服务提供现成的转码API(LVB),可以直接调用SDK。
如果不会用SRS/Nginx配置怎么办?
推荐使用腾讯云直播(CSS)或阿里云直播的API:
- 前端推流地址:
rtmp://push.xxx.com/live/{stream_id}?bizid=xxx&txSecret=xxx - PHP只需要调用云厂商的
CreateLiveStream等API,无需自己搭流媒体服务器。 - 回调处理逻辑与自建完全一致,只是请求参数不同。
这种方案PHP开发量最少,但会产生云服务费用。
PHP直播后台的核心是鉴权生成推拉流地址、处理回调、管理直播间状态以及实时消息推送,流媒体转发和视频编解码依赖SRS/FFmpeg等外部服务,使用Laravel+Workerman可以开发出一个功能完整的直播后台管理系统。