Swift

Handle Video Stats

In this step you implement OTSubscriberKitNetworkStatsDelegate to diagnose stuttering and surface network health in your app.

What you'll monitor How it is delivered How it can be used
videoPacketsLost / videoBytesReceived Periodic — videoNetworkStatsUpdated Confirm the receive path is struggling (stutter, freezes)
transport (local downlink bandwidth) Periodic — mediaLinkStatsUpdated Suggest “Your connection looks weak”
remotePublisherTransport (remote uplink) Periodic — mediaLinkStatsUpdated Suggest “The other participant’s network may be unstable”
networkDegradationSource Periodic and on change Pick local vs remote messaging
Significant network change EventnetworkConditionChanged Banners, toasts, support logs

iOS-specific: Periodic stats and network condition events are methods on the same OTSubscriberKitNetworkStatsDelegate—not a separate listener type.

Adopt the delegate in an extension:

extension VonageVideoManager: OTSubscriberKitNetworkStatsDelegate {
    // ...
}

1. Handle Subscriber Video Stats

func subscriber(_ subscriber: OTSubscriberKit, videoNetworkStatsUpdated stats: OTSubscriberKitVideoNetworkStats) {
    print("videoBytesReceived=\(stats.videoBytesReceived)")
    print("videoPacketsLost=\(stats.videoPacketsLost)")
    print("videoPacketsReceived=\(stats.videoPacketsReceived)")

    DispatchQueue.main.async {
        self.latestObservabilityStats = ObservabilityStats.fromVideoStats(
            stats,
            mediaLink: self.latestMediaLinkSnapshot
        )
    }
}

Callbacks may arrive off the main queue. Update @Published overlay state on the main queue.

func subscriber(_ subscriber: OTSubscriberKit, mediaLinkStatsUpdated mediaLinkStats: OTSubscriberKitMediaLinkStats) {
    applyMediaLinkStats(mediaLinkStats)
}

3. Handle Network Condition Events

Implement subscriber(_:networkConditionChanged:reason:) for significant changes—see Network condition:

func subscriber(
    _ subscriber: OTSubscriberKit,
    networkConditionChanged mediaLinkStats: OTSubscriberKitMediaLinkStats,
    reason: OTNetworkReason
) {
    print("networkConditionChanged reason=\(reason.rawValue)")
    applyMediaLinkStats(mediaLinkStats, networkConditionChangeReason: reason)
    handleNetworkDegradation(mediaLinkStats)
}

Shared merge helper:

private func applyMediaLinkStats(
    _ mediaLinkStats: OTSubscriberKitMediaLinkStats,
    networkConditionChangeReason: OTNetworkReason? = nil
) {
    let localBandwidth = mediaLinkStats.transport?.connectionEstimatedBandwidth
    let remoteBandwidth = mediaLinkStats.remotePublisherTransport?.connectionEstimatedBandwidth
    let localCondition = mediaLinkStats.transport?.networkCondition
    let remoteCondition = mediaLinkStats.remotePublisherTransport?.networkCondition

    DispatchQueue.main.async {
        self.latestMediaLinkSnapshot = MediaLinkSnapshot(
            localEstimatedBandwidth: localBandwidth,
            remoteEstimatedBandwidth: remoteBandwidth,
            networkDegradationSource: mediaLinkStats.networkDegradationSource,
            localNetworkCondition: localCondition,
            remoteNetworkCondition: remoteCondition,
            lastNetworkConditionChangeReason: networkConditionChangeReason
        )
        if var current = self.latestObservabilityStats {
            current.merge(mediaLink: self.latestMediaLinkSnapshot)
            self.latestObservabilityStats = current
        }
    }
}

4. React to Degradation on Network Condition Events

private func handleNetworkDegradation(_ mediaLinkStats: OTSubscriberKitMediaLinkStats) {
    let localCondition = mediaLinkStats.transport?.networkCondition ?? .unknown
    let remoteCondition = mediaLinkStats.remotePublisherTransport?.networkCondition ?? .unknown

    switch mediaLinkStats.networkDegradationSource {
    case .local:
        print("Local network degraded (condition=\(localCondition.rawValue))")
        // e.g. show: "Your connection looks weak"
    case .remote:
        print("Remote publisher network degraded (condition=\(remoteCondition.rawValue))")
        // e.g. show: "The other participant's network may be unstable"
    case .bothOrUnclear:
        print("Degradation unclear — local=\(localCondition.rawValue) remote=\(remoteCondition.rawValue)")
    default:
        break
    }
}

Define ObservabilityStats, MediaLinkSnapshot, and helpers in your project (mirror the Android sample’s model). If remotePublisherTransport stays nil, confirm the remote publisher used settings.senderStatsTrack = true.

Next: Pass merged stats into SwiftUI and render the overlay.