Todos os produtos
Search
Central de documentação

ApsaraVideo Live:Implementar comunicação de áudio e vídeo no Android

Última atualização: Jul 03, 2026

Este guia demonstra como integrar o ARTC SDK ao seu projeto Android para criar um aplicativo de áudio e vídeo em tempo real, ideal para casos de uso como transmissões ao vivo interativas e chamadas de vídeo.

Descrição dos recursos

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

  • ARTC SDK: SDK fornecido pela Alibaba Cloud que auxilia desenvolvedores a implementar rapidamente interações de áudio e vídeo em tempo real.

  • Global Realtime Transport Network (GRTN): Rede distribuída globalmente, projetada para mídia em tempo real, que garante comunicação com latência ultrabaixa, alta qualidade e segurança.

  • Canal: Sala virtual onde os usuários entram para se comunicar. Todos os participantes do mesmo canal podem interagir em tempo real.

  • Host: Usuário com permissão para publicar fluxos de áudio e vídeo em um canal e assinar fluxos publicados por outros hosts.

  • Viewer: Usuário que pode assinar fluxos de áudio e vídeo em um canal, mas não tem permissão para publicar seus próprios fluxos.

Processo básico para implementar interação de áudio e vídeo em tempo real:

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

    • Cenário de chamada de vídeo: Todos os usuários atuam como hosts, podendo publicar e assinar fluxos.

    • Cenário de transmissão interativa: Defina as funções com setClientRole antes de entrar no canal. Para usuários que publicarão fluxos, defina a função como host. Caso o usuário precise apenas assinar fluxos, defina a função como viewer.

  2. Após entrar no canal, os comportamentos de publicação e assinatura variam conforme a função do usuário:

    • Todos os participantes recebem fluxos de áudio e vídeo dentro desse canal.

    • O host publica fluxos de áudio e vídeo no canal.

    • Se um viewer desejar publicar fluxos, chame o método setClientRole para alterar sua função para host.

Projeto de exemplo

O ARTC SDK oferece um projeto de exemplo open-source para aplicativos de áudio e vídeo em tempo real.

Requisitos de ambiente

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: Dispositivo rodando Android 5.0 (SDK API Level 21) ou superior.

    Nota

    Recomendamos o uso de um dispositivo físico para testes, pois alguns emuladores podem não oferecer toda a funcionalidade necessária.

  • Rede: Conexão estável com a internet.

  • Aplicativo: Obtenha o AppID e a AppKey do seu aplicativo ARTC. Para mais detalhes, consulte Criar um aplicativo ARTC.

Criar um projeto (opcional)

Esta seção explica como criar um projeto e adicionar as permissões necessárias para interação de áudio e vídeo. Se você já possui um projeto, pule 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.

image.png

  1. Configure as informações do projeto, incluindo nome, nome do pacote, local de salvamento, linguagem (Java neste exemplo) e linguagem de configuração de build (Groovy DSL neste exemplo).

image.png

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

Configurar o projeto

Etapa 5: Criar uma interface de usuário

Crie uma interface de usuário adequada ao seu cenário. O código de exemplo abaixo, destinado a chamadas de vídeo, cria duas visualizações para exibir os fluxos de vídeo local e remoto. Utilize-o como referência durante o desenvolvimento.

Exemplo de código de interface de usuário

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

Implementação

Esta seção detalha o uso do ARTC SDK para construir um aplicativo básico de áudio e vídeo em tempo real. Copie o exemplo de código completo para o seu projeto e teste a funcionalidade. As etapas a seguir explicam as chamadas principais da API.

O diagrama abaixo ilustra o fluxo de trabalho básico para implementar uma chamada de vídeo:

image

Exemplo de código para o cenário de chamada de vídeo

Exemplo de código

