Now Playing Log Specification
This specification is currently in development while revisions are underway.
This notice will be removed once the spec is finalized.
Consumer is defined as any external application capable of reading the VRChat client log.
Producer is defined as any in-world system (typically a Media Player) that is logging "now playing" information in this specification's format.
Multiple Producers may be active at any given time, so it is the responsibility of the Consumers to choose which Producers to follow.
A Producer MUST provide logic for world authors to enable and disable the Now Playing log output.
Log frame
Each notification is one raw log message all on a single line, no newlines. It MUST be written when the feature is enabled, even when other logging is disabled. The message body starts with the line nowplaying then a space and then the minified JSON object:
nowplaying {"id":"Main-1","type":"mediaPlay","mediaTime":12.5}
The Producer MAY write a timestamp or other suffix on the same line as nowplaying. Consumers SHALL recognize the marker and MUST parse the remaining content as JSON.
The JSON object MUST be a single line. Newlines inside strings MUST be escaped. Field order does not matter. Unknown type values MUST be ignored by consumers.
id is a non-empty string that uniquely identifies each Producer. It stays constant for the life of that Producer.
It is recommended to have the id start with a prefix that identifies the unique system for that Producer followed by a hypen and a randomly generated suffix number.
eg:
FancyMedia-11067
An omitted optional field means there is no new information, except on mediaChange. On mediaChange, an omitted metadata field means the new media does not have that value.
Time
mediaTime is seconds from the start of the playable range. 0 MUST be the start of what the viewer can seek. mediaLength MUST be the length of that range, in seconds.
eg: A media item whose source occupies the 30–90 second marks is reported with
mediaLengthof60and amediaTimefrom0through60.
When the media has no finite length, the Producer MUST not include mediaLength.
The Consumer MUST retain the latest mediaLength, playback rate, and a time anchor. The time anchor is the mediaTime from the latest mediaPlay or mediaSeek, taken at the moment that event is logged.
The Consumer advances that anchor by elapsed wall time multiplied by the latest playback rate of mediaSpeed. Until a speed arrives, the rate MUST be 1.
A mediaPause MUST update the anchor and MUST stop advancement. A mediaStop MUST stop advancement. When mediaLength is present, the Consumer MUST not advance past it.
Events
mediaInfo
Patches metadata of the current media. A present field MUST replace the stored value. An absent field MUST stay unchanged. This event MUST not change the URL, owner, length, transport, or priority of the current media.
| Field | Type | Meaning |
|---|---|---|
mediaName | string | Title. |
mediaImage | string | Preview image URL. |
mediaSpeed | number | Playback rate used to advance the anchor. |
nowplaying {"id":"Main-1","type":"mediaInfo","mediaName":"Updated Title"}
nowplaying {"id":"Main-1","type":"mediaInfo","mediaImage":"https://image.example/thumb.jpg"}
nowplaying {"id":"Main-1","type":"mediaInfo","mediaSpeed":1.5}
mediaChange
Producer MUST emit this ONLY after a URL has successfully loaded. This is the new-media snapshot. It is a superset of mediaInfo: it MAY include mediaName, mediaImage, and mediaSpeed, and it also carries the identity of the media. Omitted metadata means the new media DOES NOT have that value. A later reload of the same URL is still a new snapshot.
mediaLoading MUST NOT replace the previous identity. The identity changes when this event is written.
| Field | Type | Meaning |
|---|---|---|
mediaName | string | Title. |
mediaImage | string | Preview image URL. |
mediaSpeed | number | Playback rate. Absent means 1. |
mediaLength | number | Playable length in seconds. Omit when the media has no finite length. |
mediaUrl | string | URL that loaded. |
mediaOwnerName | string | Display name of the person who submitted the media. |
hideUrl | boolean | Politeness flag. The URL remains in the payload. true asks the application not to display it. Absent or false means the application may display it. |
highPriority | boolean | This player's media is more important than normal output. When several players set it, the application decides which to follow. Absent or false means normal importance. |
nowplaying {"id":"Main-1","type":"mediaChange","mediaName":"Test Video","mediaImage":"https://image.example/thumb.jpg","mediaSpeed":1,"mediaLength":60,"mediaUrl":"https://test.com","mediaOwnerName":"Tester","hideUrl":true,"highPriority":true}
mediaLoading
Reports the load flag.
| Field | Type | Meaning |
|---|---|---|
loading | boolean | Required. true means a load has started. false means that load ended and the transport state did not change. |
loading: false covers a cancelled load that leaves the previous media in place.
Consumers MUST IMPLY loading as ended for these events: mediaPlay, mediaPause, mediaStop, and a terminal mediaError.
Producers with retry logic MUST stay in loading until all attempts have been completed. A retry MUST NOT emit mediaError and MUST NOT emit loading: false.
nowplaying {"id":"Main-1","type":"mediaLoading","loading":true}
nowplaying {"id":"Main-1","type":"mediaLoading","loading":false}
mediaPlay
Playback is running. mediaTime is the playable-range position and becomes the anchor.
A loop of the same media MUST emit a new mediaPlay with mediaTime at 0. A loop MUST NOT emit mediaStop.
nowplaying {"id":"Main-1","type":"mediaPlay","mediaTime":12.5}
mediaPause
Playback is suspended. The media stays loaded. mediaTime MUST replaces the anchor, and stop advancement.
nowplaying {"id":"Main-1","type":"mediaPause","mediaTime":27.3}
mediaStop
Playback has stopped.
| Field | Type | Meaning |
|---|---|---|
ended | boolean | Required. true means playback reached the end of the playable range on its own. false means playback was stopped before the media finished. |
Producers MUST emit only one mediaStop. This event MUST end loading. It MUST stop advancement.
nowplaying {"id":"Main-1","type":"mediaStop","ended":false}
nowplaying {"id":"Main-1","type":"mediaStop","ended":true}
mediaSeek
Producers MUST emit ONLY after a seek action succeeds. The action MAY come from any user whose seek the player accepts. Automatic clock corrections MUST NOT emit this event. A seek while paused MUST update mediaTime and MUST NOT imply play.
nowplaying {"id":"Main-1","type":"mediaSeek","mediaTime":39.42}
mediaError
Producers MUST emit ONLY when the Producer stops retrying, which MAY be on immediate failure. Internal retryies of the media MUST NOT emit this event and MUST leave the consumer in loading. This event IMPLIES loading has ended.
| Field | Type | Meaning |
|---|---|---|
error | string | Stable error code defined by the player. |
errorMsg | string | Human-readable notice from the player. |
nowplaying {"id":"Main-1","type":"mediaError","error":"InvalidURL","errorMsg":"The media could not be loaded."}