On this page

Implement Live Digital Human Broadcasting

2026-08-14

This document describes how to quickly integrate the client SDKs (ZEGO Express SDK and Digital Human SDK) to implement live digital human broadcasting.
Unlike digital human video calls, digital human broadcasting is a one-way viewing scenario - users do not interact with the digital human, they simply play stream the digital human's broadcast content for viewing.
The client only needs to log in to an RTC room and play stream; it does not need to capture or publish stream the user's audio and video. After creating a broadcast instance, the server can proactively make the digital human broadcast specified text via the TTS API.
Suitable for scenarios such as digital human live streaming, news broadcasting, event hosting, and scripted broadcasts.

Differences from Digital Human Video Calls

ItemDigital Human Video CallLive Digital Human Broadcasting
Interaction modeTwo-way: the digital human responds after the user speaksOne-way: users only view the digital human's broadcast content
Use casesConversational AI, AI customer service, digital human tutorDigital human live streaming, news broadcasting
Does the client need to capture user audio?YesNo
Digital human driving mechanismAfter receiving user audio, LLM outputs a response and drives the digital human via TTSBroadcast content is fully controlled by the server, driving the digital human via TTS
Instance creation APICreateDigitalHumanAgentInstanceCreateLiveDigitalHumanAgentInstance
Requires user_id, user_stream_idYesNo

Prerequisites

  • You have created a project in the ZEGOCLOUD Console and obtained a valid AppID and ServerSecret (for server API signing and RTC Token04 generation). For details, see Console - Project Information.
  • You have contacted ZEGOCLOUD Technical Support to enable the Digital Human PaaS service and the relevant API permissions.
  • You have obtained a valid digital_human_id (for testing, you can use the public ID: 63c3aa64-1d80-4b04-a0be-1c65614eb7eb).
  • You have integrated the broadcast digital human server APIs as described in Server Quick Start Guide.
  • You have downloaded the ZEGO Express SDK optimized for AI Agent from the download page and integrated it into your project.
Note

Digital human broadcasting does not require microphone permissions and does not publish local streams. If the same app also includes voice call or digital human video call entries, those entries still need to request microphone permissions as described in their respective documentation.

Sample Code

The following is the business backend sample code for integrating the Conversational AI Agent API. You can refer to the sample code to implement your own business logic.

The following is the client sample code. You can refer to the sample code to implement your own business logic.

The following video demonstrates how to run through the server and client (iOS) sample code and interact with the agent via voice.

Overall Business Flow

  1. On the server side, refer to the Server Quick Start Guide to run through the business backend sample code and deploy the business backend.
    • Integrate the Conversational AI Agent API to manage agents.
  1. On the client side, run through the sample code.
    • Create and manage agents through the business backend.
    • Integrate ZEGO Express SDK and the Digital Human SDK for real-time communication.

After completing the above two steps, you can view digital human broadcasts.

Core Feature Implementation

Integrating ZEGO Express SDK

The iOS quickstart integrates the SDK via CocoaPods:

Podfile
target 'ai_agent_quickstart' do
  use_frameworks! :linkage => :static
  use_modular_headers!

  pod 'ZegoExpressEngine', :path => 'libs/Express'
  pod 'Masonry', '1.1.0'
  pod 'ZegoDigitalMobile', '>= 1.3.0'
end

Digital human broadcasting is a one-way viewing scenario. You do not need to add NSMicrophoneUsageDescription in Info.plist or call requestRecordPermission:. If the same app also supports digital human video calls, you can keep the microphone permission declaration required for video calls.

When initializing the broadcast page, call ZegoExpressEngine directly without requesting microphone permission:

- (void)initZegoExpressEngine {
    ZegoEngineProfile *profile = [[ZegoEngineProfile alloc] init];
    profile.appID = appID; // Obtain from ZEGOCLOUD Console
    profile.scenario = ZegoScenarioHighQualityChatroom;
    [ZegoExpressEngine createEngineWithProfile:profile eventHandler:self];
}

Integrating the Digital Human SDK

Note
Supports iOS 12 and above.

Notify the Business Backend to Create a Broadcast Digital Human Instance

