Todos os produtos
Search
Central de documentação

ApsaraVideo Live:Audio and video calls on Android

Última atualização: Aug 19, 2026

Integre o ARTC SDK ao seu projeto Android para criar um aplicativo de áudio e vídeo em tempo real, voltado para transmissões ao vivo interativas e chamadas de vídeo.

Conceitos principais

Antes de começar, revise os seguintes conceitos fundamentais:

  • ARTC SDK: SDK da Alibaba Cloud para interações de áudio e vídeo em tempo real.

  • GRTN: Global Realtime Transport Network da Alibaba Cloud, que oferece services de comunicação de áudio e vídeo com latência ultrabaixa, alta qualidade, segurança e confiabilidade.

  • canal: Sala virtual destinada a interações de áudio e vídeo em tempo real.

  • host: Função que permite ao usuário publicar streams de áudio e vídeo em um canal e assinar streams publicados por outros hosts.

  • viewer: Função que habilita o usuário a assinar streams de áudio e vídeo em um canal, mas não a publicar streams.

image
  1. Chame setChannelProfile para definir o cenário do canal e, em seguida, chame joinChannel para entrar em um canal:

    • No cenário de chamada de vídeo, todos os usuários assumem a função de host e podem publicar e assinar streams.

    • No cenário de transmissão interativa, é necessário chamar setClientRole para configurar a função do usuário. Defina a função como host para quem vai publicar uma stream. Se o usuário precisar apenas assinar uma stream, defina sua função como viewer.

  2. Após entrar em um canal, a função do usuário determina se ele pode publicar ou assinar streams:

    • Todos os participantes de um canal podem assinar suas streams de áudio e vídeo.

    • Um host tem permissão para publicar streams de áudio e vídeo no canal.

    • Caso um viewer precise publicar uma stream, ele deve invocar o método setClientRole para alterar sua função para host.

Projeto de exemplo

A Alibaba Cloud disponibiliza um projeto de exemplo open-source do ARTC SDK para interação de áudio e vídeo em tempo real. Baixe o projeto ou visualize o código-fonte de exemplo.

Pré-requisitos

Antes de executar o projeto de exemplo, certifique-se de que seu ambiente de desenvolvimento atenda aos seguintes requisitos:

  • Ferramenta de desenvolvimento: Android Studio 2020.3.1 ou superior.

  • Dispositivo de teste: Um dispositivo físico rodando Android 5.0 (API level 21) ou posterior.

    Nota

    Utilize um dispositivo físico para testes. Emuladores podem não oferecer todas as funcionalidades necessárias.

  • Ambiente de rede: Uma conexão de rede estável.

  • Preparação do aplicativo: Obtenha o AppID e a AppKey do seu aplicativo. Para mais detalhes, consulte Create an application.

Crie um projeto

Esta seção explica como criar um projeto e adicionar as permissões necessárias para áudio e vídeo em tempo real. Caso já possua um projeto configurado, ignore esta etapa.

  1. Abra o Android Studio e selecione New Project.

  2. Selecione Phone and Tablet e escolha um modelo inicial. Este exemplo utiliza Empty Views Activity.

  3. Defina o nome do projeto, o nome do pacote, o local de salvamento, a linguagem de desenvolvimento (Java neste exemplo) e a linguagem de configuração de build (Groovy DSL neste exemplo).

  4. Clique em Finish e aguarde a sincronização do projeto.

Configure o projeto

Etapa 1: Importar o SDK

Integração automática via Maven (recomendado)

  1. Abra o arquivo settings.gradle no diretório raiz do seu projeto e adicione os repositórios Maven exigidos pelo ARTC SDK ao bloco dependencyResolutionManagement/repositories conforme abaixo:

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' }
    }
}

Nota: Se você estiver usando uma versão do Android Gradle Plugin anterior à 7.1.0, talvez não encontre esse bloco no arquivo settings.gradle. Para mais informações, consulte Android Gradle Plugin 7.1. Nesse caso, utilize a alternativa a seguir:

