Tous les produits
Search
Centre de documentation

ApsaraVideo Live:Appels audio et vidéo sur Android

Dernière mise à jour :Aug 19, 2026

Intégrez le SDK ARTC à votre projet Android pour développer une application audio et vidéo en temps réel dédiée au streaming live interactif et aux appels vidéo.

Concepts clés

Avant de commencer, familiarisez-vous avec les concepts suivants :

  • SDK ARTC : SDK d'Alibaba Cloud dédié aux interactions audio et vidéo en temps réel.

  • GRTN : Global Realtime Transport Network d'Alibaba Cloud, un réseau offrant des services de communication audio et vidéo sécurisés, fiables, de haute qualité et à très faible latence.

  • channel : Salle virtuelle permettant les interactions audio et vidéo en temps réel.

  • host : Rôle autorisant un utilisateur à publier des flux audio et vidéo dans un channel et à s'abonner aux flux publiés par d'autres hosts.

  • viewer : Rôle permettant à un utilisateur de s'abonner aux flux audio et vidéo d'un channel, sans pouvoir en publier.

image
  1. Appelez setChannelProfile pour définir le scénario du channel, puis appelez joinChannel pour rejoindre un channel :

    • Dans un scénario d'appel vidéo, tous les utilisateurs ont le rôle host et peuvent publier des flux et s'y abonner.

    • Dans un scénario de streaming interactif, vous devez appeler setClientRole pour définir le rôle de l'utilisateur. Attribuez le rôle host aux utilisateurs qui doivent publier un flux. Si un utilisateur a uniquement besoin de s'abonner à un flux, attribuez-lui le rôle viewer.

  2. Une fois qu'un utilisateur a rejoint un channel, son rôle détermine sa capacité à publier ou à s'abonner aux flux :

    • Tous les utilisateurs présents dans un channel peuvent s'abonner à ses flux audio et vidéo.

    • Un host peut publier des flux audio et vidéo dans le channel.

    • Si un viewer souhaite publier un flux, il doit appeler la méthode setClientRole pour passer au rôle host.

Projet d'exemple

Alibaba Cloud fournit un projet d'exemple open source du SDK ARTC pour l'interaction audio et vidéo en temps réel. Vous pouvez télécharger ce projet ou consulter le code source de l'exemple.

Prérequis

Avant d'exécuter le projet d'exemple, assurez-vous que votre environnement de développement répond aux exigences suivantes :

  • Outil de développement : Android Studio 2020.3.1 ou version ultérieure.

  • Appareil de test : Un appareil de test fonctionnant sous Android 5.0 (niveau d'API 21) ou version ultérieure.

    Remarque

    Utilisez un appareil physique pour vos tests. Les émulateurs peuvent ne pas disposer de toutes les fonctionnalités requises.

  • Environnement réseau : Une connexion réseau stable.

  • Préparation de l'application : Obtenez l'AppID et l'AppKey de votre application. Pour plus de détails, consultez Créer une application.

Créer un projet

Cette section explique comment créer un projet et ajouter les permissions nécessaires pour l'audio et la vidéo en temps réel. Si vous disposez déjà d'un projet, ignorez cette étape.

  1. Ouvrez Android Studio et sélectionnez New Project.

  2. Sélectionnez Phone and Tablet, puis choisissez un modèle de départ. Cet exemple utilise Empty Views Activity.

  3. Définissez le nom du projet, le nom du package, l'emplacement de sauvegarde, le langage de développement (Java dans cet exemple) et le langage de configuration de build (Groovy DSL dans cet exemple).

  4. Cliquez sur Finish et attendez la synchronisation du projet.

Configurer le projet

Étape 1 : Importer le SDK

Intégration automatique Maven (recommandée)

  1. Ouvrez le fichier settings.gradle situé dans le répertoire racine de votre projet, puis ajoutez les dépôts Maven requis par le SDK ARTC au bloc dependencyResolutionManagement/repositories comme suit :

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        // Add the Maven repositories for the ARTC SDK
        maven { url 'https://maven.aliyun.com/repository/google' }
        maven { url 'https://maven.aliyun.com/repository/public' }
    }
}

Remarque : Si vous utilisez une version d'Android Gradle Plugin antérieure à 7.1.0, ce bloc peut être absent du fichier settings.gradle. Pour plus d'informations, consultez Android Gradle Plugin 7.1. Dans ce cas, utilisez l'alternative suivante :

