fix: library + caller-only recorders + live signal indicator #2
No reviewers
Labels
No labels
bug
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: WildDragonLLC/dragonflight#2
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/library-and-signal-indicator"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Three problems from last night's testing pass.
1. Library always rendered empty
GET /api/v1/assetsreturns{ assets, total }butindex.html(andcapture.html's recent-captures grid) assumedr.datawas an array.r.data.forEach()silently threw, the grid stayed empty, the count badge showedundefined assets. Uploads were succeeding — the user just never saw them.Fix lives in
api.js:getAssets()andgetRecentCaptures()now unwrapr.data.assetscentrally and stash the count onr.total. All existing callers keep working.2. Caller-mode SRT/RTMP recorded audio only
When the recorder pulled an MPEG-TS stream from a remote SRT/RTMP source, ffmpeg connected fast enough to read the audio PMT but missed the H.264 SPS/PPS. The video stream was logged as
pix_fmt=noneand silently dropped from the stream map, so the resulting ProRes hires had onlypcm_s24leand the thumbnail step exploded because the proxy had no frames.Fix in
capture-manager.js: add-probesize 32M -analyzeduration 10M -fflags +genptsfor all network inputs and force-map 0:v:0? -map 0:a:0?so each track lives or dies on its own. After the patch the same SRT/RTMP source maps both tracks every time.3. Hitting Record gave no feedback
You couldn't tell whether ffmpeg actually connected, what was wrong if it didn't, or whether the upstream was sending anything. The UI just said "Recording 00:00:01…" forever.
capture-manager.jsnow parses ffmpeg's progress lines (frame=N fps=X) and tracksframesReceived,currentFps,lastFrameAt, plus the most recent error string.getStatus()derives asignalenum:connecting|receiving|lost(frames stopped for 5s+) |error|stopped.recorder-<id>so mam-api can reach it.GET /api/v1/recorders/:id/statusproxies the live capture status through to surfacesignal,framesReceived,currentFps,lastErrorto the client.recorders.htmlpolls that endpoint every 2 seconds while a recorder is recording and renders a badge under each active card: "Connecting…", "Receiving • 217 fr • 30 fps", "No signal — stream dropped", or the actual ffmpeg error message.Bonus: caller-only UI
Per request, dropped listener mode from the create-recorder UI entirely. The MAM is no longer offered as an RTMP/SRT server — every new recorder calls out to an external source URL. Existing listener-mode records still render but the listener mode toggle is gone.
Verified
End-to-end QA with all three protocols against a
bluenviron/mediamtxtest publisher on the same docker network:Failure path also verified — pointing a recorder at
srt://nowhere.invalid:9999surfaces "Connection error" with the actual ffmpeg error string visible on hover, no orphan container leaked.Three problems blocked the end-to-end flow: 1) Library always rendered empty because /assets returns {assets,total} but index.html (and capture.html) assumed r.data was an array. Fixed in api.js by unwrapping r.data.assets centrally; total is kept on r.total. 2) SRT/RTMP caller mode pulled audio only. ffmpeg opened the network input before the H264 SPS arrived, marked the video stream as pix_fmt=none, and silently dropped it from the stream map. Added -probesize 32M -analyzeduration 10M -fflags +genpts and explicit -map 0✌️0?/0🅰️0? so each track survives independently of when it appears. 3) Hitting Record gave no feedback about whether a stream was actually arriving. capture-manager now parses ffmpeg progress lines (frame=... fps=...) and tracks framesReceived, currentFps, lastFrameAt, lastError. getStatus() returns a derived signal enum (connecting | receiving | lost | error | stopped). The recorder controller gives each spawned container a stable network alias `recorder-<id>` and the GET /recorders/:id/status endpoint proxies the live capture status through. recorders.html polls that every 2s and renders the badge under each active card with the running frame/fps counter or the ffmpeg error. Also: * recorders.html: dropped the listener-mode UI entirely. All new recorders are caller-mode (pull). The MAM is no longer offered as an RTMP/SRT server. Legacy listener records still render but read-only.Trash icon in the library was firing PATCH /assets/:id with {status:"deleted"}. The PATCH route only accepts display_name/tags/notes so it returned "No fields to update" and the asset stayed put. * api.js: add deleteAsset(id, {hard}) helper hitting the real DELETE route. * index.html: deleteAssetPrompt now calls deleteAsset (soft archive). Confirm dialog reworded to match. * mam-api/routes/assets.js: list endpoint hides status=archived by default. Pass ?include_archived=true to see them in a future restore-from-trash view. Filtering by ?status=archived still works for power users. * All HTML: bump api.js cache-buster v=4 -> v=5 so the new helper is fetched.* Library cards now show a checkbox on hover (and persistent when selected). Click checkbox = toggle, shift-click = range. Plain click on a card with an active selection extends/shrinks the selection instead of opening preview. Floating pill at the bottom shows count + Move / Copy / Delete / Clear. Move + Copy open a tiny bin picker (current project, default to current bin). * mam-api/routes/assets.js: PATCH /:id now also accepts bin_id (null = move out of bin). New POST /:id/copy makes a reference-copy of the asset row (same S3 keys, new id) into the target bin/project. * api.js: moveAsset(id, binId) and copyAsset(id, {binId, projectId}) helpers. * All accent tokens swapped from the amber oklch(76% 0.178 52) to the Wild Dragon signature blue oklch(55% 0.20 266) = #1f3ad0 ish. Login splash + first-load splash + signal-receiving + button primary all picked it up automatically through common.css. * Loading indicator across the app uses the AMPP Safe hardhat photo gently pulsing with a tiny blue dot underneath. .ampp-loading component lives in common.css with --sm / --xs / --inline variants. Replaces the plain "Loading assets…" empty state in index.html.Two things that together stop bogus URLs from masquerading as a recording: PROBE BUTTON in the New Recorder panel. Before you commit to record, hit Probe Source - the capture container runs ffprobe with a 10s timeout against the URL and returns the parsed streams. UI shows green Signal Detected with codec/resolution/fps/audio, or red No Signal Detected with the actual ffprobe error message. For SDI it lists DeckLink devices. Listener-mode sources cannot be probed standalone (would block waiting for a publisher) and the UI says so. MAIN STATUS LABEL ON THE RECORDING CARD now mirrors the live signal instead of hardcoding Recording. So a recorder pointed at a dead URL goes Connecting... -> Connection error (red) instead of looking like everything is fine. When frames actually start arriving the label flips to Recording (blue) and the dot turns blue. If a previously-good stream drops the label switches to Signal lost (red). API: * capture: POST /capture/probe runs ffprobe and returns { ok, streams, format, error? } * mam-api: POST /api/v1/recorders/probe proxies through to the capture sidecar with a 15s outer timeoutPull request closed