Alternativa para versões do Android Gradle Plugin anteriores à 7.1.0

Abra o arquivo build.gradle no diretório raiz do seu projeto e adicione as URLs dos repositórios Maven ao bloco 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. Abra o arquivo app/build.gradle e adicione a dependência do ARTC SDK ao bloco dependencies. As informações de versão estão disponíveis em SDK download and integration. Substitua ${latest_version} pelo número específico da versão. A versão mais recente é 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'
}

Se estiver utilizando o Android Gradle Plugin 8.1 ou superior, o Android Studio recomenda que você Migre as dependências do projeto para catálogos de versões.

Integração manual

  1. Baixe a versão desejada do arquivo AAR do ARTC SDK em SDK download and integration. A versão mais recente é 7.11.0, com um nome de arquivo semelhante a AliVCSDK_ARTC-x.y.z.aar.

  2. Copie o arquivo AAR baixado para o diretório do seu projeto, como app/libs. Crie essa pasta caso ela não exista.

  3. Abra o arquivo settings.gradle na raiz do projeto e adicione o diretório que contém o arquivo AAR em 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'
        }
    }
}

Nota: Se você estiver usando uma versão do Android Gradle Plugin anterior à 7.1.0, talvez não encontre esse bloco no arquivo settings.gradle. Para mais informações, consulte Android Gradle Plugin 7.1. Nesse caso, utilize a alternativa a seguir:

Abra o arquivo build.gradle no diretório raiz do seu projeto e adicione o seguinte ao bloco allprojects/repositories:

allprojects {
    repositories {
        ...
        // Add the relative path to the directory where the ARTC SDK is locatedflatDir {
            dir 'app/libs'
        }
    }
}
  1. Abra o arquivo app/build.gradle e adicione a dependência do arquivo AAR ao bloco dependencies:

// Replace x.y.z with the corresponding version number
implementation(name:'AliVCSDK_ARTC', version: 'x.y.z', ext:'aar')
  1. Após a conclusão do build, a dependência aparecerá na seção External Libraries.

    image

Etapa 2: Especifique arquiteturas de CPU suportadas

Abra o arquivo app/build.gradle e especifique as arquiteturas de CPU suportadas no bloco defaultConfig. As arquiteturas disponíveis incluem armeabi-v7a, arm64-v8a, x86, and x86_64. Selecione aquelas adequadas ao seu caso de uso.

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

Etapa 3: Defina permissões

Configure as permissões necessárias para o seu aplicativo.

Acesse o diretório app/src/main, abra o arquivo AndroidManifest.xml e adicione as permissões requeridas.

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

Permissões de Bluetooth em tempo de execução

O arquivo AndroidManifest.xml interno do ARTC SDK declara diversas permissões. Note que cabe a você solicitar quaisquer permissões dinâmicas necessárias durante a execução. A permissão BLUETOOTH_CONNECT, introduzida no Android 12, é um exemplo disso. Ao importar o ARTC SDK, as permissões declaradas nele são mescladas ao manifesto do seu aplicativo. Recomendamos declarar explicitamente todas as permissões necessárias no seu próprio manifesto para garantir clareza e aderência ao princípio do menor privilégio.

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

Dependendo do seu cenário de negócios, caso não precise de alguma das permissões listadas, declare sua remoção no arquivo AndroidManifest.xml do projeto principal:

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

Cenários de permissão de Bluetooth:

Cenário 1: Bluetooth necessário

O targetSdk do seu aplicativo é inferior a 31

Quando o targetSdk do projeto for menor que 31, a funcionalidade Bluetooth é controlada pela permissão legada BLUETOOTH. Declare o seguinte no manifesto do seu aplicativo:

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

Nota

  • O SDK inclui uma declaração para a permissão BLUETOOTH_CONNECT. Essa permissão foi introduzida no Android 12 (API level 31) e deve ser solicitada em tempo de execução através de requestPermissions.

  • Em alguns dispositivos, se um aplicativo incluir a declaração BLUETOOTH_CONNECT, o sistema exige uma solicitação em tempo de execução para evitar uma SecurityException. Caso enfrente esse problema, adote uma das abordagens abaixo:

    • Opção 1: Remova a declaração da permissão usando tools:node="remove".

    • Opção 2: Solicite a permissão em tempo de execução.

