Watching a match
How a client is started to watch a match rather than play in it, and how the match reaches it.
In 3.01–3.10
A watching client takes the match over HTTP from a spectator service, not over a game connection.
What marks a watching client
Nothing on the wire does. A client watches because it was started to watch: it
is given the word spectator where a player’s client is given its server’s
address. Its key can be given as more than 16 characters of base64: the first
24 bytes it encodes, deciphered as Blowfish ECB under the match number written
out, give the key as their first 16 bytes.
In 3.01–3.10
The word is followed by the spectator service’s address, with no port of its own, the key, a match number and a platform name; there is no player number. A key is deciphered under the low 32 bits of the match number, written out as a signed decimal.
In 3.01–3.10
The word
replaystarts a watching client asspectatordoes.
In 3.10
A player’s key is never base64: one longer than 16 characters is cut to its first 16.
The order of the stream
-
A client answered on registration sends
StreamRequest. -
A server sends
StreamMetadata, within 30 seconds: until it arrives the client waits. -
A server sends
StreamChunkRange, with the match’s end left at zero while it runs. -
A server sends the chunks, each cut into
StreamChunkfragments sent in order and not mixed with another chunk’s. It either sends every chunk from chunk 1 upward, or joins the client late:- the chunks of setting up, from 1 to the last of them (the chunk of kind 3), and no chunk after it;
- then the key frame nearest the present, the first key frame sent on the connection;
- then the chunks from the one that key frame leads to.
Either way it skips none after that. A client takes a key frame only where it is the first one sent and arrives before the chunk after the last chunk of setting up; otherwise it keeps it but plays on from that chunk. A server SHOULD join a client late once a match is more than a few minutes in: a client sent every chunk from 1 fails partway through a long catch-up.
-
As the match ends, a server sends
StreamChunkRangeagain naming its last chunk, then that chunk. Once it has arrived the client takes no more of the stream.
The stream’s chunks run for StreamMetadata’s chunkTimeInterval each and its
key frames for its keyFrameTimeInterval, both in milliseconds.
In 3.01–3.10
A watching client asks the spectator service for the match under
/observer-mode/rest/consumer/, each request naming the platform and the match number:getGameMetaDatafor the match’s metadata,getLastChunkInfofor how far the match has come, andgetGameDataChunkandgetKeyFramefor a chunk or a key frame by number. It gives up on the metadata after a time, fetches the chunks of setting up, then catches up to the last chunk on offer and waits there for the next. Key frames arrive compressed. The metadata givesgameKey.gameId,gameEndedandchunkTimeInterval; the last chunk’s information giveschunkId,nextChunkId,availableSince,nextAvailableChunk,endStartupChunkIdandendGameChunkId.
In 3.01–3.10
A watching client first asks the service for its version, and ends by asking it to stop. The metadata also gives
keyFrameTimeInterval, a whole multiple ofchunkTimeInterval, andclientAddedLag; the last chunk’s information also giveskeyFrameId.
In 3.10
A client takes the key frame to fetch from
keyFrameId, and chunks arrive compressed too. The metadata request carries a further number where the service’s version is 1.56 or later, andgetLastChunkInfocarries one too. The metadata also givesclientBackFetchingEnabledandclientBackFetchingFreq.
What a chunk holds
A chunk is the messages a server sent the players of the match, one record after another.
A client plays records as the stream’s clock reaches their time, records of time zero at once, and the first record with a time above zero sets its clock. It skips a record of command 0 on the events channel.
The chunks of setting up carry what a player’s client is sent from its version
answer on: VersionSync first. A client holds the stream back while it loads after
VersionSync, and again while it builds the world after SpawnStart, and
goes on by itself afterwards.
In 3.10
A client whose own build differs from the one
VersionSyncnames asks its player whether to go on, and leaves where the player declines.
A key frame is laid out as a chunk is, and holds the whole world as it stands
at that point. A client joined late goes on after it from the chunk the key
frame names, or, where it names none, from the chunk its number implies (see
StreamChunk).
Messages
Each message of this system in this book, with each layout it has across the book’s patches and every note its definition carries. A layout is the message’s body, after the header its channel’s family puts in front (see Messages).
StreamMetadata
What a server streaming a match to a watching client sends first, within 30 seconds of the client’s request: how long its chunks and key frames run. A client waits on it, and stops taking the stream’s messages once the last chunk has arrived. The header carries zero.
In 3.12–3.15
Command 235 on events, sent by the server, reliable.
| Field | Type | What it is |
|---|---|---|
| unused | u32 | Unused; a server writes zero. |
| text | text, sized by remaining | A JSON object with integer members chunkTimeInterval and keyFrameTimeInterval, in milliseconds, to the end of the message. A server sends both. |
Vectors (1)
StreamChunkRange
What a server streaming a match sends to say where the match proper begins in its chunks, and, once known, where it ends. The header carries zero.
In 3.12–3.15
Command 236 on events, sent by the server, reliable.
| Field | Type | What it is |
|---|---|---|
| startGameChunk | u32 | The chunk the match proper begins with. |
| endGameChunk | u32 | The match’s last chunk, or zero while it runs. |
Vectors (2)
StreamChunk
What a server streaming a match sends to carry one fragment of a chunk or key frame of recorded messages (see Watching a match). A chunk’s facts come from its fragment 0. The fragments of a chunk come in order from 0, and the chunk is complete at index fragments − 1. A new fragment 0 drops an unfinished chunk, and a fragment with no chunk begun has no effect. The header carries zero.
In 3.12–3.15
Command 237 on events, sent by the server, reliable.
| Field | Type | What it is |
|---|---|---|
| chunk | u32 | The chunk’s id, or the key frame’s. Chunk 0 is dropped; chunks play in order from the one a client starts at, and a missing one after it stalls the stream. Key frames count separately, from 1. |
| fragments | u32 | How many fragments the chunk is cut into. |
| index | u32 | This fragment’s place, from 0. |
| kind | u8 | What the chunk is. Values: 1 ordinary, 2 keyFrame, 3 endOfStartup, 4 gameStart, 5 startup. |
| size | u32 | The whole chunk’s size in bytes. |
| duration | u32 | Seconds the chunk covers. A client reads it only from fragment 0, and uses it only for the match’s last chunk: the match ends that many seconds after that chunk starts. A value too large holds a client that joins near the end on its loading screen. |
| nextChunk | u32 | For a key frame, the ordinary chunk to go on from. Where zero or less, a client takes key frame interval over chunk interval, times the key frame’s id less one, plus the start-game chunk: the one StreamChunkRange names, or the last chunk of kind 4 to arrive. |
| data | bytes, sized by remaining | This fragment’s bytes of the chunk, to the end of the message. |
Vectors (2)
StreamRequest
What a client watching a match streamed over its connection (see Watching a match) sends each time the server answers its registration, asking for the stream from a point. The header carries zero.
In every range of this book
Command 234 on requests, sent by the client, reliable.
| Field | Type | What it is |
|---|---|---|
| wantsMetadata | u8 | One: the client wants StreamMetadata. |
| jumpToLatest | u8 | One where the client has no key frame yet and nothing past the end of the startup chunks: a client that can be joined late (see Watching a match). |
| fromChunk | u32 | The first chunk wanted: one past the highest the client holds, 1 at first. |
| fromKeyFrame | u32 | The first key frame wanted: one past the latest the client holds, 1 at first. |