Audio and video calls on Android

Updated at:

Learn how to integrate the ARTC SDK into your Android project to quickly build a simple interactive app that provides real-time audio and video for use cases like interactive live streaming and video calls.

Key concepts

Before you begin, it helps to understand the following key concepts:

  • ARTC SDK: Alibaba Cloud's SDK for quickly implementing real-time audio and video interactions.

  • GRTN: Alibaba Cloud's Global Realtime Transport Network, providing ultra-low latency, high-quality, secure, and reliable audio and video communication services.

  • channel: A virtual room for real-time audio and video interactions.

  • host: A role that allows a user to publish audio and video streams in a channel and subscribe to streams published by other hosts.

  • viewer: A role that enables a user to subscribe to audio and video streams in a channel but not publish streams.

image
  1. Call setChannelProfile to set the channel scenario, and then call joinChannel to join a channel:

    • In a video call scenario, all users have the host role and can publish and subscribe to streams.

    • In an interactive streaming scenario, you must call setClientRole to set the user role. Set the role to host for users who will publish a stream. If a user only needs to subscribe to a stream, set their role to viewer.

  2. After joining a channel, a user's role determines whether they can publish or subscribe to streams:

    • All users in a channel can subscribe to its audio and video streams.

    • A host can publish audio and video streams in the channel.

    • If a viewer needs to publish a stream, they must call the setClientRole method to switch their role to host.

Sample project

The Alibaba Cloud ARTC SDK provides an open-source sample project for real-time audio and video interaction. You can download the project or view the sample source code.

Prerequisites

Before running the sample project, ensure your development environment meets the following requirements:

  • Development tool: Android Studio 2020.3.1 or later.

  • Test device: A test device running Android 5.0 (API level 21) or later.

    Note

    Use a physical device for testing. Emulators may lack required functionality.

  • Network environment: A stable network connection.

  • Application preparation: Obtain the AppID and AppKey for your application. For details, see Create an application.

Create a project

This section explains how to create a project and add the necessary permissions for real-time audio and video. Skip this section if you already have a project.

  1. Open Android Studio and select New Project.

  2. Select Phone and Tablet, then choose a starter template. This example uses Empty Views Activity.

  1. Set the project name, package name, save location, development language (Java in this example), and build configuration language (Groovy DSL in this example).

  1. Click Finish and wait for the project to sync.

Configure the project

Step 1: Import the SDK

Maven automatic integration (recommended)

  1. Open the settings.gradle file in your project's root directory and add the Maven repositories required by the ARTC SDK to the dependencyResolutionManagement/repositories block as follows:

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

Note: If you are using a version of the Android Gradle Plugin earlier than 7.1.0, you may not find this block in the settings.gradle file. For more information, see Android Gradle Plugin 7.1. In this case, use the following alternative:

Alternative for Android Gradle Plugin versions earlier than 7.1.0

Open the build.gradle file in your project's root directory and add the Maven repository URLs to the allprojects/repositories block:

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. Open the app/build.gradle file and add the ARTC SDK dependency to the dependencies block. You can find the version information in SDK Download. Replace ${latest_version} with a specific version number. The latest version is 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'
}

If you are using Android Gradle Plugin 8.1 or later, Android Studio recommends that you Migrate project dependencies to version catalogs.

Manual integration

  1. Download the required version of the ARTC SDK AAR file from SDK Download. The latest version is 7.11.0, with a filename similar to AliVCSDK_ARTC-x.y.z.aar.

  2. Copy the downloaded AAR file into your project directory, such as app/libs. If this folder does not exist, create it.

  3. Open the settings.gradle file in your project's root directory and add the directory containing the AAR file to 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 located
        flatDir {
            dir 'app/libs'
        }
    }
}

Note: If you are using a version of the Android Gradle Plugin earlier than 7.1.0, you may not find this block in the settings.gradle file. For more information, see Android Gradle Plugin 7.1. In this case, use the following alternative:

Open the build.gradle file in your project's root directory and add the following to the allprojects/repositories block:

allprojects {
    repositories {
        ...
        // Add the relative path to the directory where the ARTC SDK is located
        flatDir {
            dir 'app/libs'
        }
    }
}
  1. Open the app/build.gradle file and add the AAR file dependency to the dependencies block:

// Replace x.y.z with the corresponding version number
implementation(name:'AliVCSDK_ARTC', version: 'x.y.z', ext:'aar')
  1. After the build completes, the dependency appears in the External Libraries section.

    image

Step 2: Specify supported CPU architectures

Open the app/build.gradle file and specify the supported CPU architectures in the defaultConfig block. Available architectures include armeabi-v7a, arm64-v8a, x86, and x86_64. Select the architectures you require.

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