O targetSdk do seu aplicativo é 31 ou superior

Quando o targetSdk do projeto for 31 ou maior, você precisará gerenciar as permissões de Bluetooth para suportar tanto versões antigas quanto recentes do Android. Declare as permissões no arquivo AndroidManifest.xml do aplicativo principal:

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

Além disso, como BLUETOOTH_CONNECT é uma permissão dinâmica, é obrigatório solicitá-la em tempo de execução:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    String[] permissions = {
        android.Manifest.permission.BLUETOOTH_CONNECT
    };
    ActivityCompat.requestPermissions(activity, permissions, REQUEST_BLUETOOTH);
}
Cenário 2: Bluetooth não necessário

Para evitar falhas ou prompts de permissão relacionados ao Bluetooth, recomendamos remover as permissões desnecessárias.

No arquivo AndroidManifest.xml do projeto principal, use tools:node="remove" para substituir e remover a permissão:

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

Outras permissões de tempo de execução

No Android 6.0 (API level 23) e versões posteriores, é obrigatório solicitar permissões perigosas em tempo de execução. Além de declará-las no arquivo AndroidManifest.xml, você deve solicitá-las programaticamente durante a execução.

As seguintes permissões exigem solicitação em tempo de execução:

  • Manifest.permission.CAMERA

  • Manifest.permission.WRITE_EXTERNAL_STORAGE

  • Manifest.permission.RECORD_AUDIO

  • Manifest.permission.READ_EXTERNAL_STORAGE

  • Manifest.permission.READ_PHONE_STATE

No Android 12 (API level 31) ou superior, também é necessário solicitar a seguinte permissão em tempo de execução:

  • Manifest.permission.BLUETOOTH_CONNECT

A tabela a seguir descreve as principais permissões e suas finalidades:

Permissão

Descrição

Finalidade

Obrigatória

Permissão de runtime

CAMERA

Concede acesso à câmera.

Capturar vídeo para comunicação em tempo real.

Sim

Android >= 6.0

RECORD_AUDIO

Concede acesso ao microfone.

Capturar áudio para comunicação em tempo real.

Sim

Android >= 6.0

INTERNET

Concede acesso à internet.

Transmitir dados de áudio e vídeo pela rede.

Sim

Não

ACCESS_NETWORK_STATE

Permite obter o status da rede.

Monitorar conectividade e otimizar a qualidade da stream, gerenciando reconexões.

Opcional

Não

ACCESS_WIFI_STATE

Permite obter o status do Wi-Fi.

Obter informações sobre a conexão Wi-Fi atual para otimizar o desempenho da rede.

Opcional

Não

MODIFY_AUDIO_SETTINGS

Permite modificar configurações de áudio.

Ajustar volume do sistema ou alternar entre dispositivos de saída (ex: alto-falante, fone de ouvido).

Opcional

Não

BLUETOOTH

Permissão de Bluetooth (funcionalidade básica)

Conectar a dispositivos Bluetooth, como headsets.

Opcional

Não

BLUETOOTH_CONNECT

Permissão de conexão Bluetooth

Comunicar-se com dispositivos Bluetooth pareados, por exemplo, para transmitir streams de áudio.

Opcional

Android >= 12

READ_PHONE_STATE

Permite acessar informações relacionadas ao estado telefônico do dispositivo.

Gerenciar streams de áudio durante chamadas telefônicas, pausando a sessão quando uma chamada iniciar.

Opcional

Android >= 6.0

READ_EXTERNAL_STORAGE

Permite ler arquivos do armazenamento externo.

Habilitar recursos como reproduzir um arquivo de áudio local como efeito sonoro ou música de fundo.

Opcional

Android >= 6.0

WRITE_EXTERNAL_STORAGE

Permite gravar no armazenamento externo.

