Learn how to integrate the ARTC SDK into your Mac project to build a simple real-time communication app for interactive live streaming and video calls.
Key concepts
Before you begin, it helps to understand the following key concepts:
-
ARTC SDK: Alibaba Cloud's SDK for quickly implementing real-time audio and video interactions.
-
GRTN: Alibaba Cloud's Global Realtime Transport Network, providing ultra-low latency, high-quality, secure, and reliable audio and video communication services.
-
channel: A virtual room for real-time audio and video interactions.
-
host: A role that allows a user to publish audio and video streams in a channel and subscribe to streams published by other hosts.
-
viewer: A role that enables a user to subscribe to audio and video streams in a channel but not publish streams.
-
Call
setChannelProfileto set the channel scenario, and then call joinChannel to join a channel:-
In a video call scenario, all users have the host role and can publish and subscribe to streams.
-
In an interactive streaming scenario, you must call
setClientRoleto set the user role. Set the role to host for users who will publish a stream. If a user only needs to subscribe to a stream, set their role to viewer.
-
-
After joining a channel, a user's role determines whether they can publish or subscribe to streams:
-
All users in a channel can subscribe to its audio and video streams.
-
A host can publish audio and video streams in the channel.
-
If a viewer needs to publish a stream, they must call the
setClientRolemethod to switch their role to host.
-
Sample project
Alibaba Cloud ARTC SDK provides an open source sample project for real-time audio and video interaction. Download or view the sample code.
Prerequisites
-
Development tool: Xcode 14.0 or later. We recommend the latest stable release.
-
Test device: A Mac running macOS 10.13 or later.
-
Network environment: A stable network connection.
-
Application preparation: Obtain the AppID and AppKey for your Real-Time Communication application. For details, see Create an application.
Create a project (optional)
This section explains how to create a project and add the required permissions for real-time audio and video interaction. If you already have a project, skip this section.
-
Open Xcode and choose File → New → Project. Select an app template and click Next. Set Interface to Storyboard and Language to Objective-C or Swift.
-
As needed, update the project configuration, including the bundle identifier, code signing, and deployment target.
Configure the project
Step 1: Configure permissions
In the Project Navigator, click the project file and switch to the Info tab. Add permissions for the camera and microphone.
|
Key |
Type |
Value |
|
Privacy - Microphone Usage Description |
String |
Describe the purpose for using the microphone, for example: "Required for voice chat." |
|
Privacy - Camera Usage Description |
String |
Describe the purpose for using the camera, for example: "Required for video chat." |
To publish your app to the App Store, you must enable sandbox mode. Some sandbox settings are required, while others are optional and depend on your app's features.
Navigate to the Signing & Capabilities tab. In the App Sandbox section, the required options are Incoming Connections (Server) and Outgoing Connections (Client) under Network, and Camera and Audio Input under Hardware.
For optional settings, if your app does not access local files, you can set the User Selected File permission to None. If your app includes features like playing background music or pushing local files, set the User Selected File permission to Read/Write.
Step 2: Import the ARTC SDK
-
Download the latest ARTC SDK package from SDK Download and decompress it.
-
Copy the files from the SDK package to your project directory. If you do not use AAC for audio encoding, you can skip adding
PluginAAC.framework. -
Open Xcode and add the dynamic libraries from the SDK. Set the Embed property for these libraries to Embed & Sign.
Step 3: Create the user interface
Create a user interface based on your real-time audio and video interaction scenario. For example, in a multi-user video call scenario, you can create a Window Controller view. Add a Custom View control to the Video Chat Controller's view. This control serves as a container. When a user joins the call, add their call view to this container. When a user leaves, remove their view and refresh the layout.
The Video Chat Controller's view contains two video rendering areas, Local View and Remote View, a Scroll View (with an embedded Text View), and a Leave Channel Button. The Window Controller is linked to the Video Chat Controller's content through a segue.
Procedure
This section explains how to use the ARTC SDK to build a basic real-time audio and video application. You can copy the code sample to test the features quickly, then follow the steps to understand the core API calls.
The following diagram shows the basic workflow for a real-time audio and video call:
The following code sample shows a basic implementation of an audio and video call:
1. Request permissions
The SDK checks for necessary permissions when a user joins a call. However, to ensure a smooth user experience, we recommend that you request camera and microphone permissions before you initiate the call.
#import <AVFoundation/AVFoundation.h>
@implementation PrivacyAuthorizer
+ (void)authorCamera:(void (^ __nullable)(BOOL granted))completion{
dispatch_block_t workBlock;
if (@available(macOS 10.14, *)) {
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if(authStatus == AVAuthorizationStatusAuthorized) {
workBlock = ^{
if (completion) completion(YES);
};
// do your logic
} else if(authStatus == AVAuthorizationStatusDenied || authStatus == AVAuthorizationStatusRestricted){
workBlock = ^{
if (completion) completion(NO);
};
// denied
} else if(authStatus == AVAuthorizationStatusNotDetermined){
// not determined?!
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
[PrivacyAuthorizer authorCamera:completion];
}];
return;
} else {
// impossible, unknown authorization status
}
}else {
workBlock = ^{
if (completion) completion(YES);
};
}
dispatch_async(dispatch_get_main_queue(), workBlock);
}
+ (void)authorMicphone:(void (^ __nullable)(BOOL granted))completion{
dispatch_block_t workBlock;
if (@available(macOS 10.14, *)) {
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
if(authStatus == AVAuthorizationStatusAuthorized) {
workBlock = ^{
if (completion) completion(YES);
};
// do your logic
} else if(authStatus == AVAuthorizationStatusDenied || authStatus == AVAuthorizationStatusRestricted){
workBlock = ^{
if (completion) completion(NO);
};
// denied
} else if(authStatus == AVAuthorizationStatusNotDetermined){
// not determined?!
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {
[PrivacyAuthorizer authorMicphone:completion];
}];
return;
} else {
// impossible, unknown authorization status
}
}else {
workBlock = ^{
if (completion) completion(YES);
};
}
dispatch_async(dispatch_get_main_queue(), workBlock);
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
// Check permissions
[PrivacyAuthorizer authorCamera:^(BOOL granted) {
if (!granted) {
NSLog(@"Camera_granted == %hhd",granted);
dispatch_async(dispatch_get_main_queue(), ^{
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:@"Camera permission is not enabled. Please enable it in <Security & Privacy>."];
[alert setAlertStyle:NSAlertStyleInformational];
[alert beginSheetModalForWindow:[self->loginViewController.view window] completionHandler:^(NSModalResponse returnCode) {
}];
});
}
}];
[PrivacyAuthorizer authorMicphone:^(BOOL granted) {
if (!granted) {
NSLog(@"Camera_granted == %hhd",granted);
dispatch_async(dispatch_get_main_queue(), ^{
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:@"Microphone permission is not enabled. Please enable it in <Security & Privacy>."];
[alert setAlertStyle:NSAlertStyleInformational];
[alert beginSheetModalForWindow:[self->loginViewController.view window] completionHandler:^(NSModalResponse returnCode) {
}];
});
}
}];
}
2. Get an authentication token
Joining an ARTC channel requires an authentication token to authenticate the user's identity. For the token generation rules, see Token Authentication. You can generate a token by using a single-parameter method or a multi-parameter method, and each method requires you to call a different joinChannel API in the SDK.
For production environments:
Because generating an authentication token requires your AppKey, hardcoding it in the client application poses a security risk. We strongly recommend that you generate tokens on your app server and pass them to the client.
For development and debugging:
During development and debugging, if your app server does not have token generation logic, you can temporarily use the logic from the API example to generate a temporary token. The following code shows an example:
#import <CommonCrypto/CommonDigest.h>
#import <CommonCrypto/CommonHMAC.h>
@implementation AppDefine
+ (NSString *)stringFromBytes:(uint8_t *)bytes length:(int)length {
NSMutableString *strArray = [NSMutableString string];
for (int i = 0; i < length; i++) {
[strArray appendFormat:@"%02x", bytes[i]];
}
return [strArray copy];
}
+(NSString*)generateJoinToken:(NSString *)oc_str {
const char * cstring = [oc_str UTF8String];
size_t length = [oc_str length] ;
uint8_t sha256_buffer[CC_SHA256_DIGEST_LENGTH];
CC_SHA256(cstring, (CC_LONG)length, sha256_buffer);
return [AppDefine stringFromBytes:sha256_buffer length:CC_SHA256_DIGEST_LENGTH];
}
3. Import the ARTC SDK
// Import the ARTC module
#import <AliRTCSdk/AliRTCSdk.h>
4. Create and initialize the engine
-
Create the RTC engine
Call the getInstance[1/2] method to create an AliRtcEngine instance.
@property (nonatomic, strong, nullable) AliRtcEngine *engine;
// Create the engine and set the delegate
_engine = [AliRtcEngine sharedInstance:self extras:nil] ;
...
-
Initialize the engine
-
Call
setChannelProfileto set the channel profile toAliRTCInteractiveLive(interactive live mode).Depending on your business needs, you can choose the interactive live mode for interactive entertainment scenarios, or the communication mode for one-to-one or one-to-many calls. Choosing the right mode ensures a smooth user experience and efficient use of network resources. You must call
setChannelProfilebefore callingsetClientRole.Mode
Publish
Subscribe
Description
Interactive live mode
-
Role-based restrictions apply. Only users with the broadcaster role can publish streams.
-
Participants can switch roles at any time during the session.
No role restrictions. All participants have permission to subscribe to streams.
-
In interactive live mode, events such as a broadcaster joining or leaving the session, or starting to publish a stream, are sent to the audience in real time. This ensures the audience is always aware of the broadcaster's status. Conversely, audience activities are not sent to broadcasters, which prevents disruption to the live stream.
-
If your application's requirements may change or you are unsure whether the audience will need to interact, we recommend using interactive live mode. This mode provides the flexibility to meet different interaction needs by changing user roles.
Communication mode
All participants can publish streams.
All participants can subscribe to streams.
-
In communication mode, all participants are aware of each other's presence.
-
This mode is functionally equivalent to assigning the broadcaster role to all users in interactive live mode. It simplifies operations by requiring fewer API calls to achieve the desired functionality.
-
-
Call
setClientRoleto set the user role toAliRTCSdkInteractive(broadcaster) orAliRTCSdkLive(audience). Note: The broadcaster role publishes and subscribes to streams by default. The audience role subscribes to streams by default, with local preview and publishing disabled.NoteWhen a user stops publishing by switching from the broadcaster role to the audience role, the SDK stops publishing local audio and video streams. When a user starts publishing by switching from the audience role to the broadcaster role, the SDK resumes publishing local streams. In either case, subscribed streams are not affected.
// Set the channel profile to interactive live mode. AliRtcInteractivelive is used for RTC scenarios. [_engine setChannelProfile:AliRtcInteractivelive]; // Set the user role. Use AliRtcClientRoleInteractive to both publish and subscribe. Use AliRtcClientRolelive to only subscribe. [_engine setClientRole:AliRtcClientRoleInteractive];
-
-
Implement common callbacks
When you create the engine, you set a Delegate object to implement the necessary callbacks. The SDK first attempts to recover from exceptions internally. For errors it cannot resolve, it notifies your application through these callbacks.
Cause
Callback and parameters
Solution
Description
Authentication failed
The
resultparameter in theonJoinChannelResultcallback returnsAliRtcErrJoinBadToken.Your app must verify the authentication token.
If authentication fails when a user calls an API, the system returns an authentication failure error in the API's callback.
Authentication token is about to expire
onAuthInfoWillExpireYour app must retrieve a new authentication token and call
refreshAuthInfo.An authentication expiration error can occur when a user calls an API or during program execution. The error is reported through an API callback or a separate error callback.
Authentication token has expired
onAuthInfoExpiredYour app must have the user rejoin the channel.
An authentication expiration error can occur when a user calls an API or during program execution. The error is reported through an API callback or a separate error callback.
Network connection exception
The
onConnectionStatusChangecallback returnsAliRtcConnectionStatusFailed.Your app must have the user rejoin the channel.
The SDK can automatically recover from network interruptions for a certain period. However, if the disconnection time exceeds the preset threshold, a timeout occurs. In this case, your app should check the network status and guide the user to rejoin the channel.
Kicked offline
onBye-
AliRtcOnByeUserReplaced: Check if multiple users are using the same UserID. -
AliRtcOnByeBeKickedOut: The user was kicked by the app server and must rejoin the channel. -
AliRtcOnByeChannelTerminated: The channel was destroyed, and the user must rejoin.
The RTC service allows an administrator to remove participants.
Local device exception
onLocalDeviceExceptionYour app needs to check permissions and whether the hardware is functioning correctly.
The RTC service supports device detection and diagnostics. When a local device exception occurs, the service notifies the client through a callback. If the SDK cannot resolve the issue, your app needs to intervene and check the device status.
- (void)onJoinChannelResult:(int)result channel:(NSString *_Nonnull)channel elapsed:(int) elapsed { dispatch_async(dispatch_get_main_queue(), ^{ [self statusListBoxAddString:[[NSString alloc]initWithFormat:@"join channel ret=%d cid:%@ elapsed:%d", result, channel, elapsed ] withColor:[NSColor redColor]]; }); } - (void)onLeaveChannelResult:(int)result stats:(AliRtcStats)stats { dispatch_async(dispatch_get_main_queue(), ^{ [self statusListBoxAddString:[[NSString alloc]initWithFormat:@"leave channel ret=%d call duration:%lld", result, stats.call_duration ] withColor:[NSColor redColor]]; }); } - (void)onAuthInfoWillExpire { dispatch_async(dispatch_get_main_queue(), ^{ }); } - (void)onAuthInfoExpired { /* token expired! */ dispatch_async(dispatch_get_main_queue(), ^{ }); } - (void)onConnectionStatusChange:(AliRtcConnectionStatus)status reason:(AliRtcConnectionStatusChangeReason)reason { dispatch_async(dispatch_get_main_queue(), ^{ }); } - (void)onRemoteUserOnLineNotify:(NSString *)uid elapsed:(int)elapsed { dispatch_async(dispatch_get_main_queue(), ^{ [self statusListBoxAddString:[[NSString alloc]initWithFormat:@"uid:%@ online reason:%d", uid, elapsed] withColor:[NSColor blueColor]]; }); } - (void)onRemoteUserOffLineNotify:(NSString *)userID offlineReason:(AliRtcUserOfflineReason)reason{ // Remove the corresponding canvas dispatch_async(dispatch_get_main_queue(), ^{ /* This function is recommended to be called on the main thread */ [self->_engine setRemoteViewConfig:nil uid:userID forTrack:AliRtcVideoTrackScreen]; [self->_engine setRemoteViewConfig:nil uid:userID forTrack:AliRtcVideoTrackCamera]; [self statusListBoxAddString:[[NSString alloc]initWithFormat:@"uid:%@ offline reason:%ld", userID, reason] withColor:[NSColor blueColor]]; }); } - (void)onLocalDeviceException:(AliRtcLocalDeviceType)deviceType exceptionType:(AliRtcLocalDeviceExceptionType)exceptionType message:(NSString *_Nullable)msg { dispatch_async(dispatch_get_main_queue(), ^{ // Process on the main thread }); } - (void)onBye:(int)code { dispatch_async(dispatch_get_main_queue(), ^{ // Process on the main thread }); } -
5. Configure audio and video properties
-
Configure audio properties
Call
setAudioProfileto set the audio encoding mode and audio scene./* Configure the audio profile and scene */ [_engine setAudioProfile:AliRtcEngineHighQualityMode audio_scene:AliRtcSceneMusicMode]; -
Configure video properties
You can set properties for the published video stream, such as resolution, bitrate, and frame rate.
// Set video encoder configuration AliRtcVideoEncoderConfiguration * videoConfig = [[AliRtcVideoEncoderConfiguration alloc]init]; videoConfig.dimensions = CGSizeMake(1280, 720); videoConfig.bitrate = 1200; // kbps videoConfig.frameRate = 15; videoConfig.keyFrameInterval = 2000; // ms [_engine setVideoEncoderConfiguration:videoConfig];
6. Configure publishing and subscribing
Configure the publishing of audio and video streams and set the default behavior to subscribe to all user streams:
-
Call
publishLocalAudioStreamto publish the audio stream. -
Call
publishLocalVideoStreamto publish the video stream. For an audio-only call, you can set this to false.
// By default, the SDK publishes the audio stream, so this call is optional.
[_engine publishLocalAudioStream:true];
// By default, the SDK publishes the video stream for video calls, so this call is optional.
// For an audio-only call, you must call publishLocalVideoStream(false) to disable video publishing.
[_engine publishLocalVideoStream:true];
// Set the default behavior to subscribe to remote audio and video streams
[_engine setDefaultSubscribeAllRemoteAudioStreams:true];
[_engine subscribeAllRemoteAudioStreams:true];
[_engine setDefaultSubscribeAllRemoteVideoStreams:true];
[_engine subscribeAllRemoteVideoStreams:true];
Note: By default, the SDK automatically publishes local audio and video streams and subscribes to all remote streams in the channel. You can call the methods described above to change this behavior.
7. Start local preview
-
Call
setLocalViewConfigto set up the local view and configure its display properties. -
Call the
startPreviewmethod to start the local video preview.
AliVideoCanvas * canvas = [[AliVideoCanvas alloc] init];
canvas.view = _localView ;
canvas.renderMode = AliRtcRenderModeAuto;
canvas.rotation = AliRtcRotationMode_0 ;
[_engine setLocalViewConfig:canvas forTrack:AliRtcVideoTrackCamera];
int ret = [_engine startPreview];
8. Join a channel
Call joinChannel to join a channel. We recommend that you use the single-parameter method by calling the joinChannel[3/4] API. After the call, check both its return value and the result from the onJoinChannelResult callback. If the function returns 0 and the result is 0, you have successfully joined the channel. Otherwise, check whether the provided token is invalid.
uint64_t timestamp = time(NULL) + 24 * 60 * 60;
AliRtcAuthInfo *info = [[AliRtcAuthInfo alloc]init];
info.channelId = self->channelId;
info.userId = self->userId;
info.appId = @ARTC_APP_ID;
info.nonce = @"";
info.timestamp = timestamp;
NSString *token_str = [NSString stringWithFormat:@"%@%@%@%@%@%lld",
info.appId,
@ARTC_APP_KEY,
info.channelId,
info.userId,
@"",
info.timestamp];
info.token = [AppDefine generateJoinToken:token_str];
int ret = [_engine joinChannel:info name:userName onResult:^(NSInteger errCode, NSString * _Nonnull channel, NSInteger elapsed) {
if( errCode != 0 && ![self->_engine isInCall] ){
// Handle errors
} else {
}
}];
-
After joining the channel, the SDK automatically publishes and subscribes to streams based on the pre-join configurations.
-
The SDK enables automatic publishing and subscribing by default to reduce the number of API calls your client needs to make.
9. Set up the remote view
When a remote user starts or stops publishing a stream, the onRemoteTrackAvailableNotify callback is triggered. You can set up or remove the remote view within this callback. The following code shows an example:
- (void)onRemoteTrackAvailableNotify:(NSString *)uid
audioTrack:(AliRtcAudioTrack)audioTrack
videoTrack:(AliRtcVideoTrack)videoTrack {
dispatch_async(dispatch_get_main_queue(), ^{
if ( videoTrack == AliRtcVideoTrackCamera ) {
[self->_engine setRemoteViewConfig:nil uid:uid forTrack:AliRtcVideoTrackScreen];
AliVideoCanvas * remoteCanvas = [[AliVideoCanvas alloc] init];
remoteCanvas.view = self->_remoteView ;
remoteCanvas.rotation = AliRtcRotationMode_0 ;
remoteCanvas.renderMode = AliRtcRenderModeAuto;
[self->_engine setRemoteViewConfig:remoteCanvas uid:uid forTrack:AliRtcVideoTrackCamera];
}
if ( videoTrack == AliRtcVideoTrackScreen ) {
[self->_engine setRemoteViewConfig:nil uid:uid forTrack:AliRtcVideoTrackCamera];
AliVideoCanvas * remoteCanvas = [[AliVideoCanvas alloc] init];
remoteCanvas.view = self->_remoteView ;
remoteCanvas.rotation = AliRtcRotationMode_0 ;
// Letterbox mode is recommended for screen sharing
remoteCanvas.renderMode = AliRtcRenderModeFill ;
[self->_engine setRemoteViewConfig:remoteCanvas uid:uid forTrack:AliRtcVideoTrackScreen];
}
if ( videoTrack == AliRtcVideoTrackNo ) {
[self->_engine setRemoteViewConfig:nil uid:uid forTrack:AliRtcVideoTrackCamera];
[self->_engine setRemoteViewConfig:nil uid:uid forTrack:AliRtcVideoTrackScreen];
}
});
}
10. Leave channel and destroy engine
When the real-time audio and video interaction ends, leave the channel and destroy the engine. Follow these steps to end the session:
-
Call
stopPreviewto stop the video preview. -
Call
leaveChannelto leave the channel. -
Call
destroyto destroy the engine and release its resources.
[_engine stopPreview];
[_engine leaveChannel] ;
[AliRtcEngine destroy] ;
_engine = nil ;
11. See it in action
In the ARTC Example v1.0 configuration form, set Channel to 12356, UserID to Test01, UserName to Test01, and select VideoChat for ExampleType. Then, click Join to join the channel.
After joining the channel, the local and remote video views are displayed side-by-side in the upper part of the window, and the video renders correctly. The log output shows join channel ret=0 cid:12356 elapsed:56, which indicates a successful join. The audio stream state changes from 0 to 2, then to 3, and the video stream state changes from 0 to 2, indicating that the audio and video streams are ready. You can click LeaveChannel in the bottom-right corner to leave the channel.