Repository navigation
Conversation
Strom's builtin.recorder numbers its inputs from zero (video_in_0, audio_in_0). The generator linked PGM to `video_in`, which the recorder does not have. Strom drops that link as stale when the flow is created, so the recorder was built with its video track unconnected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The recorder's property is `filename_prefix`. Strom ignores the unknown `prefix`, so segments were named recording_00001.mp4 instead of <production id>_00001.mp4. Output dir and upload key already carry the production id, so nothing else changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The recorder refuses raw audio ("Recorder needs AAC, MP3, AC-3, DTS or
Opus audio, but its audio input is raw"), and the audio mixer's main_out
is raw, so the recording had no audio track. Route main_out through a
builtin.audioenc (AAC) into the recorder's audio_in_0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Strom writes {filename_prefix}_{timestamp}_%05d.{ext}. Update the three
comments that describe the segment filename.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
builtin.audioenc first shipped in Strom 0.6.9. An older Strom refuses to start any flow that references it, so a production with a recording output could not go on air. Activation now checks Strom's block list and leaves the encoder out when the block is missing. The recording then has picture but no sound, and activation saves a warning on the production (activationWarnings) and logs it. If the block list can't be read, activation keeps the encoder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Studio shows WS ERROR frames as a toast, so activation warnings (such as a recording with no sound) now go out that way: broadcast to connected controllers when the production activates, and sent again to each controller that connects while the production is live. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A production that never went on air was left inactive with the warning from the failed attempt still set. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The warning toast is broadcast when the production goes active, and not at all if activation was aborted. The connect-time frame needs status 'active' as well as a stromFlowId, so an activating production or one left with a stale flow id sends nothing. controller-websocket.md documents the ERROR frames that carry activation warnings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The recorder's output_dir was recordings/<production id> for every activation, and deactivate uploads everything in it. A file left there after its upload (its delete from Strom failed, or was skipped because the production changed mid-upload) was uploaded again at the next deactivate and registered as a new RecordingDoc. Give each activation its own subdirectory, store it on the production at activate, and read it at deactivate (falling back to the shared directory for productions activated before this change). Derive RecordingDoc ids from the object key so a retried deactivate conflicts instead of registering the same object twice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A per-activation directory is forgotten once its deactivate clears recorderOutputDir, so a session whose upload or listing failed was never uploaded. Deactivate now lists every activation directory under recordings/<production id> and uploads each file whose RecordingDoc does not exist yet. The directory name carries the activation start, which becomes the RecordingDoc's startedAt; endedAt is the file's last write. The idle watchdog, startup reconcile and activation-failure reset now clear recorderBlockId and recorderOutputDir, and activation always writes both. Before, a later activation without a recorder kept the stale block id, and its deactivate split and uploaded the earlier session. The sweep keeps delete-after-upload: once the isStillRecording guard passes, it deletes each file that is in object storage, including files skipped as already registered, and removes each directory that leaves empty. Strom's media listing marks directories with is_directory, not is_dir. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fixture repeated makeActivationResult's field list. A branch that adds a field to makeActivationResult (as Eyevinn#386 does) then merged into whichever copy git matched, and left the other without it. Spreading the base keeps one list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Once uploadFiles sets abortedOnAuthError, uploadProductionRecordings skips the remaining activation directories and the legacy shared directory: the store would reject them the same way, each after a full download from Strom. Files already in object storage are still deleted from Strom, as on main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A source assignment's `record` ('off' | 'transcode' | 'passthrough',
default off) records that input on its own, beside the program
recording. Off unless set: a feed may already be recorded upstream, and
in a delayed production the mixer inputs are bridged out of the store.
- POST /productions/:id/sources takes `record`. 'passthrough' is not
built yet and is answered with 400; activation skips, with a warning,
an assignment that still carries it, and one whose input is not an
arriving feed (WHIP, SRT, EFP).
- Recorders tap the input's decoded pads before the time_offset blocks,
so they hold the feed as it arrived. The offset is a pad offset on
running time; a live trim would make the recording's timestamps jump.
- One recorder per track, so picture and sound are separate files. An
input may carry only one (an audio-only guest, a camera with no sound),
and a two-track recorder waits for both and writes nothing; on Strom
26ffa2fa that stalled recorder also stopped another input's recorder.
- Transcode: leaky queue → builtin.videoenc (H.264, 60-frame GOP) and
leaky queue → builtin.audioenc (AAC). The queues keep encoding out of the
input's streaming thread and drop frames rather than stall the tee that
feeds the mixers.
- Files go to recordings/<production>/<activation>/<mixerInput>/ as
<production>_<mixerInput>_{video,audio}_…; the program recording keeps
the activation directory itself.
- Without builtin.audioenc or builtin.videoenc inputs record the other
track; without the recorder or both encoders they are not recorded. Each
case adds an `input-recording-incomplete` activation warning.
- ProductionDoc.inputRecorderBlockIds (per input, per track) is set on
activate and cleared on every teardown path. Input files follow the
program recording: with object storage, deactivate splits every recorder
and the sweep uploads `video_in_N/` subdirectories, registering each file
with its mixerInput and track (no outputId); without it they stay on Strom.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Joining recordings to tally and tags needs each file's wall-clock start and, for each input, its source and guest. Open Live now writes recordings.json into the activation's recording directory on Strom (POST /api/media/upload): - per recorder (the program's, and each input track's): block id, output dir, and every file it opened, with `startMs`, the file's t=0 on Strom's pipeline clock in UTC ms, when RecorderFileChanged carries `start_utc_us`, and `openedAtMs`, when Open Live received the event. `startMs` is what lines up an input's separate video and audio files. Without it `openedAtMs` stands in: in a 200 s test it put the tracks 50 ms early to 15 ms late (WHIP) and 50-80 ms late (SRT) against one two-track file, varying per file. - per input: sourceId, source name, stream type, record mode, and the guest sessions on it during the activation with their invite labels. The index subscribes to Strom's event stream before the flow is created and binds to the flow once it exists, so a recorder that opens at once is not missed. It rewrites the sidecar after each event and once more when deactivate starts; it closes itself on FlowStopped/FlowDeleted, and a failed or aborted activation closes its own handle. Each write and each reconnect takes a current Strom token, since an OSC SAT expires during a long show. With object storage the sweep copies the sidecar to <prefix><production>/<activation>/ recordings.json without registering it as a recording. docs/recording-inputs.md says what a separate reader, such as a store writer, can rely on: one directory per activation with the sidecar at its top, one track per file, inputs keyed by mixerInput, and a version that changes only when a field is renamed or removed. StromClient gains media.upload, an onOpen callback on connectWebSocket, and the RecorderFileChanged/FlowStopped/FlowDeleted event shapes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…activate Deactivate closed the recording index, split every recorder, then uploaded while the flow kept running. The recorders wrote into the files the split opened until teardown: minutes of media after deactivate, in files that recordings.json did not list (on the meeting rig, ~5 min and 200 MB per track while an unreachable object store timed out). Deactivate now splits, waits until each recorder has opened its next file, stops the flow, closes the index and then uploads. The wait matters: a split lands on the next keyframe after splitNow returns, and Strom's stop does not finalise an MP4, so stopping earlier leaves the pre-split file with its last periodic moov, up to ~2 s short. The wait is bounded at 10 s; a recorder that has not opened a file is not waited for. Each recorder's last file now holds the few milliseconds between split and stop and is listed with its startMs. It may hold one frame or no media. Deactivate closes the index by handle, so a reactivation during the upload keeps its own index open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Once the object store rejects the credentials, every upload fails the same way. The sweep now leaves the activation's remaining input subdirectories, program files and recordings.json on Strom instead of downloading each one to fail again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sidecar is uploaded outside uploadFiles, so a credential rejection on it was recorded as a failed file without setting abortedOnAuthError, and the sweep went on to the production's next activation directory. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Emit builtin.liverecorder instead of builtin.recorder for the program recording and for each input track. Properties, pads and file names are the same. A track that stops and comes back continues in the same file with a gap, instead of holding up the other tracks. A Strom without Live Recorder refuses a flow that names it, so the program recording is left out with a recording-unavailable activation warning, as input recordings already are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
This PR looks like part of a multi-PR dependency stack together with #358 → #359 → #411 → #489 (dependency order; this PR is stacked on #411, which is stacked on #359, which includes #358). Holding off on automated review/merge here — this needs a human (or the relevant dev agent) to assess the whole stack together, not a per-PR pass. Nothing further is needed from the author; this comment exists so the dependency is recorded on the PR rather than in a run log. |
Live Recorder finishes each file when the flow stops (its sink sends EOS into the muxer and waits up to 3 s), so the split-then-wait sequence only added a few-millisecond trailing file per recorder and a wait of up to 10 s. Stop the flow, then upload. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
This PR is part of wagenet's multi-PR recording feature line on Members, in dependency order:
Nothing further is needed from the author; this comment records the dependency on the PRs themselves rather than in a run log. |
Opened by Claude (AI) on behalf of @wagenet.
Stacked on #411. Review only the last two commits (1efe50b, 1c71eb5).
Records through Strom's Live Recorder (
builtin.liverecorder, Eyevinn/strom#1034) instead of the Recorder, for the program recording and for each recorded input track. In Live Recorder a track that stops does not hold up the other tracks: it continues in the same file, with a gap where it was missing. The maintainers plan to deprecate the Recorder.Properties, pads, file names and the
RecorderFileChangedevent are the same, so only the block type changes. Files are fragmented MP4.Strom refuses to start a flow that names a block it lacks. On a Strom without Live Recorder, the program recording is left out with a new
recording-unavailableactivation warning, as input recordings already are, so the production still goes on air. There is no fallback to the Recorder.Deactivate no longer splits each recorder before it stops the flow (1c71eb5). Live Recorder finishes its open file when the flow stops, so the split only added a trailing file of a few milliseconds per recorder and a wait of up to 10 s. It now stops the flow, then uploads.
startMsinrecordings.jsonneeds Eyevinn/strom#1057, which adds the start time to Live Recorder'sRecorderFileChanged. Without it, files carryopenedAtMsonly, as before Eyevinn/strom#944.Testing
pnpm typecheck;pnpm test: 1111 passed. New test: no Live Recorder gives no recorder blocks and arecording-unavailablewarning.startMs.The rig run was before 1c71eb5, so the stop-only deactivate has not been run on the rig yet.
🤖 Generated with Claude Code