iOS Client Example

Before going through this iOS Client example it is recommended to get familiar with the Nabto WebRTC platform using one of the getting started guides which do not require coding.

Example iOS App

You may find the example client app in Nabto WebRTC SDK for iOS. Clone the repository to your local drive and open Demo/Demo.xcodeproj in xcode. Follow the “run the example test application” instructions in the README.md file to test the application.

iOS Swift does not come with a standard WebRTC implementation. As such the application uses a dependency on WebRTC Binaries for iOS. This is the standard WebRTC implementation precompiled for iOS. This is the same WebRTC library which is also used in Chrome etc.

The other dependency that the application has is of course the Nabto WebRTC SDK. The application is written in Swift and is only about ~300 lines of code total, split among the following 2 files.

PerfectNegotiation.swift
ViewController.swift

The first file implements the Perfect Negotiation strategy for WebRTC. ViewController.swift contains the one and only ViewController for the whole app.

Note: It is not recommended to structure your own iOS applications in this way. We skip many good practices in order to have a very short and simple to understand example.

It is important to understand the following

  • The Nabto SDK for iOS primarily handles the signaling aspect of WebRTC. We do not provide a complete WebRTC solution.
  • As stated above, this app uses a standard WebRTC implementation for everything other than signaling.

You may want to use the example app to understand how to use a standard WebRTC library such as Google’s WebRTC. This is out of scope for this guide and may be done at your own discretion. This guide will mostly focus on the usage of the Nabto WebRTC SDK for signaling purposes.

Core concepts

The Nabto WebRTC SDK for iOS is split into two packages, NabtoWebRTC and NabtoWebRTCUtil. The NabtoWebRTC core package contains code that directly interfaces with the Nabto WebRTC platform to establish client connections to devices.

The NabtoWebRTCUtil package contains code built on top of the core package, including helper classes, classes for signing JSON messages and a standardized signaling message format. You are not required to use our signaling message format, the core package can send any arbitrary JSON across the line. However, this would require you to create a custom message format that your devices and clients agree on. Since we’re using a standard device implementation, we will also be using the standard message format.

SignalingClient

In the core module, a SignalingClient represents a single logical client connection. A SignalingClient object is created using the createSignalingClient utility function. In the ViewController.swift file you can see how to set up a SignalingClient object in the ViewDidLoad function of the ViewController class.

After creating a SignalingClient, it will be in an inactive state. Usually you would create a class that implements SignalingClientObserver and add it to the client. This is not done in the example app as we use MessageTransport to handle observing the SignalingClient.

Once your SignalingClient has been created and you are observing it either using a SignalingClientObserver or MessageTransport, you call the start method to connect the client to the Nabto platform so you can begin to send and receive signaling messages.

The following code snippet gives a rough sketch of how to create client and start it. MyObserver is your own class that implements SignalingClientObserver.

let options = SignalingClientOptions(
  productId: "pr-xxxxxx",
  deviceId: "de-xxxxxx"
)
let client = createSignalingClient(options)
let observer = MyObserver()
client.addObserver(observer)
client.start();

Now you may send JSON messages back and forth with the device using the sendMessage method on the client object.

ClientMessageTransport

In the NabtoWebRTCUtil package there is a ClientMessageTransport class that is made to automate a lot of details away when using our standard message format. This class handles signing messages and sending them through the SignalingClient object. In the same ViewController.viewDidLoad method from earlier you can see how it’s set up together with the SignalingClient object. This makes the complete setup code in the example app the following:

class ViewControlller: UIViewController {
    // ...
    var signalingClient: SignalingClient? = nil
    var messageTransport: MessageTransport? = nil

    override func viewDidLoad() {
        // ...
        signalingClient = createSignalingClient(
            SignalingClientOptions(
                productId: productId,
                deviceId: deviceId
            )
        )

        do {
            messageTransport = try createClientMessageTransport(
                client: signalingClient!,
                options: .sharedSecret(sharedSecret: sharedSecret)
            )
            messageTransport?.addObserver(self)
            try signalingClient?.start()
        } catch {
            print(error)
        }
    }
}

extension ViewController: MessageTransportObserver {
    func messageTransport(_ transport: any MessageTransport, didGet message: WebrtcSignalingMessage) {
        perfectNegotiation.onMessage(message)
    }

    func messageTransport(_ transport: any MessageTransport, didFinishSetup iceServers: [SignalingIceServer]) {
        setupPeerConnection(iceServers)
    }

    func messageTransport(_ transport: any MessageTransport, didError error: any Error) {
        print("MessageTransport error: \(error)")
    }
}

In particular, pay attention to the extension that enables the ViewController to be used as an observer for the messageTransport.

The ClientMessageTransport object receives a message from the device, it will parse that message into a WebrtcSignalingMessage object which represents any possible signaling message adhering to the message format. The parsed message is then sent to all observers through the didGet message method of MessageTransportObserver. In our case we pass it on to the PerfectNegotiation object.

The second observer method, didFinishSetup, is simply called when the device and client have exchanged “setup” message, notifying each other that they’re both ready to establish a WebRTC connection. didFinishSetup carries a list of ICE servers which can be used with e.g. Google’s WebRTC to create the actual WebRTC peer connection. In our case this is done in ViewController.setupPeerConnection.

ClientMessageTransport also has a method called sendWebrtcSignalingMessage. Think of this method as being a wrapper around SignalingClient.sendMessage, and think of the observer didGet message as being a wrapper around the didGetMessage method in SignalingClientObserver.

In this way, the ClientMessageTransport object helps to ensure that messages are sent and arrive in the same standard format.

The PerfectNegotiation object is responsible for handling and sending WebrtcSignalingMessages, these are the Descriptions and Candidates which are exchanged between WebRTC peers. For example, when the WebRTC peer connection produces an SDP description that must be sent to the device.

What’s next

With this, the guide is concluded. You may use SignalingClient and ClientMessageTransport in your own project as outlined above for a simple on the rails implementation, or you may decide to familiarize yourself more with the SDK and implement your own message format on top of the core module.