/**
 * 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) {
            // Action to take when the back button is clicked
            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, always use AliRTCSdkInteractiveLive.
        mAliRtcEngine.setChannelProfile(AliRtcEngine.AliRTCSdkChannelProfile.AliRTCSdkInteractiveLive);
        // Set the user role. To both publish and subscribe, use AliRTCSdkInteractive. To only subscribe, use AliRTCSdkLive.
        mAliRtcEngine.setClientRole(AliRtcEngine.AliRTCSdkClientRole.AliRTCSdkInteractive);
        // et 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 don't need to call publishLocalAudioStream.
        mAliRtcEngine.publishLocalAudioStream(true);
        // For video calls, you don't need to call publishLocalVideoStream(true) as the SDK publishes video by default.
        // For audio-only calls, you need to call publishLocalVideoStream(false) to disable video publishing.
        mAliRtcEngine.publishLocalVideoStream(true);

        // Set default subscription 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: Must handle. We recommend notifying the user. This is reported only after the SDK's internal recovery strategies have failed. */
                        ToastHelper.showToast(VideoCallActivity.this, R.string.video_chat_connection_failed, Toast.LENGTH_SHORT);
                    } else {
                        /* TODO: Optional. Add business logic here, typically for data analytics or UI changes. */
                    }
                }
            });
        }
        @Override
        public void OnLocalDeviceException(AliRtcEngine.AliRtcEngineLocalDeviceType deviceType, AliRtcEngine.AliRtcEngineLocalDeviceExceptionType exceptionType, String msg){
            super.OnLocalDeviceException(deviceType, exceptionType, msg);
            /* TODO: Must handle. We recommend notifying the user of the device error. This is reported only after the SDK's internal recovery strategies have failed. */
            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: Must handle. When this callback is triggered, retrieve a new token for the current user and channel, then call refreshAuthInfo to update it. */
        }

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

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

        // Set the remote video stream renderer in the onRemoteTrackAvailableNotify callback.
        @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 also 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 obter detalhes sobre o código de exemplo completo, consulte Executar o projeto de demonstração do ARTC para Android.

1. Solicitar permissões

Ao iniciar uma chamada de vídeo, verifique se as permissões necessárias foram concedidas ao aplicativo:

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 exige um token de autenticação para verificar a identidade do usuário. Para detalhes sobre a geração do token, consulte Implementar autenticação baseada em token. A geração pode ocorrer via método de parâmetro único ou multiparâmetro. O método escolhido determina qual API joinChannel deve ser chamada.

Para ambientes de produção:

Como a geração do token requer sua AppKey, codificá-la diretamente no cliente representa um risco de segurança. Em produção, recomendamos fortemente gerar o token no servidor e enviá-lo ao cliente.

Para desenvolvimento e depuração:

