畅直播
  • iOS
  • Android : Java
  • macOS
  • Windows
  • Web
  • Flutter
  • 产品简介
    • 概述
    • 发布日志
    • 基本概念
    • 产品优势
    • 应用场景
    • 限制说明
    • 升级指南
  • 计费说明
  • 下载
  • 快速开始
  • 直播推流
  • 直播拉流
  • 基础功能
  • 进阶功能
  • 最佳实践
  • 常用错误码
  • 服务端 API
  • 客户端 API
  • 常见问题

快速实现畅直播

更新时间:2023-09-15 16:40

本文将介绍如何快速实现一个简单的视频直播。

1 简介

相关概念解释:

  • ZEGO Express SDK:由 ZEGO 提供的实时音视频 SDK,能够为开发者提供便捷接入、高清流畅、多平台互通、低延迟、高并发的音视频服务。
  • 推流:把采集阶段封包好的音视频数据流推送到 ZEGO 实时音视频云的过程。
  • 拉流:从 ZEGO 实时音视频云将已有音视频数据流拉取播放的过程。
  • 房间:是 ZEGO 提供的音视频空间服务,用于组织用户群,同一房间内的用户可以互相收发实时音视频及消息。
    1. 用户需要先登录某个房间,才能进行推流、拉流操作。
    2. 用户只能收到自己所在房间内的相关消息(用户进出、音视频流变化等)。

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

2 前提条件

在实现基本的畅直播功能之前,请确保:

3 实现流程

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

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

/Pics/Common/ZegoExpressEngine/common_usage.png

3.1 开通服务

  1. 超低延迟直播功能不是默认开启的,使用前请在 ZEGO 控制台 自助开通(开通步骤请参考 项目管理 - 服务配置 中的“超低延迟直播”),或联系 ZEGO 技术支持开通。

  2. CDN 直播功能不是默认开启的,使用前请在 ZEGO 控制台 自助开通(开通步骤请参考 项目管理 - 服务配置 中的 “CDN”),或联系 ZEGO 技术支持开通。

  3. CDN Plus 直播功能不是默认开启的,使用前请联系 ZEGO 技术支持开通。

3.2 初始化

1. 创建界面

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

  • 本地视频窗口
  • 远端视频窗口
  • 退出直播按钮
  • 当本地用户为主播时,才会显示本地视频窗口,即若本地用户为观众,仅显示远端视频窗口。
  • 当远端用户为主播时,才会显示远端视频窗口。
界面代码示例
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".Playing">

    <TextureView
        android:id="@+id/logView"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_alignParentTop="true"/>

    <TextureView
        android:id="@+id/previewView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/logView"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"/>

    <TextView
        android:id="@+id/roomIDTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=""
        android:layout_margin="10dp"
        android:textColor="@color/black"
        android:textSize="12sp"
        android:layout_below="@+id/logView"/>

    <TextView
        android:id="@+id/userIDTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=""
        android:layout_margin="5dp"
        android:textColor="@color/black"
        android:textSize="12sp"
        android:layout_below="@+id/roomIDTextView"/>

    <TextView
        android:id="@+id/publishStreamIDTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=""
        android:layout_margin="5dp"
        android:textColor="@color/black"
        android:textSize="12sp"
        android:layout_below="@+id/userIDTextView"/>

    <Button
        android:id="@+id/stopButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Stop"
        android:textSize="15sp"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="30dp"
        android:layout_centerHorizontal="true"/>


    <TextureView
        android:id="@+id/playView"
        android:layout_width="108dp"
        android:layout_height="192dp"
        android:layout_below="@id/logView"
        android:layout_alignParentRight="true"
        android:layout_margin="10dp"/>

</RelativeLayout>

2. 创建引擎

调用 createEngine 接口,将申请到的 AppID 传入参数 “appID” ,创建引擎单例对象。

注册回调,可将实现了 IZegoEventHandler 的对象(例如 “self”)传入参数 “eventHandler”。

