logo
当前页

实现视频通话

2025-01-21

功能简介

本文将介绍如何快速实现一个简单的实时音视频通话。

相关概念解释:

  • ZEGO Express SDK:由 ZEGO 提供的实时音视频 SDK,能够为开发者提供便捷接入、高清流畅、多平台互通、低延迟、高并发的音视频服务。
  • 流:指一组按指定编码格式封装,不断发送中的音视频数据。一个用户可以同时推多条流(例如一条推摄像头数据,一条推屏幕共享数据)也可以同时拉多条流。每条流都由一个流 ID(streamID)标识。
  • 推流:把封包好的音视频数据流推送到 ZEGO 实时音视频云的过程。
  • 拉流:从 ZEGO 实时音视频云将已有音视频数据流拉取播放的过程。
  • 房间:是 ZEGO 提供的音视频空间服务,用于组织用户群,同一房间内的用户可以互相收发实时音视频及消息。
    1. 用户需要先登录某个房间,才能进行推流、拉流操作。
    2. 用户只能收到自己所在房间内的相关消息(用户进出、音视频流变化等)。
    3. 每个房间由一个 ApplD 内唯一的 roomlD 标识。所有使用同一个 roomID 登录房间的用户即属于同房间。

更多相关概念请参考 术语说明

前提条件

在实现基本的实时音视频功能之前,请确保:

实现流程

用户通过 ZEGO Express SDK 进行视频通话的基本流程为:

用户 A、B 加入房间,用户 B 预览并将音视频流推送到 ZEGO 云服务(推流),用户 A 收到用户 B 推送音视频流的通知之后,在通知中播放用户 B 的音视频流(拉流)。

1 创建引擎

1. 创建界面

根据场景需要,为您的项目创建视频通话的用户界面。我们推荐您在项目中添加如下元素:

  • 本地视频窗口
  • 远端视频窗口
  • 结束通话按钮
Row()
{
    XComponent({ id: 'view_id', type: XComponentType.SURFACE, libraryname: 'ZegoExpressEngine' })
    .onLoad(() => {
        hilog.info(0x0000, LogTag, 'XComponent on load');
    }).width('240').height('360')
}

Row()
{
    XComponent({ id: 'remote_view_id', type: XComponentType.SURFACE, libraryname: 'ZegoExpressEngine' })
    .onLoad(() => {
        hilog.info(0x0000, LogTag, 'XComponent on load');
    }).width('240').height('360')
}

2. 创建引擎

调用 createEngine 方法,将 前提条件 中申请到的 AppID 传入参数 “appID”,创建引擎单例对象。

说明

如果需要将日志信息输出到 DevEco Studio 的 HiLog 窗口,可参考 常见问题 中的“鸿蒙系统如何打印调试日志?”实现。

// 定义 SDK 引擎对象
ZegoExpressInstance: ZegoExpressEngine| null = null

// 填写 appID 和 appSign
let appID = xxx;  // 请通过官网注册获取,格式为:1234567890
let appSign = '';  //请通过官网注册获取,格式为:@"0123456789012345678901234567890123456789012345678901234567890123"(共64个字符)

// 在 ability 中获取 Context,然后导出,并且具体的 entry page 中引入该 Context 对象
// 全局定义 Context 和 LogPath
export let Context: object
export let logPath: string

// 在 ability 的 onWindowStageCreate 方法中获取 Context
let applicationContext: common.Context;
applicationContext = this.context.getApplicationContext();
logPath = applicationContext.filesDir.toString();

// 创建引擎,通用场景接入,传入 Context 对象,并根据业务需求注册相关回调
// set log config
let logConfig = new ZegoLogConfig();
logConfig.logPath = logPath;
ZegoExpressEngine.setLogConfig(logConfig)

// create engine
let profile = new ZegoEngineProfile(KeyCenter.appID, KeyCenter.appSign, ZegoScenario.General, Context);
ZegoExpressEngine.createEngine(profile).then((engineInst) => {
    // 得到 engine 实例
    this.ZegoExpressInstance = engineInst;
})

