产品动态
产品近期公告
关于 TRTC Live 正式上线的公告
关于TRTC Conference 正式版上线的公告
Conference 商业化版本即将推出
关于多人音视频 Conference 开启内测公告
关于音视频通话 Call 正式版上线的公告
关于腾讯云音视频终端 SDK 播放升级及新增授权校验的公告
关于 TRTC 应用订阅套餐服务上线的相关说明

requestHostConnection 方法。import AtomicXCoreimport Combine// 主播A的视图控制器class AnchorAViewController {private let liveId = "主播A的房间ID"private var cancellables: Set<AnyCancellable> = []private lazy var coHostStore: CoHostStore = {return CoHostStore.create(liveID: self.liveId)}()// 用户点击“连线”按钮,并选择了主播Bfunc inviteHostB(targetHostLiveId: String) {let layout: CoHostLayoutTemplate = .hostDynamicGrid // 选择一个布局模板let timeout: TimeInterval = 30.0 // 邀请超时时间coHostStore.requestHostConnection(targetHost: targetHostLiveId,layoutTemplate: layout,timeout: timeout) { result inswitch result {case .success():print("连线邀请已发送,等待对方处理...")case .failure(let error):print("邀请发送失败: \\(error.message)")}}}}
coHostEventPublisher,您可以接收到主播 B 的处理结果。// 在 AnchorAViewController 初始化时设置监听func setupListeners() {coHostStore.coHostEventPublisher.sink { [weak self] event inswitch event {case .onCoHostRequestAccepted(let invitee): //print("主播 \\(invitee.userName) 同意了你的连线邀请")case .onCoHostRequestRejected(let invitee): //print("主播 \\(invitee.userName) 拒绝了你的邀请")case .onCoHostRequestTimeout: //print("邀请超时,对方未回应")default:break}}.store(in: &cancellables)}
coHostEventPublisher,主播B可以监听到来自主播 A 的邀请。import AtomicXCoreimport Combine// 主播B的视图控制器class AnchorBViewController {// ... coHostStore 和 cancellables 初始化 ...// 在初始化时设置监听func setupListeners() {coHostStore.coHostEventPublisher.sink { [weak self] event inif case let .onCoHostRequestReceived(inviter, _) = event { //print("收到主播 \\(inviter.userName) 的连线邀请")// self?.showInvitationDialog(from: inviter)}}.store(in: &cancellables)}}
// AnchorBViewController 的一部分func acceptInvitation(fromHostLiveId: String) {coHostStore.acceptHostConnection(fromHostLiveID: fromHostLiveId, completion: nil) //}func rejectInvitation(fromHostLiveId: String) {coHostStore.rejectHostConnection(fromHostLiveID: fromHostLiveId, completion: nil) //}
requestBattle 方法。// AnchorAViewController 的一部分private lazy var battleStore: BattleStore = BattleStore.create(liveID: self.liveId)func startPK(with opponentUserId: String) {var config = BattleConfig(duration: 300) // PK持续5分钟battleStore.requestBattle(config: config, userIDList: [opponentUserId], timeout: 30.0, completion: nil)}
battleEventPublisher 监听 PK 的开始、结束等关键事件。// 在 AnchorAViewController 的 setupListeners 方法中添加battleStore.battleEventPublisher.sink { [weak self] event inswitch event {case .onBattleStarted: //print("PK 开始")case .onBattleEnded: //print("PK 结束")default:break}}.store(in: &cancellables)
battleEventPublisher 监听到 PK 邀请。// 在 AnchorBViewController 的 setupListeners 方法中添加battleStore.battleEventPublisher.sink { [weak self] event inif case let .onBattleRequestReceived(battleId, inviter, _) = event {print("收到主播 \\(inviter.userName) 的PK挑战")// 弹出对话框,让主播B选择“接受”或“拒绝”// self?.showPKChallengeDialog(battleId: battleId)}}.store(in: &cancellables)
// AnchorBViewController 的一部分// 用户点击“接受挑战”func acceptPK(battleId: String) {battleStore.acceptBattle(battleID: battleId) { result in// ...}}// 用户点击“拒绝挑战”func rejectPK(battleId: String) {battleStore.rejectBattle(battleID: battleId) { result in// ...}}
// AnchorAViewController 的一部分// 主播主动结束 PKfunc stopPK(battleId: String) {// 调用 exitBattle 接口退出当前的 PK 对战battleStore.exitBattle(battleID: battleId) { result inswitch result {case .success:print("PK 已结束")// 此时 UI 会收到 onBattleEnded 事件,请在事件回调中处理 UI 刷新case .failure(let error):print("结束 PK 失败: \\(error.message)")}}}
// AnchorAViewController 的一部分// 主播主动断开连线func stopConnection() {// 调用 exitHostConnection 接口断开与其他主播的连接coHostStore.exitHostConnection { result inswitch result {case .success:print("连线已断开,恢复单人直播")// 此时 UI 会收到 onCoHostUserLeft 事件case .failure(let error):print("断开连线失败: \\(error.message)")}}}
// 在 setupListeners 方法中补充以下监听逻辑func setupAdditionalListeners() {// 1. 监听 PK 结束事件battleStore.battleEventPublisher.sink { [weak self] event inif case .onBattleEnded(let battleInfo, let reason) = event {print("收到 PK 结束事件,原因: \\(reason)")// 在此处移除 PK 分数视图、进度条等 UI// self?.removeBattleUI()}}.store(in: &cancellables)// 2. 监听连线断开事件coHostStore.coHostEventPublisher.sink { [weak self] event inif case .onCoHostUserLeft(let userInfo) = event {print("主播 \\(userInfo.userName) 已离开连线")// 在此处移除对方的视频画面视图,恢复单人直播布局// self?.resetToSingleStreamLayout()}}.store(in: &cancellables)}