// 创建引擎,通用场景接入,并注册 self 为 eventHandler 回调
// 不需要注册回调的话,eventHandler 参数可以传 null,后续可调用 "setEventHandler:" 方法设置回调
ZegoEngineProfile profile = new ZegoEngineProfile();
profile.appID = ;  // 请通过官网注册获取,格式为:1234567890L
profile.scenario = ZegoScenario.DEFAULT;  // 通用场景接入
profile.application = getApplication();
engine = ZegoExpressEngine.createEngine(profile, null);

3.3 登录房间

  • 生成 Token

开发者可在 ZEGO 控制台获取临时 Token(有效期为 24 小时),详情请参考 控制台 - 开发辅助

临时 Token 仅供调试,正式上线时,请从开发者的业务服务器生成 token,详情可参考 使用 Token 鉴权。如果 Token 错误,请参考 错误码 文档中的 1002067 和 1003072 排查问题。

你可以调用 loginRoom 接口登录房间。如果房间不存在,调用该接口时会创建并登录此房间。roomID 和 user 的参数由您本地生成,但是需要满足以下条件:

  • 同一个 AppID 内,需保证 “roomID” 全局唯一。
  • 同一个 AppID 内,需保证 “userID” 全局唯一,建议开发者将 “userID” 与自己业务的账号系统进行关联。
// ZegoUser 的构造方法 public ZegoUser(String userID) 会将 “userName” 设为与传的参数 “userID” 一样。“userID” 与 “userName” 不能为 “null” 否则会导致登录房间失败。 
ZegoUser user = new ZegoUser("user1");
// 只有传入 “isUserStatusNotify” 参数取值为 “true” 的 ZegoRoomConfig,才能收到 onRoomUserUpdate 回调。
ZegoRoomConfig roomConfig = new ZegoRoomConfig();
//token 由用户自己的服务端生成,为了更快跑通流程,也可以通过 ZEGO 控制台获取临时的音视频 token
roomConfig.token = "xxxx";
roomConfig.isUserStatusNotify = true;
// 登录房间
engine.loginRoom("room1", user, roomConfig, (int error, JSONObject extendedData)->{
    // (可选回调) 登录房间结果,如果仅关注登录结果,关注此回调即可
});

登录状态(房间连接状态)回调

调用登录房间接口之后,您可通过监听 onRoomStateChanged 回调实时监控自己在本房间内的连接状态。

@Override
public void onRoomStateChanged(String roomID, ZegoRoomStateChangedReason reason, int errorCode, JSONObject extendedData) {
    if(reason == ZegoRoomStateChangedReason.LOGINING)
    {
        // 登录中
    }
    else if(reason == ZegoRoomStateChangedReason.LOGINED)
    {
        // 登录成功
        //只有当房间状态是登录成功或重连成功时,推流(startPublishingStream)、拉流(startPlayingStream)才能正常收发音视频
        //将自己的音视频流推送到 ZEGO 音视频云
    }
    else if(reason == ZegoRoomStateChangedReason.LOGIN_FAILED)
    {
        // 登录失败
    }
    else if(reason == ZegoRoomStateChangedReason.RECONNECTING)
    {
        // 重连中
    }
    else if(reason == ZegoRoomStateChangedReason.RECONNECTED)
    {
        // 重连成功
    }
    else if(reason == ZegoRoomStateChangedReason.RECONNECT_FAILED)
    {
        // 重连失败
    }
    else if(reason == ZegoRoomStateChangedReason.KICK_OUT)
    {
        // 被踢出房间
    }
    else if(reason == ZegoRoomStateChangedReason.LOGOUT)
    {
        // 登出成功
    }
    else if(reason == ZegoRoomStateChangedReason.LOGOUT_FAILED)
    {
        // 登出失败
    }
}

3.4 主播预览自己的画面,并推送到 ZEGO 音视频云

1. 主播预览自己的画面

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

// 设置本地预览视图并启动预览,视图模式采用 SDK 默认的模式,等比缩放填充整个 View
ZegoCanvas previewCanvas = new ZegoCanvas(previewView);
engine.startPreview(previewCanvas);

2. 主播将自己的音视频流推送到 ZEGO 音视频云

在用户调用 loginRoom 接口后,可以直接调用 startPublishingStream 接口,传入 “streamID”,将自己的音视频流推送到 ZEGO 音视频云。您可通过监听 onPublisherStateUpdate 回调知晓推流是否成功。

