Video Call
  • iOS
  • Android : Java
  • Web
  • Flutter
  • React Native
  • Electron
  • Unity3D
  • Cocos Creator
  • Windows
  • macOS
  • Linux
  • Overview
  • Develop your app
    • Quick start
    • Enhance basic feature
      • Use Tokens for authentication
      • Config your video based on scenes
      • Check the room connection status
      • Set up common video config
      • Set up common audio config
  • Best practices
    • Implement a video call for multiple users
    • Implement call invitation
    • Implement a live audio room
  • Upgrade using advanced features
    • Advanced features
      • Configure the video
        • Watermark the video/Take snapshots
        • Improve your appearance in the video
        • Beautify & Change the voice
        • Configure video codec
        • Output the video in H.265
      • Improve video quality
        • Configure bandwidth management
        • Test network and devices in advance
        • Visualize the sound level
        • Monitor streaming quality
      • Message signaling
        • Convey extra information using SEI
        • Broadcast real-time messages to a room
        • Quotas and limits
      • Play media files
        • Play media files
        • Play sound effects
      • Share the screen
      • Mix the video streams
      • Publish multiple video streams
      • Encrypt the video streams
      • Record video media data
    • Distincitve features
      • Join multiple rooms
      • Customize the video and audio
      • Set the voice hearing range
      • Transfer traffic via the cloud proxy server
      • Use the bit mask
      • Play streams via URL
      • Play a transparent gift special effect
      • AI Voice Changer
      • In-game voice chat
  • Upgrade using Add-on
  • Resources & Reference
    • SDK
    • Sample codes
    • API reference
      • Client APIs
      • Server APIs
    • Debugging
      • Error codes
      • Logging/Version number
    • FAQs
    • Key concepts
  • Documentation
  • Video Call
  • Develop your app
  • Quick start

Quick start

Last updated:2024-01-15 17:38

This guide describes how to integrate the SDK and implement a basic one-on-one video call using ZEGOCLOUD's Video Call SDK.

Prerequisites

Before you begin, make sure you complete the following:

  • Android Studio 2020.3.1 or later
  • Android SDK Packages: Android SDK 25, Android SDK Build-Tools 25.0.2, Android SDK Platform-Tools 25.x.x or later.
  • An Android device or Simulator that is running on Android 4.4 or later and supports audio and video. We recommend you use a real device.
  • Sign up and create a project in Admin Console.

Create an Android project

Skip to this step if a project already exists.

Create a new project
  1. Open Android Studio, select File > New > Project.

  2. Configure your new project with Application name and Project location.

  3. All other items in the panel can be left as their defaults, click Next and then click Finish.

Import the SDK

The Android ABIs currently supported by the SDK: armeabi-v7a, arm64-v8a, x86, x86_64.

Choose either of the following methods to integrate the ZEGO Express SDK into your project.

Method 1: Integrate the SDK automatically (Recommended)
  1. If your Android Gradle Plugin is v7.1.0 or later: go to the root directory of your project, open the settings.gradle file, and add the following line to the dependencyResolutionManagement:

    If you can't find the above fields in settings.gradle, it's probably because your Android Gradle Plugin version is lower than v7.1.0.

    For more details, see Android Gradle Plugin Release Note v7.1.0.

    ...
    dependencyResolutionManagement {
        repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
        repositories {
            maven { url 'https://storage.zego.im/maven' }
            google()
            mavenCentral()
        }
    }

    If your Android Gradle Plugin is earlier than 7.1.0: go to the root directory of your project, open the build.gradle file, and add the following line to the allprojects:

    ...
    allprojects {
        repositories {
            maven { url 'https://storage.zego.im/maven' }
            google()
            mavenCentral()
        }
    }
  2. Go to the app directory, open the build.gradle file, and add the following line to the dependencies. (x.y.z is the SDK version number, to obtain the latest version number, see ZEGO Express-Video Android SDK Release History)

    ...
    dependencies {
        ...
        implementation 'im.zego:express-video:x.y.z'
    }