3. 设置回调

创建引擎后,您可以通过引擎实例的on方法注册回调。

注意

为避免错过事件通知,建议在创建引擎后立即注册回调

用户自己在房间内的连接状态变化通知

onRoomStateUpdate:本地调用 loginRoom 接口加入房间时,您可通过监听 onRoomStateUpdate 回调实时监控自己在本房间内的连接状态。

您可以在回调中根据不同状态处理业务逻辑。

this.ZegoExpressInstance.on('onRoomStateUpdate', (roomID: string, state: ZegoRoomState, errorCode: number, extendedData: string) => {
})

ZegoRoomState 状态含义如下:

状态枚举值含义
Disconnected0未连接状态,在登录房间前/退出房间后进入该状态。如果登录房间的过程中出现稳态异常,比如 AppID 和 AppSign 不正确,或者有相同用户名在其他地方登录导致本端被 KickOut,都会进入该状态。
Connecting1正在请求连接状态,登录房间动作执行成功后会进入该状态。通常情况下,可通过该状态进行 UI 界面的展示。如果是因为网络质量不佳产生的中断,SDK 内部会进行重试,也会进入正在请求连接状态。
Connected2连接成功状态,成功登录房间后进入该状态。此时,用户可以正常收到房间内的用户和流信息增删变化的回调通知。

详情可参考 房间连接状态说明

其他用户进出房间的通知

注意

只有在调用 loginRoom 接口登录房间时传的 ZegoRoomConfig 中的 “isUserStatusNotify” 参数为 “true” 时,用户才能收到 onRoomUserUpdate 回调。

用户自己加入房间时,不会触发此回调。

onRoomUserUpdate :同一房间内的其他用户进出房间时,您可通过此回调收到通知。

回调中的 ZegoUpdateType 参数为 “Add” 时,表示有用户进入了房间;为 “Delete” 时,表示有用户退出了房间。

this.ZegoExpressInstance.on('onRoomUserUpdate', (roomID: string, updateType: ZegoUpdateType, userList: ZegoUser[]) =>{
    // 您可以在回调中根据用户的进出/退出情况,处理对应的业务逻辑
    if(updateType == ZegoUpdateType.Add)
    {
        // 用户进入了房间
    }
    else
    {
        // 用户退出了房间
    }
});

用户推送音视频流的状态通知

onPublisherStateUpdate:根据实际应用需要,用户推送音视频流之后,当推送视频流的状态发生变更时(例如出现网络中断导致推流异常等情况),您会收到此回调,同时 SDK 会进行自动进行重试。

this.ZegoExpressInstance.on('onPublisherStateUpdate', (streamID: string, state: ZegoPublisherState, errorCode: number, extendedData: string) => {
    if (errorCode != 0) {
        // 推流状态出错
    } else {
        if(state ==ZegoPublisherState.Publishing)
        {
            // 正在推流
        }
        else if(state ==ZegoPublisherState.NoPublish)
        {
            // 没有推流
        }
        else if(state ==ZegoPublisherState.PublishRequesting)
        {
            // 正在请求推流
        }
    }
 })

用户拉取音视频流的状态通知

onPlayerStateUpdate:根据实际应用需要,用户拉取音视频流之后,当拉取的音视频流的状态发生变更时(例如出现网络中断导致拉流异常等情况),您会收到该回调,同时 SDK 会进行自动进行重试。

this.ZegoExpressInstance.on('onPlayerStateUpdate', (streamID: string, state: ZegoPlayerState, errorCode: number, extendedData: string) => {
    if (errorCode != 0) {
        // 拉流状态出错
    } else {
        if(state ==ZegoPlayerState.Playing)
        {
             // 正在拉流中
        }
        else if(state ==ZegoPlayerState.PlayRequesting)
        {
            // 正在请求拉流中
        }
        else if(state ==ZegoPlayerState.NoPlay)
        {
            // 未进行拉流
        }
    }
})

2 登录房间

1. 登录