Alternative pour les versions d'Android Gradle Plugin antérieures à 7.1.0

Ouvrez le fichier build.gradle situé dans le répertoire racine de votre projet et ajoutez les URL des dépôts Maven au bloc allprojects/repositories :

allprojects {
    repositories {
        ...
        // Add the Maven repositories for the ARTC SDK
        maven { url 'https://maven.aliyun.com/repository/google' }
        maven { url 'https://maven.aliyun.com/repository/public' }
    }
}
  1. Ouvrez le fichier app/build.gradle et ajoutez la dépendance du SDK ARTC au bloc dependencies. Vous trouverez les informations de version dans Téléchargement et intégration du SDK. Remplacez ${latest_version} par un numéro de version spécifique. La dernière version est 7.11.0.

dependencies {
    // Add the dependency for the real-time audio and video SDK// Replace ${latest_version} with a specific version number
    implementation 'com.aliyun.aio:AliVCSDK_ARTC:${latest_version}'// For versions 7.4.0 and earlier, you must add the keep dependency// implementation 'com.aliyun.aio.keep:keep:1.0.1'
}

Si vous utilisez Android Gradle Plugin 8.1 ou une version ultérieure, Android Studio recommande de Migrer les dépendances du projet vers des catalogues de versions.

Intégration manuelle

  1. Téléchargez la version requise du fichier AAR du SDK ARTC depuis Téléchargement et intégration du SDK. La dernière version est 7.11.0, avec un nom de fichier similaire à AliVCSDK_ARTC-x.y.z.aar.

  2. Copiez le fichier AAR téléchargé dans le répertoire de votre projet, par exemple app/libs. Créez ce dossier s'il n'existe pas.

  3. Ouvrez le fichier settings.gradle situé dans le répertoire racine de votre projet et ajoutez le répertoire contenant le fichier AAR à dependencyResolutionManagement/repositories :

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        // Add the relative path to the directory where the ARTC SDK is locatedflatDir {
            dir 'app/libs'
        }
    }
}

Remarque : Si vous utilisez une version d'Android Gradle Plugin antérieure à 7.1.0, ce bloc peut être absent du fichier settings.gradle. Pour plus d'informations, consultez Android Gradle Plugin 7.1. Dans ce cas, utilisez l'alternative suivante :

Ouvrez le fichier build.gradle situé dans le répertoire racine de votre projet et ajoutez ce qui suit au bloc allprojects/repositories :

allprojects {
    repositories {
        ...
        // Add the relative path to the directory where the ARTC SDK is locatedflatDir {
            dir 'app/libs'
        }
    }
}
  1. Ouvrez le fichier app/build.gradle et ajoutez la dépendance du fichier AAR au bloc dependencies :

// Replace x.y.z with the corresponding version number
implementation(name:'AliVCSDK_ARTC', version: 'x.y.z', ext:'aar')
  1. Une fois la compilation terminée, la dépendance apparaît dans la section External Libraries.

    image

Étape 2 : Spécifier les architectures CPU prises en charge

Ouvrez le fichier app/build.gradle et spécifiez les architectures CPU prises en charge dans le bloc defaultConfig. Les architectures disponibles incluent armeabi-v7a, arm64-v8a, x86, and x86_64. Sélectionnez les architectures dont vous avez besoin.

android {
    defaultConfig {
        // ...other default configurations// Support for armeabi-v7a and arm64-v8a architectures
        ndk {
             abiFilters "armeabi-v7a", "arm64-v8a"
        }
    }
}	

Étape 3 : Définir les permissions

Configurez les permissions nécessaires à votre application.

Accédez au répertoire app/src/main, ouvrez le fichier AndroidManifest.xml et ajoutez les permissions requises.

<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" /><!-- Request legacy Bluetooth permissions on older devices. --><uses-permissionandroid:name="android.permission.BLUETOOTH"android:maxSdkVersion="30" /><uses-permissionandroid:name="android.permission.BLUETOOTH_ADMIN"android:maxSdkVersion="30" /><!-- Needed only if your app communicates with already-paired Bluetooth devices. --><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" />

Permissions Bluetooth au moment de l'exécution

Le fichier AndroidManifest.xml interne du SDK ARTC déclare plusieurs permissions. Notez qu'il vous incombe de demander toute permission dynamique requise au moment de l'exécution. La permission BLUETOOTH_CONNECT, introduite dans Android 12, en fait partie. Après l'importation du SDK ARTC, ses permissions déclarées sont fusionnées dans le manifeste de votre application. Nous recommandons de déclarer explicitement toutes les permissions requises dans votre propre manifeste pour plus de clarté et afin de respecter le principe du moindre privilège.

