feat: merge multiple recording files into one session (multi-mcap, mixed formats)#21
Merged
Merged
Conversation
Refresh vite/vitest/@playwright/test and other devDependencies ahead of the multi-source merge feature work.
…urces 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).
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.
… 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).
- 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).
- .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.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
.mcap/.bag/.db3/.hdf5/.bvh) into one merged session: topics from every file are concatenated (default "sort by file order"), and the playback range becomes the union of each file's time range.CombinedSourceProxyfans outinitialize/message-cursor/backfill/etc. calls and merges the results, soIterablePlayerdrives a merged session exactly like a single-file one. Loading a single file is byte-for-byte unchanged (zero regression path).file/files/url/urls/fileManifest) is unchanged by default; a new opt-inmergeSourcesprop merges them for hosts that want it.mcap filterCLI (not just@mcap/core); CI installs themcapCLI so that fixture always runs there. A minimal ROS1.bagfixture is committed (rosbags-generated) so bag tests don't need any extra local tooling.Test plan
npm run lintnpm run typechecknpm test(571 unit tests, incl. newmergeInitialization/CombinedSourceProxy/CombinedMessageCursor/datasetSourcesgrouping tests)npm run build(SPA) andnpm run build:lib(embeddable package)npm run gen:e2e:fixtures && npm run test:e2e(35 Playwright tests, incl. new multi-mcap merge, incremental-add-merges, mixed mcap+bag, andmcap filter-derived fixture cases)mcap filterinto two files and confirmed the merged session recovers all 11 topics and the original time range