什么是连麦?如何基于 ZEGO SDK 实现直播连麦功能?

本文将介绍连麦的概念、互动形式、应用场景,以及如何使用 ZEGO SDK 来快速实现直播连麦功能。

在直播、在线教育、视频会议等场景中,”连麦”已经成为提升互动体验的核心功能。从两个主播 PK 连线,到多名学生与老师实时互动,再到多人视频会议,背后都有连麦的影子。本文将介绍连麦的概念、互动形式、应用场景,以及如何使用 ZEGO SDK 来快速实现直播连麦功能。

什么是连麦?

“连麦”原指多个麦克风同时接入同一广播设备进行互动的操作,在互联网场景下,“连麦”是指在网络直播或音视频互动中,两个及两个以上的用户通过实时视频或语音连线的方式进行多人互动的行为。

简单来说,就是观众、其他主播或特定参与者申请“上麦”,经发起者同意后,双方可以像打视频电话或语音通话一样,在直播间内共同说话、画面重叠或分屏显示,让其他观众也能看到或听到他们的实时互动。

与普通的单向直播(主播推流 → 观众拉流)不同,连麦场景中每一个参与者既是内容的发送方,也是内容的接收方,所有参与者能够实时听到、看到彼此,延迟要求极高。

模式数据流向典型延迟代表场景
单向直播(CDN)主播 → 观众>3 秒普通直播间
连麦(RTC)双向/多向实时<300 毫秒主播 PK、语聊房

连麦互动形式和应用场景

连麦常见互动形式

‍在直播场景中,连麦有多种具体的互动形式:

  • 观众连麦:主播主动邀请观众或者同意观众的邀请请求后,进行主播与观众的连麦互动。
  • 多观众连麦:支持设置多个麦位,观众可主动申请上麦,该场景一般需要加入麦位来维护连麦秩序。
  • 多主播连麦:4 个或以上主播参与互动连麦,为平衡成本和拉流端性能,通常可选择混流或对源流转码的推流策略。
  • 跨房间连麦 PK:主播与其他房间的主播进行连麦 PK 互动,房间内的观众可观看互动过程并送礼支持,实现方式包括跨房间拉流和注入流形式。

典型应用场景

社交娱乐

  • 秀场直播 PK:两位主播跨直播间实时音视频互动,各自房间的观众同步观看。
  • 语聊房:多名用户在语音房间中轮流或同时发言,可支持 50 人实时音频互动。
  • 互动播客:主持人与嘉宾多人连线,营造沉浸式节目氛围。

在线教育

  • 小班课:教师与多名学生实时互动,学生可举手上麦提问或回答。
  • 超级小班:大班拆分为多个小组,老师和助教分别连接不同小组。
  • 1v1 辅导:教师与学生一对一视频连线。

协同办公

  • 多人视频通话:支持 50 人以上的音视频群聊,适用于会议、评审等场景。
  • 远程医疗:医生与患者进行视频问诊,病历影像实时共享。

如何使用 ZEGO SDK 实现直播连麦功能?

连麦是秀场直播中最核心的互动玩法之一,连麦上线后,直播间的气氛和用户黏性通常会大幅提升。本节我们已秀场直播场景为例,介绍如何基于 ZEGO SDK 实现连麦功能。

注意:下文涉及的示例代码都是基于Android Java。

一、技术架构

连麦功能依赖两个 SDK 的协同工作:

  • ZEGO Express SDK:负责音视频推流、拉流和本地预览,这是连麦音视频能力的核心。
  • ZIM SDK(即时通讯):负责信令传递,包括连麦申请、主播响应(同意/拒绝)等业务消息的收发。

整体架构如下:

观众                         ZEGO 服务器                        主播
 |                               |                               |
 |---- ZIM 发送房间信令 --------->|-----> ZIM 推送给主播 -------->|
 |                               |                               |
 |<--- ZIM 接收同意/拒绝响应 -----|<----- ZIM 发送响应 -----------|
 |                               |                               |
 |---- Express 推流 ------------>|<---------- Express 拉流 ------|

直播阶段说明:

  • 单主播直播:主播使用 Express SDK 推流到 ZEGO 云,观众拉流观看,是默认的直播形态。
  • 多人连麦直播:连麦者(观众)也通过 Express SDK 推流,主播和其他观众同时拉取主播流 + 连麦者流。

二、前置准备

  1. ZEGO 控制台创建项目,获取 AppID 和 AppSign
  2. 在控制台开通 ZIM(即时通讯) 服务。
  3. 集成Express SDKZIM SDK(参考各自的快速开始文档)。

建议封装一个统一的 ZEGOSDKManager 单例来管理两个 SDK:

