Getting Started with Subscribing

Follow these steps to add the subscribing capability to your application.

1. Create a subscriber object

Use the create method to create a subscriber object.

let session = AVAudioSession.sharedInstance()
try session.setCategory(
    .playback,
    mode: .videoChat,
    options: [.mixWithOthers, .allowBluetooth, .allowBluetoothA2DP]
)
try session.setActive(true)

guard let subscriber = MCSubscriber.create() else {
    fatalError("Could not create subscriber.") // In production replace with a throw
}

2. Create a listener class

Create a subscriber's listener class by inheriting the MCSubscriberListener interface.

class SubListener: MCSubscriberListener {
    func onSubscribed() {}
    func onSubscribedError(_ error: String) {}    
    func onConnected() {}    
    func onConnectionError(_ status: Int32, withReason reason: String!) {}    
    func onStopped() {}    
    func onSignalingError(_ error: String) {}    
    func onStatsReport(_ report: MCStatsReport!) {}    
    func onVideoTrack(_ track: MCVideoTrack!, withMid: String) {}   
    func onAudioTrack(_ track: MCAudioTrack!, withMid: String) {}  
    func onActive(_ _: String!, tracks: [String]!, sourceId: String!) {}
    func onInactive(_ streamId: String!, sourceId: String!) {}
    func onLayers(_ mid: String!, activeLayers: [MCLayerData]!, inactiveLayers: [MCLayerData]!) {}
    func onVad(_ mid: String!, sourceId: String!) {}
    func onViewerCount(_ count: Int32) {}
}

3. Set the listener

Create an instance of the listener class and set it in the viewer.

let listener = SubListener()
subscriber.setListener(listener)

4. Set up your credentials

Get your stream name and stream ID from the dashboard and set them up in the SDK using the setCredentials method.

let credentials = MCSubscriberCredentials()
credentials.accountId =  "streamName"; // The name of the stream you want to subscribe to
credentials.streamName = "ACCOUNT"; // The ID of your Dolby.io Real-time Streaming account
credentials.apiUrl 
    = "https://director.millicast.com/api/director/subscribe"; // The subscribe API URL

guard subscriber.setCredentials(credentials) else {
    fatalError("Could not set credentials.") // In production replace with a throw
}

5. Configure the subscriber by setting your preferred options

Configure your stream to receive multi-source content.

Define your subscription preferences and then call the connect method to connect to the Millicast platform. After this step, call the subscribeWithOptions method and provide the defined options as its parameter.

let subscriberOptions = MCClientOptions()

subscriberOptions.pinnedSourceId 
    = "MySource"; // The main source that will be received by the default media stream
subscriberOptions.multiplexedAudioTrack 
    = 3; // Enables audio multiplexing and denotes the number of audio tracks to receive
         // as Voice Activity Detection (VAD) multiplexed audio
subscriberOptions.excludedSourceId
    = [ "excluded" ] // Audio streams that should not be included in the multiplex, for
                     // example your own audio stream

// Set the selected options
subscriber!.connect()
subscriber!.subscribeWithOptions(with: subscriberOptions)

If the connection fails, the listener's onConnectionError method is called with the HTTP error code and failure message. If the code is 0, double-check your internet connection or the API URL set in the credentials. If the connection is successful, the SDK calls the onConnected method.

6. Modify the onConnected method.

class SubListener: MCSubscriberListener {

    /* ... */

    func onConnected() {
        guard subscriber.subscribe() == true else {
            fatalError("Could not subscribe.") // In production replace with a throw
        }
    }

    /* ... */
}

Note that the subscriber created earlier needs to be available for the listener. The example above assumes that the listener stores a reference to that subscriber in an instance variable.

When the operation is successful, the SDK calls onSubscribed and sends you an event in the listener with the created audio and video tracks. Otherwise, the SDK calls onSubscribedError with an error message.

8. Manage broadcast events

