This doc will guide you on how to migrate from Agora to ZEGOCLOUD.
Before getting started, you will first gain a technical understanding of the key differences between Agora and ZEGOCLOUD. Afterwards, this doc will compare the code of the main features of the SDK, helping you to understand how to migrate and modify your code.
Agora | ZEGOCLOUD | |
---|---|---|
Channel/Room |
Agora uses the concept of Channel , and the joinChannel method is called to create and join a channel. Users who pass the same channel name will join the same channel,provided that the AppID is consistent. |
ZEGOCLOUD uses the concept of Room , and the loginRoom API is called to log in to a room. Users who pass the same roomID will join the same room, provided that the AppID is consistent. |
How to manage streams - using Role/Stream |
Agora manages streams through user roles, and the SDK auto publishes or subscribes to streams based on the current role: the host sends streams in the channel and receives audio and video streams published by other hosts in the channel; the audience can only receive audio and video streams published by the host in the channel; you can call setClientRole to switch the user role from audience to host. |
ZEGOCLOUD SDK does not distinguish roles, and you need to use startPublishingStream , stopPublishingStream , startPlayingStream , and stopPlayingStream to control the start and end of streaming. |
Authentication: using Token & AppSign |
When joining a channel via the app client, users need to be authenticated using a Token. |
Similarly, when joining a room, Token authentication is required. In addition, ZEGOCLOUD also supports authentication using the "AppSign" method, which is a fixed key corresponding to each AppID and is only recommended for use during the testing phase. |
For a better understanding, you can check the key concepts of ZEGOCLOUD SDK.
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
:
...
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
maven { url 'https://maven.zego.im' }
google()
mavenCentral()
}
}
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.
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://maven.zego.im' }
google()
mavenCentral()
}
}
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'
}
Add device permissions
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" />
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.**{*;}
Firstly, both SDKs require you to create an SDK instance, which is typically done when the app starts.
void createEngine() {
RtcEngineConfig config = new RtcEngineConfig();
config.mContext = getBaseContext();
config.mAppId = appId;
config.mEventHandler = mRtcEventHandler;
mRtcEngine = RtcEngine.create(config);
}
private void destroyEngine() {
RtcEngine.destroy();
}
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);
}
private void destroyEngine() {
ZegoExpressEngine.destroyEngine(null);
}
To receive event notifications thrown by the SDK, both Agora and ZEGOCLOUD require setting an object
as the event handler.
Common callback explanations:
Usually, remote rendering starts with onUserJoined
and ends with onUserOffline
.
void startListenEvent() {
private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
@Override
// Listen for the remote host joining the channel to get the uid of the host.
public void onUserJoined(int uid, int elapsed) {
showMessage("Remote user joined " + uid);
if (!audienceRole.isChecked()) return;
// Set the remote video view
runOnUiThread(() -> setupRemoteVideo(uid));
}
@Override
public void onJoinChannelSuccess(String channel, int uid, int elapsed) {
isJoined = true;
showMessage("Joined Channel " + channel);
}
@Override
public void onUserOffline(int uid, int reason) {
showMessage("Remote user offline " + uid + " " + reason);
runOnUiThread(() -> remoteSurfaceView.setVisibility(View.GONE));
}
};
mRtcEngine.addEventHandler(mRtcEventHandler);
}
void stopListenEvent() {
mRtcEngine.removeHandler(mRtcEventHandler);
}
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.
When using Agora, remote rendering usually starts in
onUserJoined
and ends inonUserOffline
.
As there is no concept of "role" in ZEGOCLOUD, onRoomUserUpdate
differs greatly from Agora's onUserJoined
and onUserOffline
. This means that both audience and host joining the room will trigger the onRoomUserUpdate
callback.
Therefore, during migration, you need to modify the rendering timing to start or end remote rendering based on updateType
received in onRoomStreamUpdate
.
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);
}
public void joinChannel(View view) {
if (checkSelfPermission()) {
ChannelMediaOptions options = new ChannelMediaOptions();
// For Live Streaming, set the channel profile as LIVE_BROADCASTING.
options.channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING;
// Set the client role as BROADCASTER or AUDIENCE according to the scenario.
if (audienceRole.isChecked()) { //Audience
options.clientRoleType = Constants.CLIENT_ROLE_AUDIENCE;
} else { //Host
options.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER;
// Display LocalSurfaceView.
setupLocalVideo();
localSurfaceView.setVisibility(View.VISIBLE);
// Start local preview.
mRtcEngine.startPreview();
}
audienceRole.setEnabled(false); // Disable the switch
// Join the channel with a temp token.
// You need to specify the user ID yourself, and ensure that it is unique in the channel.
mRtcEngine.joinChannel(token, channelName, uid, options);
} else {
Toast.makeText(getApplicationContext(), "Permissions was not granted", Toast.LENGTH_SHORT).show();
}
}
public void leaveChannel() {
mRtcEngine.leaveChannel()
}
To log in to a room, you can call the loginRoom
method.
To log out, you can call the logoutRoom
method.
Token authentication and AppSign authentication are mutually exclusive. Do not use them together:
appSign
is used when creating the engine, there is no need to pass a token when calling loginRoom
.appSign
is not used when creating the engine, a token is required for authentication when logging into the room: when calling loginRoom
method, the token needs to be set in the token
parameter of ZegoRoomConfig
.For details, see Use Tokens for authentication.
ZegoRoomConfig roomConfig = new ZegoRoomConfig();
roomConfig.token = "....";
ZegoExpressEngine.getEngine().loginRoom(roomID, user, roomConfig, ...)
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();
}
private void setupLocalVideo() {
FrameLayout container = findViewById(R.id.local_video_view_container);
// Create a SurfaceView object and add it as a child to the FrameLayout.
localSurfaceView = new SurfaceView(getBaseContext());
container.addView(localSurfaceView);
// Call setupLocalVideo with a VideoCanvas having uid set to 0.
mRtcEngine.setupLocalVideo(new VideoCanvas(localSurfaceView, VideoCanvas.RENDER_MODE_HIDDEN, 0));
// Display LocalSurfaceView.
localSurfaceView.setVisibility(View.VISIBLE);
// Start local preview.
mRtcEngine.startPreview();
}
private void setupLocalVideo() {
mRtcEngine.stopPreview();
}
Similar to Agora's video rendering method, on the Android platform, you can use SurfaceView
or TextureView
to render the video.
We recommended using
TextureView
for better performance.
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();
}
There is a significant difference between the two:
setClientRole
to switch the user role from the audience to the host.startPublishingStream
, stopPublishingStream
, startPlayingStream
, and stopPlayingStream
.During the migration process, note that "code logic related to user roles" also needs to be migrated, meaning that instead of "managing roles through Agora SDK", it should be changed to "managing roles through your app's code".
ChannelMediaOptions options;
options.channelProfile = CHANNEL_PROFILE_LIVE_BROADCASTING;
options.clientRoleType = CLIENT_ROLE_BROADCASTER;
options.autoSubscribeAudio = true;
options.autoSubscribeVideo = true;
options.publishCameraTrack = true;
mRtcEngine.joinChannel(APP_TOKEN, szChannelId, 0, options);
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();
}
To start playing a remote video stream, call the startPlayingStream
method.
And to stop the stream playing, call the stopPlayingStream
method to stop.
When a new stream is added in the
Room
where the user is located, theZegoEventHandler
will receive theonRoomStreamUpdate
event notification. You can obtain detailed information about the stream, such asuserID
andstreamID
, from this callback.
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);
}
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.
We recommend that you first review the Quick Start Basic Demo to gain a deeper understanding of the content of this doc and to learn the basic usage of the ZEGOCLOUD SDK. Then, based on your specific use case, you can select the Best Practices Demo for reference and begin the migration process.
Quick Start Basic Demo: Video Call, Live Streaming.
Best Practices Demo: Call Invitation, Co-Hosting.
SDK migration requires different migration plans to be selected based on the scenario and market conditions. Below will introduce three common migration plans.
As shown in the figure, there are four situations in call negotiation. Only when both parties use the new version application, the ZEGOCLOUD SDK is used for communication.
1.1 Logic introduction
Add an application version field in the call invitation and reply to the call invitation of the new version. The two parties determine whether to use the ZEGOCLOUD SDK for communication based on the version number during the negotiation process.
The reason for adding the version number field here is for function extension and easy control of the use of new version functions.
Not Available for Older Versions means that if a user uses an older version application, they can not join live streaming started with the new version. There are two ways to make older version users not available:
The advantages and disadvantages of the two solutions are as follows:
Plan | Advantages | Disadvantages |
---|---|---|
Intercept when joining the live streaming | Can guide customers to update the version | Requires support for business server interception of old-version applications |
Intercept when getting the list of live-streaming rooms | No need for a pre-embedded entrance, the business server directly controls | The number of live streaming rooms visible to old-version users will decrease, which may cause user churn |
2.1. Logic introduction
The not available for older versions plan is divided into three stages:
2.1.1. SDK pre-embedding stage
During the SDK pre-embedding stage, ZEGOCLOUD SDK needs to be integrated into the application but is temporarily not enabled. When the proportion of new version users reaches more than 50%, the new version users can use the ZEGOCLOUD SDK to start live streaming through business server control. At the beginning stage, the application configuration defaults to using the Agora SDK.
2.1.2. Dual-version SDK coexistence stage
When the proportion of new version users reaches more than 50%, the business server modifies the application configuration to allow new versions of the application to use the ZEGOCLOUD SDK to start live streaming. At this time, interception needs to be enabled, and old versions of the application can not join new version live streaming rooms.
2.1.3. Forced update stage
When the proportion of new version applications reaches more than 90%, forced updates can be launched. Forced updates can reduce the maintenance cost of the R&D team and facilitate the operation team to carry out activity operations.
Forced updates are suitable for use when the number of users is small. Generally, when the version retention rate is 10%, a forced update can be initiated. This can ensure that the user experience is improved and the maintenance cost for the development team.
If you have any suggestions or comments, feel free to share them with us via Discord. We value your feedback.