How to realize video call in application based on Agora Android SDK?

In many products, real-time video call is no longer a new function, such as video conferencing, social applications, online education, and even may appear in some meta cosmic scenes.

This article will teach you how to implement a video call application on Android through Agora video SDK. The sound network SDK will provide 10000 minutes of free use quota every month, which can realize all kinds of real-time audio and video scenes. Without much to say, we began to practice.

Experience video call through open source Demo

Some people may not know what the function we want to achieve is in the end. Therefore, we provide an open-source basic video call sample project on GitHub. Before starting development, you can experience the effect of audio and video calls through this sample project.

Github: https://github.com/AgoraIO/API-Examples/blob/master/Android/APIExample/app/src/main/java/io/agora/api/example/examples/basic/JoinChannelVideo.java

Technical principle of video call

What we want to achieve here is one-to-one video call. You can understand that two users realize the interworking of audio and video by joining the same channel. The data of this channel will be transmitted with low delay through Agora SD-RTN real-time network of sound network.

After the App integrates Agora SDK, the basic workflow of video call is shown in the figure below:

As shown in the figure, the steps to realize video call are as follows:

  1. Get the Token: when the app client joins the channel, you need to use the Token to verify the user's identity. In the test or production environment, get the Token from the app server.
  2. Join channel: call joinChannel to create and join the channel. app clients with the same channel name join the same channel by default. A channel can be understood as a channel dedicated to transmitting real-time audio and video data.
  3. Publish and subscribe to audio and video streams in the channel: after joining the channel, app clients can publish and subscribe to audio and video streams in the channel.

The App client needs the following information to join the channel:

  • App ID: a string randomly generated by Agora, which is used to identify your app. It can be downloaded from Agora console Get, and the detailed method can be seen This tutorial.
  • User ID: the unique identification of the user. You need to set your own user ID and make sure it is unique within the channel.
  • Token: in a test or production environment, the app client obtains a token from your server. In the process described in this article, you can get the temporary token from Agora console. The validity period of the temporary token is 24 hours.
  • Channel name: a string used to identify the video call channel.

development environment

The sound network Agora SDK has good compatibility and low requirements for hardware equipment and software system. The development environment and test environment can meet the following conditions:

  • Android SDK API Level >= 16
  • Android Studio 2.0 or above
  • Real machine supporting voice and video functions
  • App requires Android 4.1 or above devices

The following is the development environment and test environment of this paper:

development environment

  • Windows 10 home Chinese
  • Java Version SE 8
  • Android Studio 3.2 Canary 4

testing environment

  • Samsung Nexus (Android 4.4.2 API 19)
  • Mi Note 3 (Android 7.1.1 API 25)

If you haven't contacted the voice network Agora SDK before, you need to do the following preparations:

  • Register a voice network account, enter the background to create AppID and obtain Token. For detailed methods, please refer to This tutorial

https://sso2.agora.io/cn/signup?

Project settings

1. Before realizing video call, refer to the following steps to set up your project:

To create a new project, in Android Studio, select phone and tablet > empty activity and create it Android project.

After the project is created, Android Studio will automatically start synchronizing gradle. Please ensure that the synchronization is successful before proceeding to the next step.

2. Integrate SDK. This article recommends integrating Agora SDK with gradle:

a. In / gradle scripts / build Add the following code to gradle (Project:) file to add Maven central dependency:

buildscript {
     repositories {
         ...
         mavenCentral()
     }
     ...
}
 
  allprojects {
     repositories {
         ...
         mavenCentral()
     }
}

b. In / gradle scripts / build Add the following code to gradle (module:. APP) file to integrate Agora video SDK into your Android project:

...
dependencies {
 ...
 // x.y.z, please fill in the specific SDK version number, such as 3.5.0.
 // Get the latest version number through the release instructions.
 implementation 'io.agora.rtc:full-sdk:x.y.z'
}

3. Permission settings, in / APP / manifests / androidmanifest Add the following network and device permissions after ` ` in the XML file:

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

Client implementation

This section introduces some tips on how to use Agora video SDK to realize video call in your app:

1. Obtain necessary permissions

When starting the application, check whether the permissions required to realize video call have been granted in the app.

The following code is called in the onCreate function:

private static final int PERMISSION_REQ_ID = 22;
 
private static final String[] REQUESTED_PERMISSIONS = {
     Manifest.permission.RECORD_AUDIO,
     Manifest.permission.CAMERA
};
 
private boolean checkSelfPermission(String permission, int requestCode) {
    if (ContextCompat.checkSelfPermission(this, permission) !=
            PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this, REQUESTED_PERMISSIONS, requestCode);
        return false;
    }
    return true;
}

2. Realize video call logic

The following figure shows the API call sequence of video call:

a. Initialize engine

RtcEngine class contains the main methods called by the application program. The interface calling RtcEngine is best carried out in the same thread. It is not recommended to call it in different threads at the same time.

