Skip to content

Host rooms

Recordings and replays

Record matches on your server, save them and read them back.

A recording is a compact binary file (.ball2drep) with everything needed to replay a match exactly: the stadium, every input and command, and checkpoints that verify playback.

Record a match

JavaScript
import { writeFile } from 'node:fs/promises';

room.onGameStart = () => room.startRecording();

room.onGameStop = async () => {
  const blob = room.stopRecording();
  if (!blob) return;
  const bytes = Buffer.from(await blob.arrayBuffer());
  await writeFile(`recordings/${Date.now()}.ball2drep`, bytes);
};

startRecording() and stopRecording() are synchronous. stopRecording() returns the file directly.

Recordings the room finishes

Some events end a recording for you. The room then delivers it to onRecordingComplete and keeps it in lastRecording:

ReasonWhen
Recording limit reachedOne hour of play, 30 MB, or the command limit
Stadium changedBefore a new stadium loads
Room closedWhen the room closes, including close()

Handle onRecordingComplete whenever you record, so you don't lose the match that was in progress when the room closed:

JavaScript
room.onRecordingComplete = async (blob, reason) => {
  await writeFile(
    `recordings/${Date.now()}-auto.ball2drep`,
    Buffer.from(await blob.arrayBuffer()),
  );
};

Read a recording

readReplay() decodes and verifies a file without a room:

JavaScript
import { readFile } from 'node:fs/promises';
import { readReplay } from 'ball2d/node';

const replay = await readReplay(
  new Blob([await readFile('final.ball2drep')]),
);
const seconds = (replay.end - replay.initial.tick) / 60;
console.log(
  `${seconds.toFixed(0)} s, ${replay.commands.length} commands`,
);

The decoded Replay holds the stadium source, the starting state, the commands and the roster changes.

A recording can be read only by the engine build that made it. Keep the SDK version you recorded with if you need to read old files later.