すべてのプロダクト
Search
ドキュメントセンター

ApsaraVideo Live:Android での音声・ビデオ通話

最終更新日:Aug 20, 2026

ARTC SDK を Android プロジェクトに統合し、インタラクティブストリーミングやビデオ通話のためのリアルタイム音声・ビデオアプリを構築します。

基本概念

開始する前に、以下の基本概念をご確認ください:

  • ARTC SDK:Alibaba Cloud のリアルタイム音声・ビデオインタラクション用 SDK です。

  • GRTN:Alibaba Cloud のグローバルリアルタイム伝送ネットワークで、超低遅延、高品質、安全かつ信頼性の高い音声・ビデオ通信サービスを提供します。

  • チャンネル:リアルタイムの音声・ビデオインタラクションのための仮想的なルームです。

  • ホスト:チャンネル内で音声・ビデオストリームを公開し、他のホストが公開したストリームをサブスクライブできるロールです。

  • 視聴者:チャンネル内の音声・ビデオストリームをサブスクライブできますが、ストリームを公開することはできないロールです。

  1. setChannelProfile を呼び出してチャンネルシナリオを設定し、次に `joinChannel` を呼び出してチャンネルに参加します:

    • ビデオ通話シナリオでは、すべてのユーザーがホストロールを持ち、ストリームの公開とサブスクライブができます。

    • インタラクティブストリーミングシナリオでは、setClientRole を呼び出してユーザーロールを設定する必要があります。ストリームを公開するユーザーのロールをホストに設定します。ユーザーがストリームをサブスクライブするだけでよい場合は、ロールを視聴者に設定します。

  2. チャンネルに参加した後、ユーザーのロールによってストリームを公開できるか、サブスクライブできるかが決まります:

    • チャンネル内のすべてのユーザーは、その音声・ビデオストリームをサブスクライブできます。

    • ホストはチャンネル内で音声・ビデオストリームを公開できます。

    • 視聴者がストリームを公開する必要がある場合は、setClientRole メソッドを呼び出してロールをホストに切り替える必要があります。

サンプルプロジェクト

Alibaba Cloud は、リアルタイム音声・ビデオインタラクションのためのオープンソース ARTC SDK サンプルプロジェクトを提供しています。プロジェクトをダウンロードするか、サンプルソースコードを表示できます。

前提条件

サンプルプロジェクトを実行する前に、ご利用の開発環境が以下の要件を満たしていることを確認してください:

  • 開発ツール:Android Studio 2020.3.1 以降。

  • テストデバイス:Android 5.0 (API レベル 21) 以降を実行するテストデバイス。

    説明

    テストには物理デバイスを使用してください。エミュレーターでは必要な機能が不足している場合があります。

  • ネットワーク環境:安定したネットワーク接続。

  • アプリケーションの準備:アプリケーションの AppID と AppKey を取得します。詳細については、「アプリケーションの作成」をご参照ください。

プロジェクトの作成

このセクションでは、プロジェクトを作成し、リアルタイム音声・ビデオに必要な権限を追加する方法について説明します。すでにプロジェクトがある場合は、このセクションをスキップしてください。

  1. Android Studio を開き、[New Project] を選択します。

  2. Phone and Tablet を選択し、スターターテンプレートを選択します。この例では Empty Views Activity を使用します。

  3. プロジェクト名、パッケージ名、保存場所、開発言語 (この例では Java)、ビルド設定言語 (この例では Groovy DSL) を設定します。

  4. [Finish] をクリックし、プロジェクトの同期が完了するのを待ちます。

プロジェクトの設定

ステップ 1: SDK のインポート

Maven 自動連携 (推奨)

  1. プロジェクトのルートディレクトリにある settings.gradle ファイルを開き、ARTC SDK に必要な Maven リポジトリを dependencyResolutionManagement/repositories ブロックに次のように追加します:

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        // ARTC SDK の Maven リポジトリを追加
        maven { url 'https://maven.aliyun.com/repository/google' }
        maven { url 'https://maven.aliyun.com/repository/public' }
    }
}

注意:Android Gradle Plugin 7.1.0 より前のバージョンを使用している場合、このブロックは settings.gradle ファイルに見つからないことがあります。詳細については、Android Gradle Plugin 7.1 をご参照ください。この場合は、以下の代替方法を使用してください:

Android Gradle Plugin 7.1.0 より前のバージョンの代替方法

プロジェクトのルートディレクトリにある build.gradle ファイルを開き、Maven リポジトリの URL を allprojects/repositories ブロックに追加します:

allprojects {
    repositories {
        ...
        // ARTC SDK の Maven リポジトリを追加
        maven { url 'https://maven.aliyun.com/repository/google' }
        maven { url 'https://maven.aliyun.com/repository/public' }
    }
}
  1.  app/build.gradle ファイルを開き、ARTC SDK の依存関係を dependencies ブロックに追加します。バージョン情報は SDK のダウンロードと連携 で確認できます。 ${latest_version} を特定のバージョン番号に置き換えてください。最新バージョンは 7.11.0 です。

dependencies {
    // リアルタイム音声・ビデオ SDK の依存関係を追加// ${latest_version} を特定のバージョン番号に置き換えます
    implementation 'com.aliyun.aio:AliVCSDK_ARTC:${latest_version}'// バージョン 7.4.0 以前では、keep 依存関係を追加する必要があります// implementation 'com.aliyun.aio.keep:keep:1.0.1'
}

Android Gradle Plugin 8.1 以降を使用している場合、Android Studio は プロジェクトの依存関係をバージョンカタログに移行することを推奨します。

手動連携

  1.  SDK のダウンロードと連携 から必要なバージョンの ARTC SDK AAR ファイルをダウンロードします。最新バージョンは 7.11.0 で、ファイル名は AliVCSDK_ARTC-x.y.z.aar のようになります。

  2. ダウンロードした AAR ファイルをプロジェクトディレクトリ (例:app/libs) にコピーします。このフォルダが存在しない場合は作成してください。

  3. プロジェクトのルートディレクトリにある settings.gradle ファイルを開き、AAR ファイルを含むディレクトリを dependencyResolutionManagement/repositories に追加します:

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        // ARTC SDK が配置されているディレクトリへの相対パスを追加flatDir {
            dir 'app/libs'
        }
    }
}

注意:Android Gradle Plugin 7.1.0 より前のバージョンを使用している場合、このブロックは settings.gradle ファイルに見つからないことがあります。詳細については、Android Gradle Plugin 7.1 をご参照ください。この場合は、以下の代替方法を使用してください:

プロジェクトのルートディレクトリにある build.gradle ファイルを開き、以下を allprojects/repositories ブロックに追加します:

allprojects {
    repositories {
        ...
        // ARTC SDK が配置されているディレクトリへの相対パスを追加flatDir {
            dir 'app/libs'
        }
    }
}
  1.  app/build.gradle ファイルを開き、AAR ファイルの依存関係を dependencies ブロックに追加します:

// x.y.z を対応するバージョン番号に置き換えます
implementation(name:'AliVCSDK_ARTC', version: 'x.y.z', ext:'aar')
  1. ビルドが完了すると、External Libraries セクションに依存関係が表示されます。

    image