public class ZEGOSDKManager {
    public ExpressService expressService = new ExpressService();
    public ZIMService zimService = new ZIMService();

    private static final class Holder {
        private static final ZEGOSDKManager INSTANCE = new ZEGOSDKManager();
    }

    public static ZEGOSDKManager getInstance() {
        return Holder.INSTANCE;
    }

    public void initSDK(Application application, long appID, String appSign, ZegoScenario scenario) {
        expressService.initSDK(application, appID, appSign, scenario);
        zimService.initSDK(application, appID, appSign);
    }
}

在 Application.onCreate() 中初始化:

ZEGOSDKManager.getInstance().initSDK(this, YOUR_APP_ID, YOUR_APP_SIGN, ZegoScenario.LIVE);

三、连麦信令设计

连麦的申请和响应通过 ZIM SDK 的房间信令消息sendMessage实现,信令内容使用 JSON 编码,通过 extendedData 字段携带业务类型。

典型的信令结构示例(json):

{
  "action_type": "REQUEST_COHOST",
  "sender_id": "audience_001",
  "sender_name": "Alice"
}

主播收到后,响应信令:

{
  "action_type": "ACCEPT_COHOST",
  "request_id": "msg_12345"
}

四、实现流程

4.1 观众申请连麦

观众端在直播页面右下角提供”申请连麦”按钮,点击后执行以下逻辑:

第一步:发送连麦请求信令

// 观众端:点击申请连麦按钮
public void requestCoHost() {
    // 构造信令内容
    String extendedData = buildRequestSignal(RoomRequestType.REQUEST_COHOST);

    // 通过 ZIM 发送房间信令
    ZEGOSDKManager.getInstance().zimService.sendRoomRequest(extendedData,
        new RoomRequestCallback() {
            @Override
            public void onRoomRequestSend(int errorCode, String requestID) {
                if (errorCode == 0) {
                    // 发送成功,切换 UI 为"申请中"状态,按钮变为"取消申请"
                    mRoomRequestID = requestID;
                    showRequestingCoHostUI();
                } else {
                    // 发送失败,提示用户
                    showError("连麦申请失败,请重试");
                }
            }
        });
}

第二步:等待主播响应

// 观众端:监听主播的响应回调
@Override
public void onOutgoingRoomRequestAccepted(RoomRequest request) {
    RoomRequestExtendedData data = RoomRequestExtendedData.parse(request.extendedData);
    if (data != null && data.roomRequestType == RoomRequestType.REQUEST_COHOST) {
        // 主播同意,开始连麦
        requestPermissionAndStartCoHost();
    }
}

@Override
public void onOutgoingRoomRequestRejected(RoomRequest request) {
    // 主播拒绝,恢复为普通观众 UI
    showAudienceUI();
}

private void requestPermissionAndStartCoHost() {
    List<String> permissions = Arrays.asList(
        Manifest.permission.CAMERA,
        Manifest.permission.RECORD_AUDIO
    );
    requestPermissionIfNeeded(permissions, new RequestCallback() {
        @Override
        public void onResult(boolean allGranted, List<String> grantedList, List<String> deniedList) {
            if (allGranted) {
                ZEGOLiveStreamingManager.getInstance().startCoHost();
            }
        }
    });
}

4.2 主播接受或拒绝申请

第一步:收到申请时显示红点提示

当主播收到连麦申请时,用户列表上的按钮会出现红点,提醒主播有待处理的连麦请求:

// 主播端:收到房间信令回调
@Override
public void onInComingRoomRequestReceived(RoomRequest request) {
    checkRedPoint(); // 判断是否需要显示红点
}

public void checkRedPoint() {
    ZEGOSDKUser localUser = ZEGOSDKManager.getInstance().expressService.getCurrentUser();
    if (ZEGOLiveStreamingManager.getInstance().isHost(localUser.userID)) {
        List<RoomRequest> myReceivedRoomRequests =
            ZEGOSDKManager.getInstance().zimService.getMyReceivedRoomRequests();

        boolean hasCoHostRequest = false;
        for (RoomRequest roomRequest : myReceivedRoomRequests) {
            RoomRequestExtendedData data = RoomRequestExtendedData.parse(roomRequest.extendedData);
            if (data != null && data.roomRequestType == RoomRequestType.REQUEST_COHOST) {
                hasCoHostRequest = true;
                break;
            }
        }
        redPointView.setVisibility(hasCoHostRequest ? View.VISIBLE : View.GONE);
    }
}

第二步:主播在用户列表中同意或拒绝

