# Migration Guide for Android SDK

This migration guide provides some tips for upgrading or downgrading between releases of the Android SDK.

> **Getting Started with Android SDK**
>
> If you haven't already, begin by following the [Getting Started](https://docs-preview.optiview.dolby.com/pr-861/millicast/playback/players-sdks/android/sdk/getting-started-with-subscribing.md) tutorial to become familiar with the concepts to create an application that can publish and/or subscribe using the [Android](https://docs-preview.optiview.dolby.com/pr-861/millicast/playback/players-sdks/android/sdk.md) SDK.

## 1.8.x to 2.x

Below you'll find examples for migrating your applications from [v1.8.9](https://github.com/millicast/millicast-native-sdk/releases/tag/v1.8.9) to [v2.0.0](https://github.com/millicast/millicast-native-sdk/releases/tag/v2.0.0) of the [Android](https://docs-preview.optiview.dolby.com/pr-861/millicast/playback/players-sdks/android/sdk.md) SDK.

> ***NOTE:*** Publisher classes remain unchanged and do not require any migration.

### Subscribing Single Source Streams

For applications that only have a single source available for playback, track management has changed.

**1.8.x**

```kotlin
Core.initialize()

val subscriber = Core.createSubscriber()

launch {
  subscriber.state.map { it.connectionState }.distinctUntilChanged().collect {
    if (it == SubscriberConnectionState.Connected) {
      subscriber.subscribe()
    }
  }
}

launch {
  subscriber.track.collect { trackHolder ->
    when(trackHolder) {
      is TrackHolder.VideoTrackHolder -> {
        // Create the renderer:
        val view = com.millicast.video.TextureViewRenderer(applicationContext)
        // Add the renderer to the layout:
        val layout = findViewById<LinearLayout>(R.id.linear_layout_video)
        layout.addView(view)
        // Set the renderer as a sink on the video track:
        trackHolder.videoTrack.setVideoSink(view)
      }
      is TrackHolder.AudioTrackHolder -> {
        // No video, ignore
      }
    }
}

launchDefaultScope {
  val credentials = Credential(
    streamName = "STREAM_NAME",
    accountId = "ACCOUNT_ID",
    apiUrl = "https://director.millicast.com/api/director/subscribe"
  )

  subscriber.setCredentials(credentials)
  subscriber.connect()
}
```

**2.x**

```kotlin
Core.initialize()

val subscriber = Core.createSubscriber()

launch {
    subscriber.state.map { it.connectionState }.distinctUntilChanged().collect {
        if (it == SubscriberConnectionState.Connected) {
            subscriber.subscribe()
        }
    }
}

launch {
  subscriber.onRemoteTrack.collect { trackHolder ->
      when (trackHolder) {
          is RemoteAudioTrack -> {
            trackHolder.enableAsync()
          }

          is RemoteVideoTrack -> {
            trackHolder.enableAsync {videoSink = <The TextureViewRenderer View>}
          }
      }
  }
}

launchDefaultScope {
    val credentials = Credential(
        streamName = "STREAM_NAME",
        accountId = "ACCOUNT_ID",
        apiUrl = "https://director.millicast.com/api/director/subscribe"
    )

    subscriber.setCredentials(credentials)
    subscriber.connect()
}
```

Video view renderer using `TextureViewRenderer` remains the same in 2.0.0.

### Subscribing Multi-View Streams

There were a lot of complicated steps involved in projecting the sources for a multi-stream view. This complex workflow requires a lot of understanding of the WebRTC remote tracks. Using the new 2.0.0 implementation removes the need to handle sourceIds, mids and the projection data directly. This solves a lot of complexities from the old implementation.

**1.8.x**

