Connection¶
RevueSDK has no server to connect to. Devices find each other on a peer-to-peer network, and a call starts when a friend accepts your invitation. Getting there takes four steps:
- Start the SDK with
Revue.initialize(_:), passing your license and the bootstrap nodes. - Become friends with the person you want to call.
- One side invites, the other accepts.
- Both sides receive
participantDidConnect: the two of you are now connected.
There is only one room, Room.shared. You never create or join a room; friends who accept your invitation
show up in it.
Start the SDK¶
Call Revue.initialize(_:) once, with a Revue.Configuration that has at least these two values:
Revue.Configuration.licenseData: the contents of your license file. Without a valid license,initializethrows aRevueInitError.Revue.Configuration.bootstrapNodes: the nodes the SDK contacts first to join the network. The SDK has no built-in list, so with no nodes nobody can find you.
import RevueSDK
import Foundation
/// Start the SDK with your license file and the bootstrap nodes, then register your delegates.
@MainActor
func startRevue(revueDelegate: RevueDelegate, roomDelegate: RoomDelegate) throws {
let licenseURL = Bundle.main.url(forResource: "revue", withExtension: "lic")
let config = Revue.Configuration(
bootstrapNodes: revueBootstrapNodes,
licenseData: licenseURL.flatMap { try? Data(contentsOf: $0) }
)
// Throws RevueInitError, for example when the license is missing or has expired.
try Revue.shared.initialize(config)
// Register after every initialize: destroy removes all delegates.
Revue.shared.delegates.add(delegate: revueDelegate)
Room.shared.delegates.add(delegate: roomDelegate)
}
Register your delegates after initialize, as above. Restarting the SDK, the other configuration values, and
localParticipant before the SDK starts are covered in Initialization and configuration.
Bootstrap nodes¶
These five public nodes are ready to use. Pass all of them; the SDK uses whichever it can reach.
| Location | Address | Port | TCP relay port | UDP relay port |
|---|---|---|---|---|
| Taiwan, Taipei | 43.213.150.13 | 33445 | 33445 | 33446 |
| Taiwan, Taipei | 43.212.93.163 | 33445 | 33445 | 33446 |
| Singapore | 18.143.211.178 | 33445 | 33445 | 33446 |
| Hong Kong | 18.163.229.39 | 33445 | 33445 | 33446 |
| Japan, Tokyo | 13.158.239.118 | 33445 | 33445 | 33446 |
port is always used to join the network. tcpRelayPort and udpRelayPort are optional and
default to nil: leave one out and the SDK does not use that node as that kind of relay.
Without any TCP relay, devices on networks that block UDP cannot connect.
With their public keys:
import RevueSDK
/// Five public bootstrap nodes. The SDK has no built-in list: pass these, or your own, in `bootstrapNodes`.
let revueBootstrapNodes: [Revue.BootstrapNode] = [
// Taiwan, Taipei
Revue.BootstrapNode(address: "43.213.150.13", port: 33445,
publicKey: "9FF3EA9309646302E4CFF0C6A770490CCE1A61FCFEB04DDDC1886DDB6FF0390B",
tcpRelayPort: 33445, udpRelayPort: 33446),
// Taiwan, Taipei
Revue.BootstrapNode(address: "43.212.93.163", port: 33445,
publicKey: "7B7E856C72078B3896F05CD7AB76CA8840D81785BDFEDCE502867F27557F3D54",
tcpRelayPort: 33445, udpRelayPort: 33446),
// Singapore
Revue.BootstrapNode(address: "18.143.211.178", port: 33445,
publicKey: "67AB39B2F5CA0EA9D1259D12BF1092175B13343CDA5F8E05E15013DA0F77FC7B",
tcpRelayPort: 33445, udpRelayPort: 33446),
// Hong Kong
Revue.BootstrapNode(address: "18.163.229.39", port: 33445,
publicKey: "8FA77DE6925C34EA7CF655B6D99C786B20EB0807AAA8E6BE00673216A8571142",
tcpRelayPort: 33445, udpRelayPort: 33446),
// Japan, Tokyo
Revue.BootstrapNode(address: "13.158.239.118", port: 33445,
publicKey: "80D1F77158FAE08F1C26F3CF83248BB9738CF4FBF242C4299B1040FB992FB40C",
tcpRelayPort: 33445, udpRelayPort: 33446),
]
Add a friend¶
You can only invite friends. To become friends, one side sends a friend request to the other's address
(Revue.revueAddress, for example shown as a QR code) with FriendManager.addFriend(address:message:):
import RevueSDK
/// Your own address. Show it to the person who wants to add you, for example as a QR code.
@MainActor
func myRevueAddress() -> String {
Revue.shared.revueAddress
}
/// Send a friend request to someone else's address.
@MainActor
func sendFriendRequest(to address: String) throws {
try Revue.shared.friends.addFriend(address: address, message: "Hi, it's Alice")
}
The other side receives the request in its RevueDelegate and accepts it with
FriendManager.addFriendNorequest(friendId:):
import RevueSDK
/// The other side of a friend request: ask the user, then accept so the two of you become friends.
final class FriendRequestHandler: RevueDelegate {
var onFriendRequest: ((String, String) -> Void)?
func revue(_ revue: Revue, didReceiveFriendRequest friendId: String, message: String) {
onFriendRequest?(friendId, message)
}
}
/// The user tapped Accept on the friend request.
@MainActor
func acceptFriendRequest(from friendId: String) throws {
try Revue.shared.friends.addFriendNorequest(friendId: friendId)
}
Invite and accept¶
Invite a friend with FriendManager.sendInvitation(friendId:message:). message is free text that the SDK
passes along without reading it, for example to say whether this is a video or a voice call. If the
invitation cannot be sent, the call throws a FriendCallError and the friend's device does not ring.
import RevueSDK
/// Invite a friend to a call. Their device rings; the call starts when they accept.
@MainActor
func inviteToCall(friendId: String) throws {
// Throws FriendCallError when the invitation cannot be sent, for example when the friend is not reachable.
try Revue.shared.friends.sendInvitation(friendId: friendId, message: "video")
}
The friend's RevueDelegate receives the invitation. They accept with
FriendManager.acceptInvitation(friendId:) or reject with FriendManager.rejectInvitation(friendId:).
If the invitation ends without a call (cancelled, rejected or timed out), both sides receive
didEndInvitation with an InvitationEndReason.
import RevueSDK
/// The invited side: show an incoming-call screen, then accept or reject.
final class IncomingCallHandler: RevueDelegate {
var onIncomingCall: ((String) -> Void)?
var onInvitationEnded: ((String) -> Void)?
func revue(_ revue: Revue, friend friendId: String, didReceiveInvitation message: String?) {
onIncomingCall?(friendId)
}
func revue(_ revue: Revue, friend friendId: String, didEndInvitation reason: InvitationEndReason) {
// The caller cancelled, the invitation timed out, or you rejected it: close the incoming-call screen.
onInvitationEnded?(friendId)
}
}
/// The user tapped Accept. If this throws, keep the incoming-call screen so the user can try again.
@MainActor
func acceptCall(from friendId: String) throws {
try Revue.shared.friends.acceptInvitation(friendId: friendId)
}
/// The user tapped Reject.
@MainActor
func rejectCall(from friendId: String) {
try? Revue.shared.friends.rejectInvitation(friendId: friendId)
}
Connected¶
When the invitation is accepted, both sides receive participantDidConnect in their RoomDelegate. From
then on the friend is a RemoteParticipant in Room.remoteParticipants, and you can publish tracks to
them. Until then, the friend is not in the room at all.
import RevueSDK
/// Both sides get participantDidConnect once the invitation is accepted. The friend is now a remote participant.
final class CallStartedHandler: RoomDelegate {
var onConnected: ((RemoteParticipant) -> Void)?
func room(_ room: Room, participantDidConnect participant: RemoteParticipant) {
// participant is now in Room.shared.remoteParticipants. Publish your tracks to them from here.
onConnected?(participant)
}
}
To call more people, invite each of them; every friend who accepts joins the same room. How a call ends is covered in Disconnection.