我一直在搜索有关 HLS 流的很多信息,并成功地使用 nodejs 创建了一个简单的 HLS 流服务器,现在的问题是我需要在流式传输给用户之前向 .ts 块添加一层 ffmpeg 编码,而没有这一层一切正常,在我的服务器上只看到 3 个请求:
manifest.m3u8
output_000.ts
output_000.ts
output_001.ts
output_002.ts
但是当我添加一个简单的 ffmpeg 层,它从字面上复制 ts 文件中的所有内容并输出流(我当然会为每个请求添加动态过滤器,这就是为什么我需要这个 ffmpeg 层),播放器会发疯并请求整个视频只需 5 秒左右:
manifest.m3u8
output_000.ts
output_000.ts
output_001.ts
output_002.ts
output_001.ts
output_003.ts
output_002.ts
...
output_095.ts
我还注意到数字没有均匀增加并且怀疑这是问题的一部分,我尝试添加更多 ffmpeg 选项以不对正在提供给它的 .ts 文件做任何事情,因为它们是一个更大的视频。
const fs = require(`fs`);
const path = require(`path`);
const {exec, spawn} = require(`child_process`);
const pathToFfmpeg = require(`ffmpeg-static`);
export default function handler(req, res) {
const { filename } = req.query;
console.log(filename);
const filePath = path.join(process.cwd(), 'public', 'stream', `${filename}`);
const inputStream = fs.createReadStream(filePath);
// first check if that is ts file..
if(filename.indexOf(`.ts`) != -1){
const ffmpegProcess = spawn(pathToFfmpeg, [
'-f', `mpegts`,
'-i', 'pipe:0', // specify input as pipe
'-c', 'copy',
'-avoid_negative_ts', '0',
`-map_metadata`, `0`, // copy without re-encoding
'-f', 'mpegts', // output format
'pipe:1' // specify output as pipe
], {
stdio: ['pipe', 'pipe', 'pipe'] // enable logging by redirecting stderr to stdout
});
res.status(200);
res.setHeader('Content-Type', 'application/vnd.apple.mpegurl');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Access-Control-Allow-Origin', '*');
// ffmpegProcess.stderr.pipe(process.stdout); // log stderr to stdout
inputStream.pipe(ffmpegProcess.stdin);
ffmpegProcess.stdout.pipe(res);
ffmpegProcess.on('exit', (code) => {
if (code !== 0) {
console.error(`ffmpeg process exited with code ${code}`);
}
});
}else{
// if not then stream whatever file as it is
res.status(200);
res.setHeader('Content-Type', 'application/vnd.apple.mpegurl');
inputStream.pipe(res);
}
}
我曾尝试为请求的播放器提供适当的标头,但这没有用,我还尝试将“-re”选项添加到 ffmpeg 编码器本身并希望性能影响最小,但这也导致了播放问题,因为太慢了。
更新:
所以在 ChatGPT 的帮助下,它绝对不会取代我 xD,它最终给了我这些选择:
-copyts -copytb 0
我相信这是用来复制流分割文件的时间戳,从字面上模拟这个分割输出实际上是分割原始 mp4 文件时生成的输出。
所以完整的命令如下:
const ffmpegProcess = spawn(pathToFfmpeg, [
'-f', `mpegts`,
'-i', 'pipe:0', // specify input as pipe
'-c:v', 'libx264', // re-encode with libx264
'-preset', 'ultrafast', // set the encoding preset to ultrafast
'-crf', '30', // set the Constant Rate Factor (CRF) to 18 (lower CRF = higher quality)
'-map_metadata', '0',
'-movflags', 'frag_keyframe+empty_moov+default_base_moof+faststart',
'-filter:v', `drawtext=fontfile=./pdfs/fonts/Rubik-Regular.ttf: text='© CARTELMCQS.COM | ${userData.email} | ${userData.name}': x=${randomX}:y=${randomY}: fontsize=18: [email protected]: box=1: [email protected]`,
'-profile:v', 'baseline', // encoding profile
'-maxrate', '4000k',
`-copyts`, `-copytb`, `0`,
`-bsf:v`, `dump_extra`,
`-err_detect` , `ignore_err`,
'-f', 'mpegts', // output format
'pipe:1' // specify output as pipe
], {
stdio: ['pipe', 'pipe', 'pipe'] // enable logging by redirecting stderr to stdout
});
对于任何想知道这究竟是如何工作的人,基本上我首先采用常规输入 mp4 视频,然后使用 ffmpeg 命令将其转换为 HLS 流,这是简单的部分。
在此之后,我创建了自己的无服务器流式 api 端点来自己处理请求,而不是仅仅输入文件名,这让我可以更好地控制谁可以访问流,更重要的是,我可以在视频片段之前将 ffmpeg 层添加到视频片段中流式传输给用户以添加所需的任何可变文本或过滤器。