At present, Agora Native SDK only supports one rtcene instance, and each application creates only one rtcene object. All interface functions of RtcEngine class are called asynchronously unless otherwise specified. It is recommended to call the interface in the same thread. All API s with the return value of int type. Unless otherwise specified, the return value of 0 indicates that the call was successful, and the return value less than 0 indicates that the call failed.

The IRtcEngineEventHandler interface class is used by the SDK to send callback event notifications to applications. Applications obtain SDK event notifications by inheriting the interface class.

All methods of the interface class have default (empty) implementation, and the application can inherit only the concerned events as needed. In the callback method, the application should not make time-consuming or call API s that may cause blocking (such as SendMessage), otherwise it may affect the operation of the SDK.

engine = RtcEngine.create(context.getApplicationContext(), getString(R.string.agora_app_id), iRtcEngineEventHandler);
...
private final IRtcEngineEventHandler iRtcEngineEventHandler = new IRtcEngineEventHandler()
{
    /**Reports a warning during SDK runtime.
     * Warning code: https://docs.agora.io/en/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_warn_code.html*/
    @Override
    public void onWarning(int warn)
    {
        Log.w(TAG, String.format("onWarning code %d message %s", warn, RtcEngine.getErrorDescription(warn)));
    }
 
    /**Reports an error during SDK runtime.
     * Error code: https://docs.agora.io/en/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_error_code.html*/
    @Override
    public void onError(int err)
    {
        Log.e(TAG, String.format("onError code %d message %s", err, RtcEngine.getErrorDescription(err)));
        showAlert(String.format("onError code %d message %s", err, RtcEngine.getErrorDescription(err)));
    }
 
    /**Occurs when a user leaves the channel.
     * @param stats With this callback, the application retrieves the channel information,
     *              such as the call duration and statistics.*/
    @Override
    public void onLeaveChannel(RtcStats stats)
    {
        super.onLeaveChannel(stats);
        Log.i(TAG, String.format("local user %d leaveChannel!", myUid));
        showLongToast(String.format("local user %d leaveChannel!", myUid));
    }
 
    /**Occurs when the local user joins a specified channel.
     * The channel name assignment is based on channelName specified in the joinChannel method.
     * If the uid is not specified when joinChannel is called, the server automatically assigns a uid.
     * @param channel Channel name
     * @param uid User ID
     * @param elapsed Time elapsed (ms) from the user calling joinChannel until this callback is triggered*/
    @Override
    public void onJoinChannelSuccess(String channel, int uid, int elapsed)
    {
        Log.i(TAG, String.format("onJoinChannelSuccess channel %s uid %d", channel, uid));
        showLongToast(String.format("onJoinChannelSuccess channel %s uid %d", channel, uid));
        myUid = uid;
        joined = true;
        handler.post(new Runnable()
        {
            @Override
            public void run()
            {
                join.setEnabled(true);
                join.setText(getString(R.string.leave));
            }
        });
    }
 
    @Override
    public void onRemoteAudioStats(io.agora.rtc.IRtcEngineEventHandler.RemoteAudioStats remoteAudioStats) {
        statisticsInfo.setRemoteAudioStats(remoteAudioStats);
        updateRemoteStats();
    }
 
    @Override
    public void onLocalAudioStats(io.agora.rtc.IRtcEngineEventHandler.LocalAudioStats localAudioStats) {
        statisticsInfo.setLocalAudioStats(localAudioStats);
        updateLocalStats();
    }
 
    @Override
    public void onRemoteVideoStats(io.agora.rtc.IRtcEngineEventHandler.RemoteVideoStats remoteVideoStats) {
        statisticsInfo.setRemoteVideoStats(remoteVideoStats);
        updateRemoteStats();
    }
 
    @Override
    public void onLocalVideoStats(io.agora.rtc.IRtcEngineEventHandler.LocalVideoStats localVideoStats) {
        statisticsInfo.setLocalVideoStats(localVideoStats);
        updateLocalStats();
    }
 
    @Override
    public void onRtcStats(io.agora.rtc.IRtcEngineEventHandler.RtcStats rtcStats) {
        statisticsInfo.setRtcStats(rtcStats);
    }
};

b. Set local video parameters

The enableVideo() method is used to turn on video mode. It can be called before joining the channel or before calling. Before calling in the channel, it will automatically turn on the video mode and switch from audio mode to video mode in call. Call the disableVideo() method to turn off video mode.

The setuplocalvideo (videocanvas local) method is used to set the local video display information. By calling this interface, the application binds the view of the local video stream and sets the video display mode. In application development, this method is usually called after initialization for local video settings, and then added to the channel. After exiting the channel, the binding is still valid. If you need to unbind, you can call setupLocalVideo(null).