```kotlin
//Using the multi-source feature requires projecting tracks into a specified transceiver using its media ID (mid). When you start subscribing, you will receive the activity flow updates. To forward the selected media to the subscriber, use the project method of the subscriber. It requires providing the source ID you are targeting and an array of the tracks you want to project. And for more advanced cases, you may need to unproject and project tracks again. So you have to store a map of <sourceId, RemoteTrackHolder> for later usage. To achieve this you should listen to the StreamSourceActivity flow, then for every new track add only the new tracks to this stored map. Which is a big effort to do and a lot of stuff to worry about, as an example WHEN to re-init this map and etc.

Core.initialize()

val subscriber = Core.createSubscriber()

launch {
    subscriber.state.map { it.connectionState }.distinctUntilChanged().collect {
        if (it == SubscriberConnectionState.Connected) {
            subscriber.subscribe()
        }
    }
}

// Initialize the local items important for this local sample
val streamIdMap = mutableMapOf<String, StreamSourceActivity>()
val queue = Queue()
// com.millicast.utils.Queue provides a scope.launch { } that will wait
// for its head to complete before calling the next item

/**
 * Retrieve the list of new tracks given modifications on the StreamSourceActivity object
 */
fun checkTracksInStreamSourceActivity(streamSourceActivity: StreamSourceActivity): StreamSourceActivity {
  // First, ensure that you find a corresponding cached version
  streamIdMap.putIfAbsent(
    streamSourceActivity.streamId,
    streamSourceActivity.copy(activeTracks = emptyArray())
  )

  val cached = streamIdMap[streamSourceActivity.streamId]!!

  // All the new tracks, for example each track that is in the new array but not the previous
  val newTracks = streamSourceActivity.activeTracks.filter { activeTrack ->
    null == cached.activeTracks.find { it.trackId == activeTrack.trackId }
  }

  // Send a copy that contains only the new tracks
  return streamSourceActivity.copy(activeTracks = newTracks.toTypedArray())
}

/**
 * When a new source becomes active, store information about it, create a local track,
 * and send the projection command
 */
launch {
  subscriber.state.map { it.streamSourceActivities }.collect { streamSourceActivities ->
    queue.post {
      // Check whether the list of activity or tracks
      // is empty, which means that everything local needs to be flushed

      streamSourceActivities.map { activity ->
        // Fetch a copy of the activity object containing only the new tracks
        val newlyFoundRemoteTracks = checkTracksInStreamSourceActivity(activity)

        // Update the cached list of activeTracks with the one obtained at the beginning
        streamIdMap[activity.streamId] = streamIdMap[activity.streamId]!!.copy(
          activeTracks = activity.activeTracks
        )

        newlyFoundRemoteTracks
      }.forEach { activity ->
        // The activity only contains the newly added tracks
        activity.activeTracks.forEach {
          // You can add the remote track locally
          val newlyAddedLocalTrack = subscriber.addRemoteTrackForResult(it.media)

          // Send the command to project the remote content locally
          subscriber.project(
            activity.sourceId, arrayListOf(
              ProjectionData(
                trackId = it.trackId,
                media = it.media.name.lowercase(),
                mid = newlyAddedLocalTrack.mid!!
              )
            )
          )
        }
      }
    }
  }
}

launchDefaultScope {
    val credentials = Credential(
        streamName = "STREAM_NAME",
        accountId = "ACCOUNT_ID",
        apiUrl = "https://director.millicast.com/api/director/subscribe"
    )

    subscriber.setCredentials(credentials)
    subscriber.connect()
}
```

**2.x**

```kotlin
Core.initialize()

val subscriber = Core.createSubscriber()

launch {
    subscriber.state.map { it.connectionState }.distinctUntilChanged().collect {
        if (it == SubscriberConnectionState.Connected) {
            subscriber.subscribe()
        }
    }
}

launch {
  subscriber.onRemoteTrack.collect { trackHolder ->
      when (trackHolder) {
          is RemoteAudioTrack -> {
            trackHolder.enableAsync()
          }

          is RemoteVideoTrack -> {
            trackHolder.enableAsync {videoSink = <The TextureViewRenderer View>}

            trackHolder.onState.collectInLocalScope { trackState ->
                if (!trackState.isActive) {
                    // Either call trackHolder.disableAsync() and remove the video renderer view or show an UI message till source comes back online
                }
            }
          }
      }
  }
}

launchDefaultScope {
    val credentials = Credential(
        streamName = "STREAM_NAME",
        accountId = "ACCOUNT_ID",
        apiUrl = "https://director.millicast.com/api/director/subscribe"
    )

    subscriber.setCredentials(credentials)
    subscriber.connect()
}
```

