3Record a [commonroom](https://github.com/concept-collection/commonroom) call
4from the command line, for transcribing the meeting afterwards.
6The recorder joins a room as an ordinary, **visible** participant (default
7name "Recorder") that stays muted the whole time. It receives every other
8participant's audio and writes one WAV file per participant β so a transcript
9with speaker attribution needs no diarization β plus the room chat and a
10machine-readable event log. By default it also posts a one-line chat notice to
11each participant so everyone knows the meeting is being recorded.
13## Usage
15```
16npm install
17npm run build
18node dist/cli.js <room> [options]
19```
21Options:
23```
24--name <name> Display name in the room (default: Recorder)
25--out <dir> Output directory (default: ./recordings/<room>-<timestamp>)
26--duration <sec> Stop automatically after this many seconds
27--notice <text> Chat line sent to each participant on connect
28 (default: "π΄ This meeting is being recorded.")
29--no-notice Don't send any recording notice
30```
32Stop with Ctrl-C. Requires Node >= 22 (built-in WebSocket). The WebRTC stack
33is [`@roamhq/wrtc`](https://github.com/WonderInventions/node-webrtc), which
34ships prebuilt binaries for Linux and macOS.
36## Output
38```
39<out>/
40 audio/<name>-<peer8>-segN.wav one file per participant per connection
41 (48 kHz mono s16 PCM, typically)
42 chat.txt human-readable chat + join/left log
43 events.jsonl every event with ISO timestamps: join, left,
44 chat, mute/unmute, segment start/end
45 manifest.json session summary: room, participants,
46 segments with start/end times and durations
47```
49Everything is written incrementally (`tail -f chat.txt` works live; the
50manifest is rewritten at every segment boundary and every 30 s), so a crash
51loses at most about a second of audio. A file only starts when a participant's
52first real audio arrives β someone who never unmutes produces no file. If a
53participant disconnects and returns, they get a new numbered segment; the
54manifest's per-segment start times let a transcript interleave speakers on one
55timeline. During a segment, silence is padded by wall clock, so a sample's
56position in the file always tracks elapsed time.
58To transcribe: run each `audio/*.wav` through your transcriber of choice
59(e.g. whisper), offset each result by its segment's `startedAt` from
60`manifest.json`, and merge.
62## How it works
64The p2p layer is commonroom's, ported to Node: the same nostr
65presence/signaling topics (knowing the room name IS the key), the same
66schnorr-signed events (with a fresh ephemeral keypair per run), the same
67deterministic-initiator WebRTC mesh and control data channel (hello, mute
68notices, chat, bye). To the browsers in the room the recorder is
69indistinguishable from a participant whose mic and camera are muted β it
70counts toward the room cap of 8 and appears in the participant list.
72Two deliberate deviations from the browser client:
74- **Receive-only media.** The video m-line is negotiated `sendonly` from the
75 recorder's side (a placeholder track that never produces a frame), so no
76 video is ever sent to the recorder β with up to 7 participants that saves
77 several Mbit/s and all the decode CPU. Audio is symmetric (a silent
78 placeholder goes out, like any muted mic).
79- **Files instead of tiles.** Each remote audio track feeds an `RTCAudioSink`
80 whose PCM goes straight to an incrementally-written WAV.
82## Testing
84`npm run test:loopback` runs an end-to-end test with no browser: it starts the
85recorder and a synthetic participant that "speaks" a 440 Hz sine and sends a
86chat message, then verifies the WAV really contains the tone and the chat made
87it to disk. It uses the real public nostr relays, so it needs network access.
88For a real-world test, run the recorder and join the same room at
89https://concept-collection.github.io/commonroom/ from a browser.