Getting Started with Publishing

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

1. Initialize the SDK

Call the initialize method to initialize the SDK with your application context.

import androidx.fragment.app.FragmentActivity
import com.millicast.Core

class MainActivity : FragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Core.initialize()
    }
}

2. Capture audio and video

To capture media, get an array of available audio and video sources and choose the preferred sources from the list. After you start capturing audio and video, the SDK will return an audio and video track that you can add to the publisher later.

// Get the first available microphone
val audioSource = Media.audioSources<MicrophoneAudioSource>().first()

val audioTrack = try {
    audioSource.startCapture()
} catch (e: Throwable) {
    // In the case of a problem when starting the audio capture try checking your permissions
}

// Get the first camera source
val videoSource = Media.videoSources<CameraVideoSource>().first()

// Get capabilities of the available video sources, such as width, height, and frame rate
val capabilities = videoSource.capabilities

// Set the preferred capability; not setting any capability object results in setting the first one from the list
videoSource.setCapability(capabilities.first())

// Start capturing video
val videoTrack = try {
    videoSource.startCapture()
} catch (e: RuntimeException) {
    // In the case of a problem when starting the video capture
    // check the camera permissions or exclusive access from another application
}

// Handle switching between cameras
videoSource.switchCamera(object: SwitchCameraHandler {
    override fun onCameraSwitchDone(p0: Boolean) {
        TODO("Not yet implemented")
    }

    override fun onCameraSwitchError(reason: String?) {
        TODO("Not yet implemented")
    }
})

// Replace width, height, and fps with your own values
videoSource.changeCaptureFormat(width, height, fps)

3. Set logger

Optionally, set your own logger function to print Dolby.io Real-time Streaming logs according to the severity. By default, the SDK prints the standard output, where the severity is displayed first and then the message.

import com.millicast.utils.Logger

Logger.setLoggerListener({msg, logLevel -> Log.d("SDK log", msg)})

4. Create a publisher

Create a publisher object and make sure to use the publisher's methods in a coroutine context. Then, create a stream in your Dolby.io developer dashboard or using the Dolby.io Streaming REST API and set your credentials.

// Helper for later usage
fun <T> CoroutineScope.safeLaunch(
	onError: (suspend CoroutineScope.(err: Throwable) -> T)? = null,
    block: suspend CoroutineScope.() -> T
  ) = launch {
    try {
      block()
    } catch (err: Throwable) {
      onError?.invoke(this, err)
    }
  }

  val publisher = Core.createPublisher()

  // Most of the publisher's methods needs to be in a coroutine context,
  // such as viewModelScope or ServiceJob
  val coroutineScope = CoroutineScope(Dispatchers.IO)

  // In this sample, we deroute the scope to collect every new publisher's state
  // For instance, in a jetpack compose implementation it could be:
  //
  // @Composable
  // fun MyLoadingScreen(publisher: Publisher) {
  //   val state by publisher.state.collectAsState(null)
  //   
  //   state?.let {
  //     when(it.connectionState) {
  //       ConnectionState.Connected -> ...
  //       else -> ...
  //     }
  //   }
  // }
  coroutineScope.async { 
    publisher.state.collect { newPublisherState ->
      Log.d("SAMPLE", "having new State ${newPublisherState}")
    }
  }

  // Get the credentials structure from your publisher instance, fill it in, and set the modified credentials
  coroutineScope.safeLaunch {
    val credential = Credential(
      // Set the streamName, token, and API URL
      streamName = "myStreamName",
      token = "aefea56153765316754fe",
      apiUrl = "https://director.millicast.com/api/director/publish"
    )

    publisher.setCredentials(credential)
  }

5. Add the audio and video track

Add the audio and video track that you created earlier when you started capturing media.

// Use the previous publisher and coroutine scope
coroutineScope.safeLaunch {
  // Previously created tracks:

  publisher.addTrack(videoTrack);
  publisher.addTrack(audioTrack);
}

6. Publish the stream

Get a list of the available codecs and set the codecs that you want to use. By default, the SDK uses VP8 as the video codec and Opus as the audio codec.

Additionally, to publish several sources from the same application, create a publisher instance for each source. We recommend enabling discontinuous transmission that detects audio input and sends audio only when it is detected.

Use the connect method to authenticate and access Dolby.io Real-time Streaming through the Director API. Successful authentication results in opening a WebSocket connection that allows using the Dolby.io Real-time Streaming server and calling the listener's onConnected method.

Then, use the publish method to start publishing the stream. Once the publisher starts sending media, the SDK calls the listener's onPublishing method.

// Use the previous publisher and coroutine scope

coroutineScope.safeLaunch {
  val videoCodecs = Media.supportedVideoCodecs
  val audioCodecs = Media.supportedAudioCodecs

  publisher.connect()

  publisher.publish(
    Option(
      // Choose the preferred codecs
      videoCodec = videoCodecs.first(),
      audioCodec = audioCodecs.first(),
      // If you want to support multi-source, set a source ID of the publisher
      sourceId = "sourceId",
      // To publish several sources from the same application, create a publisher instance for each source
      dtx = true,
      // Enable stereo
      stereo = true
    )
  )
}

Collecting RTC statistics

You can periodically collect the WebRTC peer connection statistics if you enable them through the enableStats method of the 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.