Laravel表单提交含视频后处理的实时进度条实现方法
实现视频上传后后续操作的进度显示
核心思路
上传完成后,后端通过异步队列处理视频裁剪、加密、入库操作,同时将各步骤进度存入缓存;前端通过轮询进度API获取实时状态,更新进度条和提示文字。
后端实现(Laravel)
1. 修改上传接口,启动异步任务
修改VideoController的store方法,返回唯一任务ID,后续操作交给队列处理:
public function store(Request $request) { // 处理视频上传,保存到临时目录 $video = $request->file('video'); $tempPath = $video->store('temp_videos'); // 生成唯一任务ID $taskId = Str::uuid()->toString(); // 初始化进度缓存 Cache::put("video_process_{$taskId}", [ 'percent' => 0, 'status' => '上传完成,开始处理视频', 'completed' => false, 'error' => null ], now()->addMinutes(30)); // 分发队列任务 ProcessVideoJob::dispatch($tempPath, $taskId); // 返回任务ID给前端 return response()->json([ 'task_id' => $taskId, 'message' => '视频上传成功,开始处理' ]); }
2. 创建队列任务,更新进度
生成ProcessVideoJob,在视频处理的每个关键步骤更新缓存中的进度:
namespace App\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Cache; use FFMpeg; class ProcessVideoJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; protected $tempPath; protected $taskId; public function __construct($tempPath, $taskId) { $this->tempPath = $tempPath; $this->taskId = $taskId; } public function handle() { try { // 步骤1:视频裁剪,更新进度为30% Cache::put("video_process_{$this->taskId}", [ 'percent' => 30, 'status' => '正在裁剪视频', 'completed' => false, 'error' => null ], now()->addMinutes(30)); // 执行FFMPEG裁剪逻辑(根据需求调整) $ffmpeg = FFMpeg::open(storage_path("app/{$this->tempPath}")); $ffmpeg->filters()->clip(\FFMpeg\Coordinate\TimeCode::fromSeconds(0), \FFMpeg\Coordinate\TimeCode::fromSeconds(60)); $clipPath = storage_path("app/processed_videos/clip_{$this->taskId}.mp4"); $ffmpeg->save(new \FFMpeg\Format\Video\X264(), $clipPath); // 步骤2:视频加密,更新进度为60% Cache::put("video_process_{$this->taskId}", [ 'percent' => 60, 'status' => '正在加密视频', 'completed' => false, 'error' => null ], now()->addMinutes(30)); // 执行FFMPEG加密逻辑(根据加密方案调整) $encryptedPath = storage_path("app/processed_videos/encrypted_{$this->taskId}.mp4"); shell_exec("ffmpeg -i {$clipPath} -c:v libx264 -c:a aac -encryption_scheme cenc-aes-ctr -encryption_key your_encryption_key {$encryptedPath}"); // 步骤3:插入数据库记录,更新进度为90% Cache::put("video_process_{$this->taskId}", [ 'percent' => 90, 'status' => '正在保存记录到数据库', 'completed' => false, 'error' => null ], now()->addMinutes(30)); \App\Models\Video::create([ 'path' => str_replace(storage_path('app/'), '', $encryptedPath), // 填充其他业务字段 ]); // 处理完成,更新进度为100% Cache::put("video_process_{$this->taskId}", [ 'percent' => 100, 'status' => '视频处理完成', 'completed' => true, 'error' => null ], now()->addMinutes(30)); // 清理临时文件 unlink(storage_path("app/{$this->tempPath}")); unlink($clipPath); } catch (\Exception $e) { // 捕获错误,更新缓存状态 Cache::put("video_process_{$this->taskId}", [ 'percent' => 0, 'status' => '处理失败', 'completed' => true, 'error' => $e->getMessage() ], now()->addMinutes(30)); } } }
3. 新增进度查询API
在VideoController添加进度查询方法:
public function getProcessProgress($taskId) { $progress = Cache::get("video_process_{$taskId}", [ 'percent' => 0, 'status' => '任务不存在或已过期', 'completed' => true, 'error' => null ]); return response()->json($progress); }
添加路由:
Route::get('/video/progress/{taskId}', [VideoController::class, 'getProcessProgress'])->name('video.progress');
前端实现(JS)
1. 修改上传完成后的逻辑,启动进度轮询
更新原JS代码中的completeHandler函数:
function completeHandler(event) { console.log('Response: ' + event.target.responseText); const response = JSON.parse(event.target.responseText); if (response.task_id) { $('#videoSubmit').html('处理中...'); $("#videoSubmit").attr("disabled", true); // 每2秒轮询一次进度 const pollInterval = setInterval(() => { fetch(`{{ route('video.progress', ':taskId') }}`.replace(':taskId', response.task_id), { headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') } }) .then(res => res.json()) .then(data => { updateProgressBar(data.percent); $('#progress').text(`${data.status}: ${data.percent}%`); // 处理完成或出错时停止轮询 if (data.completed) { clearInterval(pollInterval); $('#videoSubmit').html('Submit Your Video'); $("#videoSubmit").attr("disabled", false); if (data.error) { $('.errorDisplayDiv').html(`处理失败: ${data.error}`); } else { $('.errorDisplayDiv').html('视频处理完成'); // 可添加跳转或表单重置逻辑 } } }) .catch(err => { clearInterval(pollInterval); $('#videoSubmit').html('Submit Your Video'); $("#videoSubmit").attr("disabled", false); $('.errorDisplayDiv').html('查询进度失败'); }); }, 2000); } else { // 兼容无任务ID的情况 $('#videoSubmit').html('Submit Your Video'); $("#videoSubmit").attr("disabled", false); updateProgressBar(100); $('.errorDisplayDiv').html('视频上传成功'); } }
2. 确保HTML元素支持状态显示
确认页面中有以下元素:
<div class="progress"> <div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div> </div> <div id="progress">Progress: 0%</div> <div class="errorDisplayDiv"></div>
注意事项
- 确保Laravel队列已配置并运行(执行
php artisan queue:work启动队列处理器)。 - 缓存驱动建议使用Redis,避免多服务器环境下的进度读取问题。
- 可根据实际处理步骤的耗时调整进度百分比分配(比如加密耗时更长,可设置为40%)。
- 任务缓存设置了30分钟过期,避免无效缓存占用资源。
内容的提问来源于stack exchange,提问作者vinod
相关产品推荐
相关产品推荐