// Create render view by RtcEngine
SurfaceView surfaceView = RtcEngine.CreateRendererView(context);
if(fl_local.getChildCount() > 0)
{
    fl_local.removeAllViews();
}
// Add to the local container
fl_local.addView(surfaceView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
// Setup local video to render your local camera preview
engine.setupLocalVideo(new VideoCanvas(surfaceView, RENDER_MODE_HIDDEN, 0));
// Enable video module
engine.enableVideo();

c . Join a channel

The joinChannel() method allows users to join the call channel. Users in the same channel can talk to each other, and multiple users can join the same channel for group chat. Applications with different app IDS cannot communicate with each other. If you are already in a call, the user must call leaveChannel() to exit the current call before entering the next channel.

ChannelMediaOptions option = new ChannelMediaOptions();
option.autoSubscribeAudio = true;
option.autoSubscribeVideo = true;
int res = engine.joinChannel(accessToken, channelId, "Extra Optional Data", 0, option);
if (res != 0)
{
    // Usually happens with invalid parameters
    // Error code description can be found at:
    // en: https://docs.agora.io/en/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_error_code.html
    // cn: https://docs.agora.io/cn/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_error_code.html
    showAlert(RtcEngine.getErrorDescription(Math.abs(res)));
    return;
}

d. Leave current channel

The leaveChannel() method is used to leave the channel, that is, hang up or exit the call.

After calling the joinChannel() API method, you must call leavechannel () to end the call, otherwise you cannot start the next call. No matter whether you are in a call or not, you can call leaveChannel() without side effects. This method will release all resources related to the session. This method is an asynchronous operation and does not really exit the channel when the call returns. After exiting the channel, the SDK will trigger the onLeaveChannel callback.

e. Manage cameras

The switchCamera() method is used to switch between front / rear cameras. In addition, Agora also provides the following methods to manage the camera: for example, setCameraTorchOn(boolean isOn) sets whether to turn on the flash, setCameraAutoFocusFaceModeEnabled(boolean enabled) sets whether to turn on the face focusing function, and so on.

f. When the remote user joins the channel, the remote user interface is updated.

private final IRtcEngineEventHandler iRtcEngineEventHandler = new IRtcEngineEventHandler()
{
    ...
    /**Occurs when a remote user (Communication)/host (Live Broadcast) joins the channel.
     * @param uid ID of the user whose audio state changes.
     * @param elapsed Time delay (ms) from the local user calling joinChannel/setClientRole
     *                until this callback is triggered.*/
    @Override
    public void onUserJoined(int uid, int elapsed)
    {
        super.onUserJoined(uid, elapsed);
        Log.i(TAG, "onUserJoined->" + uid);
        showLongToast(String.format("user %d joined!", uid));
        /**Check if the context is correct*/
        Context context = getContext();
        if (context == null) {
            return;
        }
        if(remoteViews.containsKey(uid)){
            return;
        }
        else{
            handler.post(() ->
            {
                /**Display remote video stream*/
                SurfaceView surfaceView = null;
                // Create render view by RtcEngine
                surfaceView = RtcEngine.CreateRendererView(context);
                surfaceView.setZOrderMediaOverlay(true);
                ViewGroup view = getAvailableView();
                remoteViews.put(uid, view);
                // Add to the remote container
                view.addView(surfaceView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
                // Setup remote video to render
                engine.setupRemoteVideo(new VideoCanvas(surfaceView, RENDER_MODE_HIDDEN, uid));
            });
        }
    }
 
    /**Occurs when a remote user (Communication)/host (Live Broadcast) leaves the channel.
     * @param uid ID of the user whose audio state changes.
     * @param reason Reason why the user goes offline:
     *   USER_OFFLINE_QUIT(0): The user left the current channel.
     *   USER_OFFLINE_DROPPED(1): The SDK timed out and the user dropped offline because no data
     *              packet was received within a certain period of time. If a user quits the
     *               call and the message is not passed to the SDK (due to an unreliable channel),
     *               the SDK assumes the user dropped offline.
     *   USER_OFFLINE_BECOME_AUDIENCE(2): (Live broadcast only.) The client role switched from
     *               the host to the audience.*/
    @Override
    public void onUserOffline(int uid, int reason)
    {
        Log.i(TAG, String.format("user %d offline! reason:%d", uid, reason));
        showLongToast(String.format("user %d offline! reason:%d", uid, reason));
        handler.post(new Runnable() {
            @Override
            public void run() {
                /**Clear render view
                 Note: The video will stay at its last frame, to completely remove it you will need to
                 remove the SurfaceView from its parent*/
                engine.setupRemoteVideo(new VideoCanvas(null, RENDER_MODE_HIDDEN, uid));
                remoteViews.get(uid).removeAllViews();
                remoteViews.remove(uid);
            }
        });
    }
    ...
};

Finish, run

Take two mobile phones and install the compiled App. If you can see two selves, it means you have succeeded. If you encounter problems in the development process, you can visit the forum to ask questions and communicate with sound network engineers

https://rtcdeveloper.agora.io/

You can also visit the background for further technical support

https://console.agora.io/

Tags: Android AI SDK

Posted by Anant on Fri, 25 Feb 2022 16:57:21 +1030