本文目录导读:

针对PHP项目中的会议录制文件存储、转码与管理,这是一个典型的视频处理与媒资管理场景,下面从架构设计、技术选型到代码实现,提供一套完整的解决方案。
整体架构设计
录制文件上传 → 存储系统 → 异步转码队列 → 转码处理 → 转码结果存储 → 播放/管理
核心组件:
- 存储层:对象存储(OSS/S3)或本地NAS
- 转码引擎:FFmpeg(命令行工具或PHP扩展)
- 队列系统:Redis + PHP队列(如 Laravel Queue / 自定义)
- 数据库:MySQL/PostgreSQL 记录元数据与转码状态
- 播放器:Video.js / HLS.js(支持HLS流)
存储方案
推荐:对象存储(阿里云OSS / AWS S3 / MinIO)
// 使用阿里云OSS SDK示例 use OSS\OssClient; $ossClient = new OssClient($accessKeyId, $accessKeySecret, $endpoint); // 上传原始录制文件 $ossClient->uploadFile($bucket, 'recordings/2025/04/original/meeting_123.mp4', $localFilePath);
优势:弹性扩容、CDN加速、支持分片上传大文件。
本地存储(小规模场景)
$basePath = '/data/recordings/';
$datePath = date('Y/m/d');
$targetPath = $basePath . $datePath . '/' . $fileName;
move_uploaded_file($_FILES['file']['tmp_name'], $targetPath);
注意:需要配置Nginx/Apache的访问权限与防盗链。
文件命名规范
function generateFileName($meetingId, $extension = 'mp4') {
$timestamp = time();
$uuid = uniqid();
return sprintf('%s_%s_%s.%s', $meetingId, $timestamp, $uuid, $extension);
}
转码方案
转码目标(推荐输出格式)
| 用途 | 格式 | 说明 |
|---|---|---|
| 播放 | HLS (.m3u8) | 自适应码率,支持分段加载 |
| 播放 | MP4 | 兼容老设备 |
| 缩略图 | JPEG/PNG | 视频封面 |
| 音频 | MP3/AAC | 语音回放 |
使用 FFmpeg 转码(核心)
class VideoTranscoder {
private $ffmpegPath = '/usr/bin/ffmpeg';
public function transcodeToHLS($inputFile, $outputDir) {
$cmd = sprintf(
'%s -i %s -c:v libx264 -c:a aac -strict -2 -f hls -hls_time 10 -hls_list_size 0 -hls_segment_filename %s/segment_%%03d.ts %s/playlist.m3u8',
$this->ffmpegPath,
escapeshellarg($inputFile),
escapeshellarg($outputDir),
escapeshellarg($outputDir)
);
exec($cmd, $output, $returnCode);
return $returnCode === 0;
}
public function generateThumbnail($inputFile, $outputFile, $time = '00:00:05') {
$cmd = sprintf(
'%s -i %s -ss %s -vframes 1 -vf scale=320:-1 %s',
$this->ffmpegPath,
escapeshellarg($inputFile),
$time,
escapeshellarg($outputFile)
);
exec($cmd, $output, $returnCode);
return $returnCode === 0;
}
}
多码率自适应转码(高级)
public function transcodeAdaptive($inputFile, $outputBase) {
// 生成 360p, 720p, 1080p
$profiles = [
['width' => 640, 'height' => 360, 'bitrate' => '800k', 'suffix' => '360p'],
['width' => 1280, 'height' => 720, 'bitrate' => '2500k', 'suffix' => '720p'],
['width' => 1920, 'height' => 1080, 'bitrate' => '5000k', 'suffix' => '1080p'],
];
foreach ($profiles as $p) {
// 每个分辨率生成独立的HLS目录
}
// 最后合并生成主playlist.m3u8
}
异步任务队列管理
数据库表结构设计
CREATE TABLE recordings (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
meeting_id VARCHAR(64) NOT NULL,
original_filename VARCHAR(255),
original_path VARCHAR(500), -- 原始文件路径/OSS key
file_size BIGINT,
duration INT, -- 视频时长(秒)
status ENUM('pending', 'uploaded', 'transcoding', 'completed', 'failed') DEFAULT 'pending',
hls_path VARCHAR(500), -- HLS播放地址
thumbnail_path VARCHAR(500), -- 缩略图路径
created_at DATETIME,
updated_at DATETIME
);
CREATE TABLE transcoding_jobs (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
recording_id BIGINT,
job_type ENUM('hls', 'thumbnail', 'audio'),
status ENUM('queued', 'processing', 'completed', 'failed'),
output_path VARCHAR(500),
error_message TEXT,
created_at DATETIME,
completed_at DATETIME
);
队列处理流程(基于Redis)
// 1. 上传完成后,将转码任务推入队列
$redis->lpush('transcode:queue', json_encode([
'recording_id' => 123,
'input_path' => '/path/to/original.mp4',
'output_dir' => '/data/hls/meeting_123/'
]));
// 2. Worker进程从队列中取出并执行
while ($job = $redis->rpop('transcode:queue')) {
$data = json_decode($job, true);
$transcoder = new VideoTranscoder();
$result = $transcoder->transcodeToHLS($data['input_path'], $data['output_dir']);
// 更新数据库状态
updateRecordingStatus($data['recording_id'], $result ? 'completed' : 'failed');
}
管理后台功能
文件管理界面(示例逻辑)
// 获取录制列表
$recordings = DB::table('recordings')
->where('meeting_id', $meetingId)
->orderBy('created_at', 'desc')
->get();
// 显示播放/下载/删除操作
foreach ($recordings as $rec) {
echo '<a href="' . $rec->hls_path . '">播放</a>';
echo '<a href="/download?id=' . $rec->id . '">下载</a>';
echo '<button onclick="deleteRecording(' . $rec->id . ')">删除</button>';
}
// 删除时,需同时清理OSS/本地文件
function deleteRecording($id) {
$rec = DB::table('recordings')->find($id);
// 删除原始文件
Storage::delete($rec->original_path);
// 删除HLS目录
Storage::deleteDirectory(dirname($rec->hls_path));
// 删除数据库记录
DB::table('recordings')->delete($id);
}
状态监控与重试机制
// 检测失败任务
$failedJobs = DB::table('transcoding_jobs')
->where('status', 'failed')
->where('created_at', '>', now()->subHours(1))
->get();
foreach ($failedJobs as $job) {
// 重试逻辑:最多重试3次
if ($job->retry_count < 3) {
$redis->lpush('transcode:queue', $job->payload);
DB::table('transcoding_jobs')
->where('id', $job->id)
->increment('retry_count');
}
}
播放器集成
<!-- HLS播放器示例 -->
<link href="https://cdn.jsdelivr.net/npm/video.js@7/dist/video-js.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/video.js@7/dist/video.min.js"></script>
<video id="my-video" class="video-js vjs-default-skin" controls width="800" height="450">
<source src="https://your-domain.com/recordings/meeting_123/playlist.m3u8" type="application/x-mpegURL">
</video>
<script>
var player = videojs('my-video');
</script>
关键注意事项
| 问题 | 解决方案 |
|---|---|
| 大文件上传超时 | 使用分片上传(OSS分片 / 前端分块) |
| 转码CPU/内存消耗 | 使用进程队列 + 限制并发数(如 Supervisor) |
| 存储成本优化 | 设置生命周期规则:原始文件自动删除,保留HLS |
| 播放卡顿 | 使用CDN加速 + 多码率自适应 |
| 安全性 | 播放地址加签(OSS防盗链 / 动态Token) |
推荐技术栈(生产级)
- 存储:阿里云OSS + CDN
- 转码服务:阿里云/腾讯云媒体处理服务(代替自建FFmpeg)
- 队列:Laravel Horizon / RabbitMQ
- 框架:Laravel(自带Queue、Storage、ORM等)
如果预算有限,可以用 FFmpeg + Redis队列 + Nginx 搭建轻量方案,能满足中小规模(日均1000+会议)需求。
需要更具体的代码实现(如分片上传、转码进度回调等)可以进一步沟通。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播