gongxiangp2/docs/视频转码问题修复说明.md

7.3 KiB
Raw Blame History

视频转码问题修复说明

问题描述

视频类素材上传后,后台转码任务未触发。素材 ID: 0895f84d-8837-465e-be50-4db57b5eab9e

根本原因分析

通过诊断脚本发现以下问题:

  1. 类型不匹配问题

    • lib/video-transcode-queue.ts 的 process 方法中,taskFn 的类型定义缺少 retryCount 参数
    • 导致 TypeScript 类型检查可能失败,或运行时参数传递错误
  2. 错误被静默吞掉

    • app/api/materials/upload/route.ts 中,转码任务入队失败时,错误被 catch 捕获但只打印日志
    • 没有更新数据库状态,导致无法追踪问题
  3. 动态导入可能失败

    • 使用 await import('@/lib/video-transcode-task') 可能因模块路径、循环依赖等问题失败
    • 错误被捕获后,用户无法感知
  4. 缺少文件验证

    • 入队前未验证视频文件是否存在
    • 如果文件不存在,转码会失败,但错误信息不够明确
  5. 队列处理逻辑不完善

    • 队列处理时缺少详细的日志和错误处理
    • 如果 process 方法本身出错,可能导致队列卡死
  6. 恢复逻辑只在 POST 请求时触发

    • 恢复逻辑只在转码 API 的 POST 方法中触发
    • GET 请求(前端轮询)时不会触发恢复,导致 pending 状态的视频无法自动恢复

修复内容

1. 修复类型不匹配 (lib/video-transcode-queue.ts)

问题:process 方法的 taskFn 类型定义与 add 方法不一致

修复:

// 修复前
private async process(
  taskFn: (materialId: string, videoSourceUrl: string, needsCoverImage: boolean) => Promise<void>
): Promise<void>

// 修复后
private async process(
  taskFn: (materialId: string, videoSourceUrl: string, needsCoverImage: boolean, retryCount?: number) => Promise<void>
): Promise<void>

2. 改进错误处理和日志 (app/api/materials/upload/route.ts)

问题:入队失败时错误被静默吞掉

修复:

  • 改用静态导入,避免动态导入失败
  • 入队失败时,记录详细错误信息到数据库
  • 保持 pending 状态,允许手动重试
  • 添加详细的日志输出
// 修复后
import { enqueueVideoTranscodeTask } from '@/lib/video-transcode-task';

if (videoSourceUrl) {
  const needsCoverImage = !previewUrl || previewUrl.trim() === '';
  
  try {
    await enqueueVideoTranscodeTask(material.id, videoSourceUrl, needsCoverImage);
    console.log('[Upload] ✅ Video transcode task enqueued successfully:', material.id);
  } catch (e: any) {
    // 记录详细错误信息到数据库
    const errorMsg = e?.message || String(e) || '未知错误';
    const errorStack = e?.stack ? `\n堆栈: ${e.stack.substring(0, 1000)}` : '';
    const fullErrorMsg = `入队失败: ${errorMsg}${errorStack}`;
    
    console.error('[Upload] ❌ Failed to enqueue video transcode task:', {
      materialId: material.id,
      videoSourceUrl,
      error: errorMsg,
      stack: e?.stack,
    });
    
    // 更新数据库状态,记录错误信息
    await prisma.material.update({
      where: { id: material.id },
      data: {
        videoProcessingStatus: 'pending',
        transcodeLastError: fullErrorMsg.substring(0, 1000),
      } as any,
    });
  }
}

3. 添加文件验证 (lib/video-transcode-task.ts)

问题:入队前未验证文件是否存在

修复:

  • 添加 validateVideoFile 函数,验证视频文件是否存在
  • 入队前先验证文件,如果不存在则抛出错误
async function validateVideoFile(videoSourceUrl: string): Promise<boolean> {
  // 验证逻辑...
}

