产品动态
产品近期公告
关于 TRTC Live 正式上线的公告
关于TRTC Conference 正式版上线的公告
Conference 商业化版本即将推出
关于多人音视频 Conference 开启内测公告
关于音视频通话 Call 正式版上线的公告
关于腾讯云音视频终端 SDK 播放升级及新增授权校验的公告
关于 TRTC 应用订阅套餐服务上线的相关说明
CoGuestStore 支持以下两种最主流的连麦场景:核心概念 | 核心职责 | 关键 API / 属性 |
CoGuestStore | 负责管理观众与主播连麦的全流程信令交互(申请、邀请、接受、拒绝、断连),并提供 Combine 发布者用于响应事件。 | state:包含 connected、applicants、invitees 的状态发布者 (StatePublisher)。applyForSeat():观众发起连麦申请。inviteToSeat():主播邀请观众上麦。acceptApplication():主播同意连麦申请。disConnect():断开连麦。 |
CoGuestState | 存储当前所有与连麦相关的用户列表信息,驱动 UI 刷新(如红点提示、连麦窗口显示)。 | connected:当前正在连麦的用户列表。applicants:当前正在申请连麦的观众列表。invitees:当前正在被邀请的观众列表。 |
HostEvent / GuestEvent | 分别定义了主播端和观众端可能收到的信令事件,通过 Store 中的 Publisher 发送。 | hostEventPublisher:发布主播侧事件(如 onGuestApplicationReceived)。guestEventPublisher:发布观众侧事件(如 onHostInvitationReceived)。 |
applyForSeat 方法。import AtomicXCorelet liveId = "房间ID"let guestStore = CoGuestStore.create(liveID: liveId)// 用户点击“申请连麦”func requestToConnect() {// timeout: 请求超时时间,例如 30 秒guestStore.applyForSeat(timeout: 30.0, extraInfo: nil) { result inswitch result {case .success():print("连麦申请已发送,等待主播处理...")case .failure(let error):print("申请发送失败: \\(error.message)")}}}
guestEventPublisher,您可以接收到主播的处理结果。// 在您的视图控制器初始化时订阅事件func subscribeGuestEvents() {guestStore.guestEventPublisher.sink { [weak self] event inif case let .onGuestApplicationResponded(isAccept, hostUser) = event {if isAccept {print("主播 \\(hostUser.userName) 同意了你的申请,准备上麦")// 1. 打开摄像头、麦克风DeviceStore.shared.openLocalCamera(isFront: true, completion: nil)DeviceStore.shared.openLocalMicrophone(completion: nil)// 2. 在此更新 UI,例如关闭申请按钮,显示连麦中的状态} else {print("主播 \\(hostUser.userName) 拒绝了你的申请")// 弹窗提示用户申请被拒绝}}}.store(in: &cancellables) // 管理订阅生命周期}
disConnect 方法即可返回普通观众状态。// 用户点击“下麦”按钮func leaveSeat() {guestStore.disConnect { result inswitch result {case .success():print("已成功下麦")case .failure(let error):print("下麦失败: \\(error.message)")}}}
cancelApplication。// 用户在等待时,点击“取消申请”func cancelRequest() {guestStore.cancelApplication { result inswitch result {case .success():print("申请已取消")case .failure(let error):print("申请取消失败: \\(error.message)")}}}
hostEventPublisher,您可以在有新观众申请时立即收到通知,并给出提示。import AtomicXCorelet liveId = "房间ID"let guestStore = CoGuestStore.create(liveID: liveId)// 订阅主播事件guestStore.hostEventPublisher.sink { [weak self] event inif case let .onGuestApplicationReceived(guestUser) = event {print("收到观众 \\(guestUser.userName) 的连麦申请")// 在此更新 UI,例如在“申请列表”按钮上显示红点}}.store(in: &cancellables)
CoGuestStore 的 state 会实时维护当前的申请者列表,您可以订阅它来刷新您的 UI。// 订阅状态变更guestStore.state.subscribe(StatePublisherSelector(keyPath: \\CoGuestState.applicants)) // 只关心申请者列表的变化.removeDuplicates().sink { applicants inprint("当前申请人数: \\(applicants.count)")// 在此刷新您的“申请者列表”UI// self.applicantListView.update(with: applicants)}.store(in: &cancellables)
// 主播点击“同意”按钮,传入申请者的 userIDfunc accept(userId: String) {guestStore.acceptApplication(userID: userId) { result inif case .success = result {print("已同意 \\(userId) 的申请,对方正在上麦")}}}// 主播点击“拒绝”按钮func reject(userId: String) {guestStore.rejectApplication(userID: userId) { result inif case .success = result {print("已拒绝 \\(userId) 的申请")}}}
inviteToSeat 方法。// 主播选择观众并发起邀请func invite(userId: String) {// timeout: 邀请超时时间guestStore.inviteToSeat(userID: userId, timeout: 30.0, extraInfo: nil) { result inif case .success = result {print("已向 \\(userId) 发出邀请,等待对方回应...")}}}
hostEventPublisher 监听 onHostInvitationResponded 事件。// 在 hostEventPublisher 的订阅中添加if case let .onHostInvitationResponded(isAccept, guestUser) = event {if isAccept {print("观众 \\(guestUser.userName) 接受了你的邀请")} else {print("观众 \\(guestUser.userName) 拒绝了你的邀请")}}
guestEventPublisher 监听 onHostInvitationReceived 事件。// 在 guestEventPublisher 的订阅中添加if case let .onHostInvitationReceived(hostUser) = event {print("收到主播 \\(hostUser.userName) 的连麦邀请")// 在此弹出一个对话框,让用户选择“接受”或“拒绝”// self.showInvitationDialog(from: hostUser)}
let inviterId = "发起邀请的主播ID" // 从 onHostInvitationReceived 事件中获取// 用户点击“接受”func accept() {guestStore.acceptInvitation(inviterID: inviterId) { result inif case .success = result {// 2. 打开摄像头、麦克风DeviceStore.shared.openLocalCamera(isFront: true, completion: nil)DeviceStore.shared.openLocalMicrophone(completion: nil)}}}// 用户点击“拒绝”func reject() {guestStore.rejectInvitation(inviterID: inviterId) { result in// ...}}