“streamID” 由您本地生成,但是需要保证:

同一个 AppID 下,“streamID” 全局唯一。如果同一个 AppID 下,不同用户各推了一条 “streamID” 相同的流,后推流的用户推流失败。

此处示例在调用 loginRoom 接口后立即进行推流。在实现具体业务时,您可选择其他时机进行推流,只要保证先调用 loginRoom 即可。

// 用户调用 loginRoom 之后再调用此接口进行推流
// 在同一个 AppID 下,开发者需要保证“streamID” 全局唯一,如果不同用户各推了一条 “streamID” 相同的流,后推流的用户会推流失败。 
engine.startPublishingStream("stream1");

如果您需要了解 Express 的摄像头/视频/麦克风/音频/扬声器相关接口,请参考 常见问题 - 如何实现开关摄像头/视频画面/麦克风/音频/扬声器?

3.5 拉取主播的音视频

进行直播时,我们需要主播的音视频。

在同一房间内的其他用户将音视频流推送到 ZEGO 音视频云时,我们会在 onRoomStreamUpdate 回调中收到音视频流新增的通知,并可以通过 ZegoStream 获取到某条流的 “streamID”。

我们可以在该回调中,调用 startPlayingStream 接口,传入 “streamID” 拉取播放该用户的音视频。您可通过监听 onPlayerStateUpdate 回调知晓是否成功拉取音视频。您可以按需选择如下三种拉流方式。

CDN 直播和超低延迟直播都不是默认开启的,请在 ZEGO 控制台自助开通或联系 ZEGO 技术支持,详情请参考 3.1 开通服务

3.5.1 CDN 拉流

调用 startPlayingStream 接口,并将 resourceMode 参数设置为 “ZegoStreamResourceMode.ONLY_CDN”,表示仅从 CDN 拉流。

CDN 拉流具有高延迟(3s 左右),弱网抗性差等特点,但价格低。

// 房间内其他用户推流/停止推流时,我们会在这里收到相应流增减的通知
public void onRoomStreamUpdate(String roomID, ZegoUpdateType updateType, ArrayList<ZegoStream> streamList, JSONObject extendedData) {
    super.onRoomStreamUpdate(roomID, updateType, streamList, extendedData);
    //当 updateType 为 ZegoUpdateType.ADD 时,代表有音视频流新增,此时我们可以调用 startPlayingStream 接口拉取播放该音视频流
    if (updateType == ZegoUpdateType.ADD) {
         // 开始拉流,设置远端拉流渲染视图,视图模式采用 SDK 默认的模式,等比缩放填充整个 View
        // 如下 playView 为 UI 界面上 View.这里为了使示例代码更加简洁,我们只拉取新增的音视频流列表中第的第一条流,在实际的业务中,建议开发者循环遍历 streamList ,拉取每一条音视频流 
        ZegoStream stream = streamList.get(0);
        playStreamID = stream.streamID;
        ZegoCanvas playCanvas = new ZegoCanvas(playView);
        ZegoPlayerConfig playerConfig = new ZegoPlayerConfig();
        playerConfig.resourceMode = ZegoStreamResourceMode.ONLY_CDN;
        engine.startPlayingStream(playStreamID, playCanvas, playerConfig);
    }
}

3.5.2 CDN Plus 拉流

调用 startPlayingStream 接口,并将 resourceMode 参数设置为 “ZegoStreamResourceMode.DEFAULT”,表示 CDN Plus 拉流。

CDN Plus 拉流会根据本地网络情况,选择 CDN 或超低延迟直播拉流,弱网抗性良好,价格较低。