export async function enqueueVideoTranscodeTask(...) {
  // 验证视频文件是否存在
  const fileExists = await validateVideoFile(videoSourceUrl)
  if (!fileExists) {
    throw new Error(`视频文件不存在: ${videoSourceUrl}`)
  }
  
  // 入队...
}

4. 改进队列处理逻辑 (lib/video-transcode-queue.ts)

问题:队列处理时缺少详细的日志和错误处理

修复:

  • 添加详细的日志输出
  • 改进错误处理,确保队列不会卡死
  • 添加任务参数验证
  • 改进 add 方法,检查重复任务
async add(...) {
  // 检查是否已在队列中等待
  const alreadyInQueue = this.queue.some(t => t.materialId === materialId)
  if (alreadyInQueue) {
    console.log(`[TranscodeQueue] Material ${materialId} is already in queue, skipping`)
    return
  }
  
  console.log(`[TranscodeQueue] Adding task to queue: materialId=${materialId}...`)
  // ...
}

private async process(...) {
  try {
    // 验证任务参数
    if (!task.materialId || !task.videoSourceUrl) {
      console.error(`[TranscodeQueue] Invalid task parameters:`, task)
      task.reject(new Error('Invalid task parameters'))
      return
    }
    
    // 详细的日志输出
    console.log(`[TranscodeQueue] 🚀 Starting task for material ${task.materialId}...`)
    
    // 改进的错误处理
    // ...
  } catch (error) {
    console.error('[TranscodeQueue] Error in process() method:', error)
    // 确保队列不会卡死
    setTimeout(() => {
      this.process(taskFn).catch(err => {
        console.error('[TranscodeQueue] Error in retry process():', err)
      })
    }, 1000)
  }
}

5. 改进恢复逻辑 (app/api/materials/transcode/route.ts)

问题:恢复逻辑只在 POST 请求时触发

修复:

  • 在 GET 请求时也触发恢复逻辑
  • 确保前端轮询时能自动恢复 pending 状态的视频
// GET - 查询转码状态
export async function GET(request: NextRequest) {
  // 懒加载恢复:第一次调用时自动恢复中断的任务(包括 GET 请求)
  await ensureRecoveryCompleted();
  
  // ...
}

新增工具

1. 诊断脚本 (scripts/diagnose-video-transcode.ts)

用于诊断视频转码问题,检查:

  • 素材信息
  • 视频文件是否存在
  • 转码状态分析
  • 转码产物检查
  • 诊断总结和建议

使用方法:

tsx scripts/diagnose-video-transcode.ts <materialId>

2. 手动触发转码脚本 (scripts/manual-trigger-transcode.ts)

用于手动触发视频转码,修复未触发转码的素材。

使用方法:

tsx scripts/manual-trigger-transcode.ts <materialId>

验证修复

  1. 检查修复后的代码

    • 所有类型定义已统一
    • 错误处理已完善
    • 日志输出已详细
  2. 测试新上传的视频

    • 上传一个视频素材
    • 检查是否自动触发转码
    • 查看日志确认入队成功
  3. 修复已有问题素材

    • 使用诊断脚本检查问题素材
    • 使用手动触发脚本修复

后续建议

  1. 监控和告警

    • 添加转码任务入队失败的监控
    • 设置告警,及时发现未触发的转码任务
  2. 定期检查

    • 定期检查 pending 状态的视频
    • 自动触发未转码的视频
  3. 改进上传逻辑

    • 确保所有视频类型都正确设置 videoSourceUrl 为本地路径
    • 对于旧版本的 S3 URL,转码时自动处理

相关文件

  • app/api/materials/upload/route.ts - 上传接口
  • lib/video-transcode-task.ts - 转码任务封装
  • lib/video-transcode-queue.ts - 转码队列
  • app/api/materials/transcode/route.ts - 转码 API
  • scripts/diagnose-video-transcode.ts - 诊断脚本
  • scripts/manual-trigger-transcode.ts - 手动触发脚本