ステップ 2: サポートされる CPU アーキテクチャの指定

 app/build.gradle ファイルを開き、サポートされる CPU アーキテクチャを defaultConfig ブロックで指定します。利用可能なアーキテクチャには armeabi-v7a、arm64-v8a、x86、x86_64 があります。必要なアーキテクチャを選択してください。

android {
    defaultConfig {
        // ...その他のデフォルト設定// armeabi-v7a および arm64-v8a アーキテクチャのサポート
        ndk {
             abiFilters "armeabi-v7a", "arm64-v8a"
        }
    }
}	

ステップ 3: 権限の設定

アプリに必要な権限を設定します。

 app/src/main ディレクトリに移動し、AndroidManifest.xml ファイルを開いて必要な権限を追加します。

<uses-feature android:name="android.hardware.camera" android:required="false" /><uses-permission android:name="android.permission.CAMERA" /><uses-permission android:name="android.permission.RECORD_AUDIO" /><uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><!-- 古いデバイスでレガシー Bluetooth 権限をリクエストします。 --><uses-permissionandroid:name="android.permission.BLUETOOTH"android:maxSdkVersion="30" /><uses-permissionandroid:name="android.permission.BLUETOOTH_ADMIN"android:maxSdkVersion="30" /><!-- アプリがペアリング済みの Bluetooth デバイスと通信する場合にのみ必要です。 --><uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /><uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /><uses-permission android:name="android.permission.READ_PHONE_STATE" /><uses-permission android:name="android.permission.WRITE_SETTINGS"tools:ignore="ProtectedPermissions" /><uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

ランタイム Bluetooth 権限

ARTC SDK 内部の AndroidManifest.xml ファイルはいくつかの権限を宣言しています。必要な動的権限はランタイムでリクエストする責任があることに注意してください。Android 12 で導入された BLUETOOTH_CONNECT 権限もその一つです。ARTC SDK をインポートすると、その宣言された権限はアプリのマニフェストにマージされます。明確化と最小権限の原則に従うため、必要なすべての権限を独自のマニフェストで明示的に宣言することを推奨します。

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /><uses-permission android:name="android.permission.BLUETOOTH" /><uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /><uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

ビジネスシナリオに基づいて、以下の権限のいずれかが不要な場合は、メインプロジェクトの AndroidManifest.xml ファイルでその削除を宣言できます:

<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" tools:node="remove"/>

Bluetooth 権限のシナリオ:

シナリオ 1: Bluetooth が必要な場合

アプリの targetSdk が 31 未満の場合

プロジェクトの targetSdk が 31 未満の場合、Bluetooth 機能はレガシーな BLUETOOTH 権限によって制御されます。アプリのマニフェストで以下を宣言してください:

<!-- Bluetooth 権限を宣言します --><uses-permission android:name="android.permission.BLUETOOTH" /><!-- API 31+ の権限をオーバーライドして削除します --><uses-permission android:name="android.permission.BLUETOOTH_CONNECT" tools:node="remove" />

注意 

  • SDK には BLUETOOTH_CONNECT 権限の宣言が含まれています。この権限は Android 12 (API レベル 31) で導入され、ランタイムで requestPermissions を呼び出してリクエストする必要があります。

  • 一部のデバイスでは、アプリに BLUETOOTH_CONNECT 宣言が含まれている場合、SecurityException を避けるために、システムはこの権限のランタイムリクエストを要求します。この問題が発生した場合は、以下のいずれかのアプローチを使用してください:

    • オプション 1: tools:node="remove" を使用して権限宣言を削除します。

    • オプション 2: ランタイムで権限をリクエストします。

アプリの targetSdk が 31 以上の場合

プロジェクトの targetSdk が 31 以上の場合、古い Android バージョンと新しい Android バージョンの両方をサポートするために Bluetooth 権限を処理する必要があります。メインアプリの AndroidManifest.xml ファイルで権限を宣言してください:

<!-- Bluetooth 権限を宣言し、古いデバイスとの互換性のために android:maxSdkVersion を 30 に設定します --><uses-permission android:name="android.permission.BLUETOOTH"/><!-- API 31+ の Bluetooth 権限を宣言します --><uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/><!-- その他の Bluetooth 権限 -->

さらに、BLUETOOTH_CONNECT は動的権限であるため、ランタイムでリクエストする必要があります:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    String[] permissions = {
        android.Manifest.permission.BLUETOOTH_CONNECT
    };
    ActivityCompat.requestPermissions(activity, permissions, REQUEST_BLUETOOTH);
}
シナリオ 2: Bluetooth が不要な場合

Bluetooth に関連するクラッシュや権限プロンプトを避けるため、不要な Bluetooth 権限を削除することを推奨します。

メインプロジェクトの AndroidManifest.xml ファイルで、tools:node="remove" を使用して権限をオーバーライドし、削除します:

<!-- ARTC SDK からの Bluetooth 権限宣言をオーバーライドして削除します --><uses-permission android:name="android.permission.BLUETOOTH" tools:node="remove" /><uses-permission android:name="android.permission.BLUETOOTH_CONNECT" tools:node="remove" />

その他のランタイム権限

Android 6.0 (API レベル 23) 以降では、危険な権限をランタイムでリクエストする必要があります。 AndroidManifest.xml ファイルで宣言するだけでなく、コード内でランタイムにリクエストする必要があります。

以下の権限はランタイムリクエストが必要です:

  • Manifest.permission.CAMERA

  • Manifest.permission.WRITE_EXTERNAL_STORAGE

  • Manifest.permission.RECORD_AUDIO

  • Manifest.permission.READ_EXTERNAL_STORAGE

  • Manifest.permission.READ_PHONE_STATE

Android 12 (API レベル 31) 以降では、以下の権限もランタイムでリクエストする必要があります:

  • Manifest.permission.BLUETOOTH_CONNECT

以下の表は、主要な権限とその目的を説明しています:

権限

説明

目的

必須

ランタイム権限

CAMERA

カメラへのアクセスを許可します。

リアルタイム通信のためにビデオをキャプチャします。

はい

Android >= 6.0

RECORD_AUDIO

マイクへのアクセスを許可します。

リアルタイム通信のために音声をキャプチャします。

はい

Android >= 6.0

INTERNET

インターネットへのアクセスを許可します。

ネットワーク経由で音声・ビデオデータを送信します。

はい

いいえ

ACCESS_NETWORK_STATE

アプリがネットワーク状態を取得できるようにします。

ネットワーク接続を監視し、再接続の管理などによってストリーミング品質を最適化します。

任意

いいえ

ACCESS_WIFI_STATE

アプリが Wi-Fi 状態を取得できるようにします。

現在の Wi-Fi 接続に関する情報を取得して、ネットワークパフォーマンスを最適化します。

任意

いいえ

MODIFY_AUDIO_SETTINGS

アプリが音声設定を変更できるようにします。

システムボリュームの調整や、音声出力デバイス (例:スピーカーフォン、ヘッドセット) の切り替えを行います。

任意

いいえ

BLUETOOTH

Bluetooth 権限 (基本機能)

Bluetooth ヘッドセットなどの Bluetooth デバイスに接続します。

任意

いいえ

BLUETOOTH_CONNECT

Bluetooth 接続権限