Durante o desenvolvimento, caso seu servidor de negócios ainda não possua a lógica de geração de tokens, utilize temporariamente a lógica do APIExample para criar um token provisório. O código de referência é o seguinte:

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

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

    /**
     * Generate a single-parameter token for joining a meeting based on channelId, userId, timestamp, 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 token for joining a meeting 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 relevantes do ARTC SDK:

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

4. Criar e inicializar o mecanismo

  • Criar o mecanismo 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 mecanismo

    • Chame setChannelProfile para configurar o canal como AliRTCSdkInteractiveLive (modo interativo).

      Conforme suas necessidades de negócio, escolha o modo interativo (adequado para cenários de entretenimento interativo) ou o modo de comunicação (ideal para transmissões um-para-um ou um-para-muitos). Selecionar o modo correto garante uma experiência fluida e uso eficiente dos recursos de rede.

      Modo

      Publicação

      Assinatura

      Descrição

      Modo interativo

      • Limitado por função. Apenas usuários com a função host podem publicar fluxos.

      • Os participantes podem alternar funções flexivelmente durante a sessão.

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

      • No modo interativo, eventos como entrada ou saída de um host do canal, ou início de publicação de fluxo, são notificados aos viewers em tempo real. Por outro lado, as atividades dos viewers não são notificadas aos hosts, garantindo uma transmissão ininterrupta.

      • Neste modo, os hosts são responsáveis pela interação, enquanto os viewers apenas consomem conteúdo. Se suas necessidades de negócio puderem mudar, considere usar o modo interativo por padrão. Sua flexibilidade permite adaptação a diferentes requisitos de interação através do ajuste das funções dos usuários.

      Modo de comunicação

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

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

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

      • Embora este modo não diferencie funções de usuário, ele equivale funcionalmente à função host no modo interativo. O objetivo é simplificar 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 pré-visualização e publicação desativadas.

      Nota

      Quando um usuário altera sua função dentro de um canal, o sistema ajusta o status de publicação de áudio e vídeo correspondentemente:

      • Alteração de host para viewer: O sistema interrompe a publicação dos fluxos locais de áudio e vídeo. Os fluxos remotos assinados não são afetados, e o usuário continua assistindo aos demais participantes.

      • Alteração de viewer para host: O sistema inicia a publicação dos fluxos locais de áudio e vídeo. Os fluxos remotos assinados permanecem inalterados, e o usuário continua assistindo aos outros participantes.

      // Set the channel mode to interactive mode, use AliRTCSdkInteractiveLive for RTC
      mAliRtcEngine.setChannelProfile(AliRtcEngine.AliRTCSdkChannelProfile.AliRTCSdkInteractiveLive);
      // Set the user role, use AliRTCSdkInteractive to both ingest and pull streams, use AliRTCSdkLive if you only pull streams without ingesting
      mAliRtcEngine.setClientRole(AliRtcEngine.AliRTCSdkClientRole.AliRTCSdkInteractive);
  • Definir callbacks comuns

    Se o SDK encontrar um problema durante a operação, ele tentará primeiro se recuperar automaticamente usando seus mecanismos internos de nova tentativa. Para erros que não conseguir resolver sozinho, o SDK notificará seu aplicativo através de interfaces de callback predefinidas.

    Abaixo estão os principais callbacks para problemas que o SDK não consegue tratar, os quais seu aplicativo deve monitorar e responder:

    Causa da exceção

    Callback e parâmetros

    Solução

    Descrição

    Falha na autenticação

    result no callback onJoinChannelResult retorna AliRtcErrJoinBadToken.

    O aplicativo deve verificar se o token está correto.

    Quando um usuário chama uma API e a autenticação falha, o callback da API 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.

    Um erro de expiração de token pode ocorrer tanto na chamada de uma API quanto durante a execução. O erro é reportado via callbacks da API ou um callback de erro separado.

    Token expirado

    onAuthInfoExpired

    O aplicativo deve entrar novamente no canal.

    Um erro de expiração de token pode ocorrer tanto na chamada de uma API quanto durante a execução. O erro é reportado via callbacks da API ou um callback de erro separado.

    Problema de conexão de rede

    O callback onConnectionStatusChange retorna AliRtcConnectionStatusFailed.

    O aplicativo deve entrar novamente no canal.

    O SDK consegue se recuperar automaticamente de breves desconexões de rede. Se o tempo de desconexão exceder um limiar, ocorrerá timeout. O aplicativo deve verificar o status da rede e orientar o usuário a reconectar.

    Removido do canal

    onBye

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

    • AliRtcOnByeBeKickedOut: O usuário foi removido do canal e precisa entrar novamente.

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

    O serviço RTC permite que um administrador remova participantes.

    Exceção de dispositivo local

    onLocalDeviceException

    Verifique as permissões do aplicativo e se o hardware está funcionando corretamente.

    Quando ocorre uma exceção de dispositivo local que o SDK não consegue resolver, ele notifica o aplicativo via callback. O aplicativo deve então intervir para 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: Must handle. We recommend notifying the user. This is reported only after the SDK's internal recovery strategies have failed. */
                        ToastHelper.showToast(VideoChatActivity.this, R.string.video_chat_connection_failed, Toast.LENGTH_SHORT);
                    } else {
                        /* TODO: Optional. Add business logic here, typically for data analytics or UI changes. */
                    }
                }
            });
        }
        @Override
        public void OnLocalDeviceException(AliRtcEngine.AliRtcEngineLocalDeviceType deviceType, AliRtcEngine.AliRtcEngineLocalDeviceExceptionType exceptionType, String msg){
            super.OnLocalDeviceException(deviceType, exceptionType, msg);
            /* TODO: Must handle. We recommend notifying the user of the device error. This is reported only after the SDK's internal recovery strategies have failed. */
            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: Must handle. When this callback is triggered, retrieve a new token for the current user and channel, then call refreshAuthInfo to update it. */
        }
    
        @Override
        public void onRemoteUserOnLineNotify(String uid, int elapsed){
            super.onRemoteUserOnLineNotify(uid, elapsed);
        }
    
        // Unset the remote video stream renderer in the onRemoteUserOffLineNotify callback.
        @Override
        public void onRemoteUserOffLineNotify(String uid, AliRtcEngine.AliRtcUserOfflineReason reason){
            super.onRemoteUserOffLineNotify(uid, reason);
        }
    
        // Set the remote video stream renderer in the onRemoteTrackAvailableNotify callback.
        @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 also 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. Definir propriedades de áudio e vídeo

  • Definir propriedades de áudio

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

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

    Configure as propriedades do fluxo de vídeo publicado, como resolução, taxa de bits 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. Definir propriedades de publicação e assinatura