Step 3: Set permissions

Set the permissions your app needs.

Go to the app/src/main directory, open the AndroidManifest.xml file, and add the required permissions.

<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-permission
  android:name="android.permission.BLUETOOTH"
  android:maxSdkVersion="30" />
<uses-permission
  android: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" />

Runtime Bluetooth permissions

The ARTC SDK's internal AndroidManifest.xml file declares several permissions. Note that you are responsible for requesting any required dynamic permissions at runtime. The BLUETOOTH_CONNECT permission, introduced in Android 12, is one such permission. After you import the ARTC SDK, its declared permissions are merged into your app's manifest. We recommend explicitly declaring all required permissions in your own manifest for clarity and to adhere to the principle of least privilege.

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

Based on your business scenario, if you do not need one of the following permissions, you can declare its removal in your main project's AndroidManifest.xml file:

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

Bluetooth permission scenarios:

Scenario 1: Bluetooth required

Your app's targetSdk is less than 31

When your project's targetSdk is less than 31, Bluetooth functionality is controlled by the legacy BLUETOOTH permission. Declare the following in your app's manifest:

<!-- 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" />
Note
  • The SDK includes a declaration for the BLUETOOTH_CONNECT permission. This permission was introduced in Android 12 (API level 31) and must be requested at runtime by calling requestPermissions.

  • On some devices, if an app includes the BLUETOOTH_CONNECT declaration, the system requires a runtime request for this permission to avoid a SecurityException. If you encounter this issue, use one of the following approaches:

    • Option 1: Remove the permission declaration by using tools:node="remove".

    • Option 2: Request the permission at runtime.

Your app's targetSdk is 31 or greater

When your project's targetSdk is 31 or greater, you must handle Bluetooth permissions to support both older and newer Android versions. Declare the permissions in your main app's AndroidManifest.xml file:

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

Additionally, because BLUETOOTH_CONNECT is a dynamic permission, you must request it at runtime:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    String[] permissions = {
        android.Manifest.permission.BLUETOOTH_CONNECT
    };
    ActivityCompat.requestPermissions(activity, permissions, REQUEST_BLUETOOTH);
}
Scenario 2: Bluetooth not required

To avoid crashes or permission prompts related to Bluetooth, we recommend removing unnecessary Bluetooth permissions.

In the AndroidManifest.xml file of the main project, use tools:node="remove" to override and remove the permission:

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

Other runtime permissions

On Android 6.0 (API level 23) and later, you must request dangerous permissions at runtime. In addition to declaring them in the AndroidManifest.xml file, you must request them in your code at runtime.

The following permissions require runtime requests:

  • Manifest.permission.CAMERA

  • Manifest.permission.WRITE_EXTERNAL_STORAGE

  • Manifest.permission.RECORD_AUDIO

  • Manifest.permission.READ_EXTERNAL_STORAGE

  • Manifest.permission.READ_PHONE_STATE

On Android 12 (API level 31) or later, you must also request the following permission at runtime:

  • Manifest.permission.BLUETOOTH_CONNECT

The following table describes key permissions and their purpose:

Permission

Description

Purpose

Required

Runtime permission

CAMERA

Grants access to the camera.

To capture video for real-time communication.

Yes

Android >= 6.0

RECORD_AUDIO

Grants access to the microphone.

To capture audio for real-time communication.

Yes

Android >= 6.0

INTERNET

Grants access to the internet.

To transmit audio and video data over the network.

Yes

No

ACCESS_NETWORK_STATE

Allows the app to get the network status.

To monitor network connectivity and optimize streaming quality, for example, by managing reconnections.

Optional

No

ACCESS_WIFI_STATE

Allows the app to get the Wi-Fi status.

Gets information about the current Wi-Fi connection to optimize network performance.

Optional

No

MODIFY_AUDIO_SETTINGS

Allows the app to modify audio settings.

To adjust system volume or switch between audio output devices (e.g., speakerphone, headset).

Optional

No

BLUETOOTH

Bluetooth permission (basic functionality)

Connects to Bluetooth devices, such as Bluetooth headsets.

Optional

No

BLUETOOTH_CONNECT

Bluetooth connection permission

Communicates with paired Bluetooth devices, such as for transmitting audio streams.

Optional

Android >= 12

READ_PHONE_STATE

Allows the app to access information related to the device's phone state.

To manage audio streams during phone calls, for example, by pausing a session when a call starts.

Optional

Android >= 6.0

READ_EXTERNAL_STORAGE

Allows the app to read files from external storage.

To enable features like playing a local audio file as a sound effect or background music.

Optional

Android >= 6.0

WRITE_EXTERNAL_STORAGE

