Skip to content

Fix/issues 90 91 - #93

Merged
keyldev merged 3 commits into
mainfrom
fix/issues-90-91
Oct 17, 2026
Merged

keyldev merged 3 commits into
mainfrom
fix/issues-90-91

Conversation

@keyldev

@keyldev keyldev commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Related

Type

  • Bug fix
  • Feature
  • Refactor / cleanup
  • Docs / CI
  • Other:

Checklist

  • Builds with 0 warnings (TreatWarningsAsErrors=true).
  • Tests pass (dotnet test); new Core logic has unit tests.
  • No layering violation — App references Core only (Infrastructure / Video / Devices wired via DI in a head).
  • Scope stays within one phase (didn't pull work from a later phase's "Не входит").
  • README / docs updated if public commands, options, or setup changed.

Platforms tested

  • Windows
  • Linux
  • macOS
  • Android
  • iOS
  • CI build only

Screenshots / notes

…, #91)

- ffmpeg recorder/web live/snapshot get an RTSP socket timeout; a camera that
  accepts TCP but never answers parked ffmpeg in open forever, so no segment
  or Recordings row ever appeared while REC stayed lit
- RecordingService handles a recorder that stops on its own: clears REC,
  finalizes the last row and raises RecordingFailed; camera page shows why
- Name Windows socket errnos (-138 = timed out) and keep the RTSP
  "Failed reading RTSP data" warning in the error instead of a bare
  "Invalid data found"
- 10.0.0 pulls System.Security.Cryptography.Pkcs 10.0.0 with
  GHSA-2fmv-rv2c-59x7 (published 2026-10-13); NuGetAudit + warnings-as-errors
  fail restore on every head
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Stop stalled RTSP recording and report unexpected recorder failures

🐞 Bug fix 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add RTSP socket timeouts so silent cameras cannot leave ffmpeg waiting indefinitely.
• Clear recording state, finalize segments, and show users why a recorder stopped unexpectedly.
• Cover recording failure paths and update a vulnerable cryptography dependency.
Diagram

