Getting Started with Publishing

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

1. Capture audio and video

Import MillicastSDK, get an array of available audio and video sources, and choose the preferred sources from the list. When you start capturing audio and video, the SDK will return an audio and video track.

import MillicastSDK

// Configure the audio session for capturing
let session = AVAudioSession.sharedInstance()
try session.setCategory(
    .playAndRecord,
    mode: .videoChat,
    options: [.mixWithOthers, .allowBluetooth, .allowBluetoothA2DP]
)
try session.setActive(true)

// Create an audio track
var audioTrack : MCAudioTrack? = nil
if
    let audioSources = MCMedia.getAudioSources(), // Get an array of audio sources
    !audioSources.isEmpty // There is at least one audio source
{
    // Choose the preferred audio source and start capturing
    let audioSource = audioSources[0]
    audioTrack = audioSource.startCapture() as? MCAudioTrack
}

// Create a video track
var videoTrack : MCVideoTrack? = nil
if
    let videoSources = MCMedia.getVideoSources(), // Get an array of available video sources
    !videoSources.isEmpty // There is at least one video source
{
    // Choose the preferred video source
    let videoSource = videoSources[0];

    // Get capabilities of the available video sources, such as
    // width, height, and frame rate of the video sources
    guard let capabilities = videoSource.getCapabilities() else {
        fatalError("No capability is available!") // In production replace with a throw
    }

    let capability = capabilities[0]; // Get the first capability
    videoSource.setCapability(capability);

    // Start video recording and create a video track
    videoTrack = videoSource.startCapture() as? MCVideoTrack
}

2. Publish a stream

2.1 Instantiate a publisher

Create a publisher object of type MCPublisher.

let publisher = MCPublisher()

You can optionally implement the MCPublisherDelegate to receive callbacks:

You can set this delegate during the initialization of MCPublisher instead. Ensure to keep the delegate alive throughout the lifetime of the publisher.

let publisherDelegate = YourPublisherDelegate() // where the `YourPublisherDelegate` is a type that you implement, that conforms to `MCPublisherDelegate` 
let publisher = MCPublisher(delegate: publisherDelegate)

2.2 Set publisher credentials

Create a stream in your Dolby.io developer dashboard or using the Dolby.io Streaming REST API. Then, set the credentials from the dashboard. All of the MCPublisher APIs are asynchronous, so call them from asynchronous contexts.

// Get the credentials structure from your publisher instance, fill it in,
// and set the modified credentials
let credentials = MCPublisherCredentials()
credentials.streamName = "streamName"; // The name of the stream you want to publish
credentials.token = "aefea56153765316754fe"; // The publishing token
credentials.apiUrl
    = "https://director.millicast.com/api/director/publish"; // The publish API URL

do {
  try await publisher.setCredentials(credentials);
} catch MCGenericError.noCredentials {
  // You have provided incomplete credentials
  // such as an empty streamName, token, or url
}

2.3 Add video and audio tracks to the publisher

To publish media, add to the publisher the tracks that you have captured earlier.

await publisher.addTrack(with: audioTrack)
await publisher.addTrack(with: videoTrack)

2.4 Configure publishing options

Configure publishing options in the publisher, such as selecting the audio and video codecs or enabling multi-source on the publisher.

let publisherOptions = MCClientOptions()

// Get a list of supported codecs
if let audioCodecs = MCMedia.getSupportedAudioCodecs() {
    // Choose the preferred audio codec
    publisherOptions.audioCodec = audioCodecs[0]

} else {
    print("No audio codecs available!") // In production, replace it with proper error handling
}

if let videoCodecs = MCMedia.getSupportedVideoCodecs() {
    // Choose the preferred video codec
    publisherOptions.videoCodec = videoCodecs[0]

} else {
    print("No video codecs available!") // In production, replace it with proper error handling
}

// To use multi-source, set a source ID of the publisher and
// enable discontinuous transmission
publisherOptions.sourceId = "MySource"
publisherOptions.dtx = true

// Enable stereo
publisherOptions.stereo = true

2.5 Publish your stream

Connect to the Millicast service and publish your streams.

do {
  try await publisher.connect()
  // using publisherOptions from step 2.4
  try await publisher.publish(with: publisherOptions)
} catch MCGenericError.restAPIError {
  // Handle invalid credentials passed to the publisher
}

3. Observe state changes

There are different publishers that emit events informing you of the state of your stream. Please note, the listeners should be setup before you start publishing so you receive all the events from a publisher.