LiveCoreView.VideoViewDelegate 协议提供的“插槽”能力,在观众连麦的视频流画面上添加自定义视图,用于显示昵称、头像等信息,或在他们关闭摄像头时提供占位图,以优化视觉体验。
CustomSeatView,该视图用于在视频流上方显示用户信息。import UIKit// 自定义的用户信息悬浮视图(前景)class CustomSeatView: UIView {lazy var nameLabel: UILabel = {let label = UILabel()label.textColor = .whitelabel.font = .systemFont(ofSize: 14)return label}()override init(frame: CGRect) {super.init(frame: frame)backgroundColor = UIColor.black.withAlphaComponent(0.5)addSubview(nameLabel)nameLabel.snp.makeConstraints { make inmake.bottom.equalToSuperview().offset(-5)make.leading.equalToSuperview().offset(5)}}}
CustomAvatarView,该视图用于在用户无视频流时作为占位图显示。import UIKit// 自定义的头像占位视图(背景)class CustomAvatarView: UIView {lazy var avatarImageView: UIImageView = {let imageView = UIImageView()imageView.tintColor = .grayreturn imageView}()override init(frame: CGRect) {super.init(frame: frame)backgroundColor = .clearlayer.cornerRadius = 30addSubview(avatarImageView)avatarImageView.snp.makeConstraints { make inmake.center.equalToSuperview()make.width.height.equalTo(60)}}}
LiveCoreView 自定义视图代理VideoViewDelegate,实现VideoViewDelegate.createCoGuestView 协议,根据 viewLayer 的值返回对应的视图。import AtomicXCore// 2. 在您的视图控制器中,遵守 VideoViewDelegate 协议class YourViewController: UIViewController, VideoViewDelegate {// ... 其他代码 ...func setupCustomView() {// 1. 设置 LiveCoreView 自定义视图代理coreView.videoViewDelegate = self}// 3. 完整实现协议方法,处理两种 viewLayerfunc createCoGuestView(seatInfo: TUISeatFullInfo, viewLayer: ViewLayer) -> UIView? {guard let userId = seatInfo.userID,!userId.isEmpty else {return nil}if viewLayer == .foreground {// 用户摄像头开启时,显示前景视图let seatView = CustomSeatView()seatView.nameLabel.text = seatInfo.userNamereturn seatView} else { // viewLayer == .background// 用户摄像头关闭时,显示背景视图let avatarView = CustomAvatarView()// 您可以在这里通过 seatInfo.userAvatar 加载用户真实头像return avatarView}}}
参数 | 类型 | 说明 |
seatInfo | TUISeatFullInfo | 麦位信息对象,包含麦上用户的详细信息。 |
seatInfo.userID | String? | 麦上用户的 ID。 |
seatInfo.userName | String? | 麦上用户的昵称。 |
seatInfo.userAvatar | String? | 麦上用户的头像 URL。 |
seatInfo.userMicrophoneStatus | TUIDeviceStatus | 麦上用户的麦克风状态。 |
seatInfo.userCameraStatus | TUIDeviceStatus | 麦上用户的摄像头状态。 |
viewLayer | ViewLayer | 视图层级枚举。 .foreground 表示前景挂件视图,始终显示在视频画面的最上层。.background 表示背景挂件视图,位于前景视图下层,仅在对应用户没有视频流(例如未开摄像头)的情况下显示,通常用于展示用户的默认头像或占位图。 |
VideoViewDelegate 添加的自定义视图的生命周期和事件?VideoViewDelegate 中的 viewLayer 参数有什么作用?viewLayer 用于区分前景和背景挂件:.foreground:前景层,始终显示在视频画面的最上层。.background:背景层,仅在对应用户没有视频流(例如未开摄像头)的情况下显示,通常用于展示用户的默认头像或占位图。coreView.videoViewDelegate = self 并成功设置了代理。createCoGuestView)。UIView 实例,而不是 nil。您可以在代理方法中添加断点或日志进行调试。文档反馈