You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 00:29:51