The OpenTok iOS SDK provides calls to access real-time network and media statistics in a video session. These calls report detailed stream quality metrics—such as packet loss, data received, and bandwidth—and can be used on any publisher or subscribed stream.
The OpenTok Video SDK exposes detailed stream-quality metrics through a high-level statistics API—recommended for most use cases—which provides audio, video, network, and sender-side statistics in a unified, session-aware form that remains stable across peer-connection transitions. For advanced debugging, the SDK also offers access to the raw WebRTC stats report, which reflects unprocessed peer-connection data.
The SDK also exposes network condition metrics that provide a high-level assessment of connection health for both publishers and subscribers. These metrics include a network condition score, the reason driving that score, and—for subscribers—a degradation source indicating which side of the connection is responsible for any observed issues. See Network condition and degradation for details.
This guide includes the following sections:
The OpenTok iOS SDK sends periodic audio, video, and media link statistics for both publishers and subscribers. These include packet counts, bitrates, frame rate data, pause/freeze metrics, codec information, and transport-level network metrics such as bandwidth estimation and network condition scoring.
Statistics are delivered through:
OTPublisherKitNetworkStatsDelegate — publisher-side stats (audio, video)
OTSubscriberKitNetworkStatsDelegate — subscriber-side stats (audio, video)
To receive them, enable the appropriate delegate on the publisher or subscriber.
Attach a class that adopts OTPublisherKitNetworkStatsDelegate:
@interface MyViewController () <OTPublisherKitDelegate, OTPublisherKitNetworkStatsDelegate>
@end
OTPublisher *publisher = [[OTPublisher alloc] initWithDelegate:self
settings:settings];
publisher.networkStatsDelegate = self;
Implement the callbacks:
- (void)publisher:(OTPublisherKit *)publisher
videoNetworkStatsUpdated:(NSArray<OTPublisherKitVideoNetworkStats *> *)statsArray {
OTPublisherKitVideoNetworkStats *stats = statsArray.firstObject;
if (!stats) return;
// For routed sessions, the first element is sufficient.
// For relayed sessions, iterate all elements if you want per-subscriber stats.
NSString *connectionId = stats.connectionId ?: @"<none>";
NSString *subscriberId = stats.subscriberId ?: @"<none>";
NSLog(@"Publisher Video Stats for connectionId: %@, subscriberId: %@", connectionId, subscriberId);
NSLog(@"Video bytes sent: %lld", stats.videoBytesSent);
NSLog(@"Video packets sent: %lld", stats.videoPacketsSent);
NSLog(@"Video packets lost: %lld", stats.videoPacketsLost);
NSLog(@"Stats timestamp: %f ms", stats.timestamp);
for (OTPublisherKitVideoLayerStats *layer in stats.videoLayers) {
NSLog(@"Layer: %dx%d", layer.width, layer.height);
NSLog(@" Encoded FPS: %f", layer.encodedFrameRate);
NSLog(@" Bitrate: %lld bps", layer.bitrate);
NSLog(@" Total bitrate (incl. RTP overhead): %lld bps", layer.totalBitrate);
NSLog(@" Codec: %@", layer.codec ?: @"unknown");
NSLog(@" Scalability mode: %@", layer.scalabilityMode ?: @"none");
NSLog(@" Quality limitation: %@", @(layer.qualityLimitationReason));
}
}
- (void)publisher:(OTPublisherKit *)publisher
audioNetworkStatsUpdated:(NSArray<OTPublisherKitAudioNetworkStats *> *)statsArray {
OTPublisherKitAudioNetworkStats *stats = statsArray.firstObject;
if (!stats) return;
// For routed sessions, the first element is sufficient.
// For relayed sessions, iterate all elements if you want per-subscriber stats.
NSString *connectionId = stats.connectionId ?: @"<none>";
NSString *subscriberId = stats.subscriberId ?: @"<none>";
NSLog(@"Publisher Audio Stats for connectionId: %@, subscriberId: %@", connectionId, subscriberId);
NSLog(@"Audio bytes sent: %lld", stats.audioBytesSent);
NSLog(@"Audio packets sent: %lld", stats.audioPacketsSent);
NSLog(@"Audio packets lost: %lld", stats.audioPacketsLost);
NSLog(@"Stats timestamp: %f ms", stats.timestamp);
}
- (void)publisher:(OTPublisherKit *)publisher
mediaLinkStatsUpdated:(NSArray<OTPublisherKitMediaLinkStats*>*)mediaLinkStats {
if (mediaLinkStats.count == 0) return;
OTPublisherKitMediaLinkStats *stats = mediaLinkStats.firstObject;
NSLog(@"Publisher uplink bandwidth: %lld bps", stats.transport.connectionEstimatedBandwidth);
NSLog(@"Network condition: %ld", (long)stats.transport.networkCondition);
NSLog(@"Condition reason: %ld", (long)stats.transport.networkConditionReason);
}
For a publisher in a routed session (one that uses the Vonage Video Media Router), the stats array includes one object, defining the statistics for the single audio or video media stream that is sent to the Vonage Video Media Router. In a relayed session, the stats array includes an object for each subscriber to the published stream.
If you are also interested in video quality events implement this callback:
- (void)publisher:(OTPublisherKit *)publisher
videoQualityChanged:(OTPublisherKitVideoNetworkStats *)stats
reason:(OTPublisherVideoEventReason)reason {
NSLog(@"Publisher video quality event: %ld", (long)reason);
}
To receive network condition change events for the publisher, implement the publisher:networkConditionChanged:mediaLinkStats:reason: callback:
- (void)publisher:(OTPublisherKit *)publisher
networkConditionChanged:(OTPublisherKitMediaLinkStats *)mediaLinkStats
reason:(OTNetworkReason)reason {
NSLog(@"Publisher network condition changed: %ld", (long)mediaLinkStats.transport.networkCondition);
NSLog(@"Reason: %ld", (long)mediaLinkStats.transport.networkConditionReason);
}
This callback is triggered when a significant change in network condition is detected for the publisher. It includes the current media link statistics with transport metrics. See Network condition and degradation source for details on interpreting network condition scores and reasons.
Attach a class that adopts OTSubscriberKitNetworkStatsDelegate:
@interface MyViewController () <OTSubscriberKitDelegate, OTSubscriberKitNetworkStatsDelegate>
@end
OTSubscriber *subscriber = [[OTSubscriber alloc] initWithStream:stream
delegate:self];
subscriber.networkStatsDelegate = self;
[session subscribe:subscriber error:nil];
Implement the callbacks:
- (void)subscriber:(OTSubscriberKit *)subscriber
videoNetworkStatsUpdated:(OTSubscriberKitVideoNetworkStats *)stats {
NSLog(@"Video bytes received: %llu", stats.videoBytesReceived);
}
- (void)subscriber:(OTSubscriberKit *)subscriber
audioNetworkStatsUpdated:(OTSubscriberKitAudioNetworkStats *)stats {
NSLog(@"Audio packets received: %llu", stats.audioPacketsReceived);
}
- (void)subscriber:(OTSubscriberKit *)subscriber
mediaLinkStatsUpdated:(OTSubscriberKitMediaLinkStats *)mediaLinkStats {
NSLog(@"Local downlink bandwidth: %lld bps", mediaLinkStats.transport.connectionEstimatedBandwidth);
NSLog(@"Remote publisher uplink bandwidth: %lld bps", mediaLinkStats.remotePublisherTransport.connectionEstimatedBandwidth);
NSLog(@"Degradation source: %ld", (long)mediaLinkStats.networkDegradationSource);
}
Additionally handle subscriber video quality changed events:
- (void)subscriber:(OTSubscriberKit *)subscriber
videoQualityChanged:(OTSubscriberKitVideoNetworkStats *)stats
reason:(OTSubscriberVideoEventReason)reason {
NSLog(@"Subscriber video quality event: %ld", (long)reason);
}
To receive network condition change events for the subscriber, implement the subscriber:networkConditionChanged:mediaLinkStats:reason: callback:
- (void)subscriber:(OTSubscriberKit *)subscriber
networkConditionChanged:(OTSubscriberKitMediaLinkStats *)mediaLinkStats
reason:(OTNetworkReason)reason {
NSLog(@"Local network condition: %ld", (long)mediaLinkStats.transport.networkCondition);
NSLog(@"Remote publisher network condition: %ld", (long)mediaLinkStats.remotePublisherTransport.networkCondition);
NSLog(@"Degradation source: %ld", (long)mediaLinkStats.networkDegradationSource);
}
This callback is triggered when a significant change in network condition is detected for the subscriber or the remote publisher. It includes the current media link statistics with local and remote transport metrics and degradation source. See Network condition and degradation source for details on interpreting network condition scores and reasons.
This section outlines the structs and properties provided by the iOS audio and video statistics API. While all Video SDK platforms expose the same set of statistics, there may be minor differences in how each platform structures or names individual fields. These variations reflect platform-specific SDK design conventions rather than differences in the underlying metrics.
For a platform-independent explanation of the available statistics and what they represent, refer to client observability overview.
OTTransportStatsRepresents shared transport-level metrics.
connectionEstimatedBandwidth – Estimated available connection bandwidth (bps).networkCondition – Current network condition score (OTNetworkConditionUnknown, OTNetworkConditionCritical, OTNetworkConditionWarning, OTNetworkConditionFair, OTNetworkConditionGood, or OTNetworkConditionExcellent).networkConditionReason – Primary reason impacting the network condition (OTNetworkReasonNone, OTNetworkReasonUnknown, OTNetworkReasonBandwidth, OTNetworkReasonPacketLoss, or OTNetworkReasonNetworkConditionChange).OTPublisherKitVideoNetworkStatsProvides statistics about a publisher’s video track. It includes:
connectionId – In a relayed session, the connection ID of the client subscribing to the stream. Undefined in a routed session.subscriberId – In a relayed session, the subscribed ID of the client subscribing to the stream. Undefined in a routed session.videoPacketsLost – Estimated video packets lost.videoPacketsSent – Video packets sent.videoBytesSent – Video bytes sent.timestamp – Unix timestamp in milliseconds when stats were gathered.startTime – The timestamp, in milliseconds since the Unix epoch, from which the cumulative totals started accumulating.videoLayers – The array of video layer statistics (see OTPublisherKitVideoLayerStats).OTPublisherKitAudioNetworkStatsProvides statistics about a publisher’s audio track. It includes:
connectionId – In a relayed session, the connection ID of the client subscribing to the stream. Undefined in a routed session.subscriberId – In a relayed session, the subscribed ID of the client subscribing to the stream. Undefined in a routed session.audioPacketsLost – Estimated packets lost.audioPacketsSent – Audio packets sent.audioBytesSent – Audio bytes sent.timestamp – Unix timestamp in milliseconds.startTime – The timestamp, in milliseconds since the Unix epoch, from which the cumulative totals started accumulating.OTPublisherKitVideoLayerStatsRepresents one simulcast layer or SVC layer.
width – Encoded frame width.height – Encoded frame height.encodedFrameRate – Encoded frames per second.bitrate – Layer bitrate (bps).totalBitrate – Layer bitrate including RTP overhead (bps).scalabilityMode – SVC/scalability descriptor (e.g., "L3T3").qualityLimitationReason – Reason for quality limitation (bandwidth, CPU, codec, resolution, or layer change).codec – The codec used by this video layer.OTSenderStatsSender-side estimation metrics (mirrored on both audio and video).
connectionMaxAllocatedBitrate – Maximum bitrate estimated for the sender connection.connectionEstimatedBandwidth – Current bandwidth estimation (bps).OTSubscriberKitVideoNetworkStatsProvides statistics about a subscriber’s video track. It includes:
videoPacketsLost – Estimated video packets lost.videoPacketsReceived – Video packets received.videoBytesReceived – Video bytes received.timestamp – Unix timestamp in milliseconds when stats were gathered.senderStats – Sender-side metrics (optional).width – Decoded frame width in pixels.height – Decoded frame height in pixels.decodedFrameRate – Decoded frames per second.bitrate – Video bitrate (bps).totalBitrate – Bitrate including RTP overhead (bps).pauseCount – Number of pauses (>5s since last frame). Includes intentional disables and audio-fallback cases.totalPausesDuration – Total pause duration (ms).freezeCount – Freeze count (WebRTC-defined freeze event).totalFreezesDuration – Total freeze duration (ms).codec – Current decoder codec.OTSubscriberKitAudioNetworkStatsProvides statistics about a subscriber’s audio track. It includes:
audioPacketsLost – Estimated packets lost.audioPacketsReceived – Packets received.audioBytesReceived – Bytes received.timestamp – Unix timestamp in milliseconds.senderStats – Sender-side metrics (optional).OTPublisherKitMediaLinkStatsProvides transport-level statistics for a publisher's connection.
transport – Transport statistics for this publisher (see OTTransportStats)OTSubscriberKitMediaLinkStatsProvides transport-level statistics for a subscriber's connections, including visibility into the remote publisher's network performance. This enables applications to diagnose whether connection issues originate from the subscriber's downlink or the publisher's uplink.
transport – Transport statistics for this subscriber's downlink connection (see OTTransportStats)remotePublisherTransport – Transport statistics for the remote publisher's uplink connection (see OTTransportStats). May be limited if sender-side statistics are not enabled. networkDegradationSource – Indicates the source of network degradation, if any (OTNetworkDegradationSourceNone, OTNetworkDegradationSourceLocal, OTNetworkDegradationSourceRemote, or OTNetworkDegradationSourceBothOrUnclear)See the sender-side statistics overview.
Sender-side statistics are received on the subscribers. To receive sender-side statistics, enable them for the stream’s publisher by setting the senderStatsTrack property to true for the OTPublisherKitSettings object used to create the publisher.
OTPublisherKitSettings *settings = [[OTPublisherKitSettings alloc] init];
settings.senderStatsTrack = YES;
OTPublisher *publisher = [[OTPublisher alloc] initWithDelegate:self
settings:settings];
If senderStatsTrack is not enabled, no sender statistics channel will be published for this publisher. The default value is NO.
If the publisher has enabled sender-side statistics, subscribers receive them automatically via the OTSubscriberKitNetworkStatsDelegate callbacks described above. The senderStats property on both OTSubscriberKitVideoNetworkStats and OTSubscriberKitAudioNetworkStats provides two metrics:
connectionMaxAllocatedBitrate — The maximum bitrate that can be estimated for the connectionconnectionEstimatedBandwidth — The current estimated bandwidth for the connectionThese metrics are calculated per audio-video bundle, so the same values appear in both video and audio statistics.
- (void)subscriber:(OTSubscriberKit *)subscriber
videoNetworkStatsUpdated:(OTSubscriberKitVideoNetworkStats *)stats
{
if (stats.senderStats) {
OTSenderStats *sender = stats.senderStats;
NSLog(@"Connection max allocated bitrate: %lld bps", (long long)sender.connectionMaxAllocatedBitrate);
NSLog(@"Connection current estimated bandwidth: %lld bps", (long long)sender.connectionEstimatedBandwidth);
}
}
The SDK provides real-time network condition metrics for both publishers and subscribers, including a condition score, the reason driving that score, and a degradation source for subscribers. For a full explanation of the network condition model, scores, reasons, and how to enable it, see the client observability overview.
Network condition data is available through two channels:
networkCondition and networkConditionReason. For subscribers, media link stats also include remotePublisherTransport and networkDegradationSource.The following example shows how to use the subscriber network condition data to identify the source of degradation:
- (void)subscriber:(OTSubscriberKit *)subscriber
networkConditionChanged:(OTSubscriberKitMediaLinkStats *)mediaLinkStats
reason:(OTNetworkReason)reason {
OTNetworkCondition localCondition = mediaLinkStats.transport.networkCondition;
OTNetworkCondition remoteCondition = mediaLinkStats.remotePublisherTransport.networkCondition;
OTNetworkDegradationSource source = mediaLinkStats.networkDegradationSource;
if (source == OTNetworkDegradationSourceLocal) {
NSLog(@"Local network is degraded (condition: %ld)", (long)localCondition);
} else if (source == OTNetworkDegradationSourceRemote) {
NSLog(@"Remote publisher network is degraded (condition: %ld)", (long)remoteCondition);
} else if (source == OTNetworkDegradationSourceBothOrUnclear) {
NSLog(@"Degradation source unclear — local: %ld, remote: %ld", (long)localCondition, (long)remoteCondition);
}
}
To get publisher low-level peer connection statistics, use the
[OTPublisherKit getRtcStatsReport:] method. This provides
RTC stats reports for the media stream. This is an asynchronous operation.
Set the [OTPublisherKit rtcStatsReportDelegate]> property and implement the
[OTPublisherKitRtcStatsReportDelegate publisher:rtcStatsReport:]> method
prior to calling [OTPublisherKit getRtcStatsReport:]. When the stats are available, the implementation
of the [OTPublisherKitRtcStatsReportDelegate publisher:rtcStatsReport:]>
message is sent. The message includes an array of OTPublisherRtcStats
objects, which includes a jsonArrayOfReports property. This is a
JSON array of RTC stats reports, which are similar to
the format the RtcStatsReport object implemented in web browsers (see
these Mozilla docs).
Also see this W3C documentation.
To get subscriber low-level peer connection statistics, use the
[OTSubscriberKit getRtcStatsReport:] method. This provides an
RTC stats report for the media stream. This is an asynchronous operation.
Set the [OTSubscriberKit rtcStatsReportDelegate]> property and implement the
[OTSubscriberKitRtcStatsReportDelegate subscriber:rtcStatsReport:]> method
prior to calling [OTSubscriberKit getRtcStatsReport:]. When the stats are available, the implementation
of the [OTSubscriberKitRtcStatsReportDelegate subscriber:rtcStatsReport:]>
message is sent. The message includes a jsonArrayOfReports parameter.
The vonage-video-ios-sdk-samples-swift sender-side statistics sample uses sender-side Statistics in a mobile app built with the iOS client SDK.