音声ストリームの送信など、ペアリング済みの Bluetooth デバイスと通信します。

任意

Android >= 12

READ_PHONE_STATE

アプリがデバイスの電話状態に関連する情報にアクセスできるようにします。

通話中に音声ストリームを管理します。例えば、通話開始時にセッションを一時停止します。

任意

Android >= 6.0

READ_EXTERNAL_STORAGE

アプリが外部ストレージからファイルを読み取れるようにします。

ローカル音声ファイルを効果音や BGM として再生するなどの機能を有効にします。

任意

Android >= 6.0

WRITE_EXTERNAL_STORAGE

アプリが外部ストレージに書き込めるようにします。

アプリケーションログや録画されたメディアなどのファイルを保存します。

任意

Android >= 6.0

ステップ 4: コードの難読化の防止 (任意)

 app/proguard-rules.pro ファイルに、以下の ProGuard ルールを追加して、SDK の公開インターフェースが難読化されるのを防ぎます。

-keep class com.aliyun.allinone.** {*;}-keep class com.aliyun.rts.network.AliHttpTool {*;}-keep class com.aliyun.common.AlivcBase {*;}-keep class com.huawei.multimedia.alivc.** {*;}-keep class com.alivc.rtc.** {*;}-keep class com.alivc.component.** {*;}-keep class org.webrtc.** {*;}

よくある質問

ARTC SDK と追加の権限

ARTC SDK をインポートすると、そのマニフェストからの権限が自動的にアプリのマニフェストにマージされます。正確なリストは SDK のバージョンによって異なる場合があります。最も正確なリストについては、SDK の .aar ファイル内の AndroidManifest.xml ファイルを確認してください。

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /><uses-permission android:name="android.permission.BLUETOOTH" /><uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /><uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

これらの権限は、主にネットワーク状態の検出、Wi-Fi 情報へのアクセス、Bluetooth 接続、音声ルーティングなどの機能をサポートするために使用されます。

権限の競合の処理

ARTC SDK は、さまざまな Android バージョンに適応するように設計されています。例えば、Android 12 (API レベル 31) 以降の新しい BLUETOOTH_CONNECT ランタイム権限を考慮しています。この設計は一般的にコンパイルエラーを防ぎますが、以下のような問題が発生する可能性があります:

  • マニフェストのマージ失敗など、予期しないランタイム動作。

  • Google Play の審査リスク:アプリが使用しない機能の権限を宣言している場合、最小権限の原則に違反しているとして拒否される可能性があります。

これらの潜在的な問題を軽減するために、以下の戦略を検討してください:

  1. 権限要件の評価
    まず、アプリが SDK の宣言するすべての権限を必要とするかどうかを判断します。アプリが Bluetooth などの機能を使用しない場合は、最小権限の原則に従うために、関連する権限宣言を削除してください。例えば、アプリの targetSdk が 31 未満の場合、BLUETOOTH_CONNECT 権限は不要です。







  2. マニフェストマージディレクティブを使用して競合を解決
    SDK によって宣言された権限が別のモジュールの宣言と競合する場合 (例えば、異なる maxSdkVersion 属性)、アプリのメイン AndroidManifest.xml で tools 名前空間ディレクティブを使用して競合を解決します:







    <manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"><!-- 例: アプリに不要な、SDK によって宣言された権限を削除します --><uses-permissionandroid:name="android.permission.BLUETOOTH_CONNECT"tools:node="remove" /><!-- 例: 権限属性をオーバーライドします --><uses-permissionandroid:name="android.permission.BLUETOOTH"android:maxSdkVersion="30"tools:replace="android:maxSdkVersion" /></manifest>

ステップ 5: UI の作成

リアルタイムインタラクティブシナリオのユーザーインターフェースを作成します。以下のサンプルコードは、ビデオ通話用にローカルとリモートのビデオを表示するための 2 つのビューを作成します。

コード例

<?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:id="@+id/video_chat_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".VideoCall.VideoCallActivity"
    >
    <LinearLayout
        android:id="@+id/ll_channel_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintBottom_toTopOf="@id/ll_video_layout"
        android:orientation="vertical">

        <LinearLayout
            android:id="@+id/ll_channel_desc"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_marginTop="12dp"
            android:layout_marginLeft="8dp"
            android:layout_marginRight="12dp"
        >
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/video_chat_channel_desc"
                />

        </LinearLayout>
        <LinearLayout
            android:id="@+id/ll_channel_id"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_marginTop="12dp"
            android:layout_marginLeft="8dp"
            android:layout_marginRight="12dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            android:visibility="visible">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="0"
                android:text="ChannelID:"
                android:layout_marginTop="5dp"
                />
            <EditText
                android:id="@+id/channel_id_input"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text=""
                android:padding="5dp"
                android:textSize="15sp"
                android:layout_marginLeft="10dp"
                android:layout_marginTop="5dp"
                android:layout_marginRight="10dp"
                android:background="@drawable/edittext_border"
                />
        </LinearLayout>
        <LinearLayout
            android:id="@+id/ll_bottom_bar"
            android:layout_width="match_parent"
            android:layout_height="48dp"
            android:layout_marginTop="20dp"
            android:orientation="horizontal"
            android:gravity="center_vertical"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@id/ll_channel_desc"
            app:layout_constraintBottom_toBottomOf="parent">
            <TextView
                android:id="@+id/join_room_btn"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="@string/video_chat_join_room"
                android:layout_marginStart="20dp"
                android:layout_marginEnd="20dp"
                android:gravity="center"
                android:padding="10dp"
                android:background="@color/layout_base_blue"
                />

        </LinearLayout>
    </LinearLayout>
    <LinearLayout
        android:id="@+id/ll_video_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintTop_toBottomOf="@id/ll_channel_layout"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="10dp"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        >

        <LinearLayout
            android:id="@+id/video_layout_1"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="0.5"
            android:orientation="horizontal">

            <FrameLayout
                android:id="@+id/fl_local"
                android:layout_width="108dp"
                android:layout_weight="0.5"
                android:layout_height="192dp"
                />
            <FrameLayout
                android:id="@+id/fl_remote"
                android:layout_marginLeft="5dp"
                android:layout_width="108dp"
                android:layout_weight="0.5"
                android:layout_height="192dp"
                />

        </LinearLayout>

        <LinearLayout
            android:id="@+id/video_layout_2"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="0.5"
            android:layout_marginTop="10dp"
            android:orientation="horizontal">

            <FrameLayout
                android:id="@+id/fl_remote2"
                android:layout_width="108dp"
                android:layout_weight="0.5"
                android:layout_height="192dp"
                />
            <FrameLayout
                android:id="@+id/fl_remote3"
                android:layout_marginLeft="5dp"
                android:layout_width="108dp"
                android:layout_weight="0.5"
                android:layout_height="192dp"
                />

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

操作手順

このセクションでは、ARTC SDK を使用して基本的なリアルタイム音声・ビデオアプリケーションを構築する方法について説明します。コードサンプルをコピーして迅速にテストし、その後、手順に従ってコア API 呼び出しを理解してください。

以下の図は、リアルタイム音声・ビデオ通話の基本的なワークフローを示しています:

以下は、ビデオ通話を実装するための基本的なプロセスを示す完全なコードサンプルです:

