@eventual-ai/mcap-viewer 0.1.0-alpha.1 → 0.1.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -49,7 +49,9 @@ const recording = await loader.openSpan(result, { signal });
49
49
  ```
50
50
 
51
51
  Each manifest supplies one streamed MCAP URL. Payload requests omit control
52
- credentials. The loader applies the requested span bounds. `discover` maps topic
52
+ credentials. Playback covers the requested span, which the MCAP records in its
53
+ `mda` metadata, so `McapRecording.open` on a saved file starts at the same time
54
+ (`bounds: null` keeps the keyframe preroll). `discover` maps topic
53
55
  names to canonical schemas and actual wire encodings:
54
56
 
55
57
  ```json
@@ -3,6 +3,11 @@ import type { LoadOptions, McapStreamSource } from "./source.js";
3
3
  import { McapRecording } from "./recording.js";
4
4
  import type { TimeBounds } from "./types.js";
5
5
  export interface ReadOptions extends LoadOptions {
6
- bounds?: TimeBounds;
6
+ /**
7
+ * Playback bounds. By default, the span MDA records in `mda` metadata
8
+ * (`start_ns`, exclusive `end_ns`), whose file starts at the preceding
9
+ * keyframe; `null` plays every message.
10
+ */
11
+ bounds?: TimeBounds | null;
7
12
  }
8
13
  export declare function readMcap(input: Blob | McapStreamSource, options?: ReadOptions): Promise<McapRecording>;
@@ -39,13 +39,30 @@ export async function readMcap(input, options = {}) {
39
39
  const byTopic = new Map();
40
40
  const messages = [];
41
41
  const metadata = new Map();
42
- const recording = new McapRecording(metadata, channels, messages, byTopic, loading, options.bounds);
42
+ let recording = new McapRecording(metadata, channels, messages, byTopic, loading, options.bounds ?? undefined);
43
+ let opened = false;
43
44
  let resolveReady;
44
45
  let rejectReady;
45
46
  const ready = new Promise((resolve, reject) => {
46
47
  resolveReady = resolve;
47
48
  rejectReady = reject;
48
49
  });
50
+ const open = () => {
51
+ opened = true;
52
+ resolveReady(recording);
53
+ };
54
+ // MDA writes its metadata before any message, so bounds are set before `open`.
55
+ const applyMdaBounds = (record) => {
56
+ if (options.bounds !== undefined || opened)
57
+ return;
58
+ const start = record.get("start_ns");
59
+ const end = record.get("end_ns");
60
+ if (start !== undefined && end !== undefined)
61
+ recording = recording.withBounds({
62
+ start: BigInt(start),
63
+ end: BigInt(end) - 1n,
64
+ });
65
+ };
49
66
  async function consume() {
50
67
  let lastTime;
51
68
  for await (const bytes of source.stream({ signal })) {
@@ -58,8 +75,11 @@ export async function readMcap(input, options = {}) {
58
75
  signal.throwIfAborted();
59
76
  if (record.type === "Schema")
60
77
  schemas.set(record.id, record);
61
- else if (record.type === "Metadata")
78
+ else if (record.type === "Metadata") {
62
79
  metadata.set(record.name, record.metadata);
80
+ if (record.name === "mda")
81
+ applyMdaBounds(record.metadata);
82
+ }
63
83
  else if (record.type === "Channel") {
64
84
  // The summary section repeats channels; keep the objects messages reference.
65
85
  if (channels.has(record.id))
@@ -104,14 +124,14 @@ export async function readMcap(input, options = {}) {
104
124
  if (bufferedEnd !== undefined && bufferedEnd !== loading.bufferedEnd) {
105
125
  loading.advance(bufferedEnd);
106
126
  if (recording.bufferedEnd !== undefined)
107
- resolveReady(recording);
127
+ open();
108
128
  }
109
129
  }
110
130
  signal.throwIfAborted();
111
131
  if (!reader.done())
112
132
  throw new Error("Incomplete MCAP recording");
113
133
  loading.finish();
114
- resolveReady(recording);
134
+ open();
115
135
  }
116
136
  void consume().catch((error) => {
117
137
  loading.fail(error);
@@ -39,14 +39,6 @@ export class DataLoader {
39
39
  async openSpan(result, options = {}) {
40
40
  if ("error" in result)
41
41
  throw new Error(`${result.error.code}: ${result.error.message}`);
42
- const recording = await McapRecording.open(result.manifest.url, options);
43
- if (options.bounds)
44
- return recording;
45
- const metadata = recording.metadata.get("mda");
46
- const start = metadata?.get("start_ns");
47
- const end = metadata?.get("end_ns");
48
- return start !== undefined && end !== undefined
49
- ? recording.withBounds({ start: BigInt(start), end: BigInt(end) - 1n })
50
- : recording;
42
+ return McapRecording.open(result.manifest.url, options);
51
43
  }
52
44
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventual-ai/mcap-viewer",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "0.1.0-alpha.2",
4
4
  "description": "Browser playback of native H.264/H.265 video from streamed MCAP recordings.",
5
5
  "license": "MIT",
6
6
  "type": "module",