This guide describes how to implement basic audio and video functions with the ZEGO Express SDK.
Basic concepts:
ZEGO Express SDK: The real-time audio and video SDK developed by ZEGOCLOUD to help you quickly build high-quality, low-latency, and smooth real-time audio and video communications into your apps across different platforms, with support for massive concurrency.
Stream publishing: The process of the client app capturing and transmitting audio and video streams to the ZEGOCLOUD Real-Time Audio and Video Cloud.
Stream playing: The process of the client app receiving and playing audio and video streams from the ZEGOCLOUD Real-Time Audio and Video Cloud.
Room: The service for organizing groups of users, allowing users in the same room to send and receive real-time audio, video, and messages to each other.
For more basic concepts, refer to the Glossary.
Before you begin, make sure you complete the following steps:
If the version of the ZEGO Express SDK you are using is under 2.17.0, to get the AppSign, contact the ZEGOCLOUD Technical Support. To upgrade the authentication mode from using the AppSign to Token, see Guide for upgrading the authentication mode from using the AppSign to Token.
The following is sample code for a basic video call and can be used for reference during development.
//
// ViewController.m
// ZegoExpressExample
//
// Copyright © 2022 Zego. All rights reserved.
//
#import "ViewController.h"
#import <ZegoExpressEngine/ZegoExpressEngine.h>
@interface ViewController ()<ZegoEventHandler>
// View for playing the audio and video streams of other users.
@property (strong, nonatomic) UIView *remoteUserView;
// Button for starting a video call.
@property (strong, nonatomic) UIButton *startVideoTalkButton;
// Button for stopping a video call.
@property (strong, nonatomic) UIButton *stopVideoTalkButton;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setupUI];
}
- (void)setupUI {
self.remoteUserView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 180, 250)];
self.remoteUserView.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview:self.remoteUserView];
self.startVideoTalkButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.view addSubview:self.startVideoTalkButton];
self.startVideoTalkButton.frame = CGRectMake(100, self.view.bounds.size.height - 280, 150, 50);
[self.startVideoTalkButton.titleLabel setFont:[UIFont systemFontOfSize:32]];
[self.startVideoTalkButton setTitle:@"Start" forState:UIControlStateNormal];
[self.startVideoTalkButton addTarget:self action:@selector(startVideoTalk:) forControlEvents:UIControlEventTouchUpInside];
self.stopVideoTalkButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.view addSubview:self.stopVideoTalkButton];
self.stopVideoTalkButton.frame = CGRectMake(100, self.view.bounds.size.height - 200, 150, 50);
[self.stopVideoTalkButton.titleLabel setFont:[UIFont systemFontOfSize:32]];
[self.stopVideoTalkButton setTitle:@"Stop" forState:UIControlStateNormal];
[self.stopVideoTalkButton setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
[self.stopVideoTalkButton addTarget:self action:@selector(stopVideoTalk:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)startVideoTalk:(UIButton *)button {
[self createEngine];
[self loginRoom];
[self startPublish];
}
- (void)stopVideoTalk:(UIButton *)button {
[[ZegoExpressEngine sharedEngine] logoutRoom];
[ZegoExpressEngine destroyEngine:^{
}];
}
- (void)createEngine {
ZegoEngineProfile *profile = [[ZegoEngineProfile alloc] init];
// Register with the official website and obtain the data in the format similar to 1234567890.
profile.appID = <#appID#>;
// Register with the official website and obtain a string of 64 characters in the format similar to @"0123456789012345678901234567890123456789012345678901234567890123".
profile.appSign = @"<#appSign#>";
// General scenario. Select a scenario based on your service requirements.
profile.scenario = ZegoScenarioDefault;
// Create an engine and set `eventHandler` to `self`. If callback registration is not needed, `eventHandler` can be set to `nil`, and you can call the `-setEventHandler:` method to set the callback later on.
[ZegoExpressEngine createEngineWithProfile:profile eventHandler:self];
}
- (void)loginRoom {
// The value of `roomID` is generated locally and must be globally unique. Users must log in to the same room to call each other.
NSString *roomID = @"room1";
// Create a user object. The `ZegoUser` constructor `userWithUserID` will set `userName` to the value of `userID`. The `userID` and `userName` parameters cannot be set to `nil`. Otherwise, logging in to a room will fail.
// The value of `userID` is generated locally and must be globally unique.
ZegoUser *user = [ZegoUser userWithUserID:@"user1"];
// The `onRoomUserUpdate` callback can be received only when `ZegoRoomConfig` in which the `isUserStatusNotify` parameter is set to `true` is passed.
ZegoRoomConfig *roomConfig = [[ZegoRoomConfig alloc] init];
// If you use the AppSign for authentication, you do not need to set the `token` parameter. If you want to use the Token for authentication, which is securer, see [Guide for upgrading the authentication mode from using the AppSign to Token](https://docs.zegocloud.com/faq/token_upgrade).
// roomConfig.token = @"<#token#>";
roomConfig.isUserStatusNotify = YES;
// Log in to a room.
[[ZegoExpressEngine sharedEngine] loginRoom:roomID user:user config:roomConfig callback:^(int errorCode, NSDictionary * _Nullable extendedData) {
// (Optional callback) Room login result. This callback is sufficient if you only need to check the login result.
if (errorCode == 0) {
NSLog(@"Room login successful.");
} else {
// Login failed. For details, see [Error codes\|_blank](/404).
NSLog(@"Room login failed.");
}
}];
}
- (void)startPublish {
// Set the local preview view and start the preview. The default view mode of the SDK is used and the entire view is filled through proportional scaling.
[[ZegoExpressEngine sharedEngine] startPreview:[ZegoCanvas canvasWithView:self.view]];
// After calling the `loginRoom` method, call this method to publish streams.
// Ensure that the value of `streamID` is globally unique under the same AppID. If different streams are published with the same `streamID`, the ones that are published after the first one will fail.
[[ZegoExpressEngine sharedEngine] startPublishingStream:@"stream1"];
}
// After the `loginRoom` method is called, you can use the `onRoomStateChanged` callback to listen for the room connection status in real time.
// For details, see https://docs.zegocloud.com/article/13398.
-(void)onRoomStateChanged:(ZegoRoomStateChangedReason)reason errorCode:(int)errorCode extendedData:(NSDictionary *)extendedData roomID:(NSString *)roomID {
if(reason == ZegoRoomStateChangedReasonLogining) {
// 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 == ZegoRoomStateChangedReasonLogined) {
// 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 == ZegoRoomStateChangedReasonLoginFailed) {
// 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.
} else if(reason == ZegoRoomStateChangedReasonReconnecting) {
// The room connection is temporarily interrupted. The SDK will retry internally if the interruption is caused by poor network quality.
} else if(reason == ZegoRoomStateChangedReasonReconnected) {
// 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 == ZegoRoomStateChangedReasonReconnectFailed) {
// 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.
} else if(reason == ZegoRoomStateChangedReasonKickOut) {
// 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.
} else if(reason == ZegoRoomStateChangedReasonLogout) {
// 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 == ZegoRoomStateChangedReasonLogoutFailed) {
// 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.
}
}
// When another user in the same room publishes or stops publishing streams, you will receive a notification of stream increase or decrease of the user.
- (void)onRoomStreamUpdate:(ZegoUpdateType)updateType streamList:(NSArray<ZegoStream *> *)streamList extendedData:(NSDictionary *)extendedData roomID:(NSString *)roomID {
// When `updateType` is set to `ZegoUpdateTypeAdd`, an audio and video stream is added, and you can call the `startPlayingStream` method to play the stream.
if (updateType == ZegoUpdateTypeAdd) {
// Start to play streams. Set the view for rendering the remote streams. The default view mode of the SDK is used and the entire view is filled through proportional scaling.
// In the following code, the value of `remoteUserView` is the same as that of `View` of the UI. For conciseness of the sample code, only the first stream in the list of newly added audio and video streams is played here. In a real service, it is recommended that you traverse the stream list to play each stream.
NSString *streamID = streamList[0].streamID;
[[ZegoExpressEngine sharedEngine] startPlayingStream:streamID canvas:[ZegoCanvas canvasWithView:self.remoteUserView]];
}
}
// You will receive this callback when another user logs in to or out of the room. If the value of `ZegoUpdateType` in the callback is `ZegoUpdateTypeAdd`, a user has logged in to the room. If the value of `ZegoUpdateType` in the callback is `ZegoUpdateTypeDelete`, a user has logged out of the room.
// This callback can be received only when `ZegoRoomConfig` in which the `isUserStatusNotify` parameter is set to `YES` is passed in the `loginRoom` method.
// The `onRoomUserUpdate` callback may be invalid for rooms with more than 500 users. If such rooms need to be supported in your service, contact ZEGOCLOUD technical support.
- (void)onRoomUserUpdate:(ZegoUpdateType)updateType userList:(NSArray<ZegoUser *> *)userList roomID:(NSString *)roomID {
if (updateType == ZegoUpdateTypeAdd) {
for (ZegoUser *user in userList) {
NSLog(@"User %@ logged in to room %@.", user.userName, roomID);
}
} else if (updateType == ZegoUpdateTypeDelete) {
for (ZegoUser *user in userList) {
NSLog(@"User %@ logged out of room %@.", user.userName, roomID);
}
}
}
// Status notification of audio and video stream publishing.
// This callback is received when the status of audio and video stream publishing of a user changes. If an exception occurs during stream publishing due to a network interruption, the SDK retries to publish the streams and triggers this status change notification.
- (void)onPublisherStateUpdate:(ZegoPublisherState)state errorCode:(int)errorCode extendedData:(NSDictionary *)extendedData streamID:(NSString *)streamID {
if (errorCode != 0) {
NSLog(@"Stream publishing exception. errorCode: %d", errorCode);
} else {
switch (state) {
case ZegoPublisherStatePublishing:
NSLog(@"Publishing streams.");
break;
case ZegoPublisherStatePublishRequesting:
NSLog(@"Requesting stream publishing.");
break;
case ZegoPublisherStateNoPublish:
NSLog(@"Streams not published.");
break;
}
}
}
// 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.
- (void)onPlayerStateUpdate:(ZegoPlayerState)state errorCode:(int)errorCode extendedData:(NSDictionary *)extendedData streamID:(NSString *)streamID {
if (errorCode != 0) {
NSLog(@"Stream playing exception. streamID: %@, errorCode: %d", streamID, errorCode);
} else {
switch (state) {
case ZegoPlayerStatePlaying:
NSLog(@"Playing streams.");
break;
case ZegoPlayerStatePlayRequesting:
NSLog(@"Requesting stream playing.");
break;
case ZegoPlayerStateNoPlay:
NSLog(@"Streams not played.");
break;
}
}
}
// You can use the `onNetworkQuality` callback to listen for the upstream and downstream network quality of users (including yourself) in a room. This callback will be received every two seconds. For details about network quality levels, see `ZegoStreamQualityLevel`.
- (void)onNetworkQuality:(NSString *)userID upstreamQuality:(ZegoStreamQualityLevel)upstreamQuality downstreamQuality:(ZegoStreamQualityLevel)downstreamQuality {
if (userID == nil) {
// Network quality of the local user.
// NSLog(@"My upstream network quality is %lu.", (unsigned long)upstreamQuality);
// NSLog(@"My downstream network quality is %lu.", (unsigned long)downstreamQuality);
} else {
// Network quality of other users in a room.
// NSLog(@"The upstream network quality of user %@ is %lu.", userID, (unsigned long)upstreamQuality);
// NSLog(@"The downstream network quality of user %@ is %lu.", userID, (unsigned long)downstreamQuality);
}
/*
ZegoStreamQualityLevelExcellent: The network quality is excellent.
ZegoStreamQualityLevelGood: The network quality is good.
ZegoStreamQualityLevelMedium: The network quality is medium.
ZegoStreamQualityLevelBad: The network quality is bad.
ZegoStreamQualityLevelDie: The network is abnormal.
ZegoStreamQualityLevelUnknown: The network quality is unknown.
*/
}
@end
The following diagram shows the basic process of User A playing a stream published by User B:
Create 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:
Import the header file
import im.zego.zegoexpress.ZegoExpressEngine;
import im.zego.zegoexpress.callback.IZegoDestroyCompletionCallback;
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.ZegoScenario;
import im.zego.zegoexpress.constants.ZegoStreamQualityLevel;
import im.zego.zegoexpress.constants.ZegoUpdateType;
import im.zego.zegoexpress.entity.ZegoCanvas;
import im.zego.zegoexpress.entity.ZegoEngineProfile;
import im.zego.zegoexpress.entity.ZegoRoomConfig;
import im.zego.zegoexpress.entity.ZegoStream;
import im.zego.zegoexpress.entity.ZegoUser;
ZegoExpressEngine engine;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Request necessary camera and audio recording permissions before the call
requestPermission();
// Start call button
findViewById(R.id.startButton).setOnClickListener(new View.OnClickListener() {
// Click to start the call
@Override
public void onClick(View view) {
// Create an instance of the Express SDK
createEngine();
// Set up event handlers
setEventHandler();
// Log in to the room
loginRoom();
// Start preview and publishing
startPublish();
}
});
// Stop call button
findViewById(R.id.stopButton).setOnClickListener(new View.OnClickListener() {
// Click to stop the call
@Override
public void onClick(View view) {
engine.logoutRoom();
ZegoExpressEngine.destroyEngine(new IZegoDestroyCompletionCallback() {
@Override
public void onDestroyCompletion() {
// Successfully destroyed
}
});
}
});
}
// Request camera and audio recording permissions
private void requestPermission() {
String[] permissionNeeded = {
"android.permission.CAMERA",
"android.permission.RECORD_AUDIO"};
if (ContextCompat.checkSelfPermission(getApplicationContext(), "android.permission.CAMERA") != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(getApplicationContext(), "android.permission.RECORD_AUDIO") != PackageManager.PERMISSION_GRANTED) {
// 101 is the requestCode, it can be any number greater than 0, and will be passed to the permission request result callback onRequestPermissionsResult
requestPermissions(permissionNeeded, 101);
}
}
Create a ZegoExpressEngine instance
Call the createEngine
method and set the appID
and appSign
parameters to the applied AppID and AppSign, respectively.
Use the scenario
parameter to pass a room scenario matching the audio and video service of your app. For details, see Config your video based on scenes.
Register a callback. You can set eventHandler
to an implemented object (for example, self
) that conforms to the IZegoEventHandler
protocol.
The SDK also supports Token-based authentication. If you want to upgrade the authentication mode, see Guide for upgrading the authentication mode from using the AppSign to Token.
// Create a ZegoExpress instance and listen for common events
void createEngine() {
ZegoEngineProfile profile = new ZegoEngineProfile();
profile.appID = appID; // Please obtain from the official website, format: 1234567890L
profile.appSign = appSign; // Please obtain from the official website, format: @"0123456789012345678901234567890123456789012345678901234567890123" (64 characters in total)
profile.scenario = ZegoScenario.BROADCAST; // Specify the use of the live broadcast scenario (Please fill in the appropriate scenario according to your actual situation)
profile.application = getApplication();
engine = ZegoExpressEngine.createEngine(profile, null);
}
Call the loginRoom
method to log in to a room. If the room does not exist, calling this method will create and log in to the room. The values of the roomID
and user
parameters are generated locally, but the following conditions must be met:
roomID
must be globally unique under the same AppID. userID
must be globally unique under the same AppID. You are advised to associate userID
with the account system of your service. // Login to the room
void loginRoom() {
// The constructor of ZegoUser, public ZegoUser(String userID), sets "userName" to be the same as the parameter "userID". Both "userID" and "userName" cannot be "null", otherwise it will cause login failure.
ZegoUser user = new ZegoUser("user2");
ZegoRoomConfig roomConfig = new ZegoRoomConfig();
// If you use appsign for authentication, the token parameter does not need to be filled in; if you need to use a more secure authentication method: token authentication, please refer to [How to Upgrade from AppSign Authentication to Token Authentication](https://doc-en.zego.im/en/faq/token_upgrade?product=ExpressVideo&platform=all)
// roomConfig.token = ;
// Only ZegoRoomConfig with the parameter "isUserStatusNotify" set to "true" can receive the onRoomUserUpdate callback.
roomConfig.isUserStatusNotify = true;
// The roomID is generated locally and needs to be globally unique. Different users need to log in to the same room to communicate.
String roomID = "room1";
// Login to the room
engine.loginRoom(roomID, user, roomConfig, (int error, JSONObject extendedData)->{
// Login result, if you only care about the login result, pay attention to this callback
if (error == 0) {
// Login successful
Toast.makeText(this, "Login successful", Toast.LENGTH_LONG).show();
} else {
// Login failed, please refer to the errorCode description: https://doc-en.zego.im/en/article/4378
Toast.makeText(this, "Login failed, please refer to the errorCode description: https://doc-en.zego.im/en/article/4378", Toast.LENGTH_LONG).show();
}
});
}
After the loginRoom
method is called, you can use the onRoomStateChanged
callback to listen for the room connection status in real time.
Start the local video preview
To view the local videos, call the startPreview
method to set the preview view and start the local preview.
Publish streams to ZEGOCLOUD audio and video cloud
After the loginRoom
method is called, you can call the startPublishingStream
method and pass streamID
to publish the audio and video streams to ZEGOCLOUD audio and video cloud. You can use the onPublisherStateUpdate
callback to listen for the publishing status.
The value of streamID
is generated locally, but the following condition must be met:
The value of streamID
must be globally unique under the same AppID. If different streams are published with the same streamID
, the ones that are published after the first one will fail.
// Preview and start streaming
void startPublish() {
// Set the local preview view and start previewing, using the SDK's default mode, which scales and fills the entire view
ZegoCanvas previewCanvas = new ZegoCanvas(findViewById(R.id.preview));
engine.startPreview(previewCanvas);
// Start streaming
// Call loginRoom before calling this method to start streaming
// Within the same AppID, developers need to ensure that the "streamID" is globally unique. If different users push a stream with the same "streamID", the user who pushes the stream later will fail to start streaming.
engine.startPublishingStream("stream2");
}
@@@Warning_How_to_switch_devices@@@
During a video call, audio and video streams of other users need to be played.
When another user in the same room publishes audio and video streams to ZEGOCLOUD audio and video cloud, you will receive a notification of new audio and video streams through the onRoomStreamUpdate
callback and can obtain streamID
of a certain stream through ZegoStream
.
You can call startPlayingStream
in the callback and pass the value of streamID
to play the audio and video stream of the user. You can use the onPlayerStateUpdate
callback to listen for the playing status.
// Set event handler
void setEventHandler() {
engine.setEventHandler(new IZegoEventHandler() {
@Override
// We will receive notifications of audio and video stream changes from other users in the room here
public void onRoomStreamUpdate(String roomID, ZegoUpdateType updateType, ArrayList<ZegoStream> streamList, JSONObject extendedData) {
super.onRoomStreamUpdate(roomID, updateType, streamList, extendedData);
// When updateType is ZegoUpdateType.ADD, it means that there is a new audio and video stream added. We can call the startPlayingStream interface to pull and play the audio and video stream
if (updateType == ZegoUpdateType.ADD) {
// Start pulling the stream, set the remote stream rendering view, and use the SDK's default mode to scale and fill the entire view
ZegoStream stream = streamList.get(0);
String playStreamID = stream.streamID;
// The remoteUserView here is the TextureView on the UI. In order to make the example code more concise, we only pull the first stream in the list of newly added audio and video streams. In actual business scenarios, it is recommended for developers to loop through the streamList and pull each audio and video stream
ZegoCanvas playCanvas = new ZegoCanvas(findViewById(R.id.remoteUserView));
engine.startPlayingStream(playStreamID, playCanvas);
}
}
});
}
@@@Express_Java_Common_Callback@@@
1. Stop pushing stream and stop preview
Call the stopPublishingStream interface to stop sending the local audio and video streams to remote users.
// Stop pushing stream
engine.stopPublishingStream();
If local preview is enabled, call the stopPreview interface to stop previewing.
// Stop local preview
engine.stopPreview();
Stop pulling stream
Call the stopPlayingStream interface to stop pulling the audio and video streams pushed by remote users.
If the developer receives a notification of "decrease" in audio and video streams through the onRoomStreamUpdate callback, please promptly call the stopPlayingStream interface to stop pulling the stream to avoid pulling an empty stream and incurring additional costs. Alternatively, developers can choose an appropriate time to proactively call the stopPlayingStream interface to stop pulling the stream based on their business needs.
// Stop pulling stream
engine.stopPlayingStream("stream1");
Call the logoutRoom interface to logout the room.
// Logout room
engine.logoutRoom();
If the user no longer needs the audio and video functions, call the destroyEngine interface to destroy the engine and release resources such as microphone, camera, memory, and CPU.
If you need to listen for callbacks to ensure that the device hardware resources are released, you can pass in "callback" when destroying the engine. This callback is only used to send notifications, and developers cannot release resources related to the engine in the callback.
If you don't need to listen for callbacks, you can pass in "null".
ZegoExpressEngine.destroyEngine(null);
We recommend you run your project on a real device. If your app runs successfully, you should hear the sound and see the video captured locally from your device.
To test out the real-time audio and video features, visit the ZEGO Express Web Demo, and enter the same AppID
, Server
and RoomID
to join the same room. If it runs successfully, you should be able to view the video from both the local side and the remote side, and hear the sound from both sides as well.
In audio-only scenarios, no video will be captured and displayed.
If you kill the process directly after calling logoutRoom , there is a certain probability that the logoutRoom signal will not be sent. In this case, the ZEGO server can only wait for the heartbeat timeout to consider that the user has exited the room. To ensure that the logoutRoom signal is sent, it is recommended to call destroyEngine and kill the process after receiving the callback.