Method 2: Copy the SDK AAR file for manual integration
  1. Download the latest version of SDK. For details, see SDK downloads .

  2. Copy the "release/Library/ZegoExpressEngine.aar" in the decompressed AAR file to your project directory, such as "app/libs".

    The files under the "release/Library/" directory in the SDK package contain:

  3. Enter the project root directory, open the "settings.gradle" file, and add the following code in "dependencyResolutionManagement".

    ...
    dependencyResolutionManagement {
        repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
        repositories {
            google()
            mavenCentral()
            flatDir {
                dir 'app/libs'
            }
        }
    }
    
    If you cannot find the above fields in "settings.gradle", it may be because your Android Gradle Plugin version is lower than v7.1.0, please refer to [Android Gradle Plugin Release Note v7.1.0\|_blank](https: //developer.android.google.cn/studio/past-releases/past-agp-releases/agp-7-1-0-release-notes#settings-gradle). At this point, please proceed as follows:
    
    Enter the project root directory, open the "build.gradle" file, and add the following code in "allprojects".
    
    ```groovy
    ...
    allprojects {
        repositories {
            google()
            mavenCentral()
            flatDir {
                dir 'app/libs'
            }
        }
    }
  4. Enter the "app" directory, open the "build.gradle" file, and add implementation(name: 'ZegoExpressEngine', ext: 'aar') in "dependencies".

    ...
    dependencies {
        ...
        implementation(name: 'ZegoExpressEngine', ext: 'aar')
    }

    And add the "ndk" node in the "defaultConfig" node to specify the supported architecture.

    ndk {
        abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
    }

    Decide which architecture to support based on the actual situation. Usually you only need to keep "armeabi-v7a" and "arm64-v8a" when publishing the App, which can reduce the APK package size.

Method 3: Copy SDK "JAR + SO" files Manual integration
  1. Download the latest version of SDK. For details, see SDK downloads .

  2. Copy the "other files (except .aar files)" in the decompressed "release/Library/" directory to your project directory, such as "app/libs".

    The "include" directory under the architecture directory is the C++ header file of the SDK, which can be ignored if you only use the Java interface for development.

  3. Add the SDK reference and open the “build.gradle” file under the “app” directory.

    1. Add the "ndk" node to the "defaultConfig" node to specify the supported architectures.

      ndk {
          abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
      }

      Decide which architecture to support based on the actual situation. Usually you only need to keep "armeabi-v7a" and "arm64-v8a" when publishing the App, which can reduce the APK package size.

    2. Add a "sourceSets" node to the "android" node, and specify the directory where "libs" is located.

      The "libs" directory in the sample code is just an example, developers can fill in according to the actual path.
      sourceSets {
          main {
              jniLibs.srcDirs = ['libs']
          }
      }
    3. Introduce all jars under "libs" in the "dependencies" node.

      dependencies {
          implementation fileTree(dir: 'libs', include: ['*.jar'])
          ......
      }

Add device permissions

Permissions can be set as needed.

Open the file app/src/main/AndroidManifest.xml, and add the following code:

<!-- Permissions required by the SDK -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

<!-- Permissions required by the App -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<uses-feature
    android:glEsVersion="0x00020000"
    android:required="true" />

<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />

Note: For Android 6.0 or later, some important permissions must be requested at runtime rather than declared statically in the file AndroidMainfest.xml, therefore, you need to add the following code to do so (requestPermissions is a method of an Android Activity).

String[] permissionNeeded = {
    "android.permission.CAMERA",
    "android.permission.RECORD_AUDIO"};

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(permissionNeeded, 101);
    }
}

Prevent class name obfuscation

To prevent the ZEGO SDK public class names from being obfuscated, you can add the following code in the file proguard-rules.pro.

-keep class **.zego.**{*;}

Implement video call functions

Sample code

The following is the sample code for implementing video call functions. Feel free to refer to it when developing.

Code for implementing a video call
package com.zegocloud.demo.quickstart;

import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import im.zego.zegoexpress.ZegoExpressEngine;
import im.zego.zegoexpress.callback.IZegoEventHandler;
import im.zego.zegoexpress.constants.ZegoPlayerState;
import im.zego.zegoexpress.constants.ZegoPublisherState;
import im.zego.zegoexpress.constants.ZegoRoomStateChangedReason;
import im.zego.zegoexpress.constants.ZegoUpdateType;
import im.zego.zegoexpress.entity.ZegoCanvas;
import im.zego.zegoexpress.entity.ZegoRoomConfig;
import im.zego.zegoexpress.entity.ZegoStream;
import im.zego.zegoexpress.entity.ZegoUser;

import java.util.ArrayList;

import org.json.JSONObject;

public class CallPageActivity extends AppCompatActivity {
    private String userID;
    private String userName;

    // The value of `roomID` is generated locally and must be globally unique.
    // Users must log in to the same room to call each other.
    private String roomID;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_call_page);
        userID = getIntent().getStringExtra("userID");
        userName = getIntent().getStringExtra("userName");
        roomID = getIntent().getStringExtra("roomID");

        startListenEvent();
        loginRoom();
        // Set a listener for the call stopping button.
        findViewById(R.id.callEndButton).setOnClickListener(new View.OnClickListener() {
            // Click to stop a call.
            @Override
            public void onClick(View view) {
                finish();
            }
        });
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (isFinishing()) {
            stopListenEvent();
            logoutRoom();
        }
    }

    void startPreview() {
        ZegoCanvas previewCanvas = new ZegoCanvas(findViewById(R.id.preview));
        ZegoExpressEngine.getEngine().startPreview(previewCanvas);
    }

    void stopPreview() {
        ZegoExpressEngine.getEngine().stopPreview();
    }

    // Log in to a room.
    void loginRoom() {
        ZegoUser user = new ZegoUser(userID, userName);
        ZegoRoomConfig roomConfig = new ZegoRoomConfig();
        // The `onRoomUserUpdate` callback can be received only when
        // `ZegoRoomConfig` in which the `isUserStatusNotify` parameter is set to
        // `true` is passed.
        roomConfig.isUserStatusNotify = true;
        ZegoExpressEngine.getEngine().loginRoom(roomID, user, roomConfig, (int error, JSONObject extendedData) -> {
            // Room login result. This callback is sufficient if you only need to
            // check the login result.
            if (error == 0) {
                // Login successful.
                // Start the preview and stream publishing.
                Toast.makeText(this, "Login successful.", Toast.LENGTH_LONG).show();
                startPreview();
                startPublish();
            } else {
                // Login failed. For details, see [Error codes\|_blank](/404).
                Toast.makeText(this, "Login failed. error = " + error, Toast.LENGTH_LONG).show();
            }
        });
    }

    void logoutRoom() {
        ZegoExpressEngine.getEngine().logoutRoom();
    }


    void startPublish() {
        // After calling the `loginRoom` method, call this method to publish streams.
        // The StreamID must be unique in the room.
        String streamID = roomID + "_" + userID + "_call";
        ZegoExpressEngine.getEngine().startPublishingStream(streamID);
    }


    void stopPublish() {
        ZegoExpressEngine.getEngine().stopPublishingStream();
    }


    void startPlayStream(String streamID) {
        findViewById(R.id.remoteUserView).setVisibility(View.VISIBLE);
        ZegoCanvas playCanvas = new ZegoCanvas(findViewById(R.id.remoteUserView));
        ZegoExpressEngine.getEngine().startPlayingStream(streamID, playCanvas);
    }

    void stopPlayStream(String streamID) {
        ZegoExpressEngine.getEngine().stopPlayingStream(streamID);
        findViewById(R.id.remoteUserView).setVisibility(View.GONE);
    }

    void startListenEvent() {
        ZegoExpressEngine.getEngine().setEventHandler(new IZegoEventHandler() {
            @Override
            // Callback for updates on the status of the streams in the room.
            public void onRoomStreamUpdate(String roomID, ZegoUpdateType updateType, ArrayList<ZegoStream> streamList, JSONObject extendedData) {
                super.onRoomStreamUpdate(roomID, updateType, streamList, extendedData);
                // When `updateType` is set to `ZegoUpdateType.ADD`, an audio and video
                // stream is added, and you can call the `startPlayingStream` method to
                // play the stream.
                if (updateType == ZegoUpdateType.ADD) {
                    startPlayStream(streamList.get(0).streamID);
                } else {
                    stopPlayStream(streamList.get(0).streamID);
                }
            }

            @Override
            // Callback for updates on the status of other users in the room.
            // Users can only receive callbacks when the isUserStatusNotify property of ZegoRoomConfig is set to `true` when logging in to the room (loginRoom).
            public void onRoomUserUpdate(String roomID, ZegoUpdateType updateType, ArrayList<ZegoUser> userList) {
                super.onRoomUserUpdate(roomID, updateType, userList);
                // You can implement service logic in the callback based on the login
                // and logout status of users.
                if (updateType == ZegoUpdateType.ADD) {
                    for (ZegoUser user : userList) {
                        String text = user.userID + "logged in to the room.";
                        Toast.makeText(getApplicationContext(), text, Toast.LENGTH_LONG).show();
                    }
                } else if (updateType == ZegoUpdateType.DELETE) {
                    for (ZegoUser user : userList) {
                        String text = user.userID + "logged out of the room.";
                        Toast.makeText(getApplicationContext(), text, Toast.LENGTH_LONG).show();
                    }
                }
            }

            @Override
            // Callback for updates on the current user's room connection status.
            public void onRoomStateChanged(String roomID, ZegoRoomStateChangedReason reason, int i, JSONObject jsonObject) {
                super.onRoomStateChanged(roomID, reason, i, jsonObject);
                if (reason == ZegoRoomStateChangedReason.LOGINING) {
                    // Logging in to a room. When `loginRoom` is called to log in to a
                    // room or `switchRoom` is called to switch to another room, the room
                    // enters this status, indicating that it is requesting a connection
                    // to the server. On the app UI, the status of logging in to the room
                    // is displayed.
                } else if (reason == ZegoRoomStateChangedReason.LOGINED) {
                    // Logging in to a room succeeds. When a user successfully logs in to
                    // a room or switches the room, the room enters this status. In this
                    // case, the user can receive notifications of addition or deletion of
                    // other users and their streams in the room. Only after a user
                    // successfully logs in to a room or switches the room,
                    // `startPublishingStream` and `startPlayingStream` can be called to
                    // publish and play streams properly.
                } else if (reason == ZegoRoomStateChangedReason.LOGIN_FAILED) {
                    // Logging in to a room fails. When a user fails to log in to a room
                    // or switch the room due to a reason such as incorrect AppID or
                    // Token, the room enters this status.
                    Toast.makeText(getApplicationContext(), "ZegoRoomStateChangedReason.LOGIN_FAILED", Toast.LENGTH_LONG).show();
                } else if (reason == ZegoRoomStateChangedReason.RECONNECTING) {
                    // The room connection is temporarily interrupted. The SDK will retry
                    // internally if the interruption is caused by poor network quality.
                } else if (reason == ZegoRoomStateChangedReason.RECONNECTED) {
                    // Reconnecting a room succeeds. The SDK will retry internally if the
                    // interruption is caused by poor network quality. If the reconnection
                    // is successful, the room enters this status.
                } else if (reason == ZegoRoomStateChangedReason.RECONNECT_FAILED) {
                    // Reconnecting a room fails. The SDK will retry internally if the
                    // interruption is caused by poor network quality. If the reconnection
                    // fails, the room enters this status.
                    Toast.makeText(getApplicationContext(), "ZegoRoomStateChangedReason.RECONNECT_FAILED", Toast.LENGTH_LONG).show();
                } else if (reason == ZegoRoomStateChangedReason.KICK_OUT) {
                    // The server forces a user to log out of a room. If a user who has
                    // logged in to room A tries to log in to room B, the server forces
                    // the user to log out of room A and room A enters this status.
                    Toast.makeText(getApplicationContext(), "ZegoRoomStateChangedReason.KICK_OUT", Toast.LENGTH_LONG).show();
                } else if (reason == ZegoRoomStateChangedReason.LOGOUT) {
                    // Logging out of a room succeeds. This is the default status of a
                    // room before login. If a user successfully logs out of a room by
                    // calling `logoutRoom` or `switchRoom`, the room enters this status.
                } else if (reason == ZegoRoomStateChangedReason.LOGOUT_FAILED) {
                    // Logging out of a room fails. If a user fails to log out of a room
                    // by calling `logoutRoom` or `switchRoom`, the room enters this
                    // status.
                }
            }

            // Status notification of audio and video stream publishing.
            @Override
            public void onPublisherStateUpdate(String streamID, ZegoPublisherState state, int errorCode, JSONObject extendedData) {
                super.onPublisherStateUpdate(streamID, state, errorCode, extendedData);
                if (errorCode != 0) {
                    // Stream publishing exception.
                }
                if (state == ZegoPublisherState.PUBLISHING) {
                    // Publishing streams.
                } else if (state == ZegoPublisherState.NO_PUBLISH) {
                    // Streams not published.
                    Toast.makeText(getApplicationContext(), "ZegoPublisherState.NO_PUBLISH", Toast.LENGTH_LONG).show();
                } else if (state == ZegoPublisherState.PUBLISH_REQUESTING) {
                    // Requesting stream publishing.
                }
            }

            // Status notifications of audio and video stream playing.
            // This callback is received when the status of audio and video stream
            // playing of a user changes. If an exception occurs during stream playing
            // due to a network interruption, the SDK automatically retries to play
            // the streams.
            @Override
            public void onPlayerStateUpdate(String streamID, ZegoPlayerState state, int errorCode, JSONObject extendedData) {
                super.onPlayerStateUpdate(streamID, state, errorCode, extendedData);
                if (errorCode != 0) {
                    // Stream playing exception.
                    Toast.makeText(getApplicationContext(), "onPlayerStateUpdate, state:" + state + "errorCode:" + errorCode, Toast.LENGTH_LONG).show();
                }
                if (state == ZegoPlayerState.PLAYING) {
                    // Playing streams.
                } else if (state == ZegoPlayerState.NO_PLAY) {
                    // Streams not played.
                    Toast.makeText(getApplicationContext(), "ZegoPlayerState.NO_PLAY", Toast.LENGTH_LONG).show();
                } else if (state == ZegoPlayerState.PLAY_REQUESTING) {
                    // Requesting stream playing.
                }
            }
        });
    }

    void stopListenEvent() {
        ZegoExpressEngine.getEngine().setEventHandler(null);
    }

}

Implement the UI

Create a UI for video calls for your project based on your scenario requirements. We recommend you add the following UI elements to your project:

  • A view for local preview
  • A view for remote video
  • A Stop button
layout
<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".CallPageActivity">


    <TextureView
        android:id="@+id/preview"
        android:layout_width="wrap_content"
        android:layout_height="731dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextureView
        android:id="@+id/remoteUserView"
        android:layout_width="150dp"
        android:layout_height="220dp"
        android:layout_marginTop="30dp"
        android:layout_marginEnd="30dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/callEndButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="50dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:srcCompat="@android:drawable/ic_menu_call" />


</androidx.constraintlayout.widget.ConstraintLayout>

Implement the video calling logic

Understand the tech

The following diagram shows the basic process of User A playing a stream published by User B:

For a better understanding, you can check the key concepts of Video Call SDK:

1. Create & destroy the ZegoExpressEngine instance

call the createEngineWithProfile method to initialize the Video Call SDK. And config the following:

  • profile: the ZegoEngineProfile object, used to config the appID and appSign, as well as the scenario you are applying the SDK to.
  • eventHandler: an event handler object, used to listen for core event callbacks, such as the callback for updates on the room connection stats changes, updates on in-room participants log in or log out, and more. You can call the setEventHandler method to set up the event handler object.

To destroy the SDK and release the resources it occupies, call the destroy method.

void createEngine() {
    ZegoEngineProfile profile = new ZegoEngineProfile();

    // Get your AppID and AppSign from ZEGOCLOUD Console
    //[My Projects -> AppID] : https://console.zegocloud.com/project
    profile.appID = appID;
    profile.appSign = appSign;
    profile.scenario = ZegoScenario.DEFAULT; // General scenario.
    profile.application = getApplication();
    ZegoExpressEngine.createEngine(profile, null);
}


// destroy engine
private void destroyEngine() {
    ZegoExpressEngine.destroyEngine(null);
}

2. Set up an event handler

Implement the ZegoEventHandler event handler to listen for event callbacks, such as the event callback on the updates when the in-room streams are added or deleted, the updates when in-room participants log in or log out, the updates when room connection state changes, and more.

  • onRoomStreamUpdate: Callback for updates on the status of the streams in the room. When new streams are published to the room or existing streams in the room stop, the SDK sends out the event notification through this callback. You can call startPlayStream() and stopPlayStream() methods in this callback.

  • onRoomStateUpdate: Callback for updates on current room connection status. When the current room connection status changes (for example, when the current user is disconnected from the room or login authentication fails), the SDK sends out the event notification through this callback.

  • onRoomUserUpdate: Callback for updates on the status of other users in the room. When other users log in or log out of the room, the SDK sends out the event notification through this callback.

void startListenEvent() {
    ZegoExpressEngine.getEngine().setEventHandler(new IZegoEventHandler() {
        @Override
        // Callback for updates on the status of the streams in the room.
        public void onRoomStreamUpdate(String roomID, ZegoUpdateType updateType, ArrayList<ZegoStream> streamList, JSONObject extendedData) {
            super.onRoomStreamUpdate(roomID, updateType, streamList, extendedData);
            // When `updateType` is set to `ZegoUpdateType.ADD`, an audio and video
            // stream is added, and you can call the `startPlayingStream` method to
            // play the stream.
            if (updateType == ZegoUpdateType.ADD) {
                startPlayStream(streamList.get(0).streamID);
            } else {
                stopPlayStream(streamList.get(0).streamID);
            }
        }

        @Override
        // Callback for updates on the status of other users in the room.
        // Users can only receive callbacks when the isUserStatusNotify property of ZegoRoomConfig is set to `true` when logging in to the room (loginRoom).
        public void onRoomUserUpdate(String roomID, ZegoUpdateType updateType, ArrayList<ZegoUser> userList) {
            super.onRoomUserUpdate(roomID, updateType, userList);
            // You can implement service logic in the callback based on the login
            // and logout status of users.
            if (updateType == ZegoUpdateType.ADD) {
                for (ZegoUser user : userList) {
                    String text = user.userID + "logged in to the room.";
                    Toast.makeText(getApplicationContext(), text, Toast.LENGTH_LONG).show();
                }
            } else if (updateType == ZegoUpdateType.DELETE) {
                for (ZegoUser user : userList) {
                    String text = user.userID + "logged out of the room.";
                    Toast.makeText(getApplicationContext(), text, Toast.LENGTH_LONG).show();
                }
            }
        }

        @Override
        // Callback for updates on the current user's room connection status.
        public void onRoomStateChanged(String roomID, ZegoRoomStateChangedReason reason, int i, JSONObject jsonObject) {
            super.onRoomStateChanged(roomID, reason, i, jsonObject);
            if (reason == ZegoRoomStateChangedReason.LOGIN_FAILED) {
                Toast.makeText(getApplicationContext(), "ZegoRoomStateChangedReason.LOGIN_FAILED", Toast.LENGTH_LONG).show();
            }
        }

        // Status notification of audio and video stream publishing.
        @Override
        public void onPublisherStateUpdate(String streamID, ZegoPublisherState state, int errorCode, JSONObject extendedData) {
            super.onPublisherStateUpdate(streamID, state, errorCode, extendedData);
            if (errorCode != 0) {
                // Stream publishing exception.
            }
            if (state == ZegoPublisherState.PUBLISHING) {
                // Publishing streams.
            } else if (state == ZegoPublisherState.NO_PUBLISH) {
                // Streams not published.
                Toast.makeText(getApplicationContext(), "ZegoPublisherState.NO_PUBLISH", Toast.LENGTH_LONG).show();
            } else if (state == ZegoPublisherState.PUBLISH_REQUESTING) {
                // Requesting stream publishing.
            }
        }

        // Status notifications of audio and video stream playing.
        @Override
        public void onPlayerStateUpdate(String streamID, ZegoPlayerState state, int errorCode, JSONObject extendedData) {
            super.onPlayerStateUpdate(streamID, state, errorCode, extendedData);
            if (errorCode != 0) {
                // Stream playing exception.
                Toast.makeText(getApplicationContext(), "onPlayerStateUpdate, state:" + state + "errorCode:" + errorCode, Toast.LENGTH_LONG).show();
            }
            if (state == ZegoPlayerState.PLAYING) {
                // Playing streams.
            } else if (state == ZegoPlayerState.NO_PLAY) {
                // Streams not played.
                Toast.makeText(getApplicationContext(), "ZegoPlayerState.NO_PLAY", Toast.LENGTH_LONG).show();
            } else if (state == ZegoPlayerState.PLAY_REQUESTING) {
                // Requesting stream playing.
            }
        }
    });
}

void stopListenEvent() {
    ZegoExpressEngine.getEngine().setEventHandler(null);
}

3. Log in & log out

To log in to a room, you can call the loginRoom method.

To log out, you can call the logoutRoom method.

void loginRoom() {
    ZegoUser user = new ZegoUser(userID, userName);
    ZegoRoomConfig roomConfig = new ZegoRoomConfig();
    // The `onRoomUserUpdate` callback can be received only when
    // `ZegoRoomConfig` in which the `isUserStatusNotify` parameter is set to
    // `true` is passed.
    roomConfig.isUserStatusNotify = true;
    ZegoExpressEngine.getEngine().loginRoom(roomID, user, roomConfig, (int error, JSONObject extendedData) -> {
        // Room login result. This callback is sufficient if you only need to
        // check the login result.
        if (error == 0) {
            // Login successful.
            // Start the preview and stream publishing.
            Toast.makeText(this, "Login successful.", Toast.LENGTH_LONG).show();
            startPreview();
            startPublish();
        } else {
            // Login failed. For details, see [Error codes\|_blank](/404).
            Toast.makeText(this, "Login failed. error = " + error, Toast.LENGTH_LONG).show();
        }
    });
}

void logoutRoom() {
    ZegoExpressEngine.getEngine().logoutRoom();
}

4. Start & stop the local video preview

To start the local video preview and to render it, call the startPreview method.

And you call the stopPreview method to stop the rendering.

void startPreview(){
    ZegoCanvas previewCanvas = new ZegoCanvas(findViewById(R.id.preview));
    ZegoExpressEngine.getEngine().startPreview(previewCanvas);
}
void stopPreview(){
    ZegoExpressEngine.getEngine().stopPreview();
}

5. Start & stop publishing streams

To start publishing a local audio or video stream to remote users, call the startPublishingStream method.

And you can call the stopPublishingStream method to stop the stream publishing.

void startPublish() {
    // After calling the `loginRoom` method, call this method to publish streams.
    // The StreamID must be unique in the room.
    ZegoCanvas previewCanvas = new ZegoCanvas(findViewById(R.id.preview));
    ZegoExpressEngine.getEngine().startPreview(previewCanvas);
    String streamID = roomID + "_" + userID + "_call";
    ZegoExpressEngine.getEngine().startPublishingStream(streamID);
}


void stopPublish() {
    ZegoExpressEngine.getEngine().stopPublishingStream();
}

6. Start & stop playing streams

You can call startPlayingStream method to start playing a remote video stream.

And to stop the stream playing, call the stopPlayingStream method to stop.

void startPlayStream(String streamID){
    findViewById(R.id.remoteUserView).setVisibility(View.VISIBLE);
    ZegoCanvas playCanvas = new ZegoCanvas(findViewById(R.id.remoteUserView));
    ZegoExpressEngine.getEngine().startPlayingStream(streamID, playCanvas);
}

void stopPlayStream(String streamID){
    ZegoExpressEngine.getEngine().stopPlayingStream(streamID);
    findViewById(R.id.remoteUserView).setVisibility(View.GONE);
}

Start and stop your app

When your app starts, you need to call the createEngine method to initialize the SDK. When your app is about to exit, you can call the destroyEngine to release SDK resources.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    createEngine();
    initViews();
}

@Override
protected void onDestroy() {
    super.onDestroy();
    destroyEngine();
}

Test your implementation

To test your implementation, run your app project on a real device. Upon successful running, you can view the local video.

For your convenience of testing experience, we got you a Web platform for debugging. On the debugging page, you can enter the AppID and room ID of the real device user, and a different user ID to log in to the same room for communicating with the real device user. After a video call starts successfully, you can hear the remote audio and view the remote video.

Resource

For a detailed demo source code, check it here.

Resolution And Pricing Attention!

Please pay close attention to the relationship between video resolution and price when implementing video call, live streaming, and other video scenarios.

When playing multiple video streams in the same room, the billing will be based on the sum of the resolutions, and different resolutions will correspond to different billing tiers.

The video streams that are included in the calculation of the final resolution are as follows:

  1. Live streaming video view (such as host view, co-host view, PKBattle view, etc.)
  2. Video call's video view for each person
  3. Screen sharing view
  4. Resolution of the cloud recording service
  5. Resolution of the Live stream creation

Before your app goes live, please make sure you have reviewed all configurations and confirmed the billing tiers for your business scenario to avoid unnecessary losses. For more details, please refer to Pricing.

Page Directory