// 主播端:用户列表中的同意/拒绝按钮
agree.setOnClickListener(v -> {
    ZEGOSDKManager.getInstance().zimService.acceptRoomRequest(
        roomRequest.requestID,
        new RoomRequestCallback() {
            @Override
            public void onRoomRequestSend(int errorCode, String requestID) {
                // 同意信令发送成功,等待观众端开始推流
            }
        });
});

disagree.setOnClickListener(v -> {
    ZEGOSDKManager.getInstance().zimService.rejectRoomRequest(
        roomRequest.requestID,
        new RoomRequestCallback() {
            @Override
            public void onRoomRequestSend(int errorCode, String requestID) {
                // 拒绝完成
            }
        });
});

4.3 主播邀请连麦

与观众申请连麦相反,主播也可以主动邀请某位观众连麦,通过 ZIM 的呼叫邀请功能实现:

// 主播端:邀请指定观众连麦
public void inviteCoHost(String targetUserID) {
    // 构造邀请扩展数据
    String extendedData = buildInviteSignal(targetUserID, InviteType.CO_HOST);

    ZEGOSDKManager.getInstance().zimService.sendRoomRequest(
        targetUserID, extendedData,
        new RoomRequestCallback() {
            @Override
            public void onRoomRequestSend(int errorCode, String requestID) {
                if (errorCode == 0) {
                    // 邀请发送成功,等待观众响应
                }
            }
        });
}

观众端收到邀请后弹出对话框,选择接受或拒绝,处理逻辑与申请连麦的响应类似。

4.4 开始连麦(音视频推流)

当观众收到主播同意连麦的信令后,调用 ZEGO Express SDK 开启摄像头预览并开始推流:

// CoHostService.java 中的 startCoHost() 实现
public void startCoHost() {
    // 1. 将当前用户标记为连麦者
    addCoHost(ZEGOSDKManager.getInstance().expressService.getCurrentUser());

    // 2. 开启摄像头和麦克风
    ZEGOSDKManager.getInstance().expressService.openCamera(true);
    ZEGOSDKManager.getInstance().expressService.openMicrophone(true);

    // 3. 开始本地视频预览
    ZegoCanvas previewCanvas = new ZegoCanvas(previewView);
    ZegoExpressEngine.getEngine().startPreview(previewCanvas);

    // 4. 开始推流(StreamID 在房间内必须唯一)
    String streamID = roomID + "_" + userID + "_cohost";
    ZegoExpressEngine.getEngine().startPublishingStream(streamID);
}

主播端收到新流通知后,自动拉取连麦者的流并渲染:

// 主播/其他观众:监听新流加入回调
@Override
public void onRoomStreamUpdate(String roomID, ZegoUpdateType updateType,
    ArrayList<ZegoStream> streamList, JSONObject extendedData) {

    if (updateType == ZegoUpdateType.ADD) {
        for (ZegoStream stream : streamList) {
            // 拉取新加入的连麦流
            ZegoCanvas playCanvas = new ZegoCanvas(getCoHostView(stream.user.userID));
            ZegoExpressEngine.getEngine().startPlayingStream(stream.streamID, playCanvas);
        }
    } else if (updateType == ZegoUpdateType.DELETE) {
        for (ZegoStream stream : streamList) {
            ZegoExpressEngine.getEngine().stopPlayingStream(stream.streamID);
        }
    }
}

4.5 结束连麦(下麦)

观众选择下麦,或主播将其移下麦时,调用以下方法:

// CoHostService.java 中的 endCoHost() 实现
public void endCoHost() {
    // 1. 从连麦者列表中移除
    removeCoHost(ZEGOSDKManager.getInstance().expressService.getCurrentUser());

    // 2. 关闭麦克风
    ZEGOSDKManager.getInstance().expressService.openMicrophone(false);

    // 3. 关闭摄像头
    ZEGOSDKManager.getInstance().expressService.openCamera(false);

    // 4. 停止本地预览
    ZEGOSDKManager.getInstance().expressService.stopPreview();

    // 5. 停止推流
    ZEGOSDKManager.getInstance().expressService.stopPublishingStream();
}

停止推流后,房间内其他用户会通过 onRoomStreamUpdate 回调感知到流的离开,从而自动停止拉取该连麦流并更新 UI。

恭喜您,完成上述步骤后,您已经实现了连麦功能。

连麦实现参考文档https://doc-zh.zego.im/solution-live-streaming-android/implement-cohosting

原创文章,作者:ZEGO即构科技,如若转载,请注明出处:https://market-blogs.zego.im/reports-technique/3373/

(0)
上一篇 4天前
下一篇 11月 13, 2024 8:24 上午

相关推荐

发表回复

登录后才能评论