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