/ concept-collection / commonroom-recorder
Sign in
concept-collection / commonroom-recorder
commonroom-recorder
Go to fileHistoryFork
srcRecord commonroom audio and chat from the command line
.gitignoreRecord commonroom audio and chat from the command line
CLAUDE.mdRecord commonroom audio and chat from the command line
package-lock.jsonRecord commonroom audio and chat from the command line
package.jsonRecord commonroom audio and chat from the command line
README.mdRecord commonroom audio and chat from the command line
tsconfig.jsonRecord commonroom audio and chat from the command line

commonroom-recorder#

Record a 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 <room> [options]

Options:

--name <name>      Display name in the room (default: Recorder)
--out <dir>        Output directory (default: ./recordings/<room>-<timestamp>)
--duration <sec>   Stop automatically after this many seconds
--notice <text>    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, which ships prebuilt binaries for Linux and macOS.

Output#

<out>/
  audio/<name>-<peer8>-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.

↑↓ move↡openescclose