基本ワークフローのコード例

/**
 * ビデオ通話シナリオの API 呼び出し例。
 */
public class VideoCallActivity extends AppCompatActivity {

    private Handler handler;
    private EditText mChannelEditText;
    private TextView mJoinChannelTextView;
    private boolean hasJoined = false;
    private FrameLayout fl_local, fl_remote, fl_remote_2, fl_remote_3;

    private AliRtcEngine mAliRtcEngine = null;
    private AliRtcEngine.AliRtcVideoCanvas mLocalVideoCanvas = null;
    private Map<String, ViewGroup> remoteViews = new ConcurrentHashMap<String, ViewGroup>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        handler = new Handler(Looper.getMainLooper());
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_video_chat);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.video_chat_main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        setTitle(getString(R.string.video_chat));
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        fl_local = findViewById(R.id.fl_local);
        fl_remote = findViewById(R.id.fl_remote);
        fl_remote_2 = findViewById(R.id.fl_remote2);
        fl_remote_3 = findViewById(R.id.fl_remote3);

        mChannelEditText = findViewById(R.id.channel_id_input);
        mChannelEditText.setText(GlobalConfig.getInstance().gerRandomChannelId());
        mJoinChannelTextView = findViewById(R.id.join_room_btn);
        mJoinChannelTextView.setOnClickListener(v -> {
            if(hasJoined) {
                destroyRtcEngine();
                mJoinChannelTextView.setText(R.string.video_chat_join_room);
            } else {
                startRTCCall();
            }
        });
    }

    public static void startActionActivity(Activity activity) {
        Intent intent = new Intent(activity, VideoCallActivity.class);
        activity.startActivity(intent);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            // 戻るボタンのクリックを処理します。
            destroyRtcEngine();
            finish();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    private FrameLayout getAvailableView() {
        if (fl_remote.getChildCount() == 0) {
            return fl_remote;
        } else if (fl_remote_2.getChildCount() == 0) {
            return fl_remote_2;
        } else if (fl_remote_3.getChildCount() == 0) {
            return fl_remote_3;
        } else {
            return null;
        }
    }

    private void handleJoinResult(int result, String channel, String userId) {
        handler.post(() -> {
            String  str = null;
            if(result == 0) {
                str = "User " + userId + " Join " + channel + " Success";
            } else {
                str = "User " + userId + " Join " + channel + " Failed, error:" + result;
            }
            ToastHelper.showToast(this, str, Toast.LENGTH_SHORT);
            ((TextView)findViewById(R.id.join_room_btn)).setText(R.string.leave_channel);
        });
    }

    private void startRTCCall() {
        if(hasJoined) {
            return;
        }
        initAndSetupRtcEngine();
        startPreview();
        joinChannel();
    }

    private void initAndSetupRtcEngine() {

        // エンジンを作成して初期化します。
        if(mAliRtcEngine == null) {
            mAliRtcEngine = AliRtcEngine.getInstance(this);
        }
        mAliRtcEngine.setRtcEngineEventListener(mRtcEngineEventListener);
        mAliRtcEngine.setRtcEngineNotify(mRtcEngineNotify);

        // チャンネルプロファイルをインタラクティブモードに設定します。RTC の場合は AliRTCSdkInteractiveLive を使用します。
        mAliRtcEngine.setChannelProfile(AliRtcEngine.AliRTCSdkChannelProfile.AliRTCSdkInteractiveLive);
        // ユーザーロールを設定します。公開とサブスクライブの両方を行う場合は AliRTCSdkInteractive を、サブスクライブのみの場合は AliRTCSdkLive を使用します。
        mAliRtcEngine.setClientRole(AliRtcEngine.AliRTCSdkClientRole.AliRTCSdkInteractive);
        // 音声プロファイルを設定します。デフォルトは高品質モード (AliRtcEngineHighQualityMode) と音楽シナリオ (AliRtcSceneMusicMode) です。
        mAliRtcEngine.setAudioProfile(AliRtcEngine.AliRtcAudioProfile.AliRtcEngineHighQualityMode, AliRtcEngine.AliRtcAudioScenario.AliRtcSceneMusicMode);
        mAliRtcEngine.setCapturePipelineScaleMode(AliRtcEngine.AliRtcCapturePipelineScaleMode.AliRtcCapturePipelineScaleModePost);

        // ビデオエンコーディングパラメーターを設定します。
        AliRtcEngine.AliRtcVideoEncoderConfiguration aliRtcVideoEncoderConfiguration = new AliRtcEngine.AliRtcVideoEncoderConfiguration();
        aliRtcVideoEncoderConfiguration.dimensions = new AliRtcEngine.AliRtcVideoDimensions(
                720, 1280);
        aliRtcVideoEncoderConfiguration.frameRate = 20;
        aliRtcVideoEncoderConfiguration.bitrate = 1200;
        aliRtcVideoEncoderConfiguration.keyFrameInterval = 2000;
        aliRtcVideoEncoderConfiguration.orientationMode = AliRtcVideoEncoderOrientationModeAdaptive;
        mAliRtcEngine.setVideoEncoderConfiguration(aliRtcVideoEncoderConfiguration);

        // SDK はデフォルトで音声を公開するため、publishLocalAudioStream を呼び出す必要はありません。
        mAliRtcEngine.publishLocalAudioStream(true);
        // ビデオ通話の場合、SDK はデフォルトでビデオを公開するため、publishLocalVideoStream(true) を呼び出す必要はありません。
        // 音声のみの通話の場合、ビデオ公開を無効にするために publishLocalVideoStream(false) を呼び出す必要があります。
        mAliRtcEngine.publishLocalVideoStream(true);

        // デフォルトでリモートの音声・ビデオストリームをサブスクライブするように設定します。
        mAliRtcEngine.setDefaultSubscribeAllRemoteAudioStreams(true);
        mAliRtcEngine.subscribeAllRemoteAudioStreams(true);
        mAliRtcEngine.setDefaultSubscribeAllRemoteVideoStreams(true);
        mAliRtcEngine.subscribeAllRemoteVideoStreams(true);

    }

    private void startPreview(){
        if (mAliRtcEngine != null) {

            if (fl_local.getChildCount() > 0) {
                fl_local.removeAllViews();
            }

            findViewById(R.id.ll_video_layout).setVisibility(VISIBLE);
            ViewGroup.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
            if(mLocalVideoCanvas == null) {
                mLocalVideoCanvas = new AliRtcEngine.AliRtcVideoCanvas();
                SurfaceView localSurfaceView = mAliRtcEngine.createRenderSurfaceView(VideoCallActivity.this);
                localSurfaceView.setZOrderOnTop(true);
                localSurfaceView.setZOrderMediaOverlay(true);
                fl_local.addView(localSurfaceView, layoutParams);
                mLocalVideoCanvas.view = localSurfaceView;
                mAliRtcEngine.setLocalViewConfig(mLocalVideoCanvas, AliRtcVideoTrackCamera);
                mAliRtcEngine.startPreview();
            }
        }
    }

    private void joinChannel() {
        String channelId = mChannelEditText.getText().toString();
        if(!TextUtils.isEmpty(channelId)) {
            String userId = GlobalConfig.getInstance().getUserId();
            String appId = ARTCTokenHelper.AppId;
            String appKey = ARTCTokenHelper.AppKey;
            long timestamp = ARTCTokenHelper.getTimesTamp();
            String token = ARTCTokenHelper.generateSingleParameterToken(appId, appKey, channelId, userId, timestamp);
            mAliRtcEngine.joinChannel(token, null, null, null);
            hasJoined = true;
        } else {
            Log.e("VideoCallActivity", "channelId is empty");
        }
    }

    private AliRtcEngineEventListener mRtcEngineEventListener = new AliRtcEngineEventListener() {
        @Override
        public void onJoinChannelResult(int result, String channel, String userId, int elapsed) {
            super.onJoinChannelResult(result, channel, userId, elapsed);
            handleJoinResult(result, channel, userId);
        }

        @Override
        public void onLeaveChannelResult(int result, AliRtcEngine.AliRtcStats stats){
            super.onLeaveChannelResult(result, stats);
        }

        @Override
        public void onConnectionStatusChange(AliRtcEngine.AliRtcConnectionStatus status, AliRtcEngine.AliRtcConnectionStatusChangeReason reason){
            super.onConnectionStatusChange(status, reason);

            handler.post(new Runnable() {
                @Override
                public void run() {
                    if(status == AliRtcEngine.AliRtcConnectionStatus.AliRtcConnectionStatusFailed) {
                        /* TODO: このコールバックは必ず処理する必要があります。SDK 内部の回復戦略が失敗した後にのみトリガーされます。ユーザーに通知することを推奨します。 */
                        ToastHelper.showToast(VideoCallActivity.this, R.string.video_chat_connection_failed, Toast.LENGTH_SHORT);
                    } else {
                        /* TODO: 任意。データ分析や UI 更新などのビジネスロジックをここに追加します。 */
                    }
                }
            });
        }
        @Override
        public void onLocalDeviceException(AliRtcEngine.AliRtcEngineLocalDeviceType deviceType, AliRtcEngine.AliRtcEngineLocalDeviceExceptionType exceptionType, String msg){
            super.onLocalDeviceException(deviceType, exceptionType, msg);
            /* TODO: このコールバックは必ず処理する必要があります。SDK 内部の回復戦略が失敗した後にのみトリガーされます。デバイスエラーをユーザーに通知することを推奨します。 */
            handler.post(new Runnable() {
                @Override
                public void run() {
                    String str = "OnLocalDeviceException deviceType: " + deviceType + " exceptionType: " + exceptionType + " msg: " + msg;
                    ToastHelper.showToast(VideoCallActivity.this, str, Toast.LENGTH_SHORT);
                }
            });
        }

    };

    private AliRtcEngineNotify mRtcEngineNotify = new AliRtcEngineNotify() {
        @Override
        public void onAuthInfoWillExpire() {
            super.onAuthInfoWillExpire();
            /* TODO: これは必ず処理する必要があります。トークンの有効期限がまもなく切れます。アプリは現在のチャンネルとユーザーの新しい認証トークンを取得し、refreshAuthInfo を呼び出す必要があります。 */
        }

        @Override
        public void onRemoteUserOnLineNotify(String uid, int elapsed){
            super.onRemoteUserOnLineNotify(uid, elapsed);
        }

        // onRemoteUserOffLineNotify コールバックで、リモートビデオストリームのレンダラーをアンバインドします。
        @Override
        public void onRemoteUserOffLineNotify(String uid, AliRtcEngine.AliRtcUserOfflineReason reason){
            super.onRemoteUserOffLineNotify(uid, reason);
        }

        // onRemoteTrackAvailableNotify コールバックで、リモートビデオストリームのレンダラーを設定します。
        @Override
        public void onRemoteTrackAvailableNotify(String uid, AliRtcEngine.AliRtcAudioTrack audioTrack, AliRtcEngine.AliRtcVideoTrack videoTrack){
            handler.post(new Runnable() {
                @Override
                public void run() {
                    if(videoTrack == AliRtcVideoTrackCamera) {
                        SurfaceView surfaceView = mAliRtcEngine.createRenderSurfaceView(VideoCallActivity.this);
                        surfaceView.setZOrderMediaOverlay(true);
                        FrameLayout view = getAvailableView();
                        if (view == null) {
                            return;
                        }
                        remoteViews.put(uid, view);
                        view.addView(surfaceView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
                        AliRtcEngine.AliRtcVideoCanvas remoteVideoCanvas = new AliRtcEngine.AliRtcVideoCanvas();
                        remoteVideoCanvas.view = surfaceView;
                        mAliRtcEngine.setRemoteViewConfig(remoteVideoCanvas, uid, AliRtcVideoTrackCamera);
                    } else if(videoTrack == AliRtcVideoTrackNo) {
                        if(remoteViews.containsKey(uid)) {
                            ViewGroup view = remoteViews.get(uid);
                            if(view != null) {
                                view.removeAllViews();
                                remoteViews.remove(uid);
                                mAliRtcEngine.setRemoteViewConfig(null, uid, AliRtcVideoTrackCamera);
                            }
                        }
                    }
                }
            });
        }

        /* 複数のデバイスが同じ userId で参加しようとするケースをアプリで処理する必要があります。 */
        @Override
        public void onBye(int code){
            handler.post(new Runnable() {
                @Override
                public void run() {
                    String msg = "onBye code:" + code;
                    ToastHelper.showToast(VideoCallActivity.this, msg, Toast.LENGTH_SHORT);
                }
            });
        }

    };

    private void destroyRtcEngine() {
        if( mAliRtcEngine != null) {
            mAliRtcEngine.stopPreview();
            mAliRtcEngine.setLocalViewConfig(null, AliRtcVideoTrackCamera);
            mAliRtcEngine.leaveChannel();
            mAliRtcEngine.destroy();
            mAliRtcEngine = null;

            handler.post(() -> {
                ToastHelper.showToast(this, "Leave Channel", Toast.LENGTH_SHORT);
            });
        }
        hasJoined = false;
        for (ViewGroup value : remoteViews.values()) {
            value.removeAllViews();
        }
        remoteViews.clear();
        findViewById(R.id.ll_video_layout).setVisibility(View.GONE);
        fl_local.removeAllViews();
        mLocalVideoCanvas = null;
    }
}

完全なサンプルコードとその実行方法の詳細については、「Android 用 ARTC デモの実行」をご参照ください。

1. 権限のリクエスト

ビデオ通話を開始する前に、アプリが必要な権限を持っていることを確認してください:

private static final int REQUEST_PERMISSION_CODE = 101;

private static final String[] PERMISSION_MANIFEST = {
    Manifest.permission.RECORD_AUDIO,
    Manifest.permission.READ_PHONE_STATE,
    Manifest.permission.WRITE_EXTERNAL_STORAGE,
    Manifest.permission.READ_EXTERNAL_STORAGE,
    Manifest.permission.CAMERA
};

private static final String[] PERMISSION_MANIFEST33 = {
    Manifest.permission.RECORD_AUDIO,
    Manifest.permission.READ_PHONE_STATE,
    Manifest.permission.CAMERA
};

private static String[] getPermissions() {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
        return PERMISSION_MANIFEST;
    }
    return PERMISSION_MANIFEST33;
}