The new 2.0.0 implementation for multi-stream is identical to the single stream view.

#### Some key differences in track management

| 1.8.x                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | 2.x                                                                                                                                                                                                                      |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Track event emitted is of type [TrackHolder](https://millicast.github.io/doc/1.8.9/android/android/com.millicast/-subscriber/tracks.html) which has two types `AudioTrackHolder(mid: String, audioTrack: AudioTrack)` and `VideoTrackHolder(mid: String, videoTrack: VideoTrack)`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | Track event emits RemoteTrack. This new track type can be a video or audio track, using `is RemoteVideoTrack` you can verify if it’s a video track. Use `is RemoteAudioTrack` to check if the track is an audio track.   |
| Enabling(projecting) tracks is a multistep process<br />- Subscribe to `SubscriberState.streamSourceActivity` flow to collect active and inactive sourceId’s<br />- Receive active tracks using [activeTracks](https://millicast.github.io/doc/1.8.9/android/android/com.millicast/-subscriber/state.html) async stream<br />- Request tracks by calling the [addRemoteTrack()/addRemoteTrackForResult()](https://millicast.github.io/doc/1.8.9/android/android/com.millicast/-subscriber/add-remote-track.html) function<br />- Project mid’s using the [project()](https://millicast.github.io/doc/1.8.9/android/android/com.millicast/-subscriber/project.html) API by passing in the [ProjectionData](https://millicast.github.io/doc/1.8.9/android/android/com.millicast.subscribers/-projection-data/index.html) | Enabling tracks is now easy<br />- Collect tracks using the `onRemoteTrack` flow<br />- Enable track by calling the `enableAsync()` for an audio track or `enableAsync(videoSink)` API for a video track                 |
| Disable(unproject) tracks using the [unproject(mids)](https://millicast.github.io/doc/1.8.9/android/android/com.millicast/-subscriber/unproject.html) function                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | Disable tracks using the `disableAsync()`function                                                                                                                                                                        |
| [layers](https://millicast.github.io/doc/1.8.9/android/android/com.millicast/-subscriber/layers.html) flow is part of Subscriber                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | `layers` flow is now part of RemoteTrack                                                                                                                                                                                 |
| To select a layer create a new [ProjectionData](https://millicast.github.io/doc/1.8.9/android/android/com.millicast.subscribers/-projection-data/index.html), set the appropriate [layerData](https://millicast.github.io/doc/1.8.9/android/android/com.millicast.subscribers.state/-layer-data/index.html) and then call [project()](https://millicast.github.io/doc/1.8.9/android/android/com.millicast/-subscriber/project.html)                                                                                                                                                                                                                                                                                                                                                                                    | To select a layer call the `enableAsync()` function and pass the [LayerDataSelection](https://millicast.github.io/doc/latest/android/android/com.millicast.subscribers.state/-layer-data-selection/index.html) to select |
| [activity](https://millicast.github.io/doc/1.8.9/android/android/com.millicast/-subscriber/activity.html) flow was part of the Subscriber. `ActivityStream.Inactive` event for a particular `sourceId` indicates the tracks associated with that sourceId are now inactive and `ActivityStream.Active` event represents the tracks associated with the `sourceId` in the event is now active                                                                                                                                                                                                                                                                                                                                                                                                                           | `isActive` is now part of the `Subscriber.onRemoteTrack`.                                                                                                                                                                |

### Changelog Overview

Please refer to the changelog for [What's new in 2.0.0 SDK Release](https://github.com/millicast/millicast-native-sdk/releases/tag/v2.0.0)