传入用户 ID 参数 “userID” 创建 ZegoUser 用户对象后,调用 loginRoom 接口,传入房间 ID 参数 “roomID” 和用户参数 “user”,登录房间。

“roomID” 和 “user” 的参数由您本地生成,但是需要满足以下条件:

  • 同一个 AppID 内,需保证 “roomID” 全局唯一。
  • 同一个 AppID 内,需保证 “userID” 全局唯一,建议开发者将其设置成一个有意义的值,可将 “userID” 与自己业务账号系统进行关联。
注意

ZegoUser 的构造方法 public ZegoUser(String userID) 会将 “userName” 设为与传的参数 “userID” 一样。“userID” 不能为 “null”,否则会导致登录房间失败。

// 创建用户
let user = new ZegoUser();
user.userID = this.currentUserID;
user.userName = this.currentUserName;

// 登录房间
let roomConfig = new ZegoRoomConfig();
roomConfig.isUserStatusNotify = true;
this.ZegoExpressInstance.loginRoom('room_id', user, roomConfig);

调用登录房间接口之后,您可通过监听 onRoomStateUpdate 回调实时监控自己在本房间内的连接状态,详情请参考 房间内的连接状态变化通知

只有当房间状态是连接成功时,才能进行推流(startPublishingStream )、拉流(startPlayingStream )等操作。

this.ZegoExpressInstance.on('onRoomStateUpdate', (roomID: string, state: ZegoRoomState, errorCode: number, extendedData: string) => {
    this.logInfo('onRoomStateUpdate. roomID: ' + roomID + ", state: " + state + ", code: "+ errorCode)
    // 更新本地登录状态
    this.currentLoginState = state;

    // 已登录房间
    if(state == ZegoRoomState.Connected)
    {
        // todo 业务逻辑,只有当房间状态是连接成功时,才能进行推流(startPublishingStream)、拉流(startPlayingStream)等操作
    }
    // 房间连接中
    else if(state == ZegoRoomState.Connecting)
    {
        // todo 业务逻辑
    }
    // 房间连接断开
    else
    {
        // todo 业务逻辑,这里可以关注回调参数 errorCode 分析具体失败的原因
    }
})

3 预览自己的画面,并推送到 ZEGO 实时音视频云

1. (可选)预览自己的画面

如果希望看到本端的画面,可调用 startPreview 接口设置预览视图,并启动本地预览。

说明

无论是否调用 startPreview 预览,都可以将自己的音视频流推送到 ZEGO 音视频云。

开启预览后,如果无法正常看到本地预览画面,可以检查设置的本地预览视图类型是否正确。

// 设置本地预览视图并启动预览,视图模式采用 SDK 默认的模式,等比缩放填充整个 View
let view = new ZegoView();
view.view = "view_id";
this.ZegoExpressInstance.startPreview(view, ZegoPublishChannel.Main);

2. 将自己的音视频流推送到 ZEGO 实时音视频云

在用户登录房间成功之后,调用 startPublishingStream 接口,传入 “streamID”,将自己的音视频流推送到 ZEGO 实时音视频云。

“streamID” 由您本地生成,但是需要保证: 同一个 AppID 下,“streamID” 全局唯一。如果同一个 AppID 下,不同用户各推了一条 “streamID” 相同的流,会导致后推流的用户推流失败。

说明

此处在登录房间成功后(即 onRoomStateUpdate 回调通知开发者当前用户的连接状态为 “Connected”),立即进行推流。在实现具体业务时,您可选择其他时机进行推流,只要保证当前房间连接状态是连接成功的即可。

this.ZegoExpressInstance.on('onRoomStateUpdate', (roomID: string, state: ZegoRoomState, errorCode: number, extendedData: string) => {
    this.logInfo('onRoomStateUpdate. roomID: ' + roomID + ", state: " + state + ", code: "+ errorCode)
    // 更新本地登录状态
    this.currentLoginState = state;

    // 已登录房间
    if(state == ZegoRoomState.Connected)
    {
        // todo 业务逻辑, 推流
        let publisherConfig = new ZegoPublisherConfig();
        publisherConfig.roomID = this.currentRoomID;
        this.ZegoExpressInstance.startPublishingStream(this.currentStreamID, publisherConfig, ZegoPublishChannel.Main);
    }
})