graph TD
  Camera["RTSP camera"] --> Recorder["Video recorder"] --> Events["Recording events"] --> Service["Recording service"] --> Archive[("Recording archive")]
  Service --> Page["Camera page"]
  Camera --> Web["Web ffmpeg"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Add an application-level recording watchdog
  • ➕ Could detect stalls beyond RTSP socket reads, including a live process that stops producing segments.
  • ➖ Requires progress tracking and process termination across recorder implementations; adds more lifecycle races.

Recommendation: Keep the socket timeout and recorder-driven stop handling: they address the reported silent-camera hang and restore recording state when a session exits. Consider a watchdog separately if healthy processes that cease producing segments are observed.

Files changed (14) +384 / -19

Enhancement (1) +2 / -1
RecordingEvent.csCarry stop details in recording events +2/-1

Carry stop details in recording events

• Adds optional recorder-provided detail to stopped events so unexpected exits can retain their cause.

src/OpenIPC.Viewer.Core/Recording/RecordingEvent.cs

Bug fix (11) +174 / -16
Localizer.csLocalize recording start and interruption failures +4/-0

Localize recording start and interruption failures

• Adds English and Russian messages that distinguish a stream that never opened from one lost after recording began.

src/OpenIPC.Viewer.App/Services/Localizer.cs

SingleCameraPageViewModel.csShow recorder failures on the camera page +10/-0

Show recorder failures on the camera page

• Subscribes to recording failures for the current camera and posts the appropriate localized error to the UI thread. Unsubscribes during disposal.

src/OpenIPC.Viewer.App/ViewModels/SingleCameraPageViewModel.cs

RecordingService.csClean up and report sessions that stop on their own +49/-2

Clean up and report sessions that stop on their own

• On unexpected stops, removes the matching active session, finalizes its last segment, disposes it, and raises state and failure notifications. Tracks recorder error text and whether a segment was opened.

src/OpenIPC.Viewer.Core/Recording/RecordingService.cs

FfmpegError.csTranslate opaque Windows socket errors +42/-2

Translate opaque Windows socket errors

• Replaces recognized FFmpeg numeric socket-error messages with readable connection failures while leaving unknown errors unchanged.

src/OpenIPC.Viewer.Video/Pipeline/FfmpegError.cs

FfmpegRuntime.csRetain relevant native FFmpeg warnings +17/-1

Retain relevant native FFmpeg warnings

• Keeps a warning when no native error was logged, allowing RTSP handshake failures to provide context instead of a generic error. Adds a reset for stale thread-local diagnostics and humanizes logged socket errors.

src/OpenIPC.Viewer.Video/Pipeline/FfmpegRuntime.cs

FfmpegVideoSession.csDiscard stale diagnostics before opening video +1/-0

Discard stale diagnostics before opening video

• Clears thread-local FFmpeg diagnostics before opening an RTSP input so a subsequent failure is not attributed to an earlier call.

src/OpenIPC.Viewer.Video/Pipeline/FfmpegVideoSession.cs

FfmpegRecordingSession.csBound recorder RTSP reads and preserve exit reasons +37/-6

Bound recorder RTSP reads and preserve exit reasons

• Sets a ten-second RTSP socket timeout for subprocess recording. Waits briefly for stderr on unexpected exit and passes its last failure line through the stopped event.

src/OpenIPC.Viewer.Video/Recording/FfmpegRecordingSession.cs

LibavformatRecordingSession.csInclude in-process recorder stop details +5/-2

Include in-process recorder stop details

• Attaches read failures and recording-loop exceptions to stopped events for the in-process recorder.

src/OpenIPC.Viewer.Video/Recording/LibavformatRecordingSession.cs

LiveFfmpeg.csBound web live-stream RTSP reads +3/-1

Bound web live-stream RTSP reads

• Adds a ten-second RTSP socket timeout to the live ffmpeg process so a silent camera does not hold it open indefinitely.

src/OpenIPC.Viewer.Web/Api/LiveFfmpeg.cs

SnapshotApi.csBound snapshot RTSP reads +1/-1

Bound snapshot RTSP reads

• Adds a ten-second RTSP socket timeout to ffmpeg snapshot capture.

src/OpenIPC.Viewer.Web/Api/SnapshotApi.cs

WebRecorder.csBound web recorder RTSP reads +5/-1

Bound web recorder RTSP reads

• Adds a ten-second RTSP socket timeout so a silent camera cannot leave the web recording process stuck during input opening.

src/OpenIPC.Viewer.Web/Api/WebRecorder.cs

Tests (1) +204 / -0
RecordingServiceTests.csTest unexpected recording stops and user stops +204/-0

Test unexpected recording stops and user stops

• Adds fake-backed tests for failure before the first segment, failure during a segment with row finalization, and user stops that must not raise a recording failure.

tests/OpenIPC.Viewer.Core.Tests/Recording/RecordingServiceTests.cs

Other (1) +4 / -2
Directory.Packages.propsPin patched cryptography XML package +4/-2

Pin patched cryptography XML package

• Raises System.Security.Cryptography.Xml from 10.0.0 to 10.0.13 to avoid a vulnerable transitive Pkcs version that causes audited restores to fail.

Directory.Packages.props

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Web recordings stay active after timeout ✓ Resolved
Description
WebRecorder adds an RTSP timeout but does not remove its active entry or reconcile its archive row
when the resulting ffmpeg process exits on its own. When a camera goes silent, later starts still
see the exited process as recording until someone explicitly requests a stop.
Code

src/OpenIPC.Viewer.Web/Api/WebRecorder.cs[R97-100]

+                     // RTSP socket timeout (µs): a camera that accepts the connect
+                     // but never answers must not park ffmpeg in open forever.
+                     "-timeout", "10000000",
+                     "-i", rtspUrl,
Evidence
The new timeout can end the process, while IsRecording and the duplicate-start check use only
_active. StartAsync inserts that entry, and only StopAsync removes it and reconciles segments.

src/OpenIPC.Viewer.Web/Api/WebRecorder.cs[58-70]
src/OpenIPC.Viewer.Web/Api/WebRecorder.cs[96-121]
src/OpenIPC.Viewer.Web/Api/WebRecorder.cs[127-165]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new timeout can terminate web ffmpeg, but its active entry and archive row are only cleaned up by an explicit stop.
## Fix Focus Areas
- src/OpenIPC.Viewer.Web/Api/WebRecorder.cs[96-121]
- src/OpenIPC.Viewer.Web/Api/WebRecorder.cs[127-165]
## Recommended Fix
Monitor the process after starting it. On an unexpected exit, atomically remove its active entry and reconcile or discard its segment rows; coordinate this cleanup with explicit stops.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Failed recordings can leave rows open ✓ Resolved
Description
OnSessionEndedAsync finalizes CurrentSegmentPath without waiting for an earlier Started or
SegmentRotated callback to finish inserting its row. If the recorder stops while that asynchronous
insertion is pending, FinalizeSegmentAsync finds no row and never revisits it, so the row later
appears without an end time.
Code

src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[R180-182]

+        var lastPath = session.CurrentSegmentPath;
+        if (lastPath is not null)
+            await FinalizeSegmentAsync(lastPath, st.Time).ConfigureAwait(false);
Evidence
The subscription starts each event handler without awaiting it. Segment handlers await repository
writes, whereas the new stop path independently looks up the row once and returns if it has not yet
been inserted.

src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[98-101]
src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[132-149]
src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[180-182]
src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[191-202]
src/OpenIPC.Viewer.Video/Recording/FfmpegRecordingSession.cs[204-218]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Concurrent event handlers allow unexpected-stop finalization to run before the segment row is inserted.
## Fix Focus Areas
- src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[98-101]
- src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[128-149]
- src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[180-182]
## Recommended Fix
Process each session's recording events in publication order, awaiting segment insertion before unexpected-stop finalization. Test a repository whose `AddAsync` remains pending when `Stopped` arrives.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Disk failures are blamed on the camera ✓ Resolved
Description
OnRecordingFailed always selects wording that says the camera stream could not be opened or was
lost. A recorder can also stop because it cannot open or write the output file, so those failures
reach the camera page with a misleading diagnosis even when the supplied detail describes the disk
error.
Code

src/OpenIPC.Viewer.App/ViewModels/SingleCameraPageViewModel.cs[R568-570]

+        var key = failure.WroteSegment ? "CameraPage.RecordingInterruptedFormat" : "CameraPage.RecordingNotStartedFormat";
+        Dispatcher.UIThread.Post(() => ErrorMessage = string.Format(CultureInfo.CurrentCulture,
+            Localizer.Instance[key], failure.Message));
Evidence
The libavformat session can fail during output-file opening or frame writing and publishes the
resulting error as a non-user stop. The new UI branch chooses camera-stream wording solely from
WroteSegment, not from the failure cause.

src/OpenIPC.Viewer.Video/Recording/LibavformatRecordingSession.cs[185-196]
src/OpenIPC.Viewer.Video/Recording/LibavformatRecordingSession.cs[234-245]
src/OpenIPC.Viewer.Video/Recording/LibavformatRecordingSession.cs[256-279]
src/OpenIPC.Viewer.App/ViewModels/SingleCameraPageViewModel.cs[565-570]
src/OpenIPC.Viewer.App/Services/Localizer.cs[557-559]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new recording-failure messages assert a camera-stream fault for failures that can instead come from the output file.
## Fix Focus Areas
- src/OpenIPC.Viewer.App/ViewModels/SingleCameraPageViewModel.cs[565-570]
- src/OpenIPC.Viewer.App/Services/Localizer.cs[557-559]
- src/OpenIPC.Viewer.App/Services/Localizer.cs[1215-1217]
## Recommended Fix
Use cause-neutral started/interrupted wording, or carry a structured failure cause from the recorder and select camera-specific wording only for camera errors.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Mobile users see 'stream lost' when recording never started ✓ Resolved
Description
OnSessionEndedAsync computes WroteSegment from a non-null CurrentSegmentPath, but
LibavformatRecordingSession.Start() assigns the backing _outputPath before opening the camera or
output file. On Android and iOS, which use LibavformatRecorder, an RTSP connection refusal or
timeout can therefore set WroteSegment = true before recording starts, causing the camera page to
show the interrupted-recording message instead of “Recording did not start”.
Code

src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[R180-188]

+        var lastPath = session.CurrentSegmentPath;
+        if (lastPath is not null)
+            await FinalizeSegmentAsync(lastPath, st.Time).ConfigureAwait(false);
+
+        h.Subscription.Dispose();
+        try { await session.DisposeAsync().ConfigureAwait(false); } catch { /* swallow */ }
+        StateChanged?.Invoke(this, id);
+        RecordingFailed?.Invoke(this, new RecordingFailure(
+            id, message ?? st.Detail ?? st.Reason.ToString(), WroteSegment: lastPath is not null));
Evidence
CurrentSegmentPath returns _outputPath, which Start() assigns before the worker attempts
avformat_open_input, output setup, or emits Started. Android and iOS register
LibavformatRecorder; OnSessionEndedAsync tests only whether that path is non-null, and the view
model uses the resulting flag to choose between the not-started and interrupted messages.

src/OpenIPC.Viewer.Video/Recording/LibavformatRecordingSession.cs[46-66]
src/OpenIPC.Viewer.Video/Recording/LibavformatRecordingSession.cs[148-152]
src/OpenIPC.Viewer.iOS/Composition.cs[56-56]
src/OpenIPC.Viewer.Android/Recording/AndroidRecorder.cs[25-31]
src/OpenIPC.Viewer.Video/Recording/LibavformatRecordingSession.cs[45-56]
src/OpenIPC.Viewer.Video/Recording/LibavformatRecordingSession.cs[132-151]
src/OpenIPC.Viewer.Video/Recording/LibavformatRecordingSession.cs[185-196]
src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[180-188]
src/OpenIPC.Viewer.App/ViewModels/SingleCameraPageViewModel.cs[565-570]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`WroteSegment` is derived from `session.CurrentSegmentPath`, which the libavformat session sets before connecting or writing a segment, so an input-opening failure is reported as an interrupted recording.
## Fix Focus Areas
- src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[128-189]
- src/OpenIPC.Viewer.Video/Recording/LibavformatRecordingSession.cs[45-56]
- src/OpenIPC.Viewer.Video/Recording/LibavformatRecordingSession.cs[185-196]
## Recommended Fix
Track per session whether `RecordingEvent.Started` was received or a segment was actually opened, and set `WroteSegment` from that state instead of path presence. Alternatively, expose `CurrentSegmentPath` only after `avformat_write_header` succeeds. Test a libavformat failure during input opening.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View review recommended (2)
5. Live view, snapshots and recording break on FFmpeg 4.x ✓ Resolved
Description
The new -timeout 10000000 input option is an RTSP socket I/O timeout only on FFmpeg 5.0 and later;
on FFmpeg 4.x the RTSP timeout option means "wait N seconds for incoming connections" and switches
the demuxer into listen mode. The web server docs tell Linux users they can `sudo apt install
ffmpeg (4.4 on Ubuntu 22.04, 4.3 on Debian 11), and the recorder falls back to ffmpeg` on PATH, so
on those systems the recorder, web live view and snapshots try to listen as an RTSP server instead
of connecting to the camera.
Code

src/OpenIPC.Viewer.Web/Api/LiveFfmpeg.cs[R24-26]

+        // -timeout: RTSP socket I/O timeout (µs) so a silent camera ends the
+        // process instead of holding a connection open forever.
+        var args = new List<string> { "-rtsp_transport", "tcp", "-timeout", "10000000", "-fflags", "nobuffer", "-i", rtspUrl };
Evidence
The web server docs list a distro ffmpeg as a supported option, and the repo's own docs say 22.04
ships 4.4. Both FfmpegSubprocessRecorder and LiveFfmpeg resolve the bundled binary first and fall
back to PATH, so no minimum CLI version is enforced. The same flag is added in
FfmpegRecordingSession, SnapshotApi and WebRecorder.

docs/web-server.md[144-145]
docs/troubleshooting.md[21-22]
src/OpenIPC.Viewer.Video/Recording/FfmpegSubprocessRecorder.cs[40-49]
src/OpenIPC.Viewer.Video/Recording/FfmpegRecordingSession.cs[88-92]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`-timeout <µs>` is a socket timeout only in FFmpeg ≥5; in 4.x it turns on RTSP listen mode, and the docs still advertise a distro ffmpeg for the web server.
## Fix Focus Areas
- src/OpenIPC.Viewer.Web/Api/LiveFfmpeg.cs[24-26]
- src/OpenIPC.Viewer.Web/Api/SnapshotApi.cs[123-123]
- src/OpenIPC.Viewer.Web/Api/WebRecorder.cs[96-100]
- src/OpenIPC.Viewer.Video/Recording/FfmpegRecordingSession.cs[88-92]
- docs/web-server.md[144-145]
## Recommended Fix
Probe `ffmpeg -version` once, then pass `-stimeout` for major version <5 and `-timeout` for ≥5. Alternatively, document and enforce FFmpeg ≥5 for the CLI and remove the `apt install ffmpeg` advice for older distros. Also correct the comment: the rename happened in FFmpeg 5, not 7.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. A fast recorder failure can leave REC stuck on ✓ Resolved
Description
StartAsync subscribes to the non-replaying session.Events stream only after
_recorder.StartAsync starts the worker, then writes _active[id] after subscribing;
OnSessionEndedAsync returns without cleanup if that entry is absent. If either recorder exits
during startup, Stopped can be lost before subscription or handled before registration, leaving
the ended session active, REC lit, and a fresh start refused.
Code

src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[R101-102]

+        var sub = session.Events.Subscribe(new CallbackObserver(ev => _ = HandleEventAsync(id, session, ev)));
      lock (_gate) _active[id] = new Handle(session, sub);
Evidence
Both recorder factories start their workers before returning the session, so a worker can publish
Stopped before RecordingService subscribes. RecordingService also writes _active[id] after
subscribing, while OnSessionEndedAsync returns when that entry is missing, establishing a second
window in which a delivered event cannot clean up the session.

src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[98-104]
src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[166-178]
src/OpenIPC.Viewer.Video/Recording/LibavformatRecordingSession.cs[55-66]
src/OpenIPC.Viewer.Video/Recording/FfmpegSubprocessRecorder.cs[33-37]
src/OpenIPC.Viewer.Video/Recording/LibavformatRecorder.cs[21-25]
src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[93-103]
src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[162-175]
src/OpenIPC.Viewer.Video/Recording/FfmpegRecordingSession.cs[184-195]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A recorder that stops during startup can emit `Stopped` before the service subscribes, or before it registers the active handle. In either case, the ended session can remain marked active.
## Fix Focus Areas
- src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[93-104]
- src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[162-189]
- src/OpenIPC.Viewer.Video/Recording/FfmpegSubprocessRecorder.cs[33-37]
- src/OpenIPC.Viewer.Video/Recording/LibavformatRecorder.cs[21-25]
## Recommended Fix
Ensure the service registers the active handle and subscribes before a session can publish its terminal event—for example, split session creation from worker start, and set `_active[id]` under the same lock before subscribing while taking the gate in the event path. Alternatively, retain and process a terminal result emitted during startup, such as through a replaying terminal event stream. Add a test for immediate recorder failure.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

7. Unrelated earlier warnings get attached to later FFmpeg errors ✓ Resolved
Description
TakeLastNativeError falls back to _lastNativeWarning, which LogCallback overwrites on every
warning-level line, while FfmpegVideoSession clears that state only before avformat_open_input.
If a later ThrowIfError call fails without producing its own diagnostic—for example,
avformat_find_stream_info after a successful open or avcodec_open2 after a probesize warning—the
exception can append an unrelated earlier warning; playback, probe, clip-export, and libav recording
paths also never clear the state.
Code

src/OpenIPC.Viewer.Video/Pipeline/FfmpegRuntime.cs[R33-36]

+        var v = _lastNativeError ?? _lastNativeWarning;
      _lastNativeError = null;
+        _lastNativeWarning = null;
      return v;
Evidence
LogCallback retains warning messages, and FfmpegException.Compose appends the diagnostic
returned by TakeLastNativeError(). The live session clears diagnostic state before
avformat_open_input but not between a successful open and avformat_find_stream_info;
ThrowIfError call sites in FfmpegPlaybackSession, FfmpegMediaProbe, LibavformatClipExporter,
and LibavformatRecordingSession do not clear it first either.

src/OpenIPC.Viewer.Video/Pipeline/FfmpegError.cs[73-81]
src/OpenIPC.Viewer.Video/Pipeline/FfmpegRuntime.cs[142-156]
src/OpenIPC.Viewer.Video/Pipeline/FfmpegVideoSession.cs[218-262]
src/OpenIPC.Viewer.Video/Pipeline/FfmpegRuntime.cs[31-45]
src/OpenIPC.Viewer.Video/Pipeline/FfmpegRuntime.cs[149-155]
src/OpenIPC.Viewer.Video/Pipeline/FfmpegVideoSession.cs[219-225]
src/OpenIPC.Viewer.Video/Pipeline/FfmpegError.cs[56-80]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The warning fallback in `TakeLastNativeError` can attach a warning from an earlier successful native call to an unrelated later failure on the same thread.
## Fix Focus Areas
- src/OpenIPC.Viewer.Video/Pipeline/FfmpegRuntime.cs[31-45]
- src/OpenIPC.Viewer.Video/Pipeline/FfmpegVideoSession.cs[219-225]
- src/OpenIPC.Viewer.Video/Pipeline/FfmpegError.cs[56-80]
## Recommended Fix
Clear native diagnostic state immediately before every FFmpeg operation whose return code is passed to `ThrowIfError`, including the live session’s calls after input opening and the playback, probe, clip-export, and libav recording paths. A helper can clear the state, invoke the operation, and check its result; consume or clear diagnostics after successful operations as needed. Alternatively, limit the warning fallback to the RTSP open path that needs it (#90).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. A stale recorder error can explain a later failure ✓ Resolved
Description
HandleEventAsync stores every RecordingEvent.Error in camera-keyed _lastError[id] without
checking session identity, while StopAsync leaves that entry intact and OnSessionEndedAsync
removes it only after the handle check. If StopAsync or DisposeAsync removes the handle first,
or an old session delivers a late error callback, a later session on the same camera that ends
without its own Error event can report the old message instead of its current stop detail.
Code

src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[144]

+                    lock (_gate) _lastError[id] = err.Message;
Evidence
The error assignment accepts callbacks without checking whether they belong to the current session.
StopAsync removes the handle without clearing _lastError, and OnSessionEndedAsync returns
before removing the entry when the handle is missing. On a subsequent unexpected stop, the cached
message takes precedence over st.Detail.

src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[106-126]
src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[170-188]
src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[107-125]
src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[128-145]
src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[168-188]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Camera-keyed recorder errors can survive a stop or arrive from an old session, then override the failure detail of a later session on the same camera.
## Fix Focus Areas
- src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[98-126]
- src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[143-145]
- src/OpenIPC.Viewer.Core/Recording/RecordingService.cs[168-188]
## Recommended Fix
Associate errors with the session handle rather than only the camera ID, reject callbacks from non-current sessions, and clear error state during every stop and replacement. If retaining `_lastError`, remove the camera's entry under `_gate` when registering a new handle in `StartAsync` and during `StopAsync`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can see how risky a PR is at a glance with blast radius labels

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/OpenIPC.Viewer.Core/Recording/RecordingService.cs Outdated
Comment thread src/OpenIPC.Viewer.Web/Api/WebRecorder.cs
Comment thread src/OpenIPC.Viewer.App/ViewModels/SingleCameraPageViewModel.cs
Comment thread src/OpenIPC.Viewer.Core/Recording/RecordingService.cs Outdated
Comment thread src/OpenIPC.Viewer.Web/Api/LiveFfmpeg.cs Outdated
Comment thread src/OpenIPC.Viewer.Video/Pipeline/FfmpegRuntime.cs Outdated
Comment thread src/OpenIPC.Viewer.Core/Recording/RecordingService.cs Outdated
Comment thread src/OpenIPC.Viewer.Core/Recording/RecordingService.cs Outdated
- RecordingService keeps per-session state and handles each session's
  events in order: no finalize before the Started insert, no stale error
  from an earlier session, WroteSegment only after a row exists
- Register the session before subscribing; recording sessions replay
  recent events, so a recorder that fails at once is still cleaned up
- Pick -stimeout for ffmpeg 4.x (where -timeout means listen); WebRecorder
  reconciles and drops a recording whose ffmpeg exits on its own
- FFmpeg warnings explain only avformat_open_input failures; neutral
  wording for recording failures (disk errors aren't the camera's fault)
@keyldev
keyldev merged commit 45db830 into main Oct 17, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant