如何在PHP(Laravel)中追加视频流片段
屏幕录制片段合并问题
我首次做视频相关开发,现在遇到个问题:想实现录制用户屏幕,每5秒把片段发去Laravel后端,后端把这些5秒片段合并成完整视频(比如3段合并成15秒),但现在合并后的视频在MacOS上打不开,不知道问题出在哪。
已尝试的代码
前端JS代码
function accessRecord(id) { navigator.mediaDevices.getDisplayMedia({ audio: false, video: true }).then(startStream).catch(failedStream) } function startStream(stream) { const mediaRecorder = new MediaRecorder(stream, { // 试过添加mimeType // mimeType: 'video/mp4' }); mediaRecorder.start(5000); mediaRecorder.ondataavailable = (e) => { fetch('http://localhost/api/session/12/video/stream', { method: 'POST', body: new Blob([e.data]), }).then(() => { console.log('success') }).catch((e) => { console.log('error') console.log(e); }) }; }
Laravel后端代码
Route::post('session/{id}/video/stream', function (Request $request) { $videoData = $request->getContent(); $filename = 'video_uuid-video.mp4'; if (!file_exists(storage_path('app/videos/' . $filename))) { touch(storage_path('app/videos/' . $filename)); } \Illuminate\Support\Facades\Storage::append('videos/' . $filename, $videoData); return response()->json(['message' => 'Video frame uploaded successfully']); });
问题根源
直接把视频片段的二进制内容追加写入文件是行不通的。像MP4、WebM这类视频文件有特定的容器结构,包含文件头、索引信息、媒体数据等部分,单个录制片段没有完整的文件头,直接拼接会破坏文件结构,导致播放器无法识别。另外,MediaRecorder默认生成的是WebM格式,强制命名为.mp4本身就不符合格式规范。
解决方案步骤
1. 前端统一录制格式
先检测浏览器支持的标准视频格式,保证片段兼容性:
// 优先选择浏览器支持的格式 const supportedMimeTypes = [ 'video/webm;codecs=vp9,opus', 'video/webm;codecs=vp8,opus', 'video/mp4;codecs=avc1,mp4a' ]; const mimeType = supportedMimeTypes.find(type => MediaRecorder.isTypeSupported(type)); const mediaRecorder = new MediaRecorder(stream, { mimeType });
2. 后端单独存储片段,用FFmpeg合并
视频合并必须依赖专业多媒体工具FFmpeg,它能正确处理视频容器结构,把片段合并为可播放的完整视频。
修改后端存储片段的逻辑
use Illuminate\Support\Str; Route::post('session/{id}/video/stream', function (Request $request) { $sessionId = $id; // 生成唯一片段文件名,按会话分组存储 $segmentName = 'segments/' . $sessionId . '_' . Str::uuid() . '.webm'; // 单独存储每个片段 \Illuminate\Support\Facades\Storage::put($segmentName, $request->getContent()); return response()->json(['message' => 'Video segment uploaded successfully']); });
添加合并接口,调用FFmpeg处理
Route::post('session/{id}/video/merge', function (Request $request) { $sessionId = $id; $segmentDir = storage_path('app/segments/'); // 获取当前会话下的所有片段,按创建时间排序 $segments = glob($segmentDir . $sessionId . '_*.webm'); usort($segments, function($a, $b) { return filemtime($a) - filemtime($b); }); // 生成FFmpeg需要的片段列表文件 $listFile = $segmentDir . $sessionId . '_list.txt'; $listContent = ''; foreach ($segments as $seg) { $listContent .= "file '" . str_replace("'", "\\'", $seg) . "'\n"; } file_put_contents($listFile, $listContent); // 生成合并后的完整视频 $outputFile = storage_path('app/videos/' . $sessionId . '_full.webm'); // 执行FFmpeg合并命令,直接复制编码保证效率 exec("ffmpeg -f concat -safe 0 -i $listFile -c copy $outputFile"); // 清理临时文件(可选) unlink($listFile); foreach ($segments as $seg) { unlink($seg); } return response()->json(['message' => 'Video merged successfully', 'video_path' => $outputFile]); });
3. 前端发送录制结束信号
停止录制时调用合并接口:
function stopRecording() { mediaRecorder.stop(); // 触发后端合并逻辑 fetch(`http://localhost/api/session/12/video/merge`, { method: 'POST' }).then(res => res.json()) .then(data => console.log(data.message)); }
关键注意事项
- 服务器必须安装FFmpeg:Linux用
apt install ffmpeg,Mac用brew install ffmpeg。 - 提前创建
storage/app/segments目录并确保读写权限。 - 如果需要输出MP4格式,修改FFmpeg命令为:
ffmpeg -f concat -safe 0 -i $listFile -c:v libx264 -c:a aac $outputFile.mp4
内容的提问来源于stack exchange,提问作者Bruno Francisco
相关产品推荐
相关产品推荐