// 房间内其他用户推流/停止推流时,我们会在这里收到相应流增减的通知
public void onRoomStreamUpdate(String roomID, ZegoUpdateType updateType, ArrayList<ZegoStream> streamList, JSONObject extendedData) {
    super.onRoomStreamUpdate(roomID, updateType, streamList, extendedData);
    //当 updateType 为 ZegoUpdateType.ADD 时,代表有音视频流新增,此时我们可以调用 startPlayingStream 接口拉取播放该音视频流
    if (updateType == ZegoUpdateType.ADD) {
         // 开始拉流,设置远端拉流渲染视图,视图模式采用 SDK 默认的模式,等比缩放填充整个             View
        // 如下 playView 为 UI 界面上 View.这里为了使示例代码更加简洁,我们只拉取新增的音视频流列表中第的第一条流,在实际的业务中,建议开发者循环遍历 streamList ,拉取每一条音视频流 
        ZegoStream stream = streamList.get(0);
        playStreamID = stream.streamID;
        ZegoCanvas playCanvas = new ZegoCanvas(playView);
        ZegoPlayerConfig playerConfig = new ZegoPlayerConfig();
        playerConfig.resourceMode = ZegoStreamResourceMode.DEFAULT;//联系技术支持开通 CDN Plus 拉流
        engine.startPlayingStream(playStreamID, playCanvas, playerConfig);
    }
}

3.5.3 超低延迟直播拉流

调用 startPlayingStream 接口,并将 resourceMode 参数设置为 “ZegoStreamResourceMode.ONLY_L3”,表示超低延迟直播拉流。

超低延迟直播拉流具有低延迟(1s 以内),弱网抗性较优等特点,价格适中。

// 房间内其他用户推流/停止推流时,我们会在这里收到相应流增减的通知
public void onRoomStreamUpdate(String roomID, ZegoUpdateType updateType, ArrayList<ZegoStream> streamList, JSONObject extendedData) {
    super.onRoomStreamUpdate(roomID, updateType, streamList, extendedData);
    //当 updateType 为 ZegoUpdateType.ADD 时,代表有音视频流新增,此时我们可以调用 startPlayingStream 接口拉取播放该音视频流
    if (updateType == ZegoUpdateType.ADD) {
         // 开始拉流,设置远端拉流渲染视图,视图模式采用 SDK 默认的模式,等比缩放填充整个 View
        // 如下 playView 为 UI 界面上 View.这里为了使示例代码更加简洁,我们只拉取新增的音视频流列表中第的第一条流,在实际的业务中,建议开发者循环遍历 streamList ,拉取每一条音视频流 
        ZegoStream stream = streamList.get(0);
        playStreamID = stream.streamID;
        ZegoCanvas playCanvas = new ZegoCanvas(playView);
        ZegoPlayerConfig playerConfig = new ZegoPlayerConfig();
        playerConfig.resourceMode = ZegoStreamResourceMode.ONLY_L3;
        engine.startPlayingStream(playStreamID, playCanvas, playerConfig);
    }
}

注意事项

如果用户在直播的过程中,遇到相关错误,可查询 错误码

4 常用功能

4.1 常见通知回调

4.1.1 我在房间内的连接状态变化通知

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

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

@Override
public void onRoomStateChanged(String roomID, ZegoRoomStateChangedReason reason, int errorCode, JSONObject extendedData)
{
    super.onRoomStateChanged(roomID, reason, errorCode, extendedData);
}

ZegoRoomStateChangedReason 状态含义如下,更多信息请参考 房间状态管理

状态 枚举值 含义
ZegoRoomStateChangedReason.LOGINING
0
正在登录房间。当调用 [loginRoom] 登录房间或 [switchRoom] 切换到目标房间时,进入该状态,表示正在请求连接服务器。通常通过该状态进行应用界面的展示。
ZegoRoomStateChangedReason.LOGINED
1
登录房间成功。当登录房间或切换房间成功后,进入该状态,表示登录房间已经成功,用户可以正常收到房间内的其他用户和所有流信息增删的回调通知。
ZegoRoomStateChangedReason.LOGIN_FAILED
2
登录房间失败。当登录房间或切换房间失败后,进入该状态,表示登录房间或切换房间已经失败,比如 AppID、AppSign 或 Token 不正确等。
ZegoRoomStateChangedReason.RECONNECTING
3
房间连接临时中断。如果因为网络质量不佳产生的中断,SDK 会进行内部重试。
ZegoRoomStateChangedReason.RECONNECTED
4
房间重新连接成功。如果因为网络质量不佳产生的中断,SDK 会进行内部重试,重连成功后进入该状态。
ZegoRoomStateChangedReason.RECONNECT_FAILED
5
房间重新连接失败。如果因为网络质量不佳产生的中断,SDK 会进行内部重试,重连失败后进入该状态。
ZegoRoomStateChangedReason.KICK_OUT
6
被服务器踢出房间。例如有相同用户名在其他地方登录房间导致本端被踢出房间,会进入该状态。
ZegoRoomStateChangedReason.LOGOUT
7
登出房间成功。没有登录房间前默认为该状态,当调用 [logoutRoom] 登出房间成功或 [switchRoom] 内部登出当前房间成功后,进入该状态。
ZegoRoomStateChangedReason.LOGOUT_FAILED
8
登出房间失败。当调用 [logoutRoom] 登出房间失败或 [switchRoom] 内部登出当前房间失败后,进入该状态。

