视频保存
This commit is contained in:
@@ -515,6 +515,18 @@ export async function startRecording(input: StartRecordingInput): Promise<StartR
|
|||||||
const args = buildFfmpegArgs(sdpFilePath, outputPath);
|
const args = buildFfmpegArgs(sdpFilePath, outputPath);
|
||||||
ffmpegProc = spawn(ffmpegBin, args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
ffmpegProc = spawn(ffmpegBin, args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
||||||
|
|
||||||
|
// ffmpeg stderr 环形缓冲:正常运行时 debug 级 + 不落盘,
|
||||||
|
// 进程异常退出时把最后 N KB stderr 一次性以 error 级 dump 出来,方便定位 code=1 类问题
|
||||||
|
// 上限 16 KB 足以覆盖典型 ffmpeg 启动失败诊断(codec/端口/SDP 解析等),且容量可控
|
||||||
|
const STDERR_TAIL_LIMIT = 16 * 1024;
|
||||||
|
let stderrTail = '';
|
||||||
|
const appendStderrTail = (text: string) => {
|
||||||
|
stderrTail += text;
|
||||||
|
if (stderrTail.length > STDERR_TAIL_LIMIT) {
|
||||||
|
stderrTail = stderrTail.slice(stderrTail.length - STDERR_TAIL_LIMIT);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const exited = new Promise<void>((resolve) => {
|
const exited = new Promise<void>((resolve) => {
|
||||||
ffmpegProc!.once('exit', (code, signal) => {
|
ffmpegProc!.once('exit', (code, signal) => {
|
||||||
const entry = recordingMap.get(id);
|
const entry = recordingMap.get(id);
|
||||||
@@ -522,8 +534,9 @@ export async function startRecording(input: StartRecordingInput): Promise<StartR
|
|||||||
entry.exitCode = code;
|
entry.exitCode = code;
|
||||||
if (!entry.stopping && code !== 0) {
|
if (!entry.stopping && code !== 0) {
|
||||||
entry.failureReason = `ffmpeg exited unexpectedly code=${code} signal=${signal ?? ''}`;
|
entry.failureReason = `ffmpeg exited unexpectedly code=${code} signal=${signal ?? ''}`;
|
||||||
|
// 异常退出:把 stderr tail 落 error 级,便于线上排查 ffmpeg 失败根因
|
||||||
log.error(
|
log.error(
|
||||||
{ recordingId: id, code, signal },
|
{ recordingId: id, code, signal, stderrTail: stderrTail.trim() },
|
||||||
'ffmpeg exited unexpectedly before stop',
|
'ffmpeg exited unexpectedly before stop',
|
||||||
);
|
);
|
||||||
// Phase B watchdog:异步触发失败回调(webhook + 用户回调),不阻塞 exit 处理
|
// Phase B watchdog:异步触发失败回调(webhook + 用户回调),不阻塞 exit 处理
|
||||||
@@ -533,7 +546,7 @@ export async function startRecording(input: StartRecordingInput): Promise<StartR
|
|||||||
// entry 还没入 map(startRecording 末尾才插入)→ 走 spawn 失败兜底,避免 webhook 漏报
|
// entry 还没入 map(startRecording 末尾才插入)→ 走 spawn 失败兜底,避免 webhook 漏报
|
||||||
// startRecording 同步路径会自己 throw + rollback,这里上报只为防止上层 DB 行已存在的边缘场景
|
// startRecording 同步路径会自己 throw + rollback,这里上报只为防止上层 DB 行已存在的边缘场景
|
||||||
log.error(
|
log.error(
|
||||||
{ recordingId: id, code, signal },
|
{ recordingId: id, code, signal, stderrTail: stderrTail.trim() },
|
||||||
'ffmpeg exited before recording entry registered',
|
'ffmpeg exited before recording entry registered',
|
||||||
);
|
);
|
||||||
fireSpawnFailure({
|
fireSpawnFailure({
|
||||||
@@ -541,7 +554,7 @@ export async function startRecording(input: StartRecordingInput): Promise<StartR
|
|||||||
roomCode: input.roomCode,
|
roomCode: input.roomCode,
|
||||||
routerId: input.routerId,
|
routerId: input.routerId,
|
||||||
exitCode: code,
|
exitCode: code,
|
||||||
failureReason: `ffmpeg exited during spawn code=${code} signal=${signal ?? ''}`,
|
failureReason: `ffmpeg exited during spawn code=${code} signal=${signal ?? ''}; stderr_tail=${stderrTail.trim().slice(-512)}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
@@ -550,7 +563,9 @@ export async function startRecording(input: StartRecordingInput): Promise<StartR
|
|||||||
|
|
||||||
ffmpegProc.stderr?.on('data', (chunk: Buffer) => {
|
ffmpegProc.stderr?.on('data', (chunk: Buffer) => {
|
||||||
const text = chunk.toString('utf-8');
|
const text = chunk.toString('utf-8');
|
||||||
// ffmpeg 默认大部分日志走 stderr;只在 debug 级别落,避免日志爆炸
|
// 1) 永远写入环形缓冲,供进程异常退出时 dump
|
||||||
|
appendStderrTail(text);
|
||||||
|
// 2) 正常实时日志仍走 debug 级,避免高频日志爆炸
|
||||||
log.debug({ recordingId: id }, `[ffmpeg] ${text.trim()}`);
|
log.debug({ recordingId: id }, `[ffmpeg] ${text.trim()}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user