Salvar arquivos como logs do aplicativo ou mídias gravadas.

Opcional

Android >= 6.0

Etapa 4: Evitar ofuscação de código (opcional)

No arquivo app/proguard-rules.pro, adicione as regras do ProGuard abaixo para impedir que as interfaces públicas do SDK sejam ofuscadas.

-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 e permissões adicionais

Ao importar o ARTC SDK, as permissões do manifesto dele são automaticamente mescladas ao manifesto do seu aplicativo. A lista exata pode variar conforme a versão do SDK. Para obter a lista mais precisa, inspecione o arquivo AndroidManifest.xml dentro do arquivo .aar do 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" />

Essas permissões servem principalmente para suportar recursos como detecção de estado de rede, acesso a informações de Wi-Fi, conexões Bluetooth e roteamento de áudio.

Resolver conflitos de permissão

O ARTC SDK foi projetado para se adaptar a diferentes versões do Android. Por exemplo, ele considera a nova permissão de runtime BLUETOOTH_CONNECT no Android 12 (API level 31) e posteriores. Embora esse design geralmente evite erros de compilação, você ainda pode encontrar problemas como:

  • Comportamento inesperado em tempo de execução, como falhas na mesclagem do Manifest.

  • Risco de reprovação na Google Play: Se seu aplicativo declarar uma permissão para um recurso que não utiliza, ele poderá ser rejeitado por violar o princípio do menor privilégio.

Para mitigar esses possíveis problemas, considere as estratégias a seguir:

  1. Avalie os requisitos de permissão. Primeiro, determine se seu aplicativo realmente precisa de todas as permissões declaradas pelo SDK. Caso não utilize um recurso como Bluetooth, remova a declaração da permissão associada para seguir o princípio do menor privilégio. Por exemplo, se o targetSdk do seu app for menor que 31, a permissão BLUETOOTH_CONNECT não é necessária.

  2. Use diretivas de mesclagem do Manifest para resolver conflitos. Se uma permissão declarada pelo SDK entrar em conflito com uma declaração em outro módulo (por exemplo, um atributo maxSdkVersion diferente), utilize uma diretiva de namespace tools no AndroidManifest.xml principal do seu app para resolver o conflito:

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

Etapa 5: Crie a UI

Crie a interface do usuário para o seu cenário interativo em tempo real. O código de exemplo abaixo cria duas views para uma chamada de vídeo, exibindo o vídeo local e o remoto.

Exemplo de código

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

Procedimento

Esta seção explica como usar o ARTC SDK para construir uma aplicação básica de áudio e vídeo em tempo real. Copie o exemplo de código para testar rapidamente e siga as etapas para compreender as chamadas principais da api.

O diagrama a seguir ilustra o fluxo de trabalho básico de uma chamada de áudio e vídeo em tempo real:

image

Abaixo está um exemplo completo de código que demonstra o processo básico para implementar uma chamada de vídeo:

Exemplo de código do fluxo de trabalho básico

