fix(encoder): observability — silence was the bug
First real recording (2026-09-01): ffmpeg died at ~0.9s mid-session with ZERO log lines while the pump fed a corpse for 9 more seconds. Three blind spots closed: - OnStderrLine discarded everything non-progress: ffmpeg's actual words (startup config, warnings, the reason it quit) now go to AppLog prefixed 'ffmpeg:' (400-char cap so a \r-choked blob can't flood). - Unexpected exit only fired ProcessFailed when code != 0 — a CLEAN exit vanished silently. Any exit we didn't request is now logged and fired, any code. - ExitCode read raced StopAsync's Dispose (tonight's 'No process is associated' line) — guarded. - VM output resolver logs once/5s when a webcam element resolves to a null frame (preview showed the cam, recording didn't — the layer skip was invisible). FfmpegEncoderTests + FramePumpTests 21/21 against the new code, 0 warnings.
This commit is contained in:
@@ -230,14 +230,35 @@ public sealed class FfmpegEncoder : IFfmpegEncoder
|
||||
OnStderrLine(process, line);
|
||||
}
|
||||
|
||||
var code = process.ExitCode;
|
||||
var stillRunning = false;
|
||||
lock (_gate) stillRunning = IsRunning;
|
||||
if (stillRunning && !_stopRequested && code != 0)
|
||||
// Read the exit code defensively: the stop path disposes the process
|
||||
// while this loop may still be waking (2026-09-01 "No process is
|
||||
// associated with this object" — a cosmetic race that poisoned the log).
|
||||
int exitCode;
|
||||
try
|
||||
{
|
||||
AppLog.Write($"FFmpeg encoder: subprocess exited unexpectedly ({code})");
|
||||
_health.LastError = $"FFmpeg exited with code {code}";
|
||||
ProcessFailed?.Invoke(this, $"FFmpeg exited with code {code}");
|
||||
exitCode = process.ExitCode;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
exitCode = -1;
|
||||
}
|
||||
|
||||
var stillRunning = false;
|
||||
var stopping = false;
|
||||
lock (_gate)
|
||||
{
|
||||
stillRunning = IsRunning;
|
||||
stopping = _stopRequested;
|
||||
}
|
||||
if (stillRunning && !stopping)
|
||||
{
|
||||
// ANY exit we did not ask for is a failure — including code 0.
|
||||
// The old `code != 0` gate let ffmpeg's clean early exit vanish
|
||||
// without a word while the pump kept feeding a corpse
|
||||
// (2026-09-01 first real recording: died at ~0.9s, zero logs).
|
||||
AppLog.Write($"FFmpeg encoder: subprocess exited unexpectedly (code {exitCode})");
|
||||
_health.LastError = $"FFmpeg exited with code {exitCode}";
|
||||
ProcessFailed?.Invoke(this, $"FFmpeg exited with code {exitCode}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -250,7 +271,17 @@ public sealed class FfmpegEncoder : IFfmpegEncoder
|
||||
private void OnStderrLine(IEncoderProcess process, string line)
|
||||
{
|
||||
var progress = FfmpegProgressParser.TryParse(line);
|
||||
if (progress == null) return;
|
||||
if (progress == null)
|
||||
{
|
||||
// THE EYES: everything ffmpeg says that isn't a progress line — startup
|
||||
// config, warnings, the real reason it quit. Was silently discarded
|
||||
// (2026-09-01: encoder died at ~0.9s, log silent). Truncated so a
|
||||
// \r-choked stats blob can't flood; noise now beats blindness.
|
||||
var text = line.Trim('\r', ' ', '\t');
|
||||
if (text.Length > 0)
|
||||
AppLog.Write($"ffmpeg: {(text.Length > 400 ? text[..400] + "…" : text)}");
|
||||
return;
|
||||
}
|
||||
|
||||
var dropped = Math.Max(0, (long)Math.Round(progress.Value.Fps * progress.Value.Duration.TotalSeconds) - progress.Value.Frame);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user