返回

LinkedIn API 视频分片上传问题排查与解决

javascript

LinkedIn API 视频分片上传问题排查

使用 LinkedIn API 上传视频,分片上传是一个复杂的过程。 过程中任何一步出现偏差,都会导致视频上传后显示不完整,或者出现只有部分片段的情况。本篇文章将分析视频分片上传的常见问题,并给出解决方案。

问题:分片上传视频只显示一个随机片段

问题: 视频按照分片成功上传,服务端也成功接收了所有的分片 ID,最终发布的视频只显示了其中一个随机分片,而非完整视频。

原因分析: 问题不在于分片生成或单个分片的上传。 问题可能出在服务端接收和合并分片数据的方式上。以下列出一些常见的原因:

  1. 初始化上传信息错误: 初始化上传时,服务端可能没有正确记录需要合并的分片数量或顺序。当后续上传完成时,服务端缺少必要信息,无法将分片按正确顺序组装成完整的视频。
  2. 分片 ID 使用错误: 可能存在部分分片在上传完成后返回了错误的 ID,或者分片 ID 被意外更改,导致服务端在最终合并分片时找不到正确的分片。
  3. 上传顺序问题: 部分 API 要求分片必须按照特定顺序上传,如果你的代码没有严格按照这个顺序,服务端可能会出错。
  4. 服务端 bug: 也有可能是 LinkedIn 服务端自身的问题。尽管这种情况比较少见,但不能完全排除。

解决方案:检查上传流程

以下给出一些检查步骤和代码示例。

步骤 1:确认分片上传初始化逻辑正确

  • 确认初始化上传 API 调用时传递了正确的参数。

  • 检查响应数据,特别留意用于最终合并分片的 uploadToken 是否有效,video urn(比如 'urn:li:video:videoId')是否是正确的。

  • 需要确保每次初始化上传都能获取到一个全新的且正确的 uploadToken 以及 video urn.

    const response = await fetch(
         `${LN_API_BASE_URL}/${LN_API_VERSION}/videos?action=initializeUpload`,
         requestOptions // 请替换为你的初始化上传请求选项
     );
    
     const result = await response.json();
    
    if(!result || !result.uploadToken || !result.video ){
     throw new Error ("init upload failed")
    }
    
    // result 应包含uploadToken 和 video (video 是 'urn:li:video:videoId')
     const { uploadToken, video} = result;
     // 使用 uploadToken, video 进行后续上传分片操作
    
    

步骤 2:确认分片生成逻辑和路径

  • 代码使用了 ffmpeg 处理视频分片。确保分片的起始时间和时长计算准确,并且正确设置了输出路径。

  • 特别需要确认每次分片的开始时间和长度。 分片的起始时间,需要从 0 开始, 以分片长度递增,一直到整个视频的时长结束。

async function splitVideo(inputPath, chunkSize, outputDir) {
  try {
    await fs.mkdir(outputDir, { recursive: true });

    const metadata = await new Promise((resolve, reject) => {
        ffmpeg.ffprobe(inputPath, (err, metadata) => {
            if (err) reject(err);
            else resolve(metadata);
        });
    });

    const duration = metadata.format.duration;
    const chunkDuration = chunkSize / (metadata.format.size / duration);
    const promises = [];

     for (let startTime = 0, chunkIndex = 0; startTime < duration; startTime += chunkDuration, chunkIndex++) {
        const outputPath = path.join(outputDir, `chunk_${chunkIndex}.mp4`);
        // 注意 startTime 从 0 开始,并使用 chunkDuration 递增。
        promises.push(processChunk(inputPath, startTime, chunkDuration, outputPath));
    }
    
    await Promise.all(promises);
    console.log('Video split successfully');
  } catch (error) {
        console.error('Error splitting video:', error);
    }
}


function processChunk(inputPath, startTime, chunkDuration, outputPath) {
    return new Promise((resolve, reject) => {
    ffmpeg(inputPath)
            .setStartTime(startTime)
            .setDuration(chunkDuration)
            .output(outputPath)
            .on('end', () => resolve())
            .on('error', reject)
            .run();
    });
}

步骤 3:确认上传分片的顺序

  • 严格按照 API 文档中要求的顺序上传分片。 某些 API 要求按照分片序号或者在返回的 instructions 中指定的顺序进行上传。
  • instructions.map 函数中需要确认正确获取分片ID
await splitVideoToTemp(fileName);
            const uploadedChunksIds = [];
            await Promise.all(
                instructions?.map(async (instruction, index) => {
                     // 从instruction 对象中,根据你的API接口定义,正确拿到index. 
                    // 如果不是数组 index 就根据instruction提供的特定顺序上传
                    const chunkName = `chunk_${index}.mp4`;
                    const outputPath = path.join(
                        process.cwd() + '/tmp/chunks',
                        chunkName
                    );
                    const blob = await getFileAsBlob(outputPath);
                    const result = uploadVideo(
                        instruction.uploadUrl, // 需要从 instruction 对象中拿到正确的 上传 url
                        blob,
                        params.pageAccessToken
                    );
                     const uploadedChunkId = await result;
                     // 注意:部分 API 接口返回的上传ID可能会不一样, 请检查接口定义,可能是在 `result.uploadedPartId`, 也可能直接是result. 请修改以下代码

                    if (uploadedChunkId) { 
                        uploadedChunksIds.push(uploadedChunkId);
                    //   delete file
                       await deleteFileFromTemp(outputPath);
                    }
                })
            );

步骤 4:检查 finalizeUpload 步骤

  • 确保 finalizeUpload 步骤中使用的 video urn, uploadToken 以及上传的所有分片的 ID( uploadedPartIds)都准确无误。
    const finalizeUploadRequest = {
         video: 'urn:li:video:videoId',  // 请使用正确video urn
         uploadToken: '',               //请使用正确uploadToken
         uploadedPartIds: [ ...ids ],       // 分片的 ids,确保排序
     }
    
    

步骤 5:加入错误重试机制

网络环境不稳定或 API 调用失败可能导致分片上传失败,增加重试逻辑。

 async function uploadWithRetry(url, blob, maxRetries = 3) {
     let retries = 0;
     while (retries < maxRetries) {
         try {
             const result = await uploadVideo(url, blob); // 假设uploadVideo 包含了上传分片的逻辑
             return result
         } catch (error) {
             console.error(`上传失败,正在重试...(第 ${retries + 1} 次)`);
             retries++;
             await new Promise(resolve => setTimeout(resolve, 1000 * retries ));  // 重试之前添加延迟
             
         }
     }
      throw new Error("分片上传失败, 已超出最大重试次数")
 }

额外建议

  • 在所有涉及 IO 操作的地方使用try...catch进行错误捕获。这有利于发现代码中可能存在的错误。
  • 严格参照API文档进行代码编写。 仔细研读接口文档。
  • 定期测试你的代码,尤其是在修改或者升级依赖之后。
  • 对分片数据进行一些简单的校验,比如blob.size等属性是否符合你的预期.

通过对上述步骤进行仔细检查, 可以定位并解决问题。 分片上传视频需要严格按照 API 的要求,任何一步出现偏差都有可能导致上传失败或视频播放异常。