public boolean checkOrRequestPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (ContextCompat.checkSelfPermission(this, "android.permission.CAMERA") != PackageManager.PERMISSION_GRANTED
                || ContextCompat.checkSelfPermission(this, "android.permission.RECORD_AUDIO") != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(getPermissions(), REQUEST_PERMISSION_CODE);
            return false;
        }
    }
    return true;
}

2. 認証トークンの取得

ARTC チャンネルに参加するには認証トークンが必要です。トークンの生成方法の詳細については、「トークンベースの認証」をご参照ください。トークン生成方法 (単一パラメーターまたは複数パラメーター) によって、呼び出す joinChannel API が決まります。

本番環境

トークンの生成には AppKey が必要です。クライアント側に AppKey をハードコーディングすることはセキュリティリスクがあるため、App Server でトークンを生成し、クライアントに送信することを強く推奨します。

開発とデバッグ

開発中に App Server がまだトークンを生成できない場合は、以下のサンプルを使用して一時的なトークンを作成してください:

public final class ARTCTokenHelper {
    /**
     * RTC AppId
     */
    public static String AppId = "";

    /**
     * RTC AppKey
     */
    public static String AppKey = "";

    /**
     * channelId、userId、nonce に基づいて単一パラメーターの会議トークンを生成します
     */
    public static String generateSingleParameterToken(String appId, String appKey, String channelId, String userId, long timestamp,  String nonce) {

        StringBuilder stringBuilder = new StringBuilder()
                .append(appId)
                .append(appKey)
                .append(channelId)
                .append(userId)
                .append(timestamp);
        String token =  getSHA256(stringBuilder.toString());
        try{
            JSONObject tokenJson = new JSONObject();
            tokenJson.put("appid", AppId);
            tokenJson.put("channelid", channelId);
            tokenJson.put("userid", userId);
            tokenJson.put("nonce", nonce);
            tokenJson.put("timestamp", timestamp);
            tokenJson.put("token", token);
            String base64Token = Base64.encodeToString(tokenJson.toString().getBytes(StandardCharsets.UTF_8), Base64.NO_WRAP);
            return base64Token;
        }catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * channelId、userId、timestamp に基づいて単一パラメーターの会議トークンを生成します
     */
    public static String generateSingleParameterToken(String appId, String appKey, String channelId, String userId, long timestamp) {
        return generateSingleParameterToken(appId, appKey, channelId, userId, timestamp, "");
    }

    public static String getSHA256(String str) {
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
            byte[] hash = messageDigest.digest(str.getBytes(StandardCharsets.UTF_8));
            return byte2Hex(hash);
        } catch (NoSuchAlgorithmException e) {
            // 例外をログに記録するか、RuntimeException として再スローすることを検討してください
            e.printStackTrace();
        }
        return "";
    }

