Skip to content
TraceItX Docs
Documentation

Session vitals

Nothing to install, one call per player. The Sessions tab fills itself.

Updated

Session Vitals tracks CPU, memory and playback quality across a session and shows them as a timeline under Sessions. It is off by default — enable it per app under the integration’s Session Vitals card. The SDK negotiates the flag on every config refresh; a session starts within one refresh of the toggle flipping on, and stops (sending its final summary) when it flips off. iOS and tvOS are both supported by the same framework.

Setup

There is no extra package: the AVPlayer integration ships inside TraceItXKit.

var config = TraceItXConfig(appId: "txx_live_…")
config.vitals = VitalsConfig(
    enabled: nil,               // nil = follow the dashboard toggle; false = opt out locally
    sampleRate: nil,            // 0.0–1.0; combined with the server rate by min()
    captureSourceQuery: false   // keep query strings on stream URLs (signed CDN URLs carry tokens)
)
try TraceItX.shared.start(config: config)

Local config can only opt out or lower the rate. It can never enable vitals the dashboard has off.

Tracking a player

import AVFoundation
import TraceItXKit

let player = AVPlayer(url: streamURL)
let handle = TraceItX.shared.trackPlayer(player, name: "main")
// … later
handle.detach()

One call observes the player and its current item. From it the timeline gets the source (URL with query stripped, protocol, live flag from the access log), startup time to first frame, play/pause/seek/rate, rebuffers, bitrate and resolution changes, DRM key system (FairPlay or none), fatal and non-fatal errors, and a stats sample every 20 s (buffer ahead, bandwidth estimate, dropped frames). Nothing is recorded per segment. AVQueuePlayer and replaceCurrentItem(with:) are followed automatically.

The SDK holds the player weakly. If you release it without calling detach(), the registration detaches itself and records a clean player_detach; calling detach() first is still the recommended path. Calling trackPlayer before the collector has started is fine — the registration is honoured the moment it does.

AVFoundation does not expose manifest, first-fragment or licence timings, so startup carries ttffMs, plus the access log’s own startup measurement (accessLogStartupMs) when AVFoundation reports one.

fairplay is detected through the asset’s resource-loader delegate. FairPlay delivered through an AVContentKeySession currently reports none: AVFoundation exposes no way to ask an asset which content-key sessions it belongs to, and the SDK never sees your session object.

Custom entries

TraceItX.shared.trackVitals("ad_break", data: ["position": "midroll", "index": 2])
handle.track("cdn_switch", data: ["from": "cdn-a", "to": "cdn-b"])   // scoped to that player

Any JSON-encodable value, capped at 2 KB serialised. Over the cap it is truncated and flagged, never dropped. Entries logged while no session is running are dropped.

Writing an integration

Implement PlayerIntegration and pass it to TraceItX.shared.trackPlayer(_:name:). attach subscribes to your player and emits through the context (seed from the player’s current state — an already-playing player must open its play span); snapshot is asked once per sampler tick from a background queue and must not block on the player; describe re-emits the current source and DRM and re-opens ongoing spans so a rotated session is self-describing; detach closes open spans and removes every observer. The AVPlayer integration is the reference.

If your detach(onComplete:) is asynchronous, note where the wait lands: TraceItX.shared.kill() and a superseding start() tear every registration down on the calling thread and wait up to 250 ms for those completions. The built-in AVPlayer integration completes inline while nothing else is draining its emissions, so it usually costs nothing; under contention its completion is ordered behind the events already queued and is run by whichever thread owns that drain, so the 250 ms window applies to it too. A custom integration that hops to its player’s own thread always waits there. Either way, that is a reason to call kill() off the main thread if you have a player registered.

What it costs

One serial background queue ticking every 20 s (clock_gettime and task_info), push-based KVO observers, chunks of at most 64 KB flushed every 30 s. The sampler pauses while the app is in the background; the last chunk is flushed inside a short background task; player events keep flowing. Vitals are never written to disk and never retried more than once.