The client calls the business backend's POST /api/start-live-digital-human. Upon receiving the request, the business backend calls ZEGOCLOUD's CreateLiveDigitalHumanAgentInstance API to create a broadcast digital human instance. For detailed parameters, see Create Broadcast Digital Human Instance. In RTC mode, the request body must at least contain room_id, digital_human_id, and config_id; user_id and user_stream_id are not required.

{
  "room_id": "room_xxxxxxxx",
  "digital_human_id": "digital_human_xxxxxxxx",
  "config_id": "mobile"
}

digital_human_id corresponds to the server-side DigitalHuman structure. Its core fields are as follows:

FieldDescription
digital_human_idDigital human ID, used to specify the digital human appearance.
encode_codeEncoding type. Only supports mobile (Android/iOS) and web (Web). Android and iOS use mobile, Web uses web.

For Android and iOS, config_id uses mobile; for Web, it uses web. The valid values for DigitalHumanConfig.EncodeCode are only mobile and web.

When calling the ZEGOCLOUD API, the business backend needs to construct the digital_human_id and config_id into a DigitalHuman object:

{
    "DigitalHumanId": "63c3aa64-1d80-4b04-a0be-1c65614eb7eb",
    "ConfigId": "mobile",
    "EncodeCode": "H264"
}

The successful server response should return the following information to the client:

FieldPurpose
agent_instance_idFor calling the proactive TTS and stop instance APIs
agent_stream_idThe ID of the digital human stream in the RTC room
agent_user_idThe digital human's user ID in the RTC room
digital_human_configConfiguration for initializing the Android/iOS Digital Human SDK

iOS uses NSURLSession to directly request the business backend and saves the instance ID, stream ID, and digital human config from the response:

- (void)startLiveDigitalHuman {
    NSURL *url = [NSURL URLWithString:
        [baseURL stringByAppendingString:@"/api/start-live-digital-human"]];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];

    NSDictionary *params = @{
        @"digital_human_id": digitalHumanId,
        @"config_id": @"mobile",
        @"room_id": roomID,
    };
    request.HTTPBody = [NSJSONSerialization dataWithJSONObject:params options:0 error:nil];

    [[[NSURLSession sharedSession] dataTaskWithRequest:request
        completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        if (error == nil && [result[@"code"] integerValue] == 0) {
            agentInstanceId = result[@"agent_instance_id"];
            agentStreamId = result[@"agent_stream_id"];
            NSString *config = result[@"digital_human_config"];
            [self initDigitalMobileSDK:config];
        }
    }] resume];
}

User Enters the Room (No Stream Publishing)

Broadcast digital human only requires logging in to the RTC room and receiving the digital human stream. The client does not need to create a local stream or call startPublishingStream.

iOS uses NSURLSession to obtain the Token, then calls loginRoom directly. The broadcast scenario does not call startPublishingStream:

- (void)loginRoomForBroadcast:(NSString *)token {
    ZegoRoomConfig *roomConfig = [[ZegoRoomConfig alloc] init];
    roomConfig.isUserStatusNotify = YES;
    roomConfig.token = token;

    ZegoUser *user = [[ZegoUser alloc] initWithUserID:userID userName:userID];
    [[ZegoExpressEngine sharedEngine] loginRoom:roomID
                                           user:user
                                         config:roomConfig
                                        callback:^(int errorCode, NSDictionary *extendedData) {
        if (errorCode == 0) {
            [self enableCustomVideoRender];
            [self startLiveDigitalHuman];
        }
    }];
}

Initializing the Digital Human SDK and Custom Rendering

Android and iOS need to pass the raw video frames and SEI data received by the ZEGO Express SDK to the Digital Human SDK, which then renders the digital human. You must enable custom video rendering before calling startPlayingStream.

iOS calls enableCustomVideoRender before startPlayingStream and forwards video frames and SEI in the digital human event handler:

