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 (Web) 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

Please refer to Integrate SDK > 2.2 > Method 2 to manually integrate the SDK. The sample project uses ZegoExpressEngine and ZegoDigitalMobile.

Add the Digital Human SDK in app/build.gradle:

app/build.gradle
dependencies {
    implementation 'im.zego:digitalmobile:1.3.0.43'
}

Declare network permissions in AndroidManifest.xml. The broadcast digital human scenario does not require declaring or requesting RECORD_AUDIO:

AndroidManifest.xml
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
Difference from Video Call

The Android quickstart retains RECORD_AUDIO in the Manifest to support both voice calls and digital human video calls. When entering LiveDigitalHumanActivity, the permission is not requested and no local audio stream is created. If only the broadcast entry is needed, this permission can be removed.

The broadcast scenario does not require runtime microphone permission. After entering the page, you can directly initialize the ZEGO Express SDK:

ZegoEngineProfile profile = new ZegoEngineProfile();
profile.appID = appID; // Obtain from ZEGOCLOUD Console
// !mark
profile.scenario = ZegoScenario.HIGH_QUALITY_CHATROOM;
profile.application = getApplication();
ZegoExpressEngine.createEngine(profile, null);
Emulator Compatibility Note

If running on an Android emulator, some audio/video capabilities and digital human rendering depend on hardware decoding and GPU acceleration on real devices, which may cause the display not to work properly or result in a black screen. It is recommended to debug and verify the digital human broadcast effect on a real device.

Integrating the Digital Human SDK

The Digital Human SDK has been published to the Maven repository. Refer to the following steps to integrate it into your project.

1

Add `maven` configuration

Choose the appropriate steps based on your Android Gradle plugin version.

2

Modify your app-level build.gradle file

dependencies {
    ...
    // Digital Human SDK dependency
    implementation "im.zego:digitalmobile:1.3.0.43"
}
Note
Supports Android 6.0 (API 23) 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

Android uses OkHttp to directly request the business backend:

private void startLiveDigitalHuman(String baseUrl, String digitalHumanId,
                                   String configId, String roomId) {
    JSONObject bodyJson = new JSONObject();
    bodyJson.put("digital_human_id", digitalHumanId);
    bodyJson.put("config_id", configId);
    bodyJson.put("room_id", roomId);

    RequestBody body = RequestBody.create(
        bodyJson.toString(), MediaType.parse("application/json; charset=utf-8"));
    Request request = new Request.Builder()
        .url(baseUrl + "/api/start-live-digital-human")
        .post(body)
        .build();

    new OkHttpClient().newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NonNull Call call, @NonNull IOException e) {
            // Handle network error
        }

        @Override
        public void onResponse(@NonNull Call call, @NonNull Response response)
            throws IOException {
            JSONObject result = new JSONObject(response.body().string());
            if (result.getInt("code") == 0) {
                String agentInstanceId = result.getString("agent_instance_id");
                String agentStreamId = result.getString("agent_stream_id");
                String digitalHumanConfig = result.getString("digital_human_config");
                // Save agentInstanceId for subsequent TTS and stop calls
                startPlayingStream(agentStreamId);
                initDigitalMobileSDK(digitalHumanConfig);
            }
        }
    });
}

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.

Android needs to first obtain the Token from the business backend using OkHttp, then call the ZEGO Express SDK to log in to the room. After successful login, enable custom video rendering, then create a broadcast digital human instance:

String tokenUrl = baseUrl + "/api/zego-token?userId=" + userId;
Request tokenRequest = new Request.Builder().url(tokenUrl).get().build();
new OkHttpClient().newCall(tokenRequest).enqueue(new Callback() {
    @Override
    public void onResponse(@NonNull Call call, @NonNull Response response)
        throws IOException {
        String token = new JSONObject(response.body().string()).getString("token");

        ZegoEngineConfig engineConfig = new ZegoEngineConfig();
        engineConfig.advancedConfig = new HashMap<String, String>() {{
            //===== Digital human specific ====//
            put("set_audio_volume_ducking_mode", "1");           // Audio volume ducking toggle, enabled by default
            put("enable_rnd_volume_adaptive", "true");           // Playback volume adaptive toggle, enabled by default
            put("sideinfo_callback_version", "3");               // Make SEI and frame correspond one-to-one, for ZEGO Express SDK 3.17
            put("sideinfo_bound_to_video_decoder", "true");      // Make SEI and frame correspond one-to-one, for ZEGO Express SDK 3.18
        }};
        ZegoExpressEngine.setEngineConfig(engineConfig);

        ZegoRoomConfig roomConfig = new ZegoRoomConfig();
        roomConfig.isUserStatusNotify = true;
        roomConfig.token = token;
        ZegoExpressEngine.getEngine().loginRoom(
            roomId, new ZegoUser(userId, userId), roomConfig,
            (errorCode, extendedData) -> {
                if (errorCode == 0) {
                    openExpressCustomRender();
                    startLiveDigitalHuman(baseUrl, digitalHumanId, "mobile", roomId);
                }
            });
    }

    @Override
    public void onFailure(@NonNull Call call, @NonNull IOException e) {
        // Handle Token retrieval failure
    }
});

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.

Android initializes the Digital Human SDK. The example involves three view member variables that need to be declared and initialized in the layout beforehand:

  • digitalView: The container for the Digital Human SDK rendering output. The digital human is ultimately drawn on this View. It is passed to the Digital Human SDK via attach during initialization.
  • loadingView: A loading placeholder view displayed before the digital human's first frame is rendered. It is hidden in the first frame draw callback.
  • digitalPic: A static cover placeholder image. Similar to loadingView, it is hidden in the first frame draw callback to prevent the screen from staying on a static image.