<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" />

Selon votre scénario métier, si vous n'avez pas besoin de l'une des permissions suivantes, vous pouvez déclarer sa suppression dans le fichier AndroidManifest.xml de votre projet principal :

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

Scénarios liés aux permissions Bluetooth :

Scénario 1 : Bluetooth requis

Le targetSdk de votre application est inférieur à 31

Lorsque le targetSdk de votre projet est inférieur à 31, la fonctionnalité Bluetooth est contrôlée par l'ancienne permission BLUETOOTH. Déclarez ce qui suit dans le manifeste de votre application :

<!-- Declare the Bluetooth permission --><uses-permission android:name="android.permission.BLUETOOTH" /><!-- Override and remove the permission for API 31+ --><uses-permission android:name="android.permission.BLUETOOTH_CONNECT" tools:node="remove" />

Remarque

  • Le SDK inclut une déclaration pour la permission BLUETOOTH_CONNECT. Cette permission a été introduite dans Android 12 (niveau d'API 31) et doit être demandée au moment de l'exécution en appelant requestPermissions.

  • Sur certains appareils, si une application contient la déclaration BLUETOOTH_CONNECT, le système exige une demande d'autorisation au moment de l'exécution pour éviter une SecurityException. Si vous rencontrez ce problème, adoptez l'une des approches suivantes :

    • Option 1 : Supprimez la déclaration de permission en utilisant tools:node="remove".

    • Option 2 : Demandez la permission au moment de l'exécution.

Le targetSdk de votre application est 31 ou supérieur

Lorsque le targetSdk de votre projet est 31 ou supérieur, vous devez gérer les permissions Bluetooth pour prendre en charge à la fois les anciennes et les nouvelles versions d'Android. Déclarez les permissions dans le fichier AndroidManifest.xml de votre application principale :

<!-- Declare the Bluetooth permission and set android:maxSdkVersion to 30 for compatibility with earlier devices --><uses-permission android:name="android.permission.BLUETOOTH"/><!-- Declare the Bluetooth permission for API 31+ --><uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/><!-- Other Bluetooth permissions -->

De plus, comme BLUETOOTH_CONNECT est une permission dynamique, vous devez la demander au moment de l'exécution :

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    String[] permissions = {
        android.Manifest.permission.BLUETOOTH_CONNECT
    };
    ActivityCompat.requestPermissions(activity, permissions, REQUEST_BLUETOOTH);
}
Scénario 2 : Bluetooth non requis

Pour éviter les plantages ou les invites de permission liés au Bluetooth, nous recommandons de supprimer les permissions Bluetooth inutiles.

Dans le fichier AndroidManifest.xml du projet principal, utilisez tools:node="remove" pour remplacer et supprimer la permission :

<!-- Override and remove the Bluetooth permission declarations from the ARTC SDK --><uses-permission android:name="android.permission.BLUETOOTH" tools:node="remove" /><uses-permission android:name="android.permission.BLUETOOTH_CONNECT" tools:node="remove" />

Autres permissions au moment de l'exécution

