LiveCoreView 是一个跨平台的视频直播核心组件,提供开播、观看、连麦和主播 PK 等关键能力。它通过“挂件”机制,支持在视频区域实时显示用户名、等级、PK 进度条等自定义信息。本文档将指导您如何通过实现代理协议,在 iOS 平台上快速定制专属的视频挂件 UI。连线视频挂件 | PK 视频挂件 |
![]() |
![]() |
LiveCoreView 通过 VideoViewDelegate 代理支持自定义视图渲染。当业务场景发生变化(例如有人上麦、开始 PK)时,LiveCoreView 会调用代理方法询问应显示的视图。实现对应的代理方法并返回自定义的 UIView 实例即可。方法 | 描述 | 对应业务场景 |
createCoGuestView | 创建观众连麦的挂件视图 | 观众连麦、邀请上麦 |
createCoHostView | 创建跨房连麦(主播连线)的挂件视图 | 主播连线 |
createBattleView | 创建 PK 场景中单个用户的挂件视图(例如头像、分数) | 主播 PK |
createBattleContainerView | 创建 PK 场景的整体容器视图(例如背景、分数 PK 条) | 主播 PK |

import UIKitclass CustomInfoView: UIView {let nameLabel = UILabel()let muteIcon = UIImageView(image: UIImage(named: "mute_icon"))init(name: String, isMuted: Bool) {super.init(frame: .zero)nameLabel.text = namemuteIcon.isHidden = !isMuted}required init?(coder: NSCoder) {fatalError("init(coder:) has not been implemented")}// ... layout code ...}
import UIKitclass EmptySeatView: UIView {let addIcon = UIImageView(image: UIImage(named: "add_icon"))let addLabel = UILabel()// ... layout code ...}
import UIKitclass CustomAvatarView: UIView {let avatarView = UIImageView(frame: .zero)init(avatarURL: String) {super.init(frame: .zero)// load avatar URL}required init?(coder: NSCoder) {fatalError("init(coder:) has not been implemented")}// ... layout code ...}
ViewController 中扩展),实现 VideoViewDelegate 的方法 createCoGuestView (观众连线)和 createCoHostView(主播连线),返回刚才创建的自定义视图。VideoViewDelegate 中的 createCoGuestView 方法,返回观众连线视频挂件。class VideoWidgetProvider: VideoViewDelegate {/// seatInfo: 麦位数据 (用户信息、音视频状态)/// viewLayer: 视图层级 (.foreground 前景视图 / .background 背景视图)func createCoGuestView(seatInfo: SeatInfo, viewLayer: ViewLayer) -> UIView? {let isUserOnSeat = !seatInfo.userInfo.userID.isEmptyswitch viewLayer {case .foreground:if isUserOnSeat {// 非空麦位,返回自定义的前景视图let widget = CustomInfoView(name: seatInfo.userInfo.userName,isMuted: seatInfo.userInfo.microphoneStatus == .off)return widget}// 空麦位,返回自定义空麦位视图return EmptySeatView()case .background:if isUserOnSeat {// 返回自定义的背景视图(未开摄像头时显示)let bgView = CustomAvatarView(avatarURL: seatInfo.userInfo.avatarURL)return bgView}return nil}}}
VideoViewDelegate 中的 createCoHostView 方法返回主播连线的视频挂件。class VideoWidgetProvider: VideoViewDelegate {/// seatInfo: 麦位数据 (用户信息、音视频状态)/// viewLayer: 视图层级 (.foreground 前景视图 / .background 背景视图)func createCoHostView(seatInfo: SeatInfo, viewLayer: ViewLayer) -> UIView? {let isUserOnSeat = !seatInfo.userInfo.userID.isEmptyswitch viewLayer {case .foreground:if isUserOnSeat {// 返回自定义的前景视图,可以返回与观众连线不同的样式let widget = CustomInfoView(name: seatInfo.userInfo.userName,isMuted: seatInfo.userInfo.microphoneStatus == .off)return widget}// 返回自定义空麦位视图,可以返回与观众连线不同的样式return EmptySeatView()case .background:if isUserOnSeat {// 返回自定义的背景视图(未开摄像头时显示),可以返回与观众连线不同的样式let bgView = CustomAvatarView(avatarURL: seatInfo.userInfo.avatarURL)return bgView}return nil}}}
参数 | 类型 | 说明 |
seatInfo | SeatInfo | 麦位信息对象,包含麦上用户的详细信息。 |
seatInfo.userInfo.userName | String | 麦上用户的昵称。 |
seatInfo.userInfo.avatarURL | String | 麦上用户的头像 URL。 |
seatInfo.userInfo.microphoneStatus | DeviceStatus | 麦上用户的麦克风状态。 |
seatInfo.userInfo.cameraStatus | DeviceStatus | 麦上用户的摄像头状态。 |
viewLayer | ViewLayer | 视图层级枚举。 .foreground 表示前景挂件视图,始终显示在视频画面的最上层。.background 表示背景挂件视图,位于前景视图下层,仅在对应用户没有视频流(例如未开摄像头)的情况下显示,通常用于展示用户的默认头像或占位图。 |

// 示例:单人分数条class MyBattleScoreView: UIView {private let scoreView = UIView()// ... layout code ...}// 示例:全局 VS 面板class MyBattleContainer: UIView {private let battleTimeView = UIImageView(frame: .zero)// 内部实现倒计时和 VS 动画}
VideoViewDelegate 的剩余方法 createBattleView 和 createBattleContainerView。class VideoWidgetProvider: VideoViewDelegate {/// 1. 创建【PK 单人信息】挂件 (显示在每个主播视频上方)func createBattleView(seatInfo: SeatInfo) -> UIView? {let scoreView = MyBattleScoreView()return scoreView}/// 2. 创建【PK 全局容器】挂件 (显示在整个视频区域上方)func createBattleContainerView() -> UIView? {let container = MyBattleContainer()return container}}
VideoWidgetProvider 注入到直播核心流程中。AnchorView 之前,先初始化 LiveCoreView 并接管其代理。import AtomicXCoreimport SnapKitclass YourAnchorViewController: UIViewController {private let anchorView: AnchorViewprivate let widgetProvider = VideoWidgetProvider()init(liveInfo: LiveInfo) {let videoView = LiveCoreView(viewType: .pushView)videoView.videoViewDelegate = widgetProvider // 接管代理anchorView = AnchorView(liveInfo: liveInfo, coreView: videoView)super.init(nibName: nil, bundle: nil)}required init?(coder: NSCoder) {fatalError("init(coder:) has not been implemented")}override func viewDidLoad() {super.viewDidLoad()view.addSubview(anchorView)anchorView.snp.makeConstraints { make inmake.edges.equalToSuperview()}}}
AudienceContainerViewDelegate 来回调核心视图的创建时机。class YourAudienceViewController: UIViewController {private let audienceView: AudienceContainerViewprivate let widgetProvider = VideoWidgetProvider()public init(roomId: String) {self.audienceView = AudienceContainerView(roomId: roomId)super.init(nibName: nil, bundle: nil)self.audienceView.delegate = self}required init?(coder: NSCoder) {fatalError("init(coder:) has not been implemented")}}extension YourAudienceViewController: AudienceContainerViewDelegate {func onCreateCoreView(for liveInfo: LiveInfo) -> LiveCoreView? {let view = LiveCoreView(viewType: .playView)view.videoViewDelegate = widgetProvider // 接管代理return view}}
SeatInfo 只提供了基础麦位信息。如果您需要获取实时倒计时、PK 分数等业务数据,就需要在您的自定义视图中对接 AtomicXCore 中的核心数据。videoViewDelegate 是弱引用。如果您在函数内部定义代理(例如 let delegate = VideoWidgetProvider()),函数执行结束后代理对象会被立即释放。ViewController 的属性(成员变量),保持强引用。AnchorView 之前完成 LiveCoreView 的 videoViewDelegate 的赋值,否则 AnchorView 已经完成了默认视图的加载VideoViewDelegate 是一种完全接管模式。一旦您设置了自定义的代理,SDK 默认的 AnchorView/AudienceView 内部实现的代理逻辑就会完全失效。LiveKit 源码中默认的实现类(例如 AnchorBattleInfoView),在您的代理中返回这些默认类的实例。.foreground) 始终位于视频层之上,所以您要检查您的 View 是否位于 .foreground 层,且 isUserInteractionEnabled 为 true,同时确保它的父视图没有禁用交互。文档反馈