---
lang: ja
path: user-guide/android-sdk/quickstart
labels: ユーザーガイド/Android SDK/クイックスタート(Android View)
metaTitle: クイックスタート(Android View) ｜ Android SDK ｜ ユーザーガイド ｜ SkyWay（スカイウェイ）
---

# 🚀 クイックスタート(Android View)

このセクションでは、SkyWay Android SDK を利用した最小限の Android View をベースとするアプリケーションを開発する方法について掲載しています。
はじめて Android SDK を利用する方はこちらを参考に導入してください。

完成品は [公式リポジトリ](https://github.com/skyway/android-sdk/tree/main/examples/AndroidView/QuickStart) にて公開しています。

尚、この記事は以下を前提に構成しています。
- Activity を用いたアプリ開発経験があること
- Kotlin の文法（関数、変数定義、呼び出し、コルーチンなど）がわかること

上記については Android Developers などを参考にしてください。
## アプリの概要

このアプリには以下のシンプルな通話機能をP2Pによって実装します。

1) ユーザーはルーム名を指定して入室できる
2) ユーザーがパーミッションの要求を許可すれば、入室と同時に同じルームのメンバーに対してカメラ映像とマイク音声を配信する
3) ユーザーは入室時から同じルームに入室しているユーザーのマイク音声とカメラ映像を視聴できる

## Android プロジェクト の作成

Android Developers の公式ウェブサイトを参考に、Android プロジェクトを作成します。

https://developer.android.com/training/basics/firstapp/creating-project?hl=ja

「Language」は kotlin を選択してください。また、「Minimum SDK」は Android 6.0(API Level: 23) 以降を選択してください。

## Android SDK のインストール

`app/build.gradle` に以下の依存関係を追加するだけでSDKのインストールは完了です。