Allows the app to write to external storage.

To save files such as application logs or recorded media.

Optional

Android >= 6.0

Step 4: Prevent code obfuscation (optional)

In the app/proguard-rules.pro file, add the following ProGuard rules to prevent the SDK's public interfaces from being obfuscated.

-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 and additional permissions

After you import the ARTC SDK, the permissions from its manifest are automatically merged into your app's manifest. The exact list can vary by SDK version. For the most accurate list, inspect the AndroidManifest.xml file inside the SDK's .aar file.

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

These permissions are primarily used to support features like network state detection, Wi-Fi information access, Bluetooth connections, and audio routing.

Handle permission conflicts

The ARTC SDK is designed to adapt to different Android versions. For example, it accounts for the new BLUETOOTH_CONNECT runtime permission on Android 12 (API level 31) and later. While this design generally prevents compilation errors, you may still encounter issues such as:

  • Unexpected runtime behavior, such as Manifest merging failures.

  • Google Play review risk: If your app declares a permission for a feature it does not use, it may be rejected for violating the principle of least privilege.

To mitigate these potential issues, consider the following strategies:

  1. Evaluate permission requirements
    First, determine if your app requires all the permissions that the SDK declares. If your app does not use a feature like Bluetooth, remove its associated permission declaration to follow the principle of least privilege. For example, if your app's targetSdk is less than 31, it does not need the BLUETOOTH_CONNECT permission.



  2. Use Manifest merging directives to resolve conflicts
    If a permission declared by the SDK conflicts with a declaration in another module (for example, a different maxSdkVersion attribute), use a tools namespace directive in your app's main AndroidManifest.xml to resolve the conflict:



    <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-permission 
            android:name="android.permission.BLUETOOTH_CONNECT"
            tools:node="remove" />
            
        <!-- Example: Override a permission attribute -->
        <uses-permission
            android:name="android.permission.BLUETOOTH"
            android:maxSdkVersion="30"
            tools:replace="android:maxSdkVersion" />
    </manifest>
    

Step 5: Create the UI

Create the user interface for your real-time interactive scenario. To get you started, we provide sample code for a video call that creates two views to display the local and remote video.

Code example

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

Procedure

This section explains how to use the ARTC SDK to build a basic real-time audio and video application. You can copy the code sample to test the features quickly, then follow the steps to understand the core API calls.

The following diagram shows the basic workflow for a real-time audio and video call:

image

The following is a complete code sample that shows the basic process for implementing a video call:

Basic workflow code example

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

For details on the complete sample code and how to run it, see Run the ARTC demo for Android.

1. Request permissions

When a user starts a video call, check if your app has been granted the necessary permissions:

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. Get an authentication token

Joining an ARTC channel requires an authentication token to verify the user's identity. For details on how to generate a token, see Token-based authentication. A token can be generated by using a single-parameter or multi-parameter method. The method you use determines which joinChannel API you must call.

Production environment:

Since generating a token requires your AppKey, hardcoding the AppKey on the client side is a security risk. In a production environment, we strongly recommend that you generate tokens on your App Server and send them to the client.

Development and debugging:

During development, if your App Server cannot yet generate tokens, you can use the logic in the following sample to create temporary tokens. The reference code is as follows:

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

Import the relevant classes and interfaces from the ARTC SDK:

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

