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
}
}