> このセクションではgradleファイルをGroovyによって記述しています。
> Kotlin DSLによる記述方法は[クイックスタート(JetPack Compose)](https://skyway.ntt.com/ja/docs/user-guide/android-sdk/quickstart-compose/)をご参照ください。

```java
// 最新の SDK バージョンに差し替えてください
// 最新の SDK バージョン情報は以下のリンクより、Maven Central Repository にて確認できます
// https://central.sonatype.com/search?q=skyway
def skywayVersion = 'x.x.x'

dependencies {
    // ... 省略

    implementation 'com.ntt.skyway:room:$skywayVersion'
}
```

## パーミッションの設定

Android SDK の動作に必要なパーミッションを `app/src/main/AndroidManifest.xml` に記述します。

以下に、映像・音声の通信をする際に必要なパーミッションの例を示します。

```xml
<!-- ネットワーク接続に必要なパーミッション  -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

<!-- カメラ映像・マイク音声の取得に必要なパーミッション  -->
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
```

## レイアウトの設定

`res/layout/activity_main.xml` に表示したいコンポーネントを記述します。

### Video 表示コンポーネントの配置する

"ローカルのビデオ"には自身の映像、"リモートのビデオ"には通話相手の映像を映します。

初期状態から以下のように変更します。

```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <LinearLayout
            android:id="@+id/videos"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_gravity="center">

            <!-- ローカルのビデオ -->
            <com.ntt.skyway.core.content.sink.SurfaceViewRenderer
                android:id="@+id/local_renderer"
                android:layout_width="150dp"
                android:layout_height="150dp">

            </com.ntt.skyway.core.content.sink.SurfaceViewRenderer>

            <!-- リモートのビデオ -->
            <com.ntt.skyway.core.content.sink.SurfaceViewRenderer
                android:id="@+id/remote_renderer"
                android:layout_width="150dp"
                android:layout_height="150dp"
                android:layout_marginStart="20dp">

            </com.ntt.skyway.core.content.sink.SurfaceViewRenderer>
        </LinearLayout>
    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
```

### ルーム名の表示枠と参加ボタンを作成する

Video 表示コンポーネントの下にルーム名の表示枠と参加ボタンを追加します。

```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout>
    <LinearLayout>
        <LinearLayout>
            <!-- ローカルのビデオ（省略） -->
            <!-- リモートのビデオ（省略） -->
        </LinearLayout>
        <!-- ルーム名の表示枠 -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="50dp"
                android:text=" room: "
                android:textAlignment="textEnd"
                android:textColor="#888888"
                android:textSize="13sp" />
            <EditText
                android:id="@+id/roomName"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:textAlignment="textStart"
                android:textColor="#000000"
                android:textSize="15sp"
                />
        </LinearLayout>

        <!-- 参加ボタン -->
        <Button
            android:id="@+id/joinButton"
            android:layout_width="150dp"
            android:layout_height="70dp"
            android:layout_gravity="center"
            android:text="Join"
            android:textSize="15sp"/>

    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
```

## SkyWay Auth Token の作成

SkyWay を利用するためには、初めに JWT（JSON Web Token） を用いて `SkyWayContext` を初期化します。

SkyWay Auth Token は本来サーバーサイドで生成するため、Android SDK にはトークンの生成機能はございません。

クイックスタートでは、Dev環境専用のAPIである `SkyWayContext.setupForDev` を用いて初期化するため、SkyWay Auth Tokenの作成は省略します。

認証認可について、詳しくは[こちら](/ja/docs/user-guide/authentication/)をご覧ください。

## 機能の実装

`MainActivity` クラスを編集します。

```kotlin
//プロジェクト作成時に決めたパッケージ名にしてください
package YOUR.SKYWAY.QUICKSTART

import ... //省略

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}
```

以降は `MainActivity` クラスに機能を実装します。

### SkyWayContext, メンバの初期化

`MainActivity` クラスから `SkyWayContext.Options` を設定します。
また、後ほど必要になるメンバをまとめて宣言しておきます。

```kotlin
class MainActivity : AppCompatActivity() {
    // SkyWayContext.Optionsの設定
    private val option = SkyWayContext.Options(
        logLevel = Logger.LogLevel.VERBOSE
    )

    // メンバの宣言
    private val appId = "YOUR_APP_ID"
    private val secretKey = "YOUR_SECRET_KEY"
    private val scope = CoroutineScope(Dispatchers.IO)
    private var localRoomMember     : LocalRoomMember?  = null
    private var room                : Room?          = null
    private var localVideoStream    : LocalVideoStream? = null
    private var localAudioStream    : LocalAudioStream? = null

    override fun onCreate(savedInstanceState: Bundle?) {
    }
}
```

### UIの初期化

`initUI()` 関数を作成し、 `onCreate()` から呼び出します。
この関数は `activity_main.xml` で記述したレイアウトの初期値を設定します。

```kotlin
class MainActivity : AppCompatActivity() {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // UIの初期化
        initUI()
    }

    // roomNameの初期値を生成する
    private fun initUI() {
        val roomName = findViewById<TextView>(R.id.roomName)
        roomName.text = UUID.randomUUID().toString()
    }
}
```

### パーミッションの要求

カメラやマイクから入力を受け取る前に、アプリケーションにはデバイスを使う権限が必要です。
`onCreate()` に以下を追記して権限を要求します。

```kotlin
override fun onCreate(savedInstanceState: Bundle?) {

    // ... 省略

    // UIの初期化
    initUI()

    // 権限の要求
    if (ContextCompat.checkSelfPermission(
            applicationContext,
            Manifest.permission.CAMERA
        ) != PermissionChecker.PERMISSION_GRANTED ||
        ContextCompat.checkSelfPermission(
            applicationContext,
            Manifest.permission.RECORD_AUDIO
        ) != PermissionChecker.PERMISSION_GRANTED
    ) {
        ActivityCompat.requestPermissions(
            this,
            arrayOf(
                Manifest.permission.CAMERA,
                Manifest.permission.RECORD_AUDIO
            ),
            0
        )
    }
}
```

### JOINボタンのハンドラを作成する

`onCreate()` で `activity_main.xml` で定義したJOINボタンの動作を設定し、`joinAndPublish()` 関数を作成します。
以降の項目では `JoinAndPublish()` に機能を実装しています。

```kotlin
override fun onCreate(savedInstanceState: Bundle?) {

    // ... 省略

    // JOINボタンの動作を設定
    val btnJoinRoom = findViewById<Button>(R.id.joinButton)
    btnJoinRoom.setOnClickListener {
        joinAndPublish()
    }
}
```

```kotlin
class MainActivity : AppCompatActivity() {

    // ... 省略

    override fun onCreate(savedInstanceState: Bundle?) {
        // ... 省略
    }

    private fun initUI() {
        // ... 省略
    }

    // JOINボタンの動作を実装する関数（Roomへの入室、映像・音声の入出力）
    private fun joinAndPublish(){

    }
}
```

### SkyWayContextのセットアップ

Android SDKの一部の機能はCoroutineでの非同期的な処理が必須となります。
事前に定義しておいた `scope` メンバを用いて別スレッドで`SkyWayContext` をセットアップします。

> SkyWayContext.setupForDev はdev環境での利用が想定されているAPIです。
> 本番環境では、SecretKeyを秘匿するためSkyWayContext.setupをご利用ください。

```kotlin
private fun joinAndPublish(){
    scope.launch() {
        val result = SkyWayContext.setupForDev(applicationContext, appId, secretKey, option)
        if (result) {
            Log.d("App", "Setup succeed")
        }
    }
}
```

### カメラからの映像取得と UI への表示、マイクからの音声取得

先ほどに続いて、同じスレッド内で入力を取得しUIへ反映します。

```kotlin
scope.launch(){
    val result = SkyWayContext.setupForDev(applicationContext, appId, secretKey, option)
    if (result) {
        Log.d("App", "Setup succeed")
    }

    // cameraリソースの取得
    val device = CameraSource.getFrontCameras(applicationContext).first()

    // camera映像のキャプチャを開始します
    val cameraOption = CameraSource.CapturingOptions(800, 800)
    CameraSource.startCapturing(applicationContext, device, cameraOption)

    // 描画やpublishが可能なStreamを作成します
    localVideoStream = CameraSource.createStream()

    // SurfaceViewRenderer を取得して描画します。
    runOnUiThread {
        val localVideoRenderer = findViewById<SurfaceViewRenderer>(R.id.local_renderer)
        localVideoRenderer.setup()
        localVideoStream!!.addRenderer(localVideoRenderer)
    }
}
```

音声を取得します。`AudioSource` は静的な object であることに注意してください。

```kotlin
        scope.launch(){
            // ... 省略

            // 音声入力を開始します
            AudioSource.start()

            // publishが可能なStreamを作成します
            val localAudioStream = AudioSource.createStream()
        }
```

### Room の作成

Streamの作成に続けて、入退室可能な `Room` を作成します。
今回はユーザーが編集可能な `roomName` 要素を部屋の名前として利用します。

```kotlin
scope.launch(){
    // ... 省略

    room = Room.findOrCreate(name = findViewById<EditText>(R.id.roomName).toString())
}
```

### Room への参加

続いて `Room` に参加し、 `publish` / `subscribe` が可能な `LocalRoomMember` を取得します。
ユーザー名は `Room` 内でユニークである必要があります。今回はUUIDを用いてランダムに決定しています。
`resultMessage` には `Room` への参加の成否を格納し、結果を `Toast` によるポップアップで通知しています。

```kotlin
scope.launch(){
    // ... 省略

    val memberInit = RoomMember.Init(name = "member_" + UUID.randomUUID())
    localRoomMember = room?.join(memberInit)

    val resultMessage = if (localRoomMember == null) "Join failed" else "Joined room"
    runOnUiThread {
        Toast.makeText(applicationContext, resultMessage, Toast.LENGTH_SHORT)
            .show()
    }
}
```

### Stream の Publish

`Room` に入室しているあるメンバーから他のメンバーへ `Stream` を公開(`publish`)をします。

今回は `type` を指定していないため、通信方式はデフォルトのP2Pになります。

これにより、他のメンバーは対象の `Stream` を購読( `subscribe` )することが可能になります。 `subscribe` は次の項で実装します。

また、 `Room` にハンドラを追加することで `publish` 時に特定の動作を行わせることが可能です。

```kotlin
scope.launch(){
    // ... 省略

    // ハンドラ
    room?.onStreamPublishedHandler = {
        // このRoom内で誰かがPublishするたびに実行される部分
    }

    // 映像、音声のPublish
    localRoomMember?.publish(localVideoStream!!)
    localRoomMember?.publish(localAudioStream!!)
}
```

ハンドラの登録は `publish` との順序が重要です。例えば `publication` が追加されたことをハンドラで検知する場合は、**予め** `Room` にハンドラを追加する必要があります。

### Stream の Subscribe

`Room` に入室中のメンバーが公開( `publish` )している stream を購読( `subscribe` )します。

**自身で `publish` した `stream` は `subscribe` できない**ことに注意してください。

今回は先ほどまで編集していた `scope.launch(){}` のスレッド内から購読時に `subscribe()` 関数を呼び出します。
`subscribe()` 関数は `MainActivity` クラスに実装します。

```kotlin
class MainActivity : AppCompatActivity() {
    // ... 省略

    private fun joinAndPublish{
      scope.launch(){
            // ... 省略


            val resultMessage = if (localRoomMember == null) "Join failed" else "Joined room"
            runOnUiThread {
                Toast.makeText(applicationContext, resultMessage, Toast.LENGTH_SHORT)
                    .show()
            }

            // 入室時に他のメンバーのStreamを購読する
            room?.publications?.forEach {
                if (it.publisher?.id == localRoomMember?.id) return@forEach
                subscribe(it)
            }

            // 誰かがStreamを公開したときに購読する
            room?.onStreamPublishedHandler = Any@{
                Log.d("room", "onStreamPublished: ${it.id}")
                if (it.publisher?.id == localRoomMember?.id) {
                    return@Any
                }
                subscribe(it)
            }

            localRoomMember?.publish(localVideoStream!!)
            localRoomMember?.publish(localAudioStream!!)
        }
    }

    // 購読(subscribe)の実装部分
    private fun subscribe(publication: RoomPublication) {
        scope.launch {
            // Publicationをsubscribeします
            val subscription = localRoomMember?.subscribe(publication)
            runOnUiThread {
                val remoteVideoRenderer =
                    findViewById<SurfaceViewRenderer>(R.id.remote_renderer)
                remoteVideoRenderer.setup()
                val remoteStream = subscription?.stream
                when (remoteStream?.contentType) {
                    // コンポーネントへの描画
                    Stream.ContentType.VIDEO -> (remoteStream as RemoteVideoStream).addRenderer(
                        remoteVideoRenderer
                    )
                    else -> {}
                }
            }
        }
    }
}
```

なお、 `AudioStream` の場合は `subscribe` を完了したタイミングでスピーカーから音声が流れます。

## 動作確認

Android Studio の Run ボタンを押下してアプリケーションを実行してください。

## 次のステップ

今回はメディアを `publish`,`subscribe` するシンプルな例でした。

その他のサンプルアプリケーションはこちらをご参照ください。

[サンプルコード](/ja/docs/sample-code/android-sdk/)

また、開発の前に開発ドキュメントもご一読ください。

[Android SDKの開発ドキュメント](/ja/docs/user-guide/android-sdk/)
