AndroidでAmazon Chime SDKを使ってみた（アプリ実装編）

# はじめに

[前回](https://qiita.com/ufoo68/items/82c505d67f49f828db0b)のつづきです。今回は実際にAndroidで実装していきます。開発には[amazon-chime-sdk-android](https://github.com/aws/amazon-chime-sdk-android)を用います。

# SDKの導入方法

基本的には[公式のガイドライン](https://github.com/aws/amazon-chime-sdk-android#setup)があるのでその内容通りにセットアップすればOKです。

まずはバイナリファイルの[最新のrelease](https://github.com/aws/amazon-chime-sdk-android/releases/latest)のもの（`amazon-chime-sdk`と`amazon-chime-sdk-media`）をダウンロードします。ダウンロードしたファイルは`/app/libs`においておきます。
次にroot projectの`build.gradle`に以下の設定を書き加えます。

```gradle:build.gradle
...
allprojects {
   repositories {
      jcenter()
      flatDir {
        dirs 'libs'
      }
   }
}
...
```

あとはappの中の`build.gradle`の`dependencies`配下に以下を書き加えます。

```gradle:build.gradle
implementation(name: 'amazon-chime-sdk', ext: 'aar')
implementation(name: 'amazon-chime-sdk-media', ext: 'aar')
```

また、`compileOptions`が以下のようになっているかも確認しておいてください。

```gradle:build.gradle
compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}
```

この設定を行えばSDKの導入は完了です。

# アプリについて

アプリの外観は↓のような感じです。ソースコードは[こちら](https://github.com/ufoo68/AmazonChimeSimple)で公開しています。

![スクリーンショット.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209689/eeb0af41-f8df-3df9-3473-6e6d094d6f66.jpeg)

かなり簡素に作ったのであまり実用的ではありませんが、最低限のビデオ通話の機能を使うことができます。
今回はルームのIDを固定してルームを作成して（前回つくったサーバーを使用しています）、その中でユーザー同士の通話機能を実装しています。
ちなみにAmazon Chime SDKの機能をフルに使ったサンプルコードは[公式のデモアプリ](https://github.com/aws/amazon-chime-sdk-android#running-the-demo-app)で公開されているので、そちらを参考にするといいと思います。

# Amazon Chimeとの通信を開始する

ルームとユーザーの作成を行うAPIを叩くための関数を[coroutine](https://developer.android.com/kotlin/coroutines?hl=ja)と[gson](https://github.com/google/gson)を使って実装しました。

```kotlin
private val gson = Gson()
private val logger = ConsoleLogger(LogLevel.DEBUG)

private val TAG = "apiRequest"

suspend fun joinMeeting(
    ioDispatcher: CoroutineDispatcher,
    meetingUrl: String,
    meetingId: String?,
    attendeeName: String?
): String? {
    return withContext(ioDispatcher) {
        val url = if (meetingUrl.endsWith("/")) meetingUrl else "$meetingUrl/"
        val serverUrl = URL("${url}join")
        try {
            val response = StringBuffer()
            with(serverUrl.openConnection() as HttpURLConnection) {
                requestMethod = "POST"
                doInput = true
                doOutput = true

                val body = gson.toJson(mutableMapOf(
                    "title" to meetingId,
                    "name" to attendeeName
                ))

                setRequestProperty("Content-type", "application/json; charset=utf-8")
                outputStream.use {
                    val input = body.toByteArray()
                    it.write(input, 0, input.size)
                }

                BufferedReader(InputStreamReader(inputStream)).use {
                    var inputLine = it.readLine()
                    while (inputLine != null) {
                        response.append(inputLine)
                        inputLine = it.readLine()
                    }
                    it.close()
                }

                if (responseCode == 200) {
                    response.toString()
                } else {
                    logger.error(TAG, "Unable to join meeting. Response code: $responseCode")
                    null
                }
            }
        } catch (exception: Exception) {
            logger.error(TAG, "There was an exception while joining the meeting: $exception")
            null
        }
    }
}
```

`joinMeeting`で返ってきたレスポンスを使ってAmazon Chimeとの通信を行います。このあたりの実装はAmazon Chime SDKを使うことで[WebRTC](https://jp.vcube.com/sdk/blog/webrtc-general-server-configuration-signaling-turn-sfu.html)などの難しい技術を理解せずにアプリケーションを実装することができます。以下はそのコード例です（[公式ページ](https://github.com/aws/amazon-chime-sdk-android/blob/master/guides/getting_started.md)より拝借）

```kotlin
// joinMeeting()で返ってきたレスポンスのデータ構造
data class JoinMeetingResponse(
    @SerializedName("JoinInfo") val joinInfo: MeetingInfo)

data class MeetingInfo(
    @SerializedName("Meeting") val meetingResponse: MeetingResponse,
    @SerializedName("Attendee") val attendeeResponse: AttendeeResponse)

data class MeetingResponse(
    @SerializedName("Meeting") val meeting: Meeting)

data class AttendeeResponse(
    @SerializedName("Attendee") val attendee: Attendee)

// Gsonを使ってレスポンスデータをパースする
val joinMeetingResponse = Gson().fromJson(
    response.toString(),
    JoinMeetingResponse::class.java
)

val configuration = MeetingSessionConfiguration(
    CreateMeetingResponse(joinMeetingResponse.joinInfo.meetingResponse.meeting),
    CreateAttendeeResponse(joinMeetingResponse.joinInfo.attendeeResponse.attendee)
)

// DefaultMeetingSessionを実行することでAmazon Chimeとの通信が実行できる
val meetingSession = DefaultMeetingSession(configuration, ConsoleLogger(), applicationContext)
```

`meetingSession`は[viewModel](https://developer.android.com/topic/libraries/architecture/viewmodel?hl=ja)で管理するといいと思います。あとはmeetingSessionで以下を実行するとビデオ通話が開始されます。（これも[公式より抜粋](https://github.com/aws/amazon-chime-sdk-android/blob/master/guides/getting_started.md)）

```kotlin
val audioVideo = meetingSession.audioVideo

// Start audio and video clients.
audioVideo.start()

// Start receiving remote video.
audioVideo.startRemoteVideo()

// Start sending local video.
audioVideo.startLocalVideo()

// Switch camera for local video between front and back.
audioVideo.switchCamera()
```

# ビデオタイルを描画する

ビデオタイル（ビデオチャットアプリの各カメラ映像）をビューに表示させるために`VideoRenderView`を使います。このviewへのビデオ映像のbindを行うために`.bindVideoView(videoRenderView, tileId)`を実行します。以下のような感じで実装しました。

```kotlin
audioVideo.addVideoTileObserver(this)

private fun showVideoTile(tileState: VideoTileState) {
    val activity = activity as Context
    val defaultVideoRenderView = DefaultVideoRenderView(activity)
    val layout = view?.findViewById<LinearLayout>(R.id.meeting_layout)
    layout?.addView(defaultVideoRenderView, ViewGroup.LayoutParams(500, 500))
    audioVideo.bindVideoView(defaultVideoRenderView,tileState.tileId)
}

override fun onVideoTileAdded(tileState: VideoTileState) {
    showVideoTile(tileState)
}

override fun onVideoTileRemoved(tileState: VideoTileState) {
    audioVideo.unbindVideoView(tileState.tileId)
}
```

簡単のため、viewの作成のところ（showVideoTile）は結構雑な実装をしています。`onVideoTileAdded`や`onVideoTileRemoved`は最初にオブザーバの登録（ここでは`audioVideo.addVideoTileObserver(this)`）を行うことで呼ばれるようになります。
ルームに新しくユーザーが参加すると`onVideoTileAdded`が呼ばれてその`tileId`が取得できます。この情報を使って`defaultVideoRenderView`をbindさせることで、ユーザーがルームに入るたびにビデオタイルも追加する形のものが実装できます。

# さいごに

Androidでビデオチャットアプリを作るのは（そもそも私自身がAndroidの実装経験が乏しいのも含めて）難しいイメージを持っていたのですが、Amazon Chime SDKを使うと難しい通信周りを意識せずにUIへの実装のみに集中してビデオチャットアプリを作ることができました。
とはいえほんの一部の機能を使っただけなので、他の機能（特に[CustomVideo](https://github.com/aws/amazon-chime-sdk-android/blob/master/guides/custom_video.md)）も触ってみたいですね。