- (BOOL)enableCustomVideoRender {
    ZegoCustomVideoRenderConfig *renderConfig =
        [[ZegoCustomVideoRenderConfig alloc] init];
    renderConfig.bufferType = ZegoVideoBufferTypeRawData;
    renderConfig.frameFormatSeries = ZegoVideoFrameFormatSeriesRGB;
    renderConfig.enableEngineRender = NO;

    ZegoExpressEngine *engine = [ZegoExpressEngine sharedEngine];
    if (!engine) {
        return NO;
    }

    [engine enableCustomVideoRender:YES config:renderConfig];
    [engine setCustomVideoRenderHandler:self];
    return YES;
}

- (void)onRemoteVideoFrameRawData:(unsigned char **)data
                       dataLength:(unsigned int *)dataLength
                            param:(ZegoVideoFrameParam *)param
                         streamID:(NSString *)streamID {
    ZDMVideoFrameParam *digitalParam = [[ZDMVideoFrameParam alloc] init];
    digitalParam.format = (ZDMVideoFrameFormat)param.format;
    digitalParam.width = param.size.width;
    digitalParam.height = param.size.height;
    digitalParam.rotation = param.rotation;

    for (int i = 0; i < 4; i++) {
        [digitalParam setStride:param.strides[i] atIndex:i];
    }

    if (self.digitalMobile) {
        [self.digitalMobile onRemoteVideoFrameRawData:data
                                           dataLength:dataLength
                                                param:digitalParam
                                             streamID:streamID];
    }
}

- (void)onPlayerSyncRecvSEI:(NSData *)data streamID:(NSString *)streamID {
    if (self.digitalMobile) {
        [self.digitalMobile onPlayerSyncRecvSEI:streamID data:data];
    }
}

Playing the Digital Human Stream

After creating the instance, the client uses the agent_stream_id returned by the server to play the digital human stream. The timing for playing streams varies slightly across platform quickstarts: Android plays the stream immediately after the create instance API succeeds; iOS and Web match the target stream via room stream update callbacks before playing.

Listen for onRoomStreamUpdate and start playing the stream only when the agentStreamId returned by the server appears in the room:

- (void)startPlayStream:(NSString *)streamId {
    [[ZegoExpressEngine sharedEngine]
        setPlayStreamBufferIntervalRange:streamId min:0 max:2000];
    [[ZegoExpressEngine sharedEngine] startPlayingStream:streamId];
}

- (void)onRoomStreamUpdate:(ZegoUpdateType)updateType
                streamList:(NSArray<ZegoStream *> *)streamList
              extendedData:(nullable NSDictionary *)extendedData
                    roomID:(NSString *)roomID {
    if (updateType == ZegoUpdateTypeAdd) {
        for (ZegoStream *stream in streamList) {
            if ([stream.streamID isEqualToString:self.agentStreamId]) {
                [self startPlayStream:self.agentStreamId];
                break;
            }
        }
    } else if (updateType == ZegoUpdateTypeDelete) {
        for (ZegoStream *stream in streamList) {
            if ([stream.streamID isEqualToString:self.agentStreamId]) {
                [[ZegoExpressEngine sharedEngine] stopPlayingStream:stream.streamID];
            }
        }
    }
}
CDN Mode Stream Playback

The above describes the RTC mode stream playback method. If using CDN mode, the client does not need to log in to an RTC room or follow the above flow to play an RTC stream. Instead, it can directly use a generic player (such as an HLS/FLV player) to play the cdn_url returned by the business backend to view the digital human broadcast. CDN mode does not require integrating the Digital Human SDK.

Proactively Making the Digital Human Broadcast Text

After the digital human instance is successfully created, the client calls the business backend's POST /api/send-agent-instance-tts, passing in the instance ID and text:

{
  "agent_instance_id": "2075416950128779264",
  "text": "Hello developer, welcome to ZEGOCLOUD AI Agent."
}

The maximum length of text is 300 characters. You can also pass additional parameters such as add_history, priority, and same_priority_option as needed. For details, see Proactive TTS Invocation.

Instance Lifecycle

A broadcast digital human instance is automatically destroyed after being idle (no broadcast tasks) for more than 900 seconds. The business side can adjust this idle timeout via MaxIdleTime. If you need to keep the instance alive for a long time, periodically invoke proactive TTS or increase MaxIdleTime.

