Skip to content

Connection state

Three different states answer three different questions:

State Question Where
Discoverable Can other people find me on the network? Revue.connectionState
Friend connection Is there a working path between me and this friend? RemoteParticipant.paths, FriendManager.getFriendPaths(friendId:)
Connected Is this friend in a call with me? participantDidConnect / participantDidDisconnect

Keep them apart in your UI: you can be discoverable with no friend connection, and a connected participant can have no friend connection for a while.

The SDK reconnects automatically after a network change, and there is no API to call for it.

Discoverable

Revue.connectionState is a SelfConnectionStatus with three levels. Your RevueDelegate receives didUpdateConnectionState whenever it changes.

Value Level
SelfConnectionStatus.offline Not discoverable: you are not on the network, so nobody can find you or call you.
SelfConnectionStatus.tcp Discoverable through a TCP relay.
SelfConnectionStatus.udp Discoverable directly, over UDP. This is the best level.
import RevueSDK

/// Show whether other people can find you on the network.
final class DiscoverableHandler: RevueDelegate {
    var onChange: ((String) -> Void)?

    func revue(_ revue: Revue, didUpdateConnectionState status: SelfConnectionStatus) {
        onChange?(discoverableText(status))
    }
}

/// The current value, for example when a screen first appears.
@MainActor
func currentDiscoverableText() -> String {
    discoverableText(Revue.shared.connectionState)
}

func discoverableText(_ status: SelfConnectionStatus) -> String {
    switch status {
    case .offline: return "Not discoverable"
    case .tcp: return "Discoverable through a TCP relay"
    case .udp: return "Discoverable directly"
    }
}

Friend connection

The friend connection is the set of paths between you and one friend, a FriendConnectionPaths. Several can work at once:

Path Meaning
FriendConnectionPaths.udpDirect Direct UDP, the best path.
FriendConnectionPaths.udpRelay UDP through a relay.
FriendConnectionPaths.tcpRelay TCP through a relay.

FriendConnectionPaths.isOnline is true when at least one path works. Every friend has a friend connection, in a call or not: your RevueDelegate receives didUpdatePaths for each friend, and FriendManager.getFriendPaths(friendId:) returns the current value. During a call, the same value is also RemoteParticipant.paths, and your RoomDelegate receives didUpdatePaths for that participant.

import RevueSDK

/// The friend connection to one friend: which paths to them work right now.
func friendConnectionText(_ paths: FriendConnectionPaths) -> String {
    if paths.contains(.udpDirect) { return "Direct" }
    if paths.contains(.udpRelay) || paths.contains(.tcpRelay) { return "Relayed" }
    return "No connection"
}

/// Friends list: every friend, in a call or not.
final class FriendListConnectionHandler: RevueDelegate {
    var onChange: ((String, FriendConnectionPaths) -> Void)?

    func revue(_ revue: Revue, friend friendId: String, didUpdatePaths paths: FriendConnectionPaths) {
        onChange?(friendId, paths)
    }
}

/// During a call: empty paths do not end the call. Keep the participant and the hang-up button.
final class InCallConnectionHandler: RoomDelegate {
    var onConnectionLost: ((RemoteParticipant) -> Void)?
    var onConnectionBack: ((RemoteParticipant) -> Void)?

    func room(_ room: Room, participant: RemoteParticipant, didUpdatePaths paths: FriendConnectionPaths) {
        if paths.isOnline {
            onConnectionBack?(participant)
        } else {
            onConnectionLost?(participant)
        }
    }
}

Empty paths during a call do not end it. Show that the connection is lost, but keep the participant on screen and keep the hang-up button working: RemoteParticipant.disconnect() works at any time. The call is over only when you receive participantDidDisconnect.

Connected

A friend is connected from participantDidConnect until participantDidDisconnect. See Connection and Disconnection.

Connection quality

While a friend connection works, Participant.connectionQuality says how good it is: a ConnectionQuality from ConnectionQuality.excellent to ConnectionQuality.bad, or ConnectionQuality.unknown before there are any statistics. The SDK rates it every second from round-trip time and packet loss. Your RoomDelegate receives didUpdateConnectionQuality only when the level changes.

import RevueSDK

/// How good the connection to a participant is while it works: signal bars next to their video.
final class ConnectionQualityHandler: RoomDelegate {
    var onChange: ((Participant, Int) -> Void)?

    func room(_ room: Room, participant: Participant, didUpdateConnectionQuality quality: ConnectionQuality) {
        onChange?(participant, signalBars(quality))
    }
}

/// Signal bars for a quality level. participant.connectionQuality holds the current value.
func signalBars(_ quality: ConnectionQuality) -> Int {
    switch quality {
    case .excellent: return 4
    case .good: return 3
    case .fair: return 2
    case .poor, .bad: return 1
    case .unknown: return 0
    }
}