debug(goLive): instrument frame pipeline — demux spawn/stderr/frames, playStream resolve, sendVideoFrame drop/send
Tile kosong meski STREAM_CREATE handshake penuh (22:18-22:19 retest): - Demuxer logs spawn args, ffmpeg stderr errors, frame count every 30 - playStream logs createStream resolved + demux done + setPacketizer - sendVideoFrame logs DROPPED (ready/track) + sent frame count
This commit is contained in:
@@ -173,6 +173,9 @@ export async function demux(
|
|||||||
const proc = spawn(FFMPEG, args, {
|
const proc = spawn(FFMPEG, args, {
|
||||||
stdio: isStream ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"],
|
stdio: isStream ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"],
|
||||||
});
|
});
|
||||||
|
console.log(
|
||||||
|
`[goLive:Demuxer] spawn ffmpeg pid=${proc.pid} input=${isStream ? "stream" : input} args=${args.join(" ")}`,
|
||||||
|
);
|
||||||
|
|
||||||
// Pipe live input straight into ffmpeg stdin — never await stream end.
|
// Pipe live input straight into ffmpeg stdin — never await stream end.
|
||||||
if (isStream && proc.stdin) {
|
if (isStream && proc.stdin) {
|
||||||
@@ -199,7 +202,14 @@ export async function demux(
|
|||||||
let stderrBuf = "";
|
let stderrBuf = "";
|
||||||
if (proc.stderr) {
|
if (proc.stderr) {
|
||||||
proc.stderr.on("data", (d: Buffer) => {
|
proc.stderr.on("data", (d: Buffer) => {
|
||||||
stderrBuf = (stderrBuf + d.toString()).slice(-16384);
|
const text = d.toString();
|
||||||
|
stderrBuf = (stderrBuf + text).slice(-16384);
|
||||||
|
// Surface actionable lines: ffmpeg errors + stream init lines
|
||||||
|
if (/error|invalid|no such|failed|cannot|not found|unable/i.test(text)) {
|
||||||
|
console.log(
|
||||||
|
`[goLive:Demuxer] ffmpeg stderr: ${text.trim().split("\n").slice(0, 4).join(" | ")}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
if (parsedMeta) return;
|
if (parsedMeta) return;
|
||||||
const streamRe = /Stream #0:(\d+): (Video|Audio): ([^,]+)/g;
|
const streamRe = /Stream #0:(\d+): (Video|Audio): ([^,]+)/g;
|
||||||
let m: RegExpExecArray | null;
|
let m: RegExpExecArray | null;
|
||||||
@@ -284,6 +294,11 @@ export async function demux(
|
|||||||
free: () => {},
|
free: () => {},
|
||||||
});
|
});
|
||||||
frameCount++;
|
frameCount++;
|
||||||
|
if (frameCount === 1 || frameCount % 30 === 0) {
|
||||||
|
console.log(
|
||||||
|
`[goLive:Demuxer] frames=${frameCount} last=${nal.length}B key=${isKeyFrame}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (proc.stdout) {
|
if (proc.stdout) {
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ export class WebRtcConnWrapper {
|
|||||||
private _audioTrack: NativeTrack | null = null;
|
private _audioTrack: NativeTrack | null = null;
|
||||||
private _videoTrack: NativeTrack | null = null;
|
private _videoTrack: NativeTrack | null = null;
|
||||||
private _videoCodec: WebRtcVideoCodec | null = null;
|
private _videoCodec: WebRtcVideoCodec | null = null;
|
||||||
|
private _videoFrameLog = 0;
|
||||||
/** Assigned by BaseMediaConnection to send the gathered SDP to Discord. */
|
/** Assigned by BaseMediaConnection to send the gathered SDP to Discord. */
|
||||||
onLocalDescription: ((sdp: string) => void) | null = null;
|
onLocalDescription: ((sdp: string) => void) | null = null;
|
||||||
|
|
||||||
@@ -114,7 +115,15 @@ export class WebRtcConnWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendVideoFrame(frame: Buffer, frametime: number): void {
|
sendVideoFrame(frame: Buffer, frametime: number): void {
|
||||||
if (!this.ready || !this._videoTrack) return;
|
if (!this.ready || !this._videoTrack) {
|
||||||
|
if (this._videoFrameLog === 0) {
|
||||||
|
console.log(
|
||||||
|
`[goLive:WebRtc] sendVideoFrame DROPPED ready=${this.ready} track=${this._videoTrack !== null}`,
|
||||||
|
);
|
||||||
|
this._videoFrameLog++;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
const clockRate = CodecPayloadType[this._videoCodec ?? "H264"].clockRate;
|
const clockRate = CodecPayloadType[this._videoCodec ?? "H264"].clockRate;
|
||||||
if (this._videoCodec === "H264") {
|
if (this._videoCodec === "H264") {
|
||||||
let spsRewritten = false;
|
let spsRewritten = false;
|
||||||
@@ -157,6 +166,12 @@ export class WebRtcConnWrapper {
|
|||||||
}
|
}
|
||||||
this._videoTrack.sendFrame(frame);
|
this._videoTrack.sendFrame(frame);
|
||||||
this._videoTrack.addTimestamp(Math.round((frametime * clockRate) / 1000));
|
this._videoTrack.addTimestamp(Math.round((frametime * clockRate) / 1000));
|
||||||
|
this._videoFrameLog++;
|
||||||
|
if (this._videoFrameLog === 1 || this._videoFrameLog % 30 === 0) {
|
||||||
|
console.log(
|
||||||
|
`[goLive:WebRtc] sendVideoFrame #${this._videoFrameLog} bytes=${frame.length} ready=${this.ready}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setPacketizer(videoCodec: string): void {
|
setPacketizer(videoCodec: string): void {
|
||||||
|
|||||||
@@ -262,15 +262,22 @@ export async function playStream(
|
|||||||
options: PlayStreamOptions = {},
|
options: PlayStreamOptions = {},
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const conn = await streamer.createStream();
|
const conn = await streamer.createStream();
|
||||||
|
console.log("[goLive:playStream] createStream resolved");
|
||||||
|
|
||||||
const { video, close: demuxClose } = await demux(prepared.output, {
|
const { video, close: demuxClose } = await demux(prepared.output, {
|
||||||
format: options.format ?? "nut",
|
format: options.format ?? "nut",
|
||||||
});
|
});
|
||||||
|
console.log(
|
||||||
|
`[goLive:playStream] demux done codec=${video?.codecName ?? "?"} ${video?.width ?? 0}x${video?.height ?? 0} fps=${video ? video.framerate_num / video.framerate_den || 30 : 30}`,
|
||||||
|
);
|
||||||
|
|
||||||
if (!video) throw new Error("No video stream in media");
|
if (!video) throw new Error("No video stream in media");
|
||||||
|
|
||||||
conn.setPacketizer(video.codecName);
|
conn.setPacketizer(video.codecName);
|
||||||
conn.mediaConnection.setSpeaking(true);
|
conn.mediaConnection.setSpeaking(true);
|
||||||
|
console.log(
|
||||||
|
`[goLive:playStream] setPacketizer(${video.codecName}) + setSpeaking done`,
|
||||||
|
);
|
||||||
|
|
||||||
const w =
|
const w =
|
||||||
typeof options.width === "function"
|
typeof options.width === "function"
|
||||||
|
|||||||
Reference in New Issue
Block a user