Skip to content

fix(recorder): finish the current file when the flow stops - #967

Closed
wagenet wants to merge 12 commits into
Eyevinn:mainfrom
wagenet:wagenet/strom-recorder-graceful-stop
Closed

wagenet wants to merge 12 commits into
Eyevinn:mainfrom
wagenet:wagenet/strom-recorder-graceful-stop

Conversation

@wagenet

@wagenet wagenet commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Important

This PR can probably be closed. Note added by Claude (Claude Code), on behalf of @wagenet, 2026-10-08.

Live Recorder (builtin.liverecorder, #1034, merged 2026-10-08) is set to replace this block, and it does not have the problem this PR fixes: a stop sends EOS into its muxer and waits up to 3 s for it to finish, and its fragmented mp4 plays without a final index (a_stop_without_eos_still_writes_the_last_fragment, a_stop_before_a_dataless_track_is_released_still_writes_the_file).

This PR stays open, in draft, only as a fallback until Live Recorder has run in a real production. It needs no review in the meantime.

Written by Claude (Claude Code), on behalf of @wagenet.

Depends on #927, and carries its commits until it merges. Review the last four commits, from fix(recorder): finish the current file when the flow stops.

No tracking issue.

Problem

POST /api/flows/:id/stop takes the pipeline straight to NULL. No EOS reaches a recorder's muxer, so the muxer never writes its final index. Only a split (split-now) finished a file. With robust muxing (reserved-moov-update-period 2 s) a stopped mp4 still plays, but:

  • its mdat box has size 0 ("to end of file") and its moov is the last periodic snapshot;
  • it is up to ~2 s short, and its tracks disagree.

Standalone Strom, videotestsrc + audiotestsrc → videoenc/audioenc → recorder, MP4:

file ended by mdat size video s audio s
_00000 split (EOS) 3187873 12.000 12.011
_00001 stop 0 10.10 9.98 (≈12 s recorded)
(earlier run) stop 0 21.87 24.09

Delete after stop leaves the files unchanged. A client that needs finished files has to split, wait for every recorder's RecorderFileChanged, then stop, which leaves a tiny tail file per recorder. A stopped mkv has no duration.

Change

  • Stop drains (BlockBuildContext::register_stop_drain). A block registers a closure that starts its work and returns a receiver for "done". PipelineManager::stop() runs every drain after removing the bus watch, waits for all of them together for at most STOP_DRAIN_TIMEOUT (5 s), then runs the pre-stop hooks (fix(recorder): record a stalled track again once it resumes #927), and then goes to NULL as before. The NULL_STATE_TIMEOUT and wedge handling are unchanged.

  • The recorder's drain sends EOS into the queue in front of each splitmuxsink pad, behind any buffers already waiting there. A pad with no queue linked (a connected track that never carried data) gets the EOS directly. Each send runs on its own thread, because a serialized event waits for the pad's stream lock, and the thread holding that lock can be parked inside splitmuxsink until another track's EOS arrives.

  • Only an open file is drained (file_opened). splitmuxsink opens a file on the first buffer of its reference track (the primary video track, or with no video the first audio track) and holds the other tracks' data until then. Before that, EOS would make it open a file just to end it: a 95 MB mp4 with an empty mdat that does not play. Caps unlock the sink before the first frame, and an x264 encoder with lookahead spends ~250 ms there while audio already arrives, so the sink's locked state is not enough.

  • Done is EOS leaving the muxer while every muxer input's peer (splitmuxsink's internal queue) has carried EOS. At a split, splitmuxsink sends its EOS straight to the muxer pads, bypassing those queues, so a split in progress at stop is not mistaken for the end.

  • Gate, drain, flush with fix(recorder): record a stalled track again once it resumes #927. fix(recorder): record a stalled track again once it resumes #927's pre-stop hook flushes every splitmuxsink sink pad so that going to NULL cannot deadlock. Run before the drain, the flushing pads refuse its EOS, every stop waits out the 5 s, and the file keeps mdat size 0. So the drain first closes fix(recorder): record a stalled track again once it resumes #927's relink gate (ending the stall watchdog and waiting out a file switch that is relinking, up to 10 s; past that the drain is skipped), then sends its EOS, and the flush follows a finished or timed-out drain. A pipeline dropped without a stop runs the hook alone. RelinkGate::close waits only on its first call, so a stuck relink does not cost the hook a second 10 s.

Rejected:

  • EOS to the whole pipeline. Pipeline EOS needs every source to forward EOS and every sink to receive it. WHIP/SRT inputs, aggregators and WHEP sinks often don't, so most real flows would wait out the timeout on every stop. It would also change what every other output does on stop. Injecting at the recorder doesn't depend on upstream at all.
  • Draining before removing the bus watch. When recorders are a flow's only sinks, their EOS makes the pipeline post EOS, and clients would see PipelineEos as if the flow had ended on its own.

Evidence

Each test below fails with the behaviour reverted, run on 20ea75df (27b5fbe1 only sets the test encoder to speed-preset=ultrafast):

claim test revert failure
finished at full length a_stopped_recording_is_finished_at_its_full_length no run_stop_drains() the stop did not finish the file: mdat size Some(0)
a stuck recording holds the stop at most 5 s a_recording_that_cannot_finish_does_not_hold_the_stop (blocks the file sink) unbounded wait stop() did not return within 15s
no delay without data a_recorder_that_never_had_data_does_not_delay_the_stop no file_opened check stop took 5 s
no file before the first video frame a_stop_before_the_first_video_frame_leaves_no_file (video dropped after caps, audio flowing) any track's data counts a recorder stopped before its first video frame left [..._00000.mp4]
no file before the first audio track's data, audio-only a_stop_before_the_first_audio_track_has_data_leaves_no_file (track 0 silent, track 1 running) any track's data counts a recorder stopped before its first audio track had data left [..._00000.mp4]
a split in progress is not the end a_split_in_progress_is_not_taken_for_the_end muxer_inputs_ended always true ..._00001.mp4 was not finished: mdat size None
queued buffers are recorded buffers_queued_at_the_stop_are_recorded (30 video buffers queued) EOS straight to the splitmuxsink pad file stops at 1.87 s, video reached the recorder to 2.87 s
one quiet track does not hold the stop a_stop_with_one_track_quiet_is_not_held_up EOS sent on the calling thread stop with one quiet track took 3.04s

The live sources in these tests never send EOS; it comes from the recorder, so source behaviour does not affect the stop.

Real server, macOS, GStreamer 1.28.6, release build, the flow from the table (720p videoenc + AAC, live test sources), on b1b04073. The reference-track check added since changes nothing for a recording with data.

build recorded stop took mdat size video s audio s
upstream af35c479 12 s 0.03 s 0 8.000 8.043
this PR (2 runs) 12 s 0.03–0.04 s 3180986 11.967 12.053

Drain log: 1 stop drain(s) finished in 2.2ms. Split then stop after 0 / 0.3 / 0.8 / 1.5 / 3 s: both files finished each time, stop 0.03 s. mkv: duration N/A upstream, 6.06 s here. mkv and mpegts drains finished in ~2 ms.

With #927 (438dac2b), each revert run once: the drain after the flush fails 4 of the 8 stop tests; no drain fails 5 of 8; no gate in the drain fails #927's a_flow_stopped_while_the_switch_relinks_leaves_nothing_holding_its_pipeline 3 of 3, now that it checks both files are finished. only_the_first_close_waits_for_a_relink covers the gate. No test guards the flush after a drain timeout.

PipelineManager::stop() time with #927, 5 runs each against 1 run of the old order:

case gate, drain, flush flush first
healthy tracks 1.2–2.8 ms 5.006 s, mdat 0
one track quiet 2 s 1.6–43 ms 5.005 s
audio ended by #927's watchdog 0.8–5.7 ms; video 10.03 s, audio 2.0 s 5.005 s, mdat 0, video 8.03 s
stop during a relink held 500 ms ~510 ms, both files finished —

Tests

Ran on macOS 27 (arm64), GStreamer 1.28.6:

  • On 438dac2b: recorder_test 30 passed, recorder_stop_test 8, recorder_stall_test 3, pipeline_lifecycle_test 4, lib recorder and pipeline::lifecycle 17; clippy and fmt clean. The full suite, Linux CI and Windows have not run on this head.
  • STROM_REQUIRE_GST_PLUGINS=1 cargo test --test recorder_stop_test on 27b5fbe1: 8 passed, 2 runs, plus 4 runs under taskpolicy -b (background QoS, close to a slow CI runner). On 20ea75df, recorder_test and pipeline_lifecycle_test passed.
  • cargo test (all backend tests, 1090): passed on 27d1c4c2, which differs from 20ea75df only in file_opened and the audio-only test.
  • cargo clippy --all-targets -- -D warnings, cargo fmt --all: clean.

Skipped: none (STROM_REQUIRE_GST_PLUGINS=1).
Linux CI (GStreamer 1.24.2): passed on 27b5fbe1; all 8 new tests ran.
Windows (fork run, the ci:windows label needs maintainer access): the recorder never opens its file on Windows (#835, on main), so 4 of the new tests failed there with no recording. 4227962 ignores those 4 on Windows, as recorder_records_when_driven_through_pipeline_start already is.

For the reviewer

  • Untested: a stop after a fix(recorder): record a stalled track again once it resumes #927 file switch but before the new file's first reference buffer may open an empty file to close it, because fix(recorder): record a stalled track again once it resumes #927 resets last_muxed_ms at the switch. Replay normally fills the new file within milliseconds.
  • A stop now waits for recorders, normally a few milliseconds, at most 5 s for a recorder whose muxer or disk is stuck.
  • During the drain, an element upstream of a recorder gets FLOW_EOS back for any buffer after the EOS. Its other branches (a tee into WHEP, say) keep running, and the pipeline goes to NULL right after.
  • muxer_inputs_ended relies on splitmuxsink closing a split fragment by sending EOS directly to the muxer pads, and file_opened on it opening a file at the reference track's first buffer. That is how GStreamer 1.28 does it (gstsplitmuxsink.c). If either changes, the split or first-frame test fails.

Known limits

  • A recorder whose storage failed mid-recording makes the stop wait the full 5 s: its queues are flushing, so the EOS never reaches the muxer. The file is the same as without the drain.
  • Windows hits that case on every stop of a recording flow until Windows: a recorder flow never reaches EOS, so splitmuxsink never finalizes the file #835 is fixed: the file write fails at the start, so the stop takes 5 s where main takes none. No file is written either way.

Out of scope

  • Process shutdown (SIGINT/SIGTERM) doesn't stop flows, so recordings are still left unfinished when the server exits. That's a separate change to the shutdown path.
  • TAMS output also uses splitmuxsink and is not drained. Finishing its last segment would start an upload during teardown.
  • A connected track that never carries data (a video-only WHIP seat's audio) leaves an unplayable file, drained or not. A separate fix is in progress.
  • The x264 lookahead. A video encoder with lookahead still holds frames that never reach the recorder. The stop finishes what reached the recorder. The Video Encoder block defaults to tune=zerolatency, where this is negligible (11.97 s video vs 12.05 s audio above).

🤖 Generated with Claude Code

wagenet and others added 5 commits September 30, 2026 13:18
The stall watchdog ends a track that has run dry while another backs up
behind it. Until now the track never came back, and the recording stayed
without it until the flow stopped.

Ending a track now parks its input: it is unlinked from its parser, the
chain behind it gets the EOS, and a probe takes its buffers off the
stream, keeping everything from the first keyframe on (up to 32 MiB).
That keyframe marks a resume, and the watchdog starts a new file. It
closes the current one (catching its EOS before the pipeline sees it),
takes splitmuxsink to NULL, requests fresh pads for every track carrying
data, and relinks them with new parsers and queues. Each track then
replays what it kept before live data passes, on its own thread because
splitmuxsink can hold one track's queue until another catches up. A new
file is the only way back in: splitmuxsink keeps a pad's EOS across
splits and state changes, and mp4mux refuses pads once a file has
started.

The parked input drops the RECONFIGURE that linking the new chain sends
upstream. An encoder answers it with an ALLOCATION query that waits in
the new queue, which then reads as drained to the watchdog.

Past FILE_FINISH_TIMEOUT the switch waits until the file sink is idle
before closing the file as it is. Stopping the muxer mid-write makes
mp4mux fail the flow.

A track that stays quiet, or whose source really ended, stays out of
the new file. A real EOS still ends the track and the recording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…during replay

Starting a new file parked every track going over at once. A video track
still being recorded was cut mid-GOP, and its part of the new file began
at its next keyframe: the frames in between reached neither file. From a
remote encoder with a GOP longer than the stall timeout, the new file then
waited on that track until the watchdog ended it, and was left empty.

Such a track is now parked at its next keyframe, after a key-unit request.
The current file keeps it up to there, and the keyframe starts the next
file. With no keyframe within KEYFRAME_CUT_TIMEOUT the track is ended
instead, since splitmuxsink opens no file without its primary video, and
a later file takes it back like any ended track.

The stall check also runs while tracks replay into the new file. A replay
larger than its queue waits on the other tracks, so one that died during
the switch held the watchdog, and the recording, until it came back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e while waiting

Parking, the keyframe cut and the stash take a frame without DELTA_UNIT
as a keyframe. tsdemux output, which SRT passthrough feeds the recorder,
flags no frame as a delta. A new file then started mid-GOP, its parser
dropped everything up to the next IDR, and with a GOP over the stall
timeout the track was ended into a parser that had output nothing, which
fails the flow. Each video track now has an h264parse or h265parse that
lives for the whole recording, ahead of a second identity where the
recorder parks and cuts it. It marks keyframes and repeats SPS/PPS
before each one, and is replaced if the codec changes.

That parser drains on EOS, so a track that takes an EOS while parked can
now have frames to replay. The parked input refuses to push its caps and
segment once it is EOS, so the new chain gets them directly.

start_next_file took the pipeline at the start and held it through every
wait. A flow stopped during a switch then outlived the drop that the
flow's leak check runs right after. It now takes the pipeline only to
relink, and only while it is running.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A video input that changes codec is refused by its keyframe parser in the
ACCEPT_CAPS query, so the caps event never reaches the probe that was to
swap the parser. A codec change fails the flow as it did before; the
recorder now handles only the first caps, and the parser is created once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A source that sent a keyframe just past the stall timeout, then nothing,
started a new file on every keyframe. Each time the video then stopped, it
froze the other tracks again for the stall timeout: 4.5 s of every 6 s, and
a file per cycle. A parked input now counts as back only once its data spans
3 s with no gap longer than the stall timeout, so a source slow enough to be
recorded before a stall is recorded after one, and one that sends less often
stays out. A longer gap starts its stash over, so the new file starts on the
first keyframe of the run and the wait loses no video.

Once the switch has taken a track for the next file, a gap no longer drops
its stash, which the new file starts with, and the run starts again. Frames
that arrive while the stash is replayed are kept after it even if the first
is not a keyframe, so the replay leaves no hole in the GOP. A full stash
drops its oldest GOP instead of everything, and once it has dropped the
frames a delta depends on, waits for a keyframe again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@srperens

srperens commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator

Noted as a draft, so I am holding off on a full review until it is marked ready for review. If you want one before then, say so here or request me as a reviewer.

wagenet and others added 3 commits October 6, 2026 17:52
A stop that landed while start_next_file was relinking found the pipeline
still held, tripping the leak check, and relinked a pipeline already going
to NULL. Blocks can now register a pre-stop hook, which the pipeline
manager runs before setting NULL. The recorder's closes a gate: a relink
under way is waited for (10 s at most), and none starts after it. The
watchdog and every wait in the switch give up once the gate is closed.

Waiting for the relink exposed a splitmuxsink hang: opening a file
overwrites the STOPPED state that PAUSED->READY sets, so a queue thread
waits forever and setting NULL never returns. The hook sends FLUSH_START
into splitmuxsink, which releases that wait.

Before GStreamer 1.24.8 a probe that drops an event frees it twice and
logs a CRITICAL. The park probe and the switch's EOS catch now discard
events with Handled; buffers are still dropped.

A replay whose segment push is refused now sends the stream's sticky
events to the new chain directly, not only when the input is at EOS:
buffers without a segment leave splitmuxsink unable to place them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Before GStreamer 1.24.8 a probe that drops an event frees it twice and
logs a CRITICAL. The stand-ins for an encoder that ignores key-unit
requests and a source whose EOS never arrives logged CI's remaining ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@srperens srperens left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: Comment. The mechanism is right, the tests drive the real stop path and ran in CI, and the 5 s bound holds. What holds this at Comment is #927. It adds a second "block work before NULL" hook to stop() at the same line as this PR, and which order the two run in decides whether either one works. A maintainer has to choose that.

Requested changes (or a maintainer decision instead)

  1. Settle the hook shape with #927 before either PR lands. This PR adds run_stop_drains() where #927 adds run_pre_stop_hooks(), both directly after self.volume_ramps.clear();:

    • #927's hook sends FLUSH_START into every splitmuxsink sink pad. If it runs before this drain, the EOS this PR sends into the queue in front of those pads is discarded at a flushing pad. Every recorder stop would then wait out the 5 s and leave the file unfinished, as on main.
    • If the drain runs first, it can land in #927's relink. The target pad picked at backend/src/blocks/builtin/recorder.rs:548 — .unwrap_or(muxer_pad); comes from peer() while #927 is replacing those queues.

    Only one order works for both: close #927's gate, then drain, then flush. Choose between one ordered hook API, landing this first and having #927 slot its gate and flush around run_stop_drains, or the reverse.

Claims

Claim Verdict Evidence
A stuck recorder holds the stop at most 5 s CONFIRMED One deadline is shared by every drain (citation below), and each receiver waits only for what remains of it
Nothing is sent before splitmuxsink has a file open CONFIRMED file_opened checks only the first track that has a pad, which is the reference track in request order (citation below)
The tests exercise the changed code, and they ran CONFIRMED recorder_stop_test builds flows through PipelineManager and calls stop(). Check (Linux) at 4227962 reported 8 passed; 0 ignored, finishing in 7.18 s, which is consistent with real recordings rather than early returns
A split in progress is not taken for the end EXTERNAL Assumes splitmuxsink sends a split's EOS straight to the muxer pads, as the body says 1.28's gstsplitmuxsink.c does. CI ran the guarding test on 1.24.2

backend/src/gst/pipeline/lifecycle.rs:329 — let deadline = Instant::now() + STOP_DRAIN_TIMEOUT;
backend/src/blocks/builtin/recorder.rs:474 — .is_some_and(|(_, activity)| activity.last_muxed_ms.load(Ordering::Relaxed) != 0)

Lifetime: the EOS threads each hold a strong gst::Pad and nothing else. A pad does not reference its parent element, so a thread still blocked at teardown does not keep the pipeline alive for stop_flow's leak check. The probe on the muxer's src pad is EVENT_DOWNSTREAM and holds the muxer weakly. Drains run only in stop() and not in Drop. That is fine: every flow teardown goes through stop() in teardown_flow.

Radius: SHARED. A new BlockBuildContext API, and stop() now waits for every flow that has a recorder: milliseconds normally, 5 s when storage has failed, and 5 s for every Windows recording stop until #835 is fixed (the body says so).

Overlaps: #927: the same code path and design decision, described above. SPECULATIVE (not verified): file_opened reads pad names stored in a OnceLock, and #927 requests fresh pads on every file switch, so whichever PR lands second should re-check that rule after a switch. #944 also reports file boundaries on the same splitmuxsink.

Tests & CI: all Linux, WASM and API checks are green at 4227962. macOS and Windows were skipped. The Windows ignores are visible and cite #835.

Confidence: HIGH

@wagenet

wagenet commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Written by Claude.

The stop order is settled on #927 (comment): close the relink gate, then drain, then flush. #927 lands first, and this PR is rebased onto it:

  • The recorder's pre-stop hook from fix(recorder): record a stalled track again once it resumes #927 keeps only the gate. Its FLUSH_START moves to a new hook that runs after the drains.
  • stop() runs the pre-stop hooks, then run_stop_drains(), then the post-drain hooks. Drop runs the same steps without the drain.
  • register_stop_drain stays a separate API, so every block's drain still runs in parallel under the one 5 s deadline.

The rebase also covers the overlap you flagged. #927 requests new splitmuxsink pads at each file switch, so file_opened and muxer_inputs_ended get re-checked after a switch. A new test stops a flow after a switch while one track is still out of the recording.

This PR doesn't change until #927 merges.

wagenet and others added 4 commits October 7, 2026 06:22
A flow stop took the pipeline straight to NULL, so the muxer never saw EOS
and never wrote its final index. An mp4 was left with an mdat of size 0 and
the moov from its last periodic update, seconds short, with its tracks
disagreeing; an mkv had no duration.

Blocks can now register a stop drain on the build context. stop() runs them
after the bus watch is removed and waits, at most 5 s for all of them, before
going to NULL. The recorder's drain sends EOS into each track's queue and is
done when EOS leaves the muxer with every muxer input ended, which a split's
EOS does not satisfy. A recorder that never had data has nothing to drain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
splitmuxsink opens a file on the first buffer of its reference track, the
primary video track or, with no video, the first audio track, and holds the
other tracks until then. A track's caps unlock it earlier: an x264 encoder
with lookahead spends about 250 ms there while audio already arrives. The
stop drain's EOS in that window made splitmuxsink open a file just to end
it, a 95 MB mp4 with an empty mdat that does not play, where a stop without
the drain leaves nothing.

The drain now runs only once the reference track has carried data. That
replaces the locked-state check, which this case passed.

Adds guards for two behaviours no test failed without: EOS goes in behind
the buffers queued in front of the muxer, and one quiet track does not hold
the other track's EOS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Windows the recorder never opens its file (Eyevinn#835), so the four tests
that check a finished recording fail there with no file, or with a stop
that waits out the drain timeout. They are ignored on Windows, like
recorder_records_when_driven_through_pipeline_start.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The recorder's pre-stop hook flushes every splitmuxsink sink pad so that
going to NULL cannot deadlock on a parked queue. The stop drain sends EOS
into those same pads, and it ran after the flush: the flushing pads refused
the EOS, every stop waited out the 5 s drain timeout, and the file was left
with an mdat size of 0.

A stop now runs gate, drain, flush:
- The drain closes the relink gate first, so no file switch is under way or
  starts while the EOS goes in. A switch relinking has the muxer in NULL, and
  an EOS sent into it left the new file unfinished.
- `stop()` runs the stop drains before the pre-stop hooks. The flush still
  follows a drain that timed out, and a pipeline dropped without a stop still
  runs the hook alone.
- `RelinkGate::close` waits only on its first call, so a relink that outlasts
  the drain's wait does not cost the hook a second one.

`run_stop_drains` is public so the track-resume rig stops its recorder the
way `stop()` does. The relink stop test now also checks that both files are
finished; it fails without the gate in the drain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@wagenet
wagenet marked this pull request as draft October 7, 2026 13:54
@wagenet
wagenet force-pushed the wagenet/strom-recorder-graceful-stop branch from 4227962 to 438dac2 Compare October 7, 2026 13:54
@srperens

srperens commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Comment by Claude (AI), on behalf of @srperens.

Thanks for this. Finishing the file on stop is the right idea, and your measurements (mdat size 0, up to ~2 s short) are what we saw too.

The Live Recorder in #1034 does the same thing inside the block. Its sink sends EOS into each track's queue at its PLAYING→PAUSED step when the pipeline is headed for NULL, and waits up to 3 s, so it needs no stop hook in PipelineManager. A pause does not trigger it. That also avoids the stop-hook ordering clash between this PR and #927. We'd value your view on whether that covers your stop cases.

Where we're heading. We're aiming to deprecate the Recorder in favour of a new block, Live Recorder (builtin.liverecorder), in #1034. It replaces splitmuxsink with a live aggregator muxer (isofmp4mux, matroskamux or mpegtsmux) and a small sink of our own that does the splitting. A track that stops then holds nothing up, and when it comes back it continues in the same file. There is no watchdog, no ended track, and no new file per stall. Both blocks will exist side by side until the new one has run in real productions. The Recorder stays as it is in the meantime.

We'd really like your help with this:

  • Test it on the rigs where you found these bugs (WHIP guests, SRT inputs, per-seat and program recordings). Swap builtin.recorder for builtin.liverecorder; the pads and properties are the same. It builds and runs on GStreamer 1.24 and later, like the rest of Strom.
  • Tell us what you think of the approach, and what it still lacks for your use.
  • Review feat(live recorder): a recorder where a quiet track holds nothing up #1034. You know the failure cases better than anyone, and its "Out of scope" section lists the gaps we know about.

@wagenet

wagenet commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

Comment by Claude (AI), on behalf of @wagenet.

Closing. Live Recorder (#1034) finishes its files on stop inside the block: its sink sends EOS into the muxer and waits up to 3 s. This PR also depended on #927, which is closed. The branch wagenet/strom-recorder-graceful-stop stays as it is.

@wagenet wagenet closed this Oct 9, 2026
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.

2 participants