React Native中用FFmpegKit转MP4为HLS流并传Firebase遇问题求助
问题解决:React Native中FFmpegKit生成多码率HLS流并上传Firebase Storage
错误原因分析
你遇到的「Output with label 'v360p' does not exist...」错误主要来自以下几个问题:
- 滤镜链仅处理了视频流,但未正确映射音频流,导致
var_stream_map中指定的音频轨不存在 - FFmpeg命令的输出路径、主播放列表名称配置与代码逻辑不匹配
- 未正确等待FFmpegKit任务执行完成,也未检查执行结果
- 未创建命令中使用的
encoded输出目录,导致文件写入失败 - 硬编码的码率与定义的resolutions数组参数不一致
修正后的完整实现函数
import FFmpegKit, { ReturnCode } from 'ffmpeg-kit-react-native'; import RNFS from 'react-native-fs'; import { storage } from '../path-to-your-firebase-config'; // 替换为你的Firebase配置路径 export async function convertToHLS(inputFilePath: string, outputFolderPath: string, postDocID: string) { try { // 定义分辨率与码率配置 const resolutions = [ { name: '360p', videoBitrate: '800k', audioBitrate: '128k', resolution: '640:360' }, { name: '480p', videoBitrate: '1500k', audioBitrate: '128k', resolution: '854:480' }, { name: '720p', videoBitrate: '3000k', audioBitrate: '128k', resolution: '1280:720' }, { name: '1080p', videoBitrate: '5000k', audioBitrate: '192k', resolution: '1920:1080' }, { name: '4K', videoBitrate: '8000k', audioBitrate: '192k', resolution: '3840:2160' } ]; // 创建必要的输出目录 const encodedDir = `${outputFolderPath}/encoded`; await RNFS.mkdir(encodedDir); // 构建滤镜链:分割视频流并处理每个分辨率的缩放与补边 const filterComplexParts = []; filterComplexParts.push(`split=${resolutions.length}[v0][v1][v2][v3][v4]`); resolutions.forEach((res, idx) => { filterComplexParts.push( `[v${idx}]scale=${res.resolution}:force_original_aspect_ratio=decrease,pad=${res.resolution}:(ow-iw)/2:(oh-ih)/2[vout${idx}]` ); }); const filterComplex = filterComplexParts.join(';'); // 构建多码率流映射参数:所有分辨率复用同一音频轨 const varStreamMap = resolutions.map((res, idx) => `v:${idx},a:0,name:${res.name}`).join(' '); // 构建码率参数,与resolutions配置对齐 const bitrateParams = []; resolutions.forEach((res, idx) => { bitrateParams.push(`-b:v:${idx} ${res.videoBitrate}`); }); bitrateParams.push(`-b:a:0 ${resolutions[0].audioBitrate}`); // 音频轨统一配置 // 拼接完整FFmpeg命令 const command = [ `-i ${inputFilePath}`, `-filter_complex "${filterComplex}"`, ...resolutions.map((_, idx) => `-map "[vout${idx}]"`), `-map 0:a`, `-c:v libx264`, `-c:a aac`, `-ar 48000`, `-preset medium`, ...bitrateParams, `-var_stream_map "${varStreamMap}"`, `-f hls`, `-hls_time 3`, `-hls_list_size 0`, `-hls_flags independent_segments`, `-hls_playlist_type vod`, `-master_pl_name master.m3u8`, `${encodedDir}/stream-%v.m3u8` ].join(' '); // 执行命令并校验结果 const session = await FFmpegKit.executeAsync(command); const returnCode = await session.getReturnCode(); if (!ReturnCode.isSuccess(returnCode)) { const errorLog = await session.getAllLogsAsString(); throw new Error(`FFmpeg执行失败: ${errorLog}`); } // 遍历输出目录上传所有HLS相关文件到Firebase const masterPlaylistPath = `${encodedDir}/master.m3u8`; const allFiles = await RNFS.readDir(encodedDir); const uploadPromises = allFiles.map(async (file) => { const storageRef = storage().ref(`posts/${postDocID}/${file.name}`); await storageRef.putFile(file.path); return storageRef.getDownloadURL(); }); const downloadURLs = await Promise.all(uploadPromises); const masterURL = downloadURLs.find(url => url.includes('master.m3u8')); return { masterPlaylistURL: masterURL, allDownloadURLs: downloadURLs }; } catch (error) { console.error('HLS转换或上传失败:', error); throw error; } }
关键修改说明
- 滤镜链重构:使用
filter_complex正确分割视频流并处理每个分辨率的缩放与补边,避免标签引用错误 - 音频流复用:所有分辨率流复用原始音频轨,无需为每个流单独处理音频,解决音频轨不存在的问题
- 参数对齐:使用resolutions数组配置生成码率参数,避免硬编码不一致
- 目录预处理:提前创建
encoded输出目录,确保文件可正常写入 - 执行校验:等待任务完成并检查返回码,失败时获取错误日志便于排查
- 完整文件上传:遍历输出目录所有文件(主播放列表+分片+分分辨率播放列表)并全部上传,确保HLS流可正常播放
内容的提问来源于stack exchange,提问作者user20150408
相关产品推荐
相关产品推荐

