Skip to content

feat: archive pro (player, timeline, calendar, clip export)#24

Merged
keyldev merged 8 commits into
mainfrom
feat/phase-16-archive-pro
Jun 20, 2026
Merged

feat: archive pro (player, timeline, calendar, clip export)#24
keyldev merged 8 commits into
mainfrom
feat/phase-16-archive-pro

Conversation

@keyldev

@keyldev keyldev commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

Turns recordings into a real archive workflow. Phase 6 shipped
recording but no playback at all, so this PR first adds the missing recordings
player (file decode + seek + duration probe), then the archive-pro layer on
top: a canvas timeline with cursor/pinch zoom and event markers, an activity
calendar, a day event list, and in/out clip export. Recording is switched to
fragmented MP4 so files survive a crash. Cross-platform (Win/Lin/Mac/Android/iOS).

Related

What's included

  • 16.1 Fragmented MP4 — both recorders write +frag_keyframe+empty_moov+default_base_moof
    (FfmpegRecordingSession, LibavformatRecordingSession), so a file is playable
    mid-record and survives a power loss / crash with no "indexing".
  • 16.2 Player + fast seek + duration probeIPlaybackSession/FfmpegPlaybackSession
    (software decode, PTS-paced, keyframe seek), IMediaProbe/FfmpegMediaProbe for an
    exact duration before the first frame. New RecordingPlayerPage (transport bar +
    seek), opened from a recording row.
  • 16.3 Activity calendarCalendarActivity aggregates recordings + events per
    local day (DST-aware), intensity-shaded month grid; clicking a day filters the
    recordings list. Per-month aggregates are cached.
  • 16.4 Canvas timelineTimelineControl (DrawingContext, markers bucketed per
    pixel column), cursor-anchored wheel + pinch zoom, drag-pan, click/marker → seek;
    adaptive time labels (hours → minutes → seconds). Motion + AI-detection markers.
  • 16.5 Clip export (in/out) — mark start/end on the timeline, export MP4 via
    stream-copy (-c copy, GOP-accurate) with an optional precise re-encode.
  • 16.6 Day event list — side panel of the day's motion/detection events with a
    type filter (All / Motion / AI), click = seek.

Cross-platform / mobile parity

  • Recording, playback, seek and probe run through FFmpeg.AutoGen (software) on all
    heads — same stack as live decode.
  • Clip export is split per head like IRecorder: ffmpeg subprocess on desktop,
    in-process LibavformatClipExporter on Android/iOS (the sandbox can't exec a
    binary). On mobile the exported clip is handed to the native share sheet.
  • Timeline zoom works on touch via pinch (wheel has no touch equivalent).

Tests

  • Unit: TimelineViewport (time↔pixel, cursor-anchored zoom, clamping), TimelineTicks
    (adaptive labels), CalendarActivity (per-day aggregation, intensity), ClipBounds
    (keyframe snap, duration).
  • Integration (SkippableFact, gated on the MediaMTX fixture): record → probe → export
    → probe; asserts a real probed duration and clip length ≈ the selection.
  • Solution builds with 0 warnings across all five heads.

How to try

  1. Record a camera (Live → REC), then open Recordings.
  2. The calendar highlights days with activity; click a day to filter.
  3. Open a recording → scrub/zoom the timeline, click a marker to jump to an event.
  4. Mark in/out → Export → pick a file (desktop) or share the clip (mobile).

Known limitations / deferred

  • Precise (frame-accurate) export on the in-process path falls back to stream-copy
    (no in-process re-encode yet).
  • H.265 smooth-seek remux not implemented (deferred; only if stalling is reported).
  • LibavformatRecordingSession has no segment rotation (single file per session) —
    limits practical Android recording length; a follow-up.
  • Phone layout of the archive/player not yet device-verified.

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

keyldev added 8 commits June 20, 2026 03:10
- IPlaybackSession/IPlaybackEngine/PlaybackOptions + FfmpegPlaybackSession (PTS-paced, keyframe seek, software decode)
- RecordingPlayerPage VM/View, Play from list, nav + DI wiring
- IMediaProbe/FfmpegMediaProbe: exact avformat duration, pre-probed into player
- TimelineViewport/TimelineTicks pure mapping (cursor-anchored zoom, adaptive labels)
- TimelineControl: DrawingContext render, segments + motion/detection markers, drag-pan, click-to-seek, marker tooltips, per-column bucketing
- player VM loads markers from events, playhead + seek-by-time wired into page
- CalendarActivity aggregation (per-day, DST-aware) + ArchiveCalendar VM/View
- recordings list filters by selected day; intensity-shaded month grid
- player side panel lists motion/detection events, type filter, click-to-seek
- IClipExporter + FfmpegSubprocessClipExporter (-c copy fast, precise re-encode), ClipBounds keyframe math
- timeline in/out selection with draggable handles
- player export bar: set in/out, precise toggle, progress, save dialog
…hase 16 G,H)

- recorder drops +faststart (defeats crash-survivable fragmented MP4); aligns to frag_keyframe+empty_moov+default_base_moof
- unit tests: TimelineViewport mapping/zoom/clamp, TimelineTicks, CalendarActivity aggregation, ClipBounds keyframe math
- LibavformatClipExporter: in-process libav stream-copy cut (seek-to-keyframe +
  trim, muxer rebases ts to zero). Android/iOS can't spawn ffmpeg, so the
  subprocess exporter only ran on desktop — export was broken on mobile.
- IClipExporter registered per head (subprocess on desktop, libav on Android/iOS),
  same split as IRecorder; removed from SharedComposition.
- TimelineControl: pinch-to-zoom (cursor/midpoint-anchored) so touch devices can
  zoom the timeline — the wheel handler had no touch equivalent.
- Mobile clip export hands the file to the native share sheet (no visible save
  path on phone); desktop unchanged (reveal-in-folder via IShareService)
- Calendar caches per-month aggregates; month navigation reuses them, page
  re-entry (LoadAsync) clears so new recordings show
- Integration test (Skippable on MediaMTX): record -> probe -> export -> probe,
  asserts real duration + clip length ~ selection
- ROADMAP: Phase 16 -> Done
- 86400s over 1000px @80px target = 6912s; smallest nice step >= that is 7200s (2h)
- ChooseStep was correct; the test's expected value/comment were off by one rung
@keyldev keyldev merged commit 88fe857 into main Jun 20, 2026
6 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