4.1.2 其他用户进出房间的通知

onRoomUserUpdate :同一房间内的其他用户进出房间时,您可通过此回调收到通知。回调中的参数 ZegoUpdateType 为 ZegoUpdateType.ADD 时,表示有用户进入了房间;ZegoUpdateType 为 ZegoUpdateType.DELETE 时,表示有用户退出了房间。

  • 只有在登录房间 loginRoom 时传的配置 ZegoRoomConfig 中的 isUserStatusNotify 参数为 true 时,用户才能收到房间内其他用户的回调。
  • 房间人数大于 500 人的情况下 onRoomUserUpdate 回调不保证有效。若业务场景存在房间人数大于 500 的情况,请联系 ZEGO 技术支持。
@Override
public void onRoomUserUpdate(String roomID, ZegoUpdateType updateType, ArrayList<ZegoUser> userList) {
    super.onRoomUserUpdate(roomID, updateType, userList);
    // 您可以在回调中根据用户的进出/退出情况,处理对应的业务逻辑
    if (updateType == ZegoUpdateType.ADD) {
        for (ZegoUser user : userList) {
            //("用户 %s 进入了房间 %s", user.userName, roomID);
        }
    } else if (updateType == ZegoUpdateType.DELETE) {
        for (ZegoUser user : userList) {
            //("用户 %s 退出了房间 %s", user.userName, roomID);
        }
    }
}

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

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

@Override
public void onPublisherStateUpdate(String streamID, ZegoPublisherState state, int errorCode, JSONObject extendedData) {
    super.onPublisherStateUpdate(streamID, state, errorCode, extendedData);
    if (errorCode != 0) {
        Log.e("推流状态出错 errorCode: %d", errorCode);
    } else {
        switch (state) {
            case PUBLISHING:
                //("正在推流");
                break;
            case PUBLISH_REQUESTING:
                //("正在请求推流");
                break;
            case NO_PUBLISH:
                //("没有推流");
                break;
        }
    }
}

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

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

@Override
public void onPlayerStateUpdate(String streamID, ZegoPlayerState state, int errorCode, JSONObject extendedData) {
    super.onPlayerStateUpdate(streamID, state, errorCode, extendedData);
    if (errorCode != 0) {
        Log.e("拉流状态出错 streamID: %s, errorCode:%d", streamID, errorCode);
    } else {
        switch (state) {
            case PLAYING:
                //("正在拉流中");
                break;
            case PLAY_REQUESTING:
                //("正在请求拉流中");
                break;
            case NO_PLAY:
                //("未进行拉流");
                break;
        }
    }
}

4.2 停止直播

4.2.1 停止推送/拉取音视频流

1. 停止推流,停止预览

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

// 停止推流
engine.stopPublishingStream();

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

// 停止本地预览
engine.stopPreview();

2. 停止拉流

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

// 停止拉流
engine.stopPlayingStream("stream1");

4.2.2 退出房间

调用 logoutRoom 接口退出房间。

// 退出房间
engine.logoutRoom();

4.2.3 销毁引擎

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

  • 如果需要监听回调,来确保设备硬件资源释放完成,可在销毁引擎时传入 “callback”。该回调只用于发送通知,开发者不可以在回调内释放与引擎相关的资源。

  • 如果不需要监听回调,可传入 “null”。

ZegoExpressEngine.destroyEngine(null);

5 调试畅直播功能

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

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

6 直播 API 调用时序

时序图

7 常见问题

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

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

相关文档

本篇目录