/**
 * 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;
    }
}

Para detalhes sobre o código de exemplo completo e como executá-lo, consulte Run the ARTC demo for Android.

1. Solicitar permissões

Antes de iniciar uma chamada de vídeo, verifique se o aplicativo possui as permissões necessárias:

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. Obter um token de autenticação

Entrar em um canal ARTC requer um token de autenticação. Para detalhes sobre como gerar um token, consulte Token-based authentication. O método de geração do token (parâmetro único ou múltiplos parâmetros) determina qual api joinChannel deve ser chamada.

Ambiente de produção:

A geração de um token exige sua AppKey. Codificar a AppKey diretamente no cliente representa um risco de segurança, portanto, recomendamos fortemente gerar tokens no seu Servidor de Aplicativos e enviá-los ao cliente.

Desenvolvimento e depuração:

Durante o desenvolvimento, caso seu Servidor de Aplicativos ainda não possa gerar tokens, utilize o exemplo abaixo para criar tokens temporários:

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. Importar classes do ARTC SDK

Importe as classes e interfaces do ARTC SDK:

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

4. Crie e inicializar o engine

  • Crie o engine RTC

    Chame o método getInstance[1/2] para criar uma instância de AliRTCEngine.

    private AliRtcEngine mAliRtcEngine = null;
    if(mAliRtcEngine == null) {
        mAliRtcEngine = AliRtcEngine.getInstance(this);
    }
  • Inicializar o engine

    • Chame setChannelProfile para definir o perfil do canal como AliRTCSdkInteractiveLive (modo interativo).

      Escolha entre o modo interativo para entretenimento e o modo de comunicação para chamadas. Selecionar o modo correto garante uma experiência de usuário fluida e uso eficiente da rede.

      Modo

      Publicação

      Assinatura

      Descrição

      Modo interativo

      1. Restrições baseadas em funções se aplicam. Apenas usuários com a função host podem publicar streams.

      2. Os participantes podem trocar de função a qualquer momento durante a sessão.

      Sem restrições de função. Todos os participantes têm permissão para assinar streams.

      1. No modo interativo, eventos como entrada ou saída de um host são enviados aos viewers. A atividade de um viewer não é enviada ao host, garantindo uma experiência ininterrupta para ele.

      2. Se você prevê que os viewers possam precisar interagir no futuro, recomendamos o uso do modo interativo. Sua flexibilidade permite ajustar as funções dos usuários para atender a diferentes necessidades de interação.

      Modo de comunicação

      Sem restrições de função. Todos os participantes têm permissão para publicar streams.

      Sem restrições de função. Todos os participantes têm permissão para assinar streams.

      1. No modo de comunicação, os participantes têm ciência da presença uns dos outros na sessão.

      2. Embora este modo não diferencie funções de usuário, ele é funcionalmente equivalente à função host no modo interativo. Isso simplifica as operações, permitindo que os usuários obtenham a funcionalidade desejada com menos chamadas de api.

    • Chame setClientRole para definir a função do usuário como AliRTCSdkInteractive (host) ou AliRTCSdkLive (viewer). Nota: A função host publica e assina por padrão. A função viewer apenas assina por padrão, com visualização prévia e publicação desativadas.

      Nota

      Quando um usuário troca de função em um canal, o sistema ajusta o status de publicação das streams de áudio e vídeo adequadamente:

      • Trocar de host para viewer: O sistema interrompe a publicação das streams locais de áudio e vídeo. As streams remotas assinadas não são afetadas, e o usuário pode continuar assistindo aos outros participantes.

      • Trocar de viewer para host: O sistema inicia a publicação das streams locais de áudio e vídeo. As streams remotas assinadas permanecem inalteradas, e o usuário pode continuar assistindo aos outros participantes.

      // 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);
  • Configure callbacks comuns

    Quando o SDK encontra um problema, ele tenta primeiro uma recuperação automática. Para erros que não consegue resolver, o SDK notifica seu aplicativo por meio de callbacks.

    Abaixo estão os principais callbacks para problemas irrecuperáveis que sua aplicação deve tratar:

    Causa da exceção

    Callback e parâmetros

    Solução

    Descrição

    Falha na autenticação

    O parâmetro result do callback onJoinChannelResult é AliRtcErrJoinBadToken.

    Seu aplicativo deve verificar se o token está correto.

    Se a autenticação falhar quando um usuário chamar uma api, o callback retornará um erro de falha de autenticação.

    Token prestes a expirar

    onAuthInfoWillExpire

    Obtenha um novo token e chame refreshAuthInfo para atualizar as informações de autenticação.

    Um erro de expiração de token pode ocorrer tanto ao chamar uma api quanto durante a execução. O erro é reportado através de um callback de api ou de um callback de erro separado.

    Token expirado

    onAuthInfoExpired

    Seu aplicativo deve entrar novamente no canal.

    Um erro de expiração de token pode ocorrer tanto ao chamar uma api quanto durante a execução. O erro é reportado através de um callback de api ou de um callback de erro separado.

    Problema de conexão de rede

    O callback onConnectionStatusChange retorna AliRtcConnectionStatusFailed.

    Seu aplicativo deve entrar novamente no canal.

    O SDK recupera-se automaticamente de breves desconexões de rede. Se a desconexão exceder um limite predefinido, ocorre um timeout e a conexão é encerrada. Seu aplicativo deve verificar o status da rede e solicitar que o usuário entre novamente no canal.

    Removido do canal

    onBye

    • AliRtcOnByeUserReplaced: Verifique se outro usuário entrou com o mesmo userId.

    • AliRtcOnByeBeKickedOut: O usuário foi removido do canal pelo Servidor de Aplicativos e deve entrar novamente.

    • AliRtcOnByeChannelTerminated: O canal foi encerrado e o usuário deve entrar novamente.

    O service RTC permite que um administrador remova participantes.

    Exceção de dispositivo local

    onLocalDeviceException

    Seu aplicativo deve verificar as permissões e se o hardware está funcionando corretamente.

    O service RTC suporta detecção e diagnóstico de dispositivos. Quando ocorre uma exceção de dispositivo local que o SDK não consegue resolver, ele notifica seu aplicativo através de um callback. Seu aplicativo deve então verificar o status do dispositivo.

    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. Defina propriedades de áudio e vídeo

  • Defina propriedades de áudio

    Chame setAudioProfile para configurar o modo de codificação de áudio e o cenário de áudio.

    mAliRtcEngine.setAudioProfile(AliRtcEngine.AliRtcAudioProfile.AliRtcEngineHighQualityMode, AliRtcEngine.AliRtcAudioScenario.AliRtcSceneMusicMode);
  • Defina propriedades de vídeo

    Configure as propriedades da stream de vídeo publicada, como resolução, bitrate e taxa de quadros.

    // 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. Configure propriedades de publicação e assinatura

Configure a publicação de streams e o comportamento padrão de assinatura:

  • Chame publishLocalAudioStream para publicar a stream de áudio.

  • Chame publishLocalVideoStream para publicar a stream de vídeo. Para uma chamada apenas de áudio, defina isso como 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);
Nota

Por padrão, o SDK publica automaticamente streams locais e assina streams remotas. Chame os métodos acima para desativar esse comportamento automático.

7. Iniciar a pré-visualização local

  • Chame setLocalViewConfig para configurar a view de pré-visualização local. Isso requer um objeto AliRtcVideoCanvas.

  • Chame o método startPreview para iniciar a pré-visualização do vídeo local.

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. Entrar no canal

Chame joinChannel para entrar em um canal. Se o token for gerado com base na regra de parâmetro único, chame a interface de parâmetro único [joinChannel[1/3]](t2309760.xdita#758b964acc7jd) do SDK. Se o token for gerado com base na regra de múltiplos parâmetros, chame a interface de múltiplos parâmetros [joinChannel[2/3]](t2309760.xdita#766e40a1cfk4p) do SDK. Após entrar no canal, o callback onJoinChannelResult fornece o resultado. Se o result for 0, você entrou no canal com sucesso. Caso contrário, verifique se o token fornecido é inválido.

 mAliRtcEngine.joinChannel(token, null, null, null);
Nota
  • Após entrar no canal, o SDK publica e assina streams com base nos parâmetros definidos antes da entrada.

  • O SDK publica e assina streams automaticamente por padrão para reduzir o número de chamadas de api necessárias.

9. Configure a view remota

Durante a inicialização do engine, configure o callback mAliRtcEngine.setRtcEngineNotify. No callback onRemoteTrackAvailableNotify, configure a view remota para cada usuário remoto:

@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. Sair do canal e destruir o engine

Quando a sessão de áudio e vídeo terminar, saia do canal e destrua o engine:

  1. Chame stopPreview para parar a pré-visualização de vídeo.

  2. Chame leaveChannel para sair do canal.

  3. Chame destroy para destruir o engine e liberar seus recursos.

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

11. Demonstração

image

Documentos relacionados

Data structures

AliRtcEngine interface