Commit 6d51145
authored
feat: merge multiple recording files into one session (multi-mcap, mixed formats) (#21)
* chore: bump dependencies to latest within existing semver ranges
Refresh vite/vitest/@playwright/test and other devDependencies ahead of
the multi-source merge feature work.
* feat(sources): add CombinedSourceProxy to merge multiple recording sources
Introduce an ISourceHandle interface that both WorkerSerializedSource
(single file) and the new CombinedSourceProxy (N files/formats fanned
out to independent Workers) implement, so IterablePlayer can drive a
merged multi-source session without any change to its own logic.
- ISourceHandle: structural surface IterablePlayer depends on.
- mergeInitialization: pure merge of N per-source Initialization
results (topics concatenated in file order, same-name topics folded
together with accumulated sourceLabels, start/end unioned, topicStats
merged).
- CombinedMessageCursor: parallel k-way merge of child message cursors
by receiveTime, tagging each message with its origin for later
routing.
- CombinedSourceProxy: fans out initialize/getMessageCursor/
getBackfillMessages/getAdjacentMessage/preparePlaybackBuffer/
getLoadProgress/getDataQualityReport to only the relevant member
sources in parallel, and routes resolveMessageBatch/
resolveMessageForHighFrequencyLane back to the originating member
(required for per-Worker SharedArrayBuffer payload rings).
TopicInfo gains an optional sourceLabels field (unset for single-file
sessions, so existing UI is unaffected).
* feat(viewer): merge multiple recording files into one session by default
Add session grouping on top of the existing dataset list:
- DatasetItem gains an optional groupId (datasetGroupKey/groupDatasets
helpers); items without one behave exactly as a standalone group of
one, so single-file loading is byte-for-byte unchanged.
- RosViewerImpl builds one IterablePlayer per active group instead of
per dataset: a group of one still wires WorkerSerializedSource
directly; 2+ members are fanned out to independent Workers (in
parallel, any mix of mcap/bag/db3/hdf5/bvh) and combined through
CombinedSourceProxy.
- Default interactive behavior: dropping/opening more recording files
while a session is active merges them into that session (its topic
list and time range grow); dropping files with nothing loaded starts
one fresh merged session. Tar extraction and history replay keep
today's "open an independent, switchable session" behavior.
- Embed API (`file`/`files`/`url`/`urls`/`fileManifest`) is unchanged
by default; a new opt-in `mergeSources` prop merges them into one
group for hosts that want it.
AppShell's active-dataset lookup now resolves by group key so
extension context and Navbar/title labeling keep working for merged
sessions.
* feat(sidebar): surface merged-session provenance in topic "more" menu and Data tab
- TopicRow's overflow menu shows a "Source: a.mcap, b.mcap" line when a
topic's sourceLabels are populated (multi-source sessions only; the
menu is unchanged for single-file sessions).
- Sidebar's Data tab now renders one row per merged session (grouped
by datasetGroupKey) instead of one row per file: a merged session
shows "first.name +N", the full member list, and a "{count} files
merged" badge, while a standalone file renders exactly as before.
Adds sidebar.topicRow.sourceFiles and sidebar.datasetGroup.fileCount
i18n strings (en/zh/ja).
* test: add multi-mcap and mcap+bag fixture generators for merge testing
- gen-test-mcap-multi-base.mjs / gen-test-mcap-multi-incremental.mjs:
a "base recording" (camera + joint_states, 0-5s) and a separately-
authored "incremental analysis" file (one new topic, 3-7s) that
mirrors the described external hand-pose-overlay workflow. Loading
both exercises topic concatenation and the union time range.
- gen-test-mcap-filtered.mjs: derives a third fixture from the base
file using the real `mcap filter` CLI (not @mcap/core), so at least
one multi-source test loads a CLI-derived file alongside a
hand-authored one.
- gen-test-bag.py: writes a minimal ROS1 .bag (Python `rosbags`
package, since there is no maintained JS/TS ROS1 bag writer) for
mcap+bag mixed-format tests; verified against the app's own
@foxglove/rosbag reader.
Both the CLI-derived and bag fixtures self-skip (exit 0, no output
file) when their external tool isn't installed, so `npm run
gen:e2e:fixtures` keeps working for contributors without them; CI
installs both (follow-up commit).
* ci: install mcap CLI for fixture generation; commit bag source fixture
- .github/workflows/ci.yml (e2e job): download the pinned mcap-cli
Linux binary from GitHub releases so gen-test-mcap-filtered.mjs's
`mcap filter` step actually runs in CI instead of self-skipping.
- gen-test-bag.py now follows gen-test-hdf5.py's pattern: the ROS1 bag
is generated once into the committed
test-fixtures/media/minimal-multi.bag, and gen:e2e:fixtures just
copies it into public/examples/test_multi.bag. This means CI and
most contributors never need the `rosbags` Python package at all;
it's only required to regenerate the source fixture.
- docs/DEVELOPMENT.md: document the new fixtures, the two optional
external tools and their auto-skip behavior, and a multi-source
merge acceptance checklist.
* test(e2e): cover multi-mcap merge, mcap+bag mixing, and incremental add
Extend tests/multi-sources.spec.ts with:
- Two mcap files opened together merge into one session: topics from
both files appear, and the playback range becomes the union of each
file's time range (0-5s + 3-7s -> 0-7s), not either file's own range.
- Dropping a second file onto an already-loaded session extends it
in place (topics/range grow) instead of creating a separate,
switchable dataset; the Data tab shows exactly one merged-session
row.
- The topic "more" menu surfaces which file a topic came from once
sessions are merged.
- mcap + bag mixed-format merge, and a file derived via the real
`mcap filter` CLI merging with a hand-authored one; both self-skip
when their optional fixture is unavailable.
Adds a `playback-time-line` test id to PlaybackBar so tests can assert
the merged/union time range shown next to the scrubber.1 parent d284936 commit 6d51145
32 files changed
Lines changed: 3136 additions & 1414 deletions
File tree
- .github/workflows
- docs
- scripts
- src
- app
- core
- players
- types
- features
- viewer
- workspace
- playback
- sidebar
- topic-list
- infra/workers
- test-fixtures/media
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
| 25 | + | |
| 26 | + | |
25 | 27 | | |
26 | 28 | | |
27 | 29 | | |
| |||
30 | 32 | | |
31 | 33 | | |
32 | 34 | | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
33 | 46 | | |
34 | 47 | | |
35 | 48 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
41 | | - | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
32 | 36 | | |
33 | 37 | | |
34 | 38 | | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
35 | 46 | | |
36 | 47 | | |
37 | 48 | | |
| |||
81 | 92 | | |
82 | 93 | | |
83 | 94 | | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
0 commit comments