private void initDigitalMobileSDK(String digitalHumanConfig) {
    digitalMobileSDK = ZegoDigitalHuman.create(this);
    digitalMobileSDK.start(digitalHumanConfig,
        new IZegoDigitalMobile.ZegoDigitalMobileListener() {
            @Override
            public void onSurfaceFirstFrameDraw() {
                loadingView.setVisibility(View.GONE);
                digitalPic.setVisibility(View.GONE);
            }
        });
    digitalMobileSDK.attach(digitalView);
}

In openExpressCustomRender, configure RAW_DATA and forward the callback data to the Digital Human SDK. onPlayerSyncRecvSEI is used to receive the SEI (Supplemental Enhancement Information) data parsed by the ZEGO Express SDK and forward it to the Digital Human SDK. The SEI carries additional information such as lip-sync and expressions needed for driving the digital human, which is critical for accurate lip-sync. For details on SEI, see SEI Advanced Features.

private void openExpressCustomRender() {
    ZegoCustomVideoRenderConfig renderConfig = new ZegoCustomVideoRenderConfig();
    renderConfig.bufferType = ZegoVideoBufferType.RAW_DATA;
    renderConfig.frameFormatSeries = ZegoVideoFrameFormatSeries.RGB;
    renderConfig.enableEngineRender = false;
    ZegoExpressEngine.getEngine().enableCustomVideoRender(true, renderConfig);

    ZegoExpressEngine.getEngine().setCustomVideoRenderHandler(
        new IZegoCustomVideoRenderHandler() {
            @Override
            public void onRemoteVideoFrameRawData(
                ByteBuffer[] data, int[] dataLength, ZegoVideoFrameParam param,
                String streamID) {
                IZegoDigitalMobile.ZegoVideoFrameParam digitalParam =
                    new IZegoDigitalMobile.ZegoVideoFrameParam();
                digitalParam.format =
                    IZegoDigitalMobile.ZegoVideoFrameFormat.getZegoVideoFrameFormat(
                        param.format.value());
                digitalParam.height = param.height;
                digitalParam.width = param.width;
                digitalParam.rotation = param.rotation;
                for (int i = 0; i < 4; i++) {
                    digitalParam.strides[i] = param.strides[i];
                }

                if (digitalMobileSDK != null) {
                    digitalMobileSDK.onRemoteVideoFrameRawData(
                        data, dataLength, digitalParam, streamID);
                }
            }
        });

    ZegoExpressEngine.getEngine().setEventHandler(new IZegoEventHandler() {
        @Override
        public void onPlayerSyncRecvSEI(String streamID, byte[] data) {
            if (digitalMobileSDK != null) {
                digitalMobileSDK.onPlayerSyncRecvSEI(streamID, 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.

The Android quickstart directly plays the agent_stream_id after the broadcast digital human instance is successfully created, without using onRoomStreamUpdate:

// Set play stream buffer interval range to mitigate stuttering caused by network jitter; min is the initial buffer (ms), max is the maximum buffer (ms)
ZegoExpressEngine.getEngine()
    .setPlayStreamBufferIntervalRange(agent_stream_id, 100, 2000);
ZegoExpressEngine.getEngine().startPlayingStream(agent_stream_id);
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.

Android uses OkHttp to directly call the TTS API:

JSONObject bodyJson = new JSONObject();
bodyJson.put("agent_instance_id", agentInstanceId);
bodyJson.put("text", text);

Request request = new Request.Builder()
    .url(baseUrl + "/api/send-agent-instance-tts")
    .post(RequestBody.create(bodyJson.toString(),
        MediaType.parse("application/json; charset=utf-8")))
    .build();
new OkHttpClient().newCall(request).enqueue(new Callback() {
    @Override
    public void onResponse(@NonNull Call call, @NonNull Response response)
        throws IOException {
        JSONObject result = new JSONObject(response.body().string());
        if (result.getInt("code") != 0) {
            // Handle TTS request failure
        }
    }

    @Override
    public void onFailure(@NonNull Call call, @NonNull IOException e) {
        // Handle network error
    }
});

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.

@Override
protected void onDestroy() {
    super.onDestroy();
    JSONObject bodyJson = new JSONObject();
    bodyJson.put("agent_instance_id", agentInstanceId);
    Request request = new Request.Builder()
        .url(baseUrl + "/api/stop")
        .post(RequestBody.create(bodyJson.toString(),
            MediaType.parse("application/json; charset=utf-8")))
        .build();
    new OkHttpClient().newCall(request).enqueue(new Callback() {
        @Override
        public void onResponse(@NonNull Call call, @NonNull Response response) {
            ZegoExpressEngine.getEngine().stopPlayingStream(agentStreamId);
            ZegoExpressEngine.getEngine().logoutRoom();
            digitalMobile.stop();
            ZegoExpressEngine.destroyEngine(null);
        }

        @Override
        public void onFailure(@NonNull Call call, @NonNull IOException e) {
            // Even if the request fails, release local RTC and Digital Human SDK resources
            ZegoExpressEngine.getEngine().logoutRoom();
            digitalMobile.stop();
            ZegoExpressEngine.destroyEngine(null);
        }
    });
}

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.

Android 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 (Android uses mobile), and room_id are correct, and that the business backend signature is valid.
No stream / No videoConfirm agent_stream_id matches the server response; check if enableCustomVideoRender was called before startPlayingStream.
Display stays on static imageCheck if digitalView/loadingView/digitalPic are correctly attached; confirm video frames and SEI are forwarded to digitalMobileSDK; run on a real device, not an emulator.
Inaccurate lip-syncConfirm SEI data is forwarded via onPlayerSyncRecvSEI; confirm SEI-related parameters in advanceConfig match the ZEGO Express SDK version.
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.

Previous

Quick Start Digital Human Video Call

Next

Display Subtitles