NSDictionary *params = @{
    @"agent_instance_id": agentInstanceId,
    @"text": text,
};
NSURL *url = [NSURL URLWithString:
    [baseURL stringByAppendingString:@"/api/send-agent-instance-tts"]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
request.HTTPBody = [NSJSONSerialization dataWithJSONObject:params options:0 error:nil];

[[NSURLSession.sharedSession dataTaskWithRequest:request
    completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
    if (error == nil && [result[@"code"] integerValue] == 0) {
        NSLog(@"Broadcast sent successfully");
    }
}] resume];

Leaving the Room and Ending the Broadcast

When exiting, you need to stop the Agent instance, stop playing the stream, log out of the RTC room, and destroy the client SDK. Regardless of whether the stop API call succeeds, RTC resources should be released to avoid lingering rooms and engines.

- (void)stopLiveDigitalHuman {
    NSDictionary *params = @{ @"agent_instance_id": agentInstanceId };
    NSURL *url = [NSURL URLWithString:
        [baseURL stringByAppendingString:@"/api/stop"]];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    request.HTTPBody = [NSJSONSerialization dataWithJSONObject:params options:0 error:nil];

    [[[NSURLSession sharedSession] dataTaskWithRequest:request
        completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        [[ZegoExpressEngine sharedEngine] stopPlayingStream:agentStreamId];
        [[ZegoExpressEngine sharedEngine] logoutRoom];
        [digitalMobile stop];
        [ZegoExpressEngine destroyEngine:nil];
    }] resume];
}

RTC and CDN Modes

This document uses RTC mode as an example: the client logs in to a room and plays the agent_stream_id. If you need large-scale live streaming, you can use CDN mode:

ModeInstance Creation ParametersClient Playback MethodUse Cases
RTCroom_idZEGO Express SDK to play RTC streamLow latency, small-scale interaction
CDNcdn_urlUse a player to play CDN streamLarge-scale live streaming

Both RTC and CDN modes use agent_instance_id to call the proactive TTS and stop instance APIs. CDN mode does not require the client to log in to an RTC room or integrate the Digital Human SDK. For CDN stream playback, you can use any CDN-compatible playback method without special configuration.

Listening to Callbacks

Please listen to the ZEGO Express SDK's room login, play stream status, and error callbacks. Record the agent_instance_id, agent_stream_id, request_id, and error information in the business backend to help troubleshoot instance creation, stream playback, or TTS failures. The request_id is returned by the ZEGOCLOUD server and is a key identifier for troubleshooting server-side issues. When an exception occurs, be sure to provide this information along with the above details to ZEGOCLOUD Technical Support.

During integration testing, it is strongly recommended to listen for callbacks received by the business backend (events where the Event is Exception). Use Data.Code and Data.Message to quickly identify issues with LLM/TTS parameter configurations. For callback and error code details, see Receiving Callbacks and Exception Event Error Codes.

Note

If the digital human display stays on a static image, please check the following: whether the digital human configuration is valid, whether agent_stream_id is correct, whether custom video rendering was enabled before startPlayingStream, and whether video frames and SEI data are being passed to the Digital Human SDK.

Troubleshooting Checklist

If you encounter issues during integration, check against the following checklist item by item.

iOS Troubleshooting Checklist

SymptomTroubleshooting Direction
Room login failedCheck if the Token is valid, if AppID matches, and if the network can reach ZEGOCLOUD services.
Instance creation failedConfirm that digital_human_id, config_id (iOS uses mobile), and room_id are correct, and that the business backend signature is valid.
No stream / No videoMatch agentStreamId in onRoomStreamUpdate before playing the stream; confirm enableCustomVideoRender is called before startPlayingStream.
Display stays on static imageConfirm video frames and SEI are forwarded to digitalMobile; Info.plist does not need microphone permission but must have network permission.
TTS not broadcastingConfirm agent_instance_id is correct, text ≤ 300 characters, and the instance has not been destroyed due to 900 seconds of idle time.

For more error codes, see Exception Event Error Codes. For callback listening, see Receiving Callbacks.

2026-07-28

Previous

Quick Start with Digital Human

Next

Display Subtitles