LiveCoreView.VideoViewDelegate 协议提供的“插槽”能力,在视频流画面上添加自定义视图,用于显示昵称、头像、PK 进度条等信息,或在主播关闭摄像头时提供占位图,以优化视觉体验。
import UIKitimport SnapKit// 自定义的用户信息悬浮视图(前景)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)}}}
import UIKitimport SnapKit// 自定义的头像占位视图(背景)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.createCoHostView 协议,根据 viewLayer 的值返回对应的视图。import AtomicXCoreimport RTCRoomEngine// 在您的视图控制器中,遵守 VideoViewDelegate 协议class YourViewController: UIViewController, VideoViewDelegate {// ... 其他代码 ...func setupCustomView() {// 1. 设置 LiveCoreView 自定义视图代理coreView.videoViewDelegate = self}// 2. 完整实现协议方法,处理两种 viewLayerfunc createCoHostView(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 == .backgroundlet 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 表示背景挂件视图,位于前景视图下层,仅在对应用户没有视频流(例如未开摄像头)的情况下显示,通常用于展示用户的默认头像或占位图 |

import AtomicXCoreimport RTCRoomEngineimport SnapKit// 自定义PK 用户视图class CustomBattleUserView: UIView {private let scoreView: UIView = {let view = UIView()view.backgroundColor = .black.withAlphaComponent(0.4)view.layer.cornerRadius = 12return view}()private lazy var scoreLabel: UILabel = {let label = UILabel()label.textColor = .whitelabel.font = .systemFont(ofSize: 14, weight: .bold)return label}()private var userId: Stringprivate let battleStore: BattleStoreprivate var cancellableSet: Set<AnyCancellable> = []init(liveId: String, battleUser: TUIBattleUser) {self.userId = battleUser.userIdself.battleStore = BattleStore.create(liveID: liveId)super.init(frame: .zero)backgroundColor = .clearisUserInteractionEnabled = false// UI 布局setupUI()// 订阅分数变化subscribeBattleState()}private func setupUI() {addSubview(scoreView)scoreView.addSubview(scoreLabel)scoreLabel.snp.makeConstraints { make inmake.leading.trailing.equalToSuperview().inset(5)}scoreView.snp.makeConstraints { make inmake.height.equalTo(24)make.bottom.equalToSuperview().offset(-5)make.trailing.equalToSuperview().offset(-5)}}// 订阅 PK 分数变化private func subscribeBattleState() {battleStore.state.subscribe(StatePublisherSelector(keyPath: \\BattleState.battleScore)).removeDuplicates().receive(on: RunLoop.main).sink { battleUsers inguard let self = self else { return }guard let score = battleScore[self.userId] else { return }// 更新 UIself.scoreLabel.text = "\\(battleUser.score)"}.store(in: &cancellableSet)}}
LiveCoreView 自定义视图代理VideoViewDelegate,实现 VideoViewDelegate.createBattleView 协议// 1. 设置 LiveCoreView 自定义视图代理coreView.videoViewDelegate = self// 2. 让您的视图控制器遵守 VideoViewDelegate 协议extension YourViewController: VideoViewDelegate {public func createBattleView(battleUser: TUIBattleUser) -> UIView? {// CustomBattleUserView 是您自定义的PK用户信息视图let customView = CustomBattleUserView(liveId:liveId, battleUser:battleUser)return customView}}
参数 | 类型 | 说明 |
battleUser | TUIBattleUser | PK 用户信息对象 |
battleUser.roomId | String | PK 的房间 ID |
battleUser.userId | String | PK 用户 ID |
battleUser.userName | String | PK 用户昵称 |
battleUser.avatarUrl | String | PK 用户头像地址 |
battleUser.score | UInt | PK 分数 |

LiveCoreView 自定义视图代理VideoViewDelegate,实现 VideoViewDelegate.createBattleContainerView 协议。// 1. 设置 LiveCoreView 自定义视图代理coreView.videoViewDelegate = self// 2. 让您的视图控制器遵守 VideoViewDelegate 协议并设置代理extension YourViewController: VideoViewDelegate {func createBattleContainerView() -> UIView? {return CustomBattleContainerView()}}
礼物类型 | 分数计算规则 | 示例 |
基础礼物 | 礼物价值 × 5 | 10元礼物 → 50分 |
中级礼物 | 礼物价值 × 8 | 50元礼物 → 400分 |
高级礼物 | 礼物价值 × 12 | 100元礼物 → 1200分 |
特效礼物 | 固定高分数 | 520元礼物 → 1314分 |

Store/Component | 功能描述 | API 文档 |
LiveCoreView | 直播视频流展示与交互的核心视图组件:负责视频流渲染和视图挂件处理,支持主播直播、观众连麦、主播连线等场景。 | |
DeviceStore | 音视频设备控制:麦克风(开关 / 音量)、摄像头(开关 / 切换 / 画质)、屏幕共享,设备状态实时监听。 | |
CoHostStore | 主播跨房连线:支持多布局模板(动态网格等),发起 / 接受 / 拒绝连线,连麦主播互动管理。 | |
BattleStore | 主播 PK 对战:发起 PK(配置时长 / 对手),管理 PK 状态(开始 / 结束),同步分数,监听对战结果。 |
targetHostLiveId 是否正确,并且对方直播间处于正常开播状态。VideoViewDelegate 添加的自定义视图的生命周期和事件?文档反馈