4. Create and initialize the engine

  • Create the RTC engine

    Call the getInstance[1/2] method to create an AliRTCEngine instance.

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

    • Call setChannelProfile to set the channel profile to AliRTCSdkInteractiveLive (interactive mode).

      You can choose between interactive mode for entertainment and communication mode for calls. Choosing the right mode ensures a smooth user experience and efficient network use.

      Mode

      Publishing

      Subscribing

      Description

      Interactive mode

      1. Role-based restrictions apply. Only users assigned the host role can publish streams.

      2. Participants can switch roles at any time during the session.

      No role restrictions. All participants have permission to subscribe to streams.

      1. In interactive mode, events such as a host joining or leaving are sent to viewers. A viewer's activity is not sent to the host, which ensures an uninterrupted experience for the host.

      2. If you anticipate that viewers may need to interact in the future, we recommend using interactive mode. Its flexibility lets you adjust user roles to meet different interaction needs.

      Communication mode

      No role restrictions. All participants have permission to publish streams.

      No role restrictions. All participants have permission to subscribe to streams.

      1. In communication mode, participants are aware of each other's presence in the session.

      2. Although this mode does not differentiate user roles, it is functionally equivalent to the host role in interactive mode. This simplifies operations, allowing users to achieve the desired functionality with fewer API calls.

    • Call setClientRole to set the user role to AliRTCSdkInteractive (host) or AliRTCSdkLive (viewer). Note: The host role publishes and subscribes by default. The viewer role only subscribes by default, with preview and publishing disabled.

      Note

      When a user switches roles in a channel, the system adjusts the publishing status of audio and video streams accordingly:

      • Switch from host to viewer: The system stops publishing local audio and video streams. Subscribed remote streams are not affected, and the user can continue to watch other participants.

      • Switch from viewer to host: The system starts publishing local audio and video streams. Subscribed remote streams remain unchanged, and the user can continue to watch other participants.

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

    If the SDK encounters an issue, it first attempts to recover automatically with its internal retry mechanisms. For errors it cannot resolve, the SDK notifies your app through callbacks.

    The following are key callbacks for issues that the SDK cannot handle, which your application must listen for and respond to:

    Cause of exception

    Callback and parameters

    Solution

    Description

    Authentication failed

    The result parameter of the onJoinChannelResult callback is AliRtcErrJoinBadToken.

    Your app must check if the token is correct.

    If authentication fails when a user calls an API, the API's callback returns an authentication failure error.

    Token about to expire

    onAuthInfoWillExpire

    Get a new token and call refreshAuthInfo to update the authentication information.

    A token expiration error can occur either when an API is called or during runtime. The error is reported through an API callback or a separate error callback.

    Token expired

    onAuthInfoExpired

    Your app must rejoin the channel.

    A token expiration error can occur either when an API is called or during runtime. The error is reported through an API callback or a separate error callback.

    Network connection issue

    The onConnectionStatusChange callback returns AliRtcConnectionStatusFailed.

    Your app must rejoin the channel.

    The SDK can automatically recover from brief network disconnections. If the disconnection time exceeds a preset threshold, a timeout occurs and the connection is dropped. Your app must check the network status and guide the user to rejoin the channel.

    Kicked from the channel

    onBye

    • AliRtcOnByeUserReplaced: Check if another user has joined with the same userId.

    • AliRtcOnByeBeKickedOut: The user was kicked from the channel by the App Server and must rejoin.

    • AliRtcOnByeChannelTerminated: The channel was terminated, and the user must rejoin.

    The RTC service allows an administrator to remove participants.

    Local device exception

    onLocalDeviceException

    Your app must check permissions and whether the hardware is working correctly.

    The RTC service supports device detection and diagnostics. When a local device exception occurs that the SDK cannot resolve, it notifies your app via a callback. Your app must then intervene to check the device status.

    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. Set audio and video properties

  • Set audio properties

    Call setAudioProfile to set the audio encoding mode and audio scenario.

    mAliRtcEngine.setAudioProfile(AliRtcEngine.AliRtcAudioProfile.AliRtcEngineHighQualityMode, AliRtcEngine.AliRtcAudioScenario.AliRtcSceneMusicMode);
  • Set video properties

    Set properties for the published video stream, such as resolution, bitrate, and frame rate.

    // 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. Set publishing and subscribing properties

Configure stream publishing and the default subscription behavior:

  • Call publishLocalAudioStream to publish the audio stream.

  • Call publishLocalVideoStream to publish the video stream. For an audio-only call, set this to 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);
Note

By default, the SDK automatically publishes local streams and subscribes to remote streams. You can call the methods above to disable this automatic behavior.

7. Start the local preview

  • Call setLocalViewConfig to configure the local preview view. This requires an AliRtcVideoCanvas object.

  • Call the startPreview method to start the local video preview.

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. Join the channel

Call joinChannel to join a channel. If the token is generated based on the single-parameter rule, call the SDK's single-parameter joinChannel[1/3] interface. If the token is generated based on the multi-parameter rule, call the SDK's multi-parameter joinChannel[2/3] interface. After you join the channel, the onJoinChannelResult callback provides the result. If the result is 0, you have successfully joined the channel. Otherwise, check if the provided token is invalid.

 mAliRtcEngine.joinChannel(token, null, null, null);
Note
  • After you join the channel, the SDK publishes and subscribes to streams based on the parameters set before you joined.

  • The SDK automatically publishes and subscribes to streams by default to reduce the number of required API calls.

9. Set the remote view

When initializing the engine, set the mAliRtcEngine.setRtcEngineNotify callback. In the onRemoteTrackAvailableNotify callback, set the remote view for the remote user. The sample code is as follows:

@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. Leave the channel and destroy the engine

When the audio and video session ends, leave the channel and destroy the engine. Follow the steps below to end the session.

  1. Call stopPreview to stop the video preview.

  2. Call leaveChannel to leave the channel.

  3. Call destroy to destroy the engine and release its resources.

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

11. Demonstration

image

Related documents

Data structures

AliRtcEngine interface