3.1 Connection state to the Millicast service

To monitor the state of the websocket connection of the publisher, use the websocketState(). This informs you are connected to the Millicast service or when you are disconnected.

To see the list of possible states refer: MCConnectionState

for await state in publisher.websocketState() {
  // Log or handle the state change
}

You can also receive other events on the publisher, like viewer activity and viewer count:

The delegate equivalent for this event is defined in client(_:didreceivewebsocketconnectionstate:)

3.2 Publishing state

To monitor the state of the publishing, listen to the events emitted by peerConnectionState()

for await state in publisher.peerConnectionState() {
  // Log or handle the state change
}

The delegate equivalent for this event is defined in client(_:didreceivertcpeerconnectionstate:)

3.3 Viewers of your stream

Listen to the viewer activity events of your stream using activity() or the combine publisher activityPublisher.
This event can also be listen from the publisherDidReceiveFirstViewerActive(_:) and publisherDidReceiveLastViewerInactive(_:) delegate methods defined in MCPublisherDelegate

for await activity in publisher.activity() {
  case .inactive:
    // TODO: Received when the last viewer of your stream goes inactive
    break
  case .active:
    // TODO: Received when the first viewer starts viewing your stream
    break
}

Number of viewers viewing your stream in a given time is yet another vital aspect for you as a publisher. To listen to viewer count updates, use viewerCount()

for await viewerCount in publisher.viewerCount() {
  // use the `viewerCount` for displaying on the UI
}

The Combine Publisher for this event is viewercountpublisher and the delegate equivalent is client(_:didreceiveviewercount:)

4. Collect WebRTC statistics

Set the enableStats method to true to collect statistics.

WebRTC peer connection statistics can be periodically collected by enabling them through the enableStats method of the publisher. After enabling the statistics, you will get a report every second or at the preset frequency you scheduled through the statsReport() async stream or the statsReportPublisher publisher.

Alternatively use the delegate methodclient(_:didreceivestatsreport:)

The identifiers and way to browse the stats are following the RTC specification.
The report contains the MCStatsReport object, which is a collection of several MCStats 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.

await publisher.enableStats(true)

for await statsReport in publisher.statsReport() {
    // Parse the stats report for logging or display on to the user interface
}

5. Disable automatic reconnection

By default, the publisher and subscriber attempt to reconnect automatically in case of network errors. To disable auto reconnection in connection options, use the following code:

let connectionOptions = MCConnectionOptions()
connectionOptions.autoReconnect = false

do {
  try await publisher.connect(with: connectionOptions)
} catch error {
  //...
}

In the case of network issues when auto reconnection is enabled, the connect method does not return as long as there is no network connection. You can abort the method at any time using the following code:

Task {
  for await state in publisher.state() {
    if case .connectionError(let status, let reason) = state {
      try await publisher.disconnect()
    }
  }
}

do {
  try await publisher.connect(with: connectionOptions)
} catch MCAsyncOperationCancelledError.aborted {
  // The operation has been aborted by the disconnect call above
}

6. Error handling

To listen to the http errors emitted by the publisher, use the httpError() or it's equivalent combine publisher - httpErrorPublisher()

The emitted error will be of type MCHttpConnectionError

for await error in publisher.httpError() {
  // Handle http error
}

(or)

Using the combine publisher:

publisher.httpErrorPublisher()
  .sink { error in
      // Handle http error
  }

To listen to the signalling errors emitted by the publisher, use the signalingError() or it's equivalent combine publisher - signalingErrorPublisher()

The emitted error will be of type MCSignalingError

for await error in publisher.signalingError() {
  // Handle signalling error
}

(or)

Using the combine publisher:

publisher.signalingErrorPublisher()
  .sink { error in
  // Handle signalling error
  }

The delegate methods to receive http and signalling errors are client(_:didReceiveHTTPConnectionError:) and client(_:didReceiveSignalingError:)

7. Stop publishing

When you finish publishing your content, stop the publishing by calling unpublish() and clear the tracks [clearTracks()](https://millicast.github.io/doc/latest/apple/documentation/millicastsdk/mcpublisher/cleartracks(completionhandler:). Then disconnect from the millicast server by calling disconnect() method.

// Stop publishing your content
try await publisher.unpublish()

// Clears all tracks added to the publisher.
await publisher.clearTracks()

// Disconnect the millicast service
try await publisher.disconnect()