    private static String byte2Hex(byte[] bytes) {
        StringBuilder stringBuilder = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                // char にはシングルクォートを使用します
                stringBuilder.append('0');
            }
            stringBuilder.append(hex);
        }
        return stringBuilder.toString();
    }

    public static long getTimesTamp() {
        return System.currentTimeMillis() / 1000 + 60 * 60 * 24;
    }
}

3. ARTC SDK クラスのインポート

ARTC SDK のクラスとインターフェースをインポートします:

// ARTC クラスをインポート
import com.alivc.rtc.AliRtcEngine;
import com.alivc.rtc.AliRtcEngineEventListener;
import com.alivc.rtc.AliRtcEngineNotify;

4. エンジンの作成と初期化

  • RTC エンジンの作成

    getInstance[1/2] メソッドを呼び出して AliRTCEngine インスタンスを作成します。

    private AliRtcEngine mAliRtcEngine = null;
    if(mAliRtcEngine == null) {
        mAliRtcEngine = AliRtcEngine.getInstance(this);
    }
  • エンジンの初期化

    • setChannelProfile を呼び出して、チャンネルプロファイルを AliRTCSdkInteractiveLive (インタラクティブモード) に設定します。

      エンターテインメント向けのインタラクティブモードと、通話向けの通信モードから選択します。適切なモードを選択することで、スムーズなユーザーエクスペリエンスと効率的なネットワーク利用が保証されます。

      モード

      公開

      サブスクライブ

      説明

      インタラクティブモード

      1. ロールベースの制限が適用されます。ホストロールが割り当てられたユーザーのみがストリームを公開できます。

      2. 参加者はセッション中にいつでもロールを切り替えることができます。

      ロール制限はありません。すべての参加者がストリームをサブスクライブする権限を持っています。

      1. インタラクティブモードでは、ホストの参加や退出などのイベントが視聴者に送信されます。視聴者のアクティビティはホストに送信されないため、ホストの中断のないエクスペリエンスが保証されます。

      2. 将来的に視聴者がインタラクションする必要があると予想される場合は、インタラクティブモードを使用することを推奨します。その柔軟性により、さまざまなインタラクションのニーズに合わせてユーザーロールを調整できます。

      通信モード

      ロール制限はありません。すべての参加者がストリームを公開する権限を持っています。

      ロール制限はありません。すべての参加者がストリームをサブスクライブする権限を持っています。

      1. 通信モードでは、参加者はお互いのセッション内での存在を認識します。

      2. このモードはユーザーロールを区別しませんが、機能的にはインタラクティブモードのホストロールと同等です。これにより操作が簡素化され、ユーザーはより少ない API 呼び出しで目的の機能を実現できます。

    • setClientRole を呼び出して、ユーザーロールを AliRTCSdkInteractive (ホスト) または AliRTCSdkLive (視聴者) に設定します。注意:ホストロールはデフォルトで公開とサブスクライブを行います。視聴者ロールはデフォルトでサブスクライブのみを行い、プレビューと公開は無効です。

      説明

      ユーザーがチャンネル内でロールを切り替えると、システムはそれに応じて音声・ビデオストリームの公開状態を調整します:

      • ホストから視聴者への切り替え:システムはローカルの音声・ビデオストリームの公開を停止します。サブスクライブしているリモートストリームは影響を受けず、ユーザーは他の参加者を引き続き視聴できます。

      • 視聴者からホストへの切り替え:システムはローカルの音声・ビデオストリームの公開を開始します。サブスクライブしているリモートストリームは変更されず、ユーザーは他の参加者を引き続き視聴できます。

      // チャンネルプロファイルをインタラクティブモードに設定します。RTC では、常に AliRTCSdkInteractiveLive を使用します。
      mAliRtcEngine.setChannelProfile(AliRtcEngine.AliRTCSdkChannelProfile.AliRTCSdkInteractiveLive);
      // ユーザーロールを設定します。ストリームの公開とサブスクライブの両方を行う場合は AliRTCSdkInteractive を、サブスクライブのみの場合は AliRTCSdkLive を使用します。
      mAliRtcEngine.setClientRole(AliRtcEngine.AliRTCSdkClientRole.AliRTCSdkInteractive);
  • 共通コールバックの設定

    SDK が問題に遭遇した場合、まず自動回復を試みます。解決できないエラーについては、SDK はコールバックを通じてアプリに通知します。

    以下は、アプリケーションが処理する必要がある回復不能な問題に関する主要なコールバックです:

    例外の原因

    コールバックとパラメーター

    ソリューション

    説明

    認証失敗

    onJoinChannelResult コールバックの result パラメーターが AliRtcErrJoinBadToken です。

    アプリはトークンが正しいかどうかを確認する必要があります。

    ユーザーが API を呼び出す際に認証が失敗すると、コールバックは認証失敗エラーを返します。

    トークンの有効期限が近い

    onAuthInfoWillExpire

    新しいトークンを取得し、refreshAuthInfo を呼び出して認証情報を更新します。

    トークンの有効期限エラーは、API 呼び出し時またはランタイム中に発生する可能性があります。エラーは API コールバックまたは別のエラーコールバックを通じて報告されます。

    トークンの有効期限切れ

    onAuthInfoExpired

    アプリはチャンネルに再参加する必要があります。

    トークンの有効期限エラーは、API 呼び出し時またはランタイム中に発生する可能性があります。エラーは API コールバックまたは別のエラーコールバックを通じて報告されます。

    ネットワーク接続の問題

    onConnectionStatusChange コールバックは AliRtcConnectionStatusFailed を返します。

    アプリはチャンネルに再参加する必要があります。

    SDK は短いネットワーク切断から自動的に回復します。切断が事前に設定されたしきい値を超えると、タイムアウトが発生し、接続が切断されます。アプリはネットワーク状態を確認し、ユーザーにチャンネルへの再参加を促す必要があります。

    チャンネルからキックされる

    onBye

    • AliRtcOnByeUserReplaced:別のユーザーが同じ userId で参加したかどうかを確認します。

    • AliRtcOnByeBeKickedOut:ユーザーは App Server によってチャンネルからキックされたため、再参加する必要があります。

    • AliRtcOnByeChannelTerminated:チャンネルが終了したため、ユーザーは再参加する必要があります。

    RTC サービスでは、管理者が参加者を削除できます。

    ローカルデバイスの例外

    onLocalDeviceException

    アプリは権限とハードウェアが正しく動作しているかを確認する必要があります。

    RTC サービスはデバイスの検出と診断をサポートしています。SDK が解決できないローカルデバイスの例外が発生した場合、コールバックを通じてアプリに通知します。アプリはデバイスの状態を確認する必要があります。

    private AliRtcEngineEventListener mRtcEngineEventListener = new AliRtcEngineEventListener() {
        @Override
        public void onJoinChannelResult(int result, String channel, String userId, int elapsed) {
            super.onJoinChannelResult(result, channel, userId, elapsed);
            handleJoinResult(result, channel, userId);
        }
    
        @Override
        public void onLeaveChannelResult(int result, AliRtcEngine.AliRtcStats stats){
            super.onLeaveChannelResult(result, stats);
        }
    
        @Override
        public void onConnectionStatusChange(AliRtcEngine.AliRtcConnectionStatus status, AliRtcEngine.AliRtcConnectionStatusChangeReason reason){
            super.onConnectionStatusChange(status, reason);
    
            handler.post(new Runnable() {
                @Override
                public void run() {
                    if(status == AliRtcEngine.AliRtcConnectionStatus.AliRtcConnectionStatusFailed) {
                        /* TODO: このコールバックは必ず処理する必要があります。SDK 内部の回復戦略が失敗した後にのみトリガーされます。ユーザーに通知することを推奨します。 */
                        ToastHelper.showToast(VideoChatActivity.this, R.string.video_chat_connection_failed, Toast.LENGTH_SHORT);
                    } else {
                        /* TODO: 任意。データ分析や UI 更新などのビジネスロジックをここに追加します。 */
                    }
                }
            });
        }
        @Override
        public void onLocalDeviceException(AliRtcEngine.AliRtcEngineLocalDeviceType deviceType, AliRtcEngine.AliRtcEngineLocalDeviceExceptionType exceptionType, String msg){
            super.onLocalDeviceException(deviceType, exceptionType, msg);
            /* TODO: このコールバックは必ず処理する必要があります。SDK 内部の回復戦略が失敗した後にのみトリガーされます。デバイスエラーをユーザーに通知することを推奨します。 */
            handler.post(new Runnable() {
                @Override
                public void run() {
                    String str = "OnLocalDeviceException deviceType: " + deviceType + " exceptionType: " + exceptionType + " msg: " + msg;
                    ToastHelper.showToast(VideoChatActivity.this, str, Toast.LENGTH_SHORT);
                }
            });
        }
    
    };
    
    private AliRtcEngineNotify mRtcEngineNotify = new AliRtcEngineNotify() {
        @Override
        public void onAuthInfoWillExpire() {
            super.onAuthInfoWillExpire();
            /* TODO: これは必ず処理する必要があります。トークンの有効期限がまもなく切れます。アプリは現在のチャンネルとユーザーの新しい認証トークンを取得し、refreshAuthInfo を呼び出す必要があります。 */
        }
    
        @Override
        public void onRemoteUserOnLineNotify(String uid, int elapsed){
            super.onRemoteUserOnLineNotify(uid, elapsed);
        }
    
        // onRemoteUserOffLineNotify コールバックで、リモートビデオストリームのレンダラーをアンバインドします。
        @Override
        public void onRemoteUserOffLineNotify(String uid, AliRtcEngine.AliRtcUserOfflineReason reason){
            super.onRemoteUserOffLineNotify(uid, reason);
        }
    
        // onRemoteTrackAvailableNotify コールバックで、リモートビデオストリームのレンダラーを設定します。
        @Override
        public void onRemoteTrackAvailableNotify(String uid, AliRtcEngine.AliRtcAudioTrack audioTrack, AliRtcEngine.AliRtcVideoTrack videoTrack){
            handler.post(new Runnable() {
                @Override
                public void run() {
                    if(videoTrack == AliRtcVideoTrackCamera) {
                        SurfaceView surfaceView = mAliRtcEngine.createRenderSurfaceView(VideoChatActivity.this);
                        surfaceView.setZOrderMediaOverlay(true);
                        FrameLayout view = getAvailableView();
                        if (view == null) {
                            return;
                        }
                        remoteViews.put(uid, view);
                        view.addView(surfaceView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
                        AliRtcEngine.AliRtcVideoCanvas remoteVideoCanvas = new AliRtcEngine.AliRtcVideoCanvas();
                        remoteVideoCanvas.view = surfaceView;
                        mAliRtcEngine.setRemoteViewConfig(remoteVideoCanvas, uid, AliRtcVideoTrackCamera);
                    } else if(videoTrack == AliRtcVideoTrackNo) {
                        if(remoteViews.containsKey(uid)) {
                            ViewGroup view = remoteViews.get(uid);
                            if(view != null) {
                                view.removeAllViews();
                                remoteViews.remove(uid);
                                mAliRtcEngine.setRemoteViewConfig(null, uid, AliRtcVideoTrackCamera);
                            }
                        }
                    }
                }
            });
        }
    
        /* 複数のデバイスが同じ userId で参加しようとするケースをアプリで処理する必要があります。 */
        @Override
        public void onBye(int code){
            handler.post(new Runnable() {
                @Override
                public void run() {
                    String msg = "onBye code:" + code;
                    ToastHelper.showToast(VideoChatActivity.this, msg, Toast.LENGTH_SHORT);
                }
            });
        }
    };
    
    mAliRtcEngine.setRtcEngineEventListener(mRtcEngineEventListener);
    mAliRtcEngine.setRtcEngineNotify(mRtcEngineNotify);

5. 音声・ビデオプロパティの設定

  • 音声プロパティの設定

    setAudioProfile を呼び出して、音声エンコーディングモードと音声シナリオを設定します。

    mAliRtcEngine.setAudioProfile(AliRtcEngine.AliRtcAudioProfile.AliRtcEngineHighQualityMode, AliRtcEngine.AliRtcAudioScenario.AliRtcSceneMusicMode);
  • ビデオプロパティの設定

    公開するビデオストリームのプロパティ (解像度、ビットレート、フレームレートなど) を設定します。

    // ビデオエンコーディングパラメーターを設定します。
    AliRtcEngine.AliRtcVideoEncoderConfiguration aliRtcVideoEncoderConfiguration = new AliRtcEngine.AliRtcVideoEncoderConfiguration();
    aliRtcVideoEncoderConfiguration.dimensions = new AliRtcEngine.AliRtcVideoDimensions(
                    720, 1280);
    aliRtcVideoEncoderConfiguration.frameRate = 20;
    aliRtcVideoEncoderConfiguration.bitrate = 1200;
    aliRtcVideoEncoderConfiguration.keyFrameInterval = 2000;
    aliRtcVideoEncoderConfiguration.orientationMode = AliRtcVideoEncoderOrientationModeAdaptive;
    mAliRtcEngine.setVideoEncoderConfiguration(aliRtcVideoEncoderConfiguration);

6. 公開とサブスクライブのプロパティ設定

ストリームの公開とデフォルトのサブスクリプション動作を設定します:

  • publishLocalAudioStream を呼び出して音声ストリームを公開します。

  • publishLocalVideoStream を呼び出してビデオストリームを公開します。音声のみの通話の場合は、これを false に設定します。

// SDK はデフォルトで音声を公開するため、publishLocalAudioStream を呼び出す必要はありません。
mAliRtcEngine.publishLocalAudioStream(true);
// ビデオ通話の場合、SDK はデフォルトでビデオを公開するため、publishLocalVideoStream(true) を呼び出す必要はありません。
// 音声のみの通話の場合、ビデオ公開を無効にするために publishLocalVideoStream(false) を呼び出す必要があります。
mAliRtcEngine.publishLocalVideoStream(true);

// デフォルトでリモートの音声・ビデオストリームをサブスクライブするように設定します。
mAliRtcEngine.setDefaultSubscribeAllRemoteAudioStreams(true);
mAliRtcEngine.subscribeAllRemoteAudioStreams(true);
mAliRtcEngine.setDefaultSubscribeAllRemoteVideoStreams(true);
mAliRtcEngine.subscribeAllRemoteVideoStreams(true);
説明

デフォルトでは、SDK はローカルストリームを自動的に公開し、リモートストリームをサブスクライブします。上記のメソッドを呼び出して、この自動動作を無効にすることができます。

7. ローカルプレビューの開始

  • setLocalViewConfig を呼び出してローカルプレビュービューを設定します。これには AliRtcVideoCanvas オブジェクトが必要です。

  • startPreview メソッドを呼び出して、ローカルビデオプレビューを開始します。

mLocalVideoCanvas = new AliRtcEngine.AliRtcVideoCanvas();
SurfaceView localSurfaceView = mAliRtcEngine.createRenderSurfaceView(VideoChatActivity.this);
localSurfaceView.setZOrderOnTop(true);
localSurfaceView.setZOrderMediaOverlay(true);
FrameLayout fl_local = findViewById(R.id.fl_local);
fl_local.addView(localSurfaceView, layoutParams);
mLocalVideoCanvas.view = localSurfaceView;
mAliRtcEngine.setLocalViewConfig(mLocalVideoCanvas, AliRtcVideoTrackCamera);
mAliRtcEngine.startPreview();

8. チャンネルへの参加

joinChannel を呼び出してチャンネルに参加します。トークンが単一パラメーターのルールに基づいて生成された場合は、SDK の単一パラメーター joinChannel[1/3] インターフェースを呼び出します。トークンが複数パラメーターのルールに基づいて生成された場合は、SDK の複数パラメーター joinChannel[2/3] インターフェースを呼び出します。チャンネルに参加すると、onJoinChannelResult コールバックが結果を提供します。result が 0 の場合、チャンネルへの参加に成功しています。それ以外の場合は、提供されたトークンが無効でないか確認してください。

 mAliRtcEngine.joinChannel(token, null, null, null);
説明
  • チャンネルに参加すると、SDK は参加前に設定されたパラメーターに基づいてストリームを公開およびサブスクライブします。

  • SDK は、必要な API 呼び出しの数を減らすために、デフォルトでストリームを自動的に公開およびサブスクライブします。

9. リモートビューの設定

エンジン初期化中に、mAliRtcEngine.setRtcEngineNotify コールバックを設定します。onRemoteTrackAvailableNotify コールバックで、各リモートユーザーのリモートビューを設定します:

@Override
public void onRemoteTrackAvailableNotify(String uid, AliRtcEngine.AliRtcAudioTrack audioTrack, AliRtcEngine.AliRtcVideoTrack videoTrack){
    handler.post(new Runnable() {
        @Override
        public void run() {
            if(videoTrack == AliRtcVideoTrackCamera) {
                SurfaceView surfaceView = mAliRtcEngine.createRenderSurfaceView(VideoChatActivity.this);
                surfaceView.setZOrderMediaOverlay(true);
                FrameLayout fl_remote = findViewById(R.id.fl_remote);
                if (fl_remote == null) {
                    return;
                }
                fl_remote.addView(surfaceView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
                AliRtcEngine.AliRtcVideoCanvas remoteVideoCanvas = new AliRtcEngine.AliRtcVideoCanvas();
                remoteVideoCanvas.view = surfaceView;
                mAliRtcEngine.setRemoteViewConfig(remoteVideoCanvas, uid, AliRtcVideoTrackCamera);
            } else if(videoTrack == AliRtcVideoTrackNo) {
                FrameLayout fl_remote = findViewById(R.id.fl_remote);
                fl_remote.removeAllViews();
                mAliRtcEngine.setRemoteViewConfig(null, uid, AliRtcVideoTrackCamera);
            }
        }
    });
}

10. チャンネルからの退出とエンジンの破棄

音声・ビデオセッションが終了したら、チャンネルから退出してエンジンを破棄します:

  1. stopPreview を呼び出してビデオプレビューを停止します。

  2. leaveChannel を呼び出してチャンネルから退出します。

  3. destroy を呼び出してエンジンを破棄し、そのリソースを解放します。

private void destroyRtcEngine() {
    mAliRtcEngine.stopPreview();
    mAliRtcEngine.setLocalViewConfig(null, AliRtcVideoTrackCamera);
    mAliRtcEngine.leaveChannel();
    mAliRtcEngine.destroy();
    mAliRtcEngine = null;
}

11. デモンストレーション

image

関連ドキュメント

データ構造

AliRtcEngine インターフェース