文档中心>直播 SDK>UI 自定义>直播视频组件>调整视频直播挂件 (iOS UIKit)

调整视频直播挂件 (iOS UIKit)

最近更新时间:2026-02-27 14:30:10

我的收藏
LiveCoreView 是一个跨平台的视频直播核心组件,提供开播、观看、连麦和主播 PK 等关键能力。它通过“挂件”机制,支持在视频区域实时显示用户名、等级、PK 进度条等自定义信息。本文档将指导您如何通过实现代理协议,在 iOS 平台上快速定制专属的视频挂件 UI。

效果展示

连线视频挂件
PK 视频挂件




准备工作

在开始调整视频挂件前,请先参考 主播开播观众观看 完成主流程的搭建。

核心原理

LiveCoreView 通过 VideoViewDelegate 代理支持自定义视图渲染。当业务场景发生变化(例如有人上麦、开始 PK)时,LiveCoreView 会调用代理方法询问应显示的视图。实现对应的代理方法并返回自定义的 UIView 实例即可。
方法
描述
对应业务场景
createCoGuestView
创建观众连麦的挂件视图
观众连麦、邀请上麦
createCoHostView
创建跨房连麦(主播连线)的挂件视图
主播连线
createBattleView
创建 PK 场景中单个用户的挂件视图(例如头像、分数)
主播 PK
createBattleContainerView
创建 PK 场景的整体容器视图(例如背景、分数 PK 条)
主播 PK

调整连线场景挂件

当观众与主播进行连线或者主播间跨房连线时,直播间界面会从单人模式切换为多人布局。此时,需要在连线的视频窗口上展示特定的用户信息,例如昵称、等级或“静音”状态提示,以便区分不同麦位。

适用场景

主播和观众视频连麦时,修改麦位上的用户信息(例如昵称、等级、静音图标)。
主播与主播跨房连线时,修改对方主播的显示样式(例如昵称、等级、静音图标)。
修改无视频画面时的默认背景(例如占位头像)。
修改空麦位时的视图实现。

视图层级示意



实现步骤

步骤1:准备自定义挂件 View

定义三个基础视图类,分别用于展示用户信息、空麦位提示和无视频流时的背景。
创建自定义的用户信息视图:
说明:
您可以参考 TUILiveKit 开源项目中的 AnchorCoGuestView.swift 文件来了解默认效果的实现逻辑。
import UIKit

class CustomInfoView: UIView {
let nameLabel = UILabel()
let muteIcon = UIImageView(image: UIImage(named: "mute_icon"))
init(name: String, isMuted: Bool) {
super.init(frame: .zero)
nameLabel.text = name
muteIcon.isHidden = !isMuted
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// ... layout code ...
}
创建自定义的空麦位视图:
说明:
您可以参考 TUILiveKit 开源项目中的 AnchorEmptySeatView.swift 文件来了解默认效果的实现逻辑。
import UIKit

class EmptySeatView: UIView {
let addIcon = UIImageView(image: UIImage(named: "add_icon"))
let addLabel = UILabel()
// ... layout code ...
}
创建自定义无视频流时的头像占位视图:
说明:
您可以参考 TUILiveKit 开源项目中的 AnchorBackgroundWidgetView.swift 文件来了解默认效果的实现逻辑。
import UIKit

class 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 ...
}

步骤2:实现代理逻辑

创建一个代理类(或在现有 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.isEmpty
switch 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.isEmpty
switch 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 表示背景挂件视图,位于前景视图下层,仅在对应用户没有视频流(例如未开摄像头)的情况下显示,通常用于展示用户的默认头像或占位图。

调整主播 PK 场景挂件

PK 是直播中互动性最强的环节。在 PK 过程中,画面通常根据参与人数被分割为几部分。开发者需要在画面上方或中间区域添加 PK 专用的 UI,分数PK条、每个麦位上的分数显示、倒计时动画或“VS”特效图标,以营造紧张的竞技氛围。

视图层级示意


说明:
PK 功能依赖于连线功能,所以需要先在主播连线的情况下才能发起 PK 请求

实现步骤

步骤1:准备自定义 UI 组件

PK 场景通常需要两类视图:
1. 单人挂件:显示在每个主播窗口上(例如:分数胶囊)。
2. 全局容器:覆盖在整体画面上(例如:VS 动画、倒计时)。
说明:
您可以参考 TUILiveKit 开源项目中的 AnchorBattleMemberInfoView.swiftAnchorBattleInfoView.swift 文件来了解默认效果的实现逻辑。
// 示例:单人分数条
class MyBattleScoreView: UIView {
private let scoreView = UIView()
// ... layout code ...
}

// 示例:全局 VS 面板
class MyBattleContainer: UIView {
private let battleTimeView = UIImageView(frame: .zero)
// 内部实现倒计时和 VS 动画
}

步骤2: 实现代理逻辑

实现VideoViewDelegate的剩余方法 createBattleViewcreateBattleContainerView
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 AtomicXCore
import SnapKit

class YourAnchorViewController: UIViewController {
private let anchorView: AnchorView
private 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 in
make.edges.equalToSuperview()
}
}
}
观众端集成:观众端通过 AudienceContainerViewDelegate 来回调核心视图的创建时机。
class YourAudienceViewController: UIViewController {
private let audienceView: AudienceContainerView
private 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
}
}

进阶:获取实时业务数据

在开发 PK 等复杂场景时,您会发现 SeatInfo 只提供了基础麦位信息。如果您需要获取实时倒计时、PK 分数等业务数据,就需要在您的自定义视图中对接 AtomicXCore中的核心数据。
Store/Component
功能描述
API 文档
CoGuestStore
观众连线数据:已连线用户列表、邀请列表,申请列表等。
CoHostStore
主播连线数据:已连线用户列表、邀请列表,申请列表等。
BattleStore
PK 数据:当前 PK 信息、PK 用户列表、PK 分数列表。

常见问题

为什么我设置了代理,但自定义视图没有显示?

请检查以下两点:
1. 代理对象被释放了videoViewDelegate 是弱引用。如果您在函数内部定义代理(例如 let delegate = VideoWidgetProvider()),函数执行结束后代理对象会被立即释放。
解决:请务必将代理对象声明为 ViewController 的属性(成员变量),保持强引用。
2. 设置时机太晚:创建主播视图的挂件时必须在创建AnchorView之前完成 LiveCoreViewvideoViewDelegate 的赋值,否则AnchorView已经完成了默认视图的加载

我只想修改“连麦”视图,“PK”视图想保留默认的,怎么办?

VideoViewDelegate 是一种完全接管模式。一旦您设置了自定义的代理,SDK 默认的 AnchorView/AudienceView 内部实现的代理逻辑就会完全失效。
解决:您需要在您的代理类中实现所有相关方法。对于您不想修改的部分(例如 PK 视图),您可以:
1. 复制源码:找到 LiveKit 源码中默认的实现类(例如 AnchorBattleInfoView),在您的代理中返回这些默认类的实例。
2. 手动实现:参考默认样式,快速写一个类似的简单视图返回。

自定义挂件显示了,但无法点击?

因为前景视图 (.foreground) 始终位于视频层之上,所以您要检查您的 View 是否位于 .foreground 层,且 isUserInteractionEnabledtrue,同时确保它的父视图没有禁用交互。