Viewing sessions¶
media.play.sessions
A viewing session is state that Sapsan maintains for a viewer accessing a stream. It associates player requests with authorization and tracks activity, delivered bytes, and archive use. This makes it possible to observe a viewing session as a whole, even when delivery consists of many separate HTTP requests.
For example, an HLS player requests a master playlist, renditions, and a sequence of segments. Quality adaptation and new HTTP connections should not, by themselves, turn one viewing session into hundreds of viewers. Sapsan finds the existing session and associates subsequent requests with it.
How HTTP requests are grouped¶
For ordinary HTTP playback, identity consists of the following:
- Stream name — the requested media resource.
- Request kind — playback is separate from publishing.
- Client IP address — accounting for configured trusted proxies.
- Access token — for a validated JWT ticket, its grant identifier
jtiis used.
Sapsan uses these values to look up the session on every request. If a matching session exists, the request updates its activity. Otherwise, a new session is created and admission is checked. A fragment request can also start a session: fetching the master playlist is not a mandatory first step.
The server assigns the session identifier. An ordinary HTTP client does not need to generate its own session UUID or retain a special cookie. User-Agent is recorded in statistics but is not part of the grouping key above.
The token must reach the related requests. For HTTP playback, the ?token= parameter takes precedence over Authorization: Bearer …. JWT issuance and delivery are described under access tokens.
This accounting depends on the selected identity, not the physical device. Several players using the same IP, token, and stream may share one session. A change in IP or token identifier may create a new one. Issue separate tickets and configure trusted proxies correctly to account for viewers separately.
Lifecycle and statistics¶
Requests and delivered data keep a session active. After 60 seconds without activity, the session expires; cleanup runs periodically, so closure does not necessarily coincide with closing a browser tab.
An open session record and an active viewer are different measures. The current implementation marks a session active after delivering 10 KiB of data. A single request for a small playlist therefore does not necessarily increase the active viewer count. The threshold measures delivered bytes and does not prove that a person watched the picture. Only sessions that became active enter the closed-session log.
The following information is tracked:
- Time — opening, last activity, duration, and closure.
- Delivery — protocol and delivered byte count.
- Client — IP,
User-Agent, and user identifier if supplied by authorization. - Archive — a flag indicating DVR access. After seeking into the archive, it stays set even when playback returns to live.
A live → archive → live transition does not itself create three viewing sessions. Likewise, segment requests and network connections should not be added up as viewers.
SRT delivery quality¶
A session opened over SRT carries a protocol-level delivery slice — current connection counters in the active sessions response:
| Field | Meaning |
|---|---|
rtt_ms, rtt_max_ms |
current RTT and the maximum over the session lifetime |
send_buffer_packets |
packets in the send buffer — an early sign of congestion at the viewer |
packets_sent |
packets sent — the denominator of the loss ratio |
lost_packets |
losses: gaps in the outgoing packet sequence |
drop_too_late_packets |
of the losses — packets stepped over as stale: the latency budget did not cover their recovery |
drop_requests |
of the losses — packets dropped on the receiver's command: the viewer reported the range as no longer needed |
retransmitted_packets |
re-sent packets — replies to the viewer's NAK reports |
naks_received |
the viewer's NAK reports: how many times it reported losses |
health |
connection health verdict: ok, degraded, or bad |
An active session's values keep updating: the node periodically fixes the counters into the session record. On closure, the node record receives the final totals and the verdict of the last window.
The health verdict describes the delivery window — the last minute of the connection, not its whole life: the loss share and the current RTT against twice the delivery latency. Past failures stay in the past: once the window is clean, the verdict returns to ok, and "when it was dropping" is read from the slice history (see minute history). A connection break does not spoil the verdict: a clean session closed by a viewer that went silent keeps the verdict of its last window, and the departure itself is described by the close reason.
Sessions of other protocols carry no srt section at all — the absence of the field does not mean zero losses.
Session minute history¶
Sessions with a user_id owner — from the delivery client map or the owner tag — are written to the central metrics store as minute points: a session graph is built by the same mechanism as the stream input graphs. Each series carries the labels name (stream), user_id, and session:
| Series | What it shows |
|---|---|
stream_play_srt_session_rtt_seconds, ..._rtt_max_seconds |
RTT — current and maximum |
stream_play_srt_session_send_buffer_packets |
send buffer, packets |
stream_play_srt_session_packets_sent_total, ..._bytes_total |
packets and bytes sent |
stream_play_srt_session_lost_packets_total |
losses |
stream_play_srt_session_drop_too_late_packets_total, ..._drop_requests_total |
losses by cause: stepped over as stale and dropped on the receiver's command |
stream_play_srt_session_retransmitted_packets_total, ..._naks_received_total |
retransmissions and NAK reports |
The series are read from the central built-in Prometheus server by the same range query as stream series. The depth of detail is the store window, six hours by default; a restart of the metrics plane resets the history. Deeper than the window, the general totals of the closed record are available — bytes, duration, close reason. Anonymous sessions start no series: without an owner there are no graphs, not zeros.
Session authorization¶
The primary recommended admission method is a JWT ticket. The streamer validates it locally, while session state groups requests and their traffic. An existing session does not eliminate JWT validation on the next HTTP request.
For legacy integrations with an external auth backend, permission is retained for a defined period. Subsequent requests reuse the decision within this window; authorization is required again when it expires. auth_duration, which defaults to 180 seconds, is the authorization decision lifetime, not the 60-second idle timeout.
Checking subsequent HTTP requests and terminating an already open continuous response are different operations. Session tracking alone does not mean that an authorization decision change instantly interrupts every ongoing MPEG-TS delivery.
How this differs from link validation on an nginx edge¶
Signed-link validation determines whether a specific request is allowed. For example, the nginx secure_link module validates a link's authenticity and expiration. Viewing-session accounting requires state that connects separate player requests.
| Task | Link validation alone | Sapsan session tracking |
|---|---|---|
| Admit a segment request | Validates the supplied link | Checks access and associates the request with a viewing session. |
| Count a viewing session | Individual requests do not define viewing-session boundaries by themselves | Tracks session activity and lifecycle. |
| Account for traffic | Per-request data is available | Bytes accumulate in the session. |
| Analyze archive seeking and protocol | Requires additional aggregation | This information is part of the session record. |
| Collect data from several edges | Requires a shared accounting mechanism | Central collects streamer data and provides cluster history. |
Nginx can be integrated with external state and analytics. This comparison concerns a setup where the edge only validates links: link validation alone is not viewing-session tracking.
Cluster sessions and moving between edges¶
With load balancing, a viewer may start playback on edge A and continue on edge B. To analyze this viewing session, central collects server data, stores closed sessions in a shared log, and produces history that includes a record-merging stage.
In cluster history, records with the same recognized identity are merged if their intervals overlap or are separated by no more than 30 minutes. Delivered bytes are summed, the time range spans the merged interval, and nodes lists the participating servers. This provides a model of logical viewing sessions over individual server records.
The current implementation has a limitation: history reads effectively rely on matching full id values. When playback opens on another edge, this ID may change because it includes opening time. Merging every transition between edges is therefore not yet guaranteed. The JWT ticket identifier alone does not remove this limitation of the current history output.
The current active-session list and merged closed-session history are different views. The number of rows in the active list does not automatically indicate unique people over a period or the outcome of historical merging.
Viewing sessions through the API¶
Current sessions for a stream in central:
curl -fsS -u "$ADMIN_USER:$ADMIN_PASSWORD" \
'http://central:8080/central/api-v4/play-sessions/active?stream=cam1&limit=100'
History with merged records:
curl -fsS -u "$ADMIN_USER:$ADMIN_PASSWORD" \
'http://central:8080/central/api-v4/play-sessions/closed?stream=cam1&limit=100'
The response contains a sessions array. The id, bytes, duration, and dvr fields describe the viewing session; node and nodes describe streamer participation. uncovered_nodes identifies servers whose data collection is incomplete. An uncovered server must not be treated as a server with zero viewers.
For local diagnostics, the corresponding /streamer/api-v4/play-sessions/active and /streamer/api-v4/play-sessions/closed paths are available on the streamer. History collection in central is asynchronous: local session closure does not mean it immediately appears in the shared log.