Sous Android 6.0 (niveau d'API 23) et versions ultérieures, vous devez demander les permissions dangereuses au moment de l'exécution. En plus de les déclarer dans le fichier AndroidManifest.xml, vous devez les solliciter dans votre code lors de l'exécution.

Les permissions suivantes nécessitent une demande au moment de l'exécution :

  • Manifest.permission.CAMERA

  • Manifest.permission.WRITE_EXTERNAL_STORAGE

  • Manifest.permission.RECORD_AUDIO

  • Manifest.permission.READ_EXTERNAL_STORAGE

  • Manifest.permission.READ_PHONE_STATE

Sous Android 12 (niveau d'API 31) ou version ultérieure, vous devez également demander la permission suivante au moment de l'exécution :

  • Manifest.permission.BLUETOOTH_CONNECT

Le tableau suivant décrit les principales permissions et leur objectif :

Permission

Description

Objectif

Obligatoire

Permission d'exécution

CAMERA

Accorde l'accès à la caméra.

Capturer la vidéo pour la communication en temps réel.

Oui

Android >= 6.0

RECORD_AUDIO

Accorde l'accès au microphone.

Capturer l'audio pour la communication en temps réel.

Oui

Android >= 6.0

INTERNET

Accorde l'accès à Internet.

Transmettre des données audio et vidéo sur le réseau.

Oui

Non

ACCESS_NETWORK_STATE

Permet à l'application d'obtenir l'état du réseau.

Surveiller la connectivité réseau et optimiser la qualité du streaming, par exemple en gérant les reconnexions.

Facultatif

Non

ACCESS_WIFI_STATE

Permet à l'application d'obtenir l'état du Wi-Fi.

Obtenir des informations sur la connexion Wi-Fi actuelle pour optimiser les performances réseau.

Facultatif

Non

MODIFY_AUDIO_SETTINGS

Permet à l'application de modifier les paramètres audio.

Ajuster le volume système ou basculer entre les périphériques de sortie audio (par exemple, haut-parleur, casque).

Facultatif

Non

BLUETOOTH

Permission Bluetooth (fonctionnalité de base)

Se connecter à des périphériques Bluetooth, tels que des casques Bluetooth.

Facultatif

Non

BLUETOOTH_CONNECT

Permission de connexion Bluetooth

Communiquer avec des périphériques Bluetooth appairés, par exemple pour transmettre des flux audio.

Facultatif

Android >= 12

READ_PHONE_STATE

Permet à l'application d'accéder aux informations liées à l'état téléphonique de l'appareil.

Gérer les flux audio pendant les appels téléphoniques, par exemple en mettant une session en pause lorsqu'un appel démarre.

Facultatif

Android >= 6.0

READ_EXTERNAL_STORAGE

Permet à l'application de lire des fichiers depuis le stockage externe.

Activer des fonctionnalités telles que la lecture d'un fichier audio local comme effet sonore ou musique de fond.

Facultatif

Android >= 6.0

WRITE_EXTERNAL_STORAGE

Permet à l'application d'écrire sur le stockage externe.

Sauvegarder des fichiers tels que des journaux d'application ou des médias enregistrés.

Facultatif

Android >= 6.0

Étape 4 : Empêcher l'obfuscation du code (facultatif)

Dans le fichier app/proguard-rules.pro, ajoutez les règles ProGuard suivantes pour empêcher l'obfuscation des interfaces publiques du 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.** {*;}

FAQ

ARTC SDK et permissions supplémentaires

Lorsque vous importez l'ARTC SDK, les permissions définies dans son manifeste fusionnent automatiquement avec celui de votre application. Cette liste varie selon la version du SDK. Pour obtenir la liste la plus précise, examinez le fichier AndroidManifest.xml situé à l'intérieur du fichier .aar du 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" />

Ces permissions servent principalement à prendre en charge des fonctionnalités telles que la détection de l'état du réseau, l'accès aux informations Wi-Fi, les connexions Bluetooth et le routage audio.

Gérer les conflits de permissions

L'ARTC SDK s'adapte aux différentes versions d'Android. Il prend notamment en compte la nouvelle permission d'exécution BLUETOOTH_CONNECT introduite dans Android 12 (API level 31) et les versions ultérieures. Bien que cette conception prévienne généralement les erreurs de compilation, vous pouvez rencontrer les problèmes suivants :

  • Un comportement inattendu lors de l'exécution, comme des échecs de fusion du manifeste.

  • Un risque de rejet par Google Play : si votre application déclare une permission pour une fonctionnalité qu'elle n'utilise pas, elle peut être refusée pour non-respect du principe du moindre privilège.

Pour atténuer ces problèmes potentiels, adoptez les stratégies suivantes :

  1. Évaluez les besoins en permissions. Déterminez d'abord si votre application nécessite toutes les permissions déclarées par le SDK. Si elle n'utilise pas une fonctionnalité comme le Bluetooth, supprimez la déclaration de permission associée afin de respecter le principe du moindre privilège. Par exemple, si le targetSdk de votre application est inférieur à 31, la permission BLUETOOTH_CONNECT n'est pas nécessaire.

  2. Utilisez des directives de fusion du manifeste pour résoudre les conflits. Si une permission déclarée par le SDK entre en conflit avec une déclaration d'un autre module (par exemple, un attribut maxSdkVersion différent), utilisez une directive de namespace tools dans le fichier AndroidManifest.xml principal de votre application pour résoudre le conflit :

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"><!-- Example: Remove a permission declared by the SDK that your app does not need --><uses-permissionandroid:name="android.permission.BLUETOOTH_CONNECT"tools:node="remove" /><!-- Example: Override a permission attribute --><uses-permissionandroid:name="android.permission.BLUETOOTH"android:maxSdkVersion="30"tools:replace="android:maxSdkVersion" /></manifest>

Étape 5 : Créer l'interface utilisateur

Créez l'interface utilisateur pour votre scénario d'interaction en temps réel. L'exemple de code suivant crée deux vues pour un appel vidéo afin d'afficher les flux vidéo locaux et distants.

Exemple de code

<?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>

Procédure

Cette section explique comment utiliser l'ARTC SDK pour créer une application audio et vidéo en temps réel de base. Copiez l'exemple de code pour effectuer un test rapide, puis suivez les étapes afin de comprendre les appels API fondamentaux.

Le diagramme suivant illustre le flux de travail de base d'un appel audio et vidéo en temps réel :

image

L'exemple de code complet ci-dessous présente le processus de base pour mettre en œuvre un appel vidéo :

Exemple de code du flux de travail de base

/**
 * API call example for a video call scenario.
 */
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) {
            // Handle the back button click.
            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() {

        // Create and initialize the engine.
        if(mAliRtcEngine == null) {
            mAliRtcEngine = AliRtcEngine.getInstance(this);
        }
        mAliRtcEngine.setRtcEngineEventListener(mRtcEngineEventListener);
        mAliRtcEngine.setRtcEngineNotify(mRtcEngineNotify);

        // Set the channel profile to interactive mode. For RTC, use AliRTCSdkInteractiveLive.
        mAliRtcEngine.setChannelProfile(AliRtcEngine.AliRTCSdkChannelProfile.AliRTCSdkInteractiveLive);
        // Set the user role. Use AliRTCSdkInteractive to publish and subscribe, or AliRTCSdkLive to only subscribe.
        mAliRtcEngine.setClientRole(AliRtcEngine.AliRTCSdkClientRole.AliRTCSdkInteractive);
        // Set the audio profile. The default is high-quality mode (AliRtcEngineHighQualityMode) and music scenario (AliRtcSceneMusicMode).
        mAliRtcEngine.setAudioProfile(AliRtcEngine.AliRtcAudioProfile.AliRtcEngineHighQualityMode, AliRtcEngine.AliRtcAudioScenario.AliRtcSceneMusicMode);
        mAliRtcEngine.setCapturePipelineScaleMode(AliRtcEngine.AliRtcCapturePipelineScaleMode.AliRtcCapturePipelineScaleModePost);

        // Set video encoding parameters.
        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);

        // The SDK publishes audio by default, so you do not need to call publishLocalAudioStream.
        mAliRtcEngine.publishLocalAudioStream(true);
        // For a video call, you do not need to call publishLocalVideoStream(true) because the SDK publishes video by default.
        // For an audio-only call, you must call publishLocalVideoStream(false) to disable video publishing.
        mAliRtcEngine.publishLocalVideoStream(true);

        // Set the default to subscribe to remote audio and video streams.
        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: This callback must be handled. It is triggered only after the SDK's internal recovery strategies fail. We recommend notifying the user. */
                        ToastHelper.showToast(VideoCallActivity.this, R.string.video_chat_connection_failed, Toast.LENGTH_SHORT);
                    } else {
                        /* TODO: Optional. Add business logic here, such as for data analytics or UI updates. */
                    }
                }
            });
        }
        @Override
        public void onLocalDeviceException(AliRtcEngine.AliRtcEngineLocalDeviceType deviceType, AliRtcEngine.AliRtcEngineLocalDeviceExceptionType exceptionType, String msg){
            super.onLocalDeviceException(deviceType, exceptionType, msg);
            /* TODO: This callback must be handled. It is triggered only after the SDK's internal recovery strategies fail. We recommend notifying the user of the device error. */
            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: This must be handled. The token is about to expire. Your app must get a new authentication token for the current channel and user, and then call refreshAuthInfo. */
        }

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

        // In the onRemoteUserOffLineNotify callback, unbind the renderer for the remote video stream.
        @Override
        public void onRemoteUserOffLineNotify(String uid, AliRtcEngine.AliRtcUserOfflineReason reason){
            super.onRemoteUserOffLineNotify(uid, reason);
        }

        // In the onRemoteTrackAvailableNotify callback, set up the renderer for the remote video stream.
        @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);
                            }
                        }
                    }
                }
            });
        }

        /* Your app must handle cases where multiple devices attempt to join with the same 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;
    }
}

Pour plus de détails sur le code d'exemple complet et son exécution, consultez Exécuter la démo ARTC pour Android.

1. Demander les permissions

Avant de lancer un appel vidéo, vérifiez que votre application dispose des permissions requises :

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. Obtenir un jeton d'authentification

Rejoindre un canal ARTC nécessite un jeton d'authentification. Pour savoir comment générer un jeton, consultez Authentification basée sur des jetons. La méthode de génération du jeton (à paramètre unique ou multi-paramètres) détermine quelle API joinChannel appeler.

Environnement de production :

La génération d'un jeton requiert votre AppKey. Coder en dur l'AppKey côté client constitue un risque de sécurité. Nous vous recommandons donc vivement de générer les jetons sur votre serveur d'application avant de les transmettre au client.

Développement et débogage :

Pendant la phase de développement, si votre serveur d'application ne peut pas encore générer de jetons, utilisez l'exemple suivant pour créer des jetons temporaires :

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

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

    /**
     * Generate a single-parameter meeting token based on channelId, userId, and 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;
    }

    /**
     * Generate a single-parameter meeting token based on channelId, userId, and 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) {
            // Consider logging the exception and/or re-throwing as a 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) {
                // Use single quote for char
                stringBuilder.append('0');
            }
            stringBuilder.append(hex);
        }
        return stringBuilder.toString();
    }

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

3. Importer les classes du SDK ARTC

Importez les classes et interfaces du SDK ARTC :

// Import ARTC classes
import com.alivc.rtc.AliRtcEngine;
import com.alivc.rtc.AliRtcEngineEventListener;
import com.alivc.rtc.AliRtcEngineNotify;

4. Créer et initialiser le moteur

  • Créer le moteur RTC

    Appelez la méthode getInstance[1/2] pour créer une instance AliRTCEngine.

    private AliRtcEngine mAliRtcEngine = null;
    if(mAliRtcEngine == null) {
        mAliRtcEngine = AliRtcEngine.getInstance(this);
    }
  • Initialiser le moteur

    • Appelez setChannelProfile pour définir le profil du canal sur AliRTCSdkInteractiveLive (mode interactif).

      Choisissez entre le mode interactif pour le divertissement et le mode communication pour les appels. La sélection du mode approprié garantit une expérience utilisateur fluide et une utilisation efficace du réseau.

      Mode

      Publication

      Abonnement

      Description

      Mode interactif

      1. Des restrictions basées sur les rôles s'appliquent. Seuls les utilisateurs ayant le rôle d'hôte peuvent publier des flux.

      2. Les participants peuvent changer de rôle à tout moment pendant la session.

      Aucune restriction de rôle. Tous les participants ont l'autorisation de s'abonner aux flux.

      1. En mode interactif, les événements tels que l'arrivée ou le départ d'un hôte sont envoyés aux spectateurs. L'activité d'un spectateur n'est pas transmise à l'hôte, ce qui assure une expérience ininterrompue pour ce dernier.

      2. Si vous prévoyez que les spectateurs puissent avoir besoin d'interagir ultérieurement, nous recommandons d'utiliser le mode interactif. Sa flexibilité permet d'ajuster les rôles des utilisateurs pour répondre à différents besoins d'interaction.

      Mode communication

      Aucune restriction de rôle. Tous les participants ont l'autorisation de publier des flux.

      Aucune restriction de rôle. Tous les participants ont l'autorisation de s'abonner aux flux.

      1. En mode communication, les participants sont informés de la présence des autres dans la session.

      2. Bien que ce mode ne différencie pas les rôles des utilisateurs, il est fonctionnellement équivalent au rôle d'hôte en mode interactif. Cela simplifie les opérations et permet aux utilisateurs d'obtenir les fonctionnalités souhaitées avec moins d'appels API.

    • Appelez setClientRole pour définir le rôle de l'utilisateur sur AliRTCSdkInteractive (hôte) ou AliRTCSdkLive (spectateur). Remarque : Le rôle d'hôte publie et s'abonne par défaut. Le rôle de spectateur s'abonne uniquement par défaut, tandis que la prévisualisation et la publication sont désactivées.

      Remarque

      Lorsqu'un utilisateur change de rôle dans un canal, le système ajuste automatiquement l'état de publication des flux audio et vidéo :

      • Passage d'hôte à spectateur : Le système arrête la publication des flux audio et vidéo locaux. Les flux distants abonnés ne sont pas affectés et l'utilisateur peut continuer à regarder les autres participants.

      • Passage de spectateur à hôte : Le système démarre la publication des flux audio et vidéo locaux. Les flux distants abonnés restent inchangés et l'utilisateur peut continuer à regarder les autres participants.

      // Set the channel profile to interactive mode. In RTC, always use AliRTCSdkInteractiveLive.
      mAliRtcEngine.setChannelProfile(AliRtcEngine.AliRTCSdkChannelProfile.AliRTCSdkInteractiveLive);
      // Set the user role. Use AliRTCSdkInteractive to both publish and subscribe to streams, or AliRTCSdkLive to only subscribe.
      mAliRtcEngine.setClientRole(AliRtcEngine.AliRTCSdkClientRole.AliRTCSdkInteractive);
  • Configurer les callbacks courants

    Lorsque le SDK rencontre un problème, il tente d'abord une récupération automatique. Pour les erreurs qu'il ne peut pas résoudre, le SDK notifie votre application via des callbacks.

    Voici les principaux callbacks pour les problèmes irrécupérables que votre application doit gérer :

    Cause de l'exception

    Callback et paramètres

    Solution

    Description

    Échec de l'authentification

    Le paramètre result du callback onJoinChannelResult est AliRtcErrJoinBadToken.

    Votre application doit vérifier si le token est correct.

    Si l'authentification échoue lorsqu'un utilisateur appelle une API, le callback renvoie une erreur d'échec d'authentification.

    Expiration imminente du token

    onAuthInfoWillExpire

    Obtenez un nouveau token et appelez refreshAuthInfo pour mettre à jour les informations d'authentification.

    Une erreur d'expiration du token peut survenir lors d'un appel API ou pendant l'exécution. L'erreur est signalée via un callback API ou un callback d'erreur distinct.

    Token expiré

    onAuthInfoExpired

    Votre application doit rejoindre le canal à nouveau.

    Une erreur d'expiration du token peut survenir lors d'un appel API ou pendant l'exécution. L'erreur est signalée via un callback API ou un callback d'erreur distinct.

    Problème de connexion réseau

    Le callback onConnectionStatusChange renvoie AliRtcConnectionStatusFailed.

    Votre application doit rejoindre le canal à nouveau.

    Le SDK récupère automatiquement après de brèves déconnexions réseau. Si la déconnexion dépasse un seuil prédéfini, un délai d'attente se produit et la connexion est interrompue. Votre application doit vérifier l'état du réseau et inviter l'utilisateur à rejoindre le canal.

    Exclusion du canal

    onBye

    • AliRtcOnByeUserReplaced : Vérifiez si un autre utilisateur a rejoint avec le même userId.

    • AliRtcOnByeBeKickedOut : L'utilisateur a été exclu du canal par le serveur d'application et doit le rejoindre à nouveau.

    • AliRtcOnByeChannelTerminated : Le canal a été terminé et l'utilisateur doit le rejoindre à nouveau.

    Le service RTC permet à un administrateur de retirer des participants.

    Exception de périphérique local

    onLocalDeviceException

    Votre application doit vérifier les permissions et le bon fonctionnement du matériel.

    Le service RTC prend en charge la détection et le diagnostic des périphériques. Lorsqu'une exception de périphérique local survient et que le SDK ne peut pas la résoudre, il notifie votre application via un callback. Votre application doit alors vérifier l'état du périphérique.

    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: This callback must be handled. It is triggered only after the SDK's internal recovery strategies fail. We recommend notifying the user. */
                        ToastHelper.showToast(VideoChatActivity.this, R.string.video_chat_connection_failed, Toast.LENGTH_SHORT);
                    } else {
                        /* TODO: Optional. Add business logic here, such as for data analytics or UI updates. */
                    }
                }
            });
        }
        @Override
        public void onLocalDeviceException(AliRtcEngine.AliRtcEngineLocalDeviceType deviceType, AliRtcEngine.AliRtcEngineLocalDeviceExceptionType exceptionType, String msg){
            super.onLocalDeviceException(deviceType, exceptionType, msg);
            /* TODO: This callback must be handled. It is triggered only after the SDK's internal recovery strategies fail. We recommend notifying the user of the device error. */
            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: This must be handled. The token is about to expire. Your app must get a new authentication token for the current channel and user, and then call refreshAuthInfo. */
        }
    
        @Override
        public void onRemoteUserOnLineNotify(String uid, int elapsed){
            super.onRemoteUserOnLineNotify(uid, elapsed);
        }
    
        // In the onRemoteUserOffLineNotify callback, unbind the renderer for the remote video stream.
        @Override
        public void onRemoteUserOffLineNotify(String uid, AliRtcEngine.AliRtcUserOfflineReason reason){
            super.onRemoteUserOffLineNotify(uid, reason);
        }
    
        // In the onRemoteTrackAvailableNotify callback, set up the renderer for the remote video stream.
        @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);
                            }
                        }
                    }
                }
            });
        }
    
        /* Your app must handle cases where multiple devices attempt to join with the same 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. Définir les propriétés audio et vidéo

  • Définir les propriétés audio

    Appelez setAudioProfile pour configurer le mode d'encodage audio et le scénario audio.

    mAliRtcEngine.setAudioProfile(AliRtcEngine.AliRtcAudioProfile.AliRtcEngineHighQualityMode, AliRtcEngine.AliRtcAudioScenario.AliRtcSceneMusicMode);
  • Définir les propriétés vidéo

    Configurez les propriétés du flux vidéo publié, telles que la résolution, le débit binaire et la fréquence d'images.

    // Set video encoding parameters.
    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. Configurer les propriétés de publication et d'abonnement

Configurez la publication des flux et le comportement d'abonnement par défaut :

  • Appelez publishLocalAudioStream pour publier le flux audio.

  • Appelez publishLocalVideoStream pour publier le flux vidéo. Pour un appel audio uniquement, définissez cette valeur sur false.

// The SDK publishes audio by default, so you do not need to call publishLocalAudioStream.
mAliRtcEngine.publishLocalAudioStream(true);
// For a video call, you do not need to call publishLocalVideoStream(true) because the SDK publishes video by default.
// For an audio-only call, you must call publishLocalVideoStream(false) to disable video publishing.
mAliRtcEngine.publishLocalVideoStream(true);

// Set the default to subscribe to remote audio and video streams.
mAliRtcEngine.setDefaultSubscribeAllRemoteAudioStreams(true);
mAliRtcEngine.subscribeAllRemoteAudioStreams(true);
mAliRtcEngine.setDefaultSubscribeAllRemoteVideoStreams(true);
mAliRtcEngine.subscribeAllRemoteVideoStreams(true);
Remarque

Par défaut, le SDK publie automatiquement les flux locaux et s'abonne aux flux distants. Vous pouvez appeler les méthodes ci-dessus pour désactiver ce comportement automatique.

7. Démarrer la prévisualisation locale

  • Appelez setLocalViewConfig pour configurer la vue de prévisualisation locale. Cela nécessite un objet AliRtcVideoCanvas.

  • Appelez la méthode startPreview pour lancer la prévisualisation vidéo locale.

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. Rejoindre le canal

Appelez joinChannel pour rejoindre un canal. Si le token est généré selon la règle à paramètre unique, appelez l'interface [joinChannel[1/3]](t2309760.xdita#758b964acc7jd) à paramètre unique du SDK. Si le token est généré selon la règle multi-paramètres, appelez l'interface [joinChannel[2/3]](t2309760.xdita#766e40a1cfk4p) multi-paramètres du SDK. Une fois le canal rejoint, le callback onJoinChannelResult fournit le résultat. Si result vaut 0, vous avez rejoint le canal avec succès. Sinon, vérifiez si le token fourni est invalide.

 mAliRtcEngine.joinChannel(token, null, null, null);
Remarque
  • Après avoir rejoint le canal, le SDK publie et s'abonne aux flux en fonction des paramètres définis avant la connexion.

  • Le SDK publie et s'abonne automatiquement aux flux par défaut afin de réduire le nombre d'appels API nécessaires.

9. Configurer la vue distante

Lors de l'initialisation du moteur, configurez le callback mAliRtcEngine.setRtcEngineNotify. Dans le callback onRemoteTrackAvailableNotify, configurez la vue distante pour chaque utilisateur distant :

@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. Quitter le canal et détruire le moteur

Lorsque la session audio et vidéo se termine, quittez le canal et détruisez le moteur :

  1. Appelez stopPreview pour arrêter la prévisualisation vidéo.

  2. Appelez leaveChannel pour quitter le canal.

  3. Appelez destroy pour détruire le moteur et libérer ses ressources.

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

11. Démonstration

image

Documents associés

Structures de données

Interface AliRtcEngine