Configure a publicação de fluxos de áudio/vídeo e defina a assinatura padrão para os fluxos de todos os usuários:

  • Chame publishLocalAudioStream para publicar um fluxo de áudio.

  • Chame publishLocalVideoStream para publicar um fluxo de vídeo. Para chamadas apenas de áudio, defina este valor como false.

// The SDK publishes audio by default, so you don't need to call publishLocalAudioStream.
mAliRtcEngine.publishLocalAudioStream(true);
// For video calls, you don't need to call publishLocalVideoStream(true) as the SDK publishes video by default.
// For audio-only calls, you need to call publishLocalVideoStream(false) to disable video publishing.
mAliRtcEngine.publishLocalVideoStream(true);

// Set default subscription to all 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 os fluxos locais de áudio e vídeo e assina os fluxos de áudio e vídeo de todos os outros usuários no canal. Chame os métodos acima para substituir esse comportamento padrão.

7. Ativar pré-visualização local

  • Chame setLocalViewConfig para configurar a visualização de renderização local e definir as propriedades de exibição do vídeo local.

  • Chame 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 em um canal

Chame joinChannel para entrar no canal. Se o token foi gerado pelo método de parâmetro único, chame a operação [joinChannel[1/3]](t2309760.xdita#758b964acc7jd). Se foi gerado pelo método multiparâmetro, chame o método [joinChannel[2/3]](t2309760.xdita#766e40a1cfk4p). O resultado é retornado no callback onJoinChannelResult. Um resultado 0 indica entrada bem-sucedida. Um resultado diferente de zero pode indicar um token inválido.

 mAliRtcEngine.joinChannel(token, null, null, null);
Nota
  • Após entrar no canal, o SDK publicará e assinará fluxos de acordo com os parâmetros definidos anteriormente.

  • O SDK publica e assina automaticamente por padrão para reduzir o número de chamadas de API que o cliente precisa fazer.

9. Definir a visualização remota

Ao inicializar o mecanismo, configure o callback correspondente com mAliRtcEngine.setRtcEngineNotify. No callback onRemoteTrackAvailableNotify, configure a visualização para o usuário remoto. Código de exemplo:

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

Ao finalizar a sessão, saia do canal e destrua o mecanismo:

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

  2. Chame leaveChannel para sair do canal.

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

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

11. Demonstração do efeito

image

Referências

Estruturas de dados

Classe AliRtcEngine