When broadcast events occur, the SDK calls the corresponding callback in the listener object. The following Subscriber event listeners are available:

  • onActive: Called whenever a new source starts publishing a stream. It contains the stream ID, the IDs of tracks within a stream, and the source ID.
  • onInactive: Called whenever a source is no longer published within a stream. It contains the stream ID and the source ID.
  • onStopped: Called whenever a stream stops.
  • onVad: Called whenever a source ID is multiplexed into an audio track based on the voice activity level. It contains the media ID of the track and the source ID.
  • onLayers: Called whenever Simulcast or Scalable Video Coding (SVC) layers are available. It contains arrays of the LayerData object that you can use in the select method.
  • onViewerCount: Called each time a new viewer enters or leaves a stream. All clients connected to the stream are notified about the current number of viewers.

9. Project media

Using the multi-source feature requires projecting tracks into a specified transceiver using its media ID (mid). When you start subscribing, you receive the onActive event with the track IDs and the source ID. In order to project a track into a transceiver, you must use the project method of the subscriber. You need to specify the source ID you are targeting and an array of the tracks you want to project.

By default, only one video and audio track is negotiated in the SDP. If there are several publishers sending media in one stream, you can dynamically add more tracks using the addRemoteTrack method each time you receive the onActive event. The method adds a new transceiver and renegotiates locally SDP. When successful, the SDK creates new tracks and calls the onAudioTrack and onVideoTrack callback, so you can get the tracks and their corresponding Media IDs.

// Get mid either from the `on_track` callback of the listener object or by calling the getMid method with the track ID
/* Option 1 */
class SubListener: MCSubscriberListener {  
  /* ... */
    func onVideoTrack(_ track: MCVideoTrack!, withMid: String) {
    // Store the mid value somewhere 
    }   
  /* ... */
}

/* option 2 */
let mid = subscriber!.getMid(track.getId());

// Project a video track
let projectionData = MCProjectionData()
projectionData.mid = mid // The media ID of the transceiver you want to project into
projectionData.media = "video" // The media track type, either video or audio
projectionData.trackId = trackId // The name of the track on the media server side, which is the track ID you get in the onActive event

subscriber!.project(sourceId, [projectionData])

subscriber!.addRemoteTrack("video"); // "audio" or "video" depending on the type of track you want to add

To stop projecting the track, call the unproject method, which requires an array of the media IDs that you want to stop projecting.

10. Select a layer that you want to receive

When a publisher uses Simulcast or the SVC mode when sending a video feed, the media server automatically chooses the right layer to send to the subscriber according to the bandwidth estimation. However, you can force the server to send you a specific layer by calling the select method.

For example, if the sender uses Simulcast, it is possible to receive three different encoding IDs: 'h' for the high resolution, 'm' for the medium one, and 'l' for the low. In order to choose the medium resolution, you have to do the following:

let layerData = MCLayerData()
layerData.encodingId = "m" // The encoding ID, which is the ID of the Simulcast layer
layerData.temporalLayerId = 1 // The ID of the temporal layer
layerData.spatialLayerId = 0 // The ID of the spatial layer

subscriber.select(layerData);
// The null value means the server will make automatic selection

11. Render video

The SDK provides a UIKit view for rendering a video track. This UIKit view is obtained from the MCIosVideoRenderer as follows:

let renderer = MCIosVideoRenderer()

guard let view = renderer.getView() else {
    fatalError("Could not retrieve view from renderer.") // In production replace with a throw
}

You can insert the view element obtained from the renderer in any UIKit view hierarchy. In SwiftUI, wrap the UIKit view in UIViewRepresentable. To display a track in the view, add the renderer to the view element by modifying the onVideoTrack method of the MCSubscriberListener. Note that audio and video tracks need to be enabled.

class SubListener: MCSubscriberListener {

    /* ... */

    func onVideoTrack(_ track: MCVideoTrack, withMid mid: String) {
        track.enable(true)
        DispatchQueue.main.async {
            track.add(renderer)
        }
    }
    
    func onAudioTrack(_ track: MCAudioTrack, withMid mid: String) {
        track.enable(true)
    }

    /* ... */
}

Collecting RTC statistics

You can periodically collect the WebRTC peer connection statistics if you enable them through the enableStats method of the viewer or publisher. After enabling the statistics, you will get a report every second through the onStatsReport callback in the listener object. The identifiers and way to browse the stats are following the RTC specification.
The report contains the StatsReport object, which is a collection of several Stats objects. They all have a specific type, whether it is inbound, outbound, codec, or media. Inbound is the statistics of incoming transport for the viewer and outbound is a type of outgoing statistics for the publisher.