✅ - Fully supported ⚠️ - Partially supported 🚫 - Not supported
| Scenario | Support |
|---|---|
| VOD | ✅ |
| Live Events | ⚠️ |
| Live channels | ⚠️ |
| Server-side Ad insertion | 🚫 |
| Client-side Ad insertion | 🚫 |
session - A session means the viewing session of a piece of content.
sessionId - Refers to the unique identifier for a viewing session of a piece of content.
event - Refers to a set of data that corresponds to the viewing experience.
An analytics specification needs a reliable event flow, it is crucial that the following events are implemented correctly for the backend to be able to churn out session data.
{
event: "event_enum",
sessionId: "UID"
timestamp: 1634911668339, // UTC time. The client SHOULD send valid UTC time.
playhead: 0, // The current playhead position in milliseconds, if the content is Live should be UTC time. -1 if unknown
duration: 0, // The duration of the content in milliseconds. VOD = length of stream, Live = live edge in UTC. -1 if unknown
payload?: {
// Unique to each event
// If needed additional custom fields may be added here as well but the server MAY ignore them.
}
}
Some fields are NOT sent by the client. They are OPTIONAL and populated by the ingest server after it receives an event. Clients MUST NOT set them, and consumers MUST treat them as absent unless present.
domain - OPTIONAL string. The ingest server SHOULD derive this from the request's HTTP Origin header — the scheme, host, and optional port of the page that produced the event (e.g. "https://example.com"). It is server-derived and MUST NOT be trusted as a client-supplied value. When the Origin header is absent from the request, the server omits the field entirely (it is never set to an empty string or a placeholder). Because it is optional, it never appears in any event's required set, so events remain valid whether or not it is present.
{
event: "event_enum",
sessionId: "UID",
timestamp: 1634911668339,
playhead: 0,
duration: 0,
domain?: "https://example.com", // server-populated from the HTTP Origin header; omitted when the header is absent
}

Sent when the client knows it should start a viewing session of content.
This SHOULD be the first event sent by the client to the server.
A restart of content SHOULD create a new session.
The server SHOULD handle events arriving out of order. Each event carries a sessionId and a timestamp for ordering in post.
MUST be sent ONCE per session.
MUST be a unique sessionId.
{
event: "init",
sessionId: "",
timestamp: -1,
playhead: -1, // if the player has an expected startTime, eg. if user continues watching a movie, use that value here.
duration: -1,
}
Contains non-critical metadata connected to the session.
Optional event that can be sent at any time. It is recommended to send it in the interval between the init and stop events.
The server SHOULD merge metadata payloads with previous metadata payloads from the session.
The server SHOULD handle live toggling from true to false (dynamic to static transitions).
{
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0,
payload: {
live?: false,
contentTitle?: "",
},
}
customMetadataIdcustomMetadataId - OPTIONAL number. A client-defined grouping key that a client can set in the metadata payload to label its own custom metadata event types. It lets a client distinguish between several kinds of metadata events it emits (for example, one grouping key for one category of custom metadata and another for a different category), without the specification prescribing what those categories mean.
The value is opaque to the server: the client assigns it however it likes, and the server MUST NOT interpret or validate it. It is carried through the ingest server unchanged so that downstream consumers can filter by it. Being optional, it is fully back-compatible — omitting it leaves existing behaviour unchanged, and it never appears in the metadata event's required set.
The specification defines only the field and its meaning at the event level. How the id is physically persisted (the storage column, index, or query shape used to filter by it) is owned by the downstream worker/storage layer, not by the specification or the ingest server.
{
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0,
payload: {
contentTitle?: "",
customMetadataId?: 1, // client-defined grouping key for the client's own custom metadata event types; opaque to the server
},
}
Sent on an interval, if a certain number of heartbeat events are missing the server can close a session without receiving a stopped event.
MUST be sent at a fixed interval.
The interval SHOULD be agreed upon between client and server.
{
event: "heartbeat",
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0,
payload?: {
}
}
Sent when the contentUrl has been attached to the client player.
MUST be sent ONCE per session.
{
event: "loading",
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0,
}
Sent when there is enough buffered content for the player to start playing the content.
MUST be sent ONCE per session.
{
event: "loaded",
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0
}
Sent when playback starts or resumes, when the playhead starts to move.
{
event: "playing",
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0
}
Sent when the player is paused due to a pause request.
Should not be sent when playback stops for other reasons, e.g. buffering or seeking.
{
event: "paused",
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0
}
Sent when the player starts buffering. Buffering that happens when seeking or loading should be ignored.
Note, since it is possible to pause and unpause during buffering, the corresponding playing should not trigger until after buffered, since that is when the playhead can start moving again.
{
event: "buffering",
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0
}
Sent when the player has finished buffering.
If the buffering is interrupted by seeking, buffered SHOULD still be sent (before the seeking event) to close the buffering period.
If the buffering is interrupted by stopped, buffered should NOT be sent.
{
event: "buffered",
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0
}
Sent when the player starts seeking to a new playhead time.
The playhead MUST be the current playhead time NOT the target playhead time.
Note, since it is possible to pause and unpause during seeking, the corresponding playing should not trigger until after seeked, since that is when the playhead can start moving again.
MUST not be sent during loading.
{
event: "seeking",
sessionId: "",
timestamp: 0,
playhead: 0, // the current playhead
duration: 0
}
Sent when the player has finished seeking to the new playhead time and is ready to start playing.
playhead MUST be the new playhead time.
{
event: "seeked",
sessionId: "",
timestamp: 0, // the new playhead
playhead: 0,
duration: 0
}
Can be sent after loaded event, before the stopped event.
Sent when the player successfully switches to a new bitrate.
{
event: "bitrate_changed",
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0,
payload: {
bitrate: 0, // bitrate in Kbps
width?: 0, // video width in pixels
height?: 0, // video height in pixels
videoBitrate?: 0, // if available provide the bitrate for the video track
audioBitrate?: 0, // if available provide the bitrate for the audio track
}
}
Sent when playback stops.
Can be sent after init.
{
event: "stopped",
sessionId: string,
timestamp: 0,
playhead: 0,
duration: 0,
payload: {
reason: "", // eg. "ended", "aborted", "error"
}
}
Sent when a fatal error occurs.
The following stopped event SHOULD be sent with reason: "error".
{
event: "error",
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0,
payload: {
category?: "", // eg. NETWORK, DECODER, etc.
code: "",
message?: "",
data?: {}
}
}
Sent when a non-fatal error occurs.
A playback error that the player can recover from without interruption.
{
event: "warning",
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0,
payload: {
category?: "", // eg. NETWORK, DECODER, osv.
code "",
message?: "",
data?: {}
}
}
Generated using TypeDoc