4 拉取其他用户的音视频流

进行视频通话时,我们需要拉取到其他用户的音视频流。

onRoomStreamUpdate:在同一房间内的其他用户将音视频流推送到 ZEGO 实时音视频云时,我们会在此回调中收到音视频流新增的通知。

我们可以在该回调中,调用 startPlayingStream 接口,传入 “streamID” 拉取播放该用户的音视频流。

拉取播放用户的音视频流后,如果无法正常看到远端画面,可以检查设置的远端拉流渲染视图类型是否正确。

this.ZegoExpressInstance.on('onRoomStreamUpdate', (roomID: string, updateType: ZegoUpdateType, streamList: ZegoStream[], extendedData: string) => {
    this.logInfo('onRoomStreamUpdate. roomID: ' + roomID + ", updateType: " + updateType + ", extendedData: "+ extendedData)
    if(updateType == ZegoUpdateType.Add)
    {
        // 这里示例拉流默认的第一条流,具体取决于业务逻辑
        // 开始拉流,设置远端拉流渲染视图,视图模式采用 SDK 默认的模式,等比缩放填充整个 View
        let stream = streamList[0];
        let view = new ZegoView();
        view.view = "remote_view_id";
        let playerConfig = new ZegoPlayerConfig();
        playerConfig.resourceMode = ZegoStreamResourceMode.Default;
        this.ZegoExpressInstance.startPlayingStream(stream.streamID, view, playerConfig);
    }
})

注意事项

如果您在音视频通话的过程中,遇到相关错误,可查询 错误码

5 在线测试推拉流功能

在真机中运行项目,运行成功后,可以看到本端视频画面。

为方便体验,ZEGO 提供了一个 Web 端调试示例,在该页面下,输入相同的 AppID、RoomID,输入不同的 UserID、以及对应的 Token,即可加入同一房间与真机设备互通。当成功开始音视频通话时,可以听到远端的音频,看到远端的视频画面。

6 停止音视频通话

停止推送/拉取音视频流

1. 停止推流/预览

调用 stopPublishingStream 接口停止向远端用户发送本端的音视频流。

// 停止推流
this.ZegoExpressInstance.stopPublishingStream();

如果启用了本地预览,调用 stopPreview 接口停止预览。

// 停止本地预览
this.ZegoExpressInstance.stopPreview()

2. 停止拉流

调用 stopPlayingStream 接口停止拉取远端推送的音视频流。

// 停止拉流
this.ZegoExpressInstance.stopPlayingStream(this.currentPlayStreamID);

退出房间

调用 logoutRoom 接口退出房间。

// 退出房间
this.ZegoExpressInstance.logoutRoom(this.currentRoomID);

销毁引擎

如果用户彻底不使用音视频功能时,可调用 destroyEngine 接口销毁引擎,释放麦克风、摄像头、内存、CPU 等资源。

ZegoExpressEngine.destroyEngine();
// reset local engine
this.ZegoExpressInstance = null;

API 时序图

整个推拉流过程的 API 调用时序如下图:

常见问题

  1. 调用 logoutRoom 接口退出房间时,能否直接杀掉进程?

    调用 logoutRoom 接口后直接杀掉进程,有一定概率会导致 logoutRoom 信令没发出去,ZEGO 服务端只能等心跳超时后才认为该用户退出了房间,为了确保 logoutRoom 信令发送出去,建议再调用 destroyEngine 接口并收到回调后再杀进程。

  2. 鸿蒙系统如何打印调试日志?

    鸿蒙系统提供了与 Android Log 类似的日志打印工具类 HiLog,具体用法可参考 HiLog 日志打印

Previous

集成 SDK

Next

通话质量监测

当前页

返回到顶部