实现秒开秒切
秒开和秒切可以优化直播体验,让观众能够更快速地进入直播间并切换直播间,提升观看的连贯性和流畅度,详见什么是秒开秒切。本文介绍如何通过声网秒开秒切场景化 API(VideoLoader API)集成秒开秒切功能到实时音视频互动中。
示例项目
声网在 GitHub 上提供开源 VideoLoaderAPI
示例项目供你参考。
准备开发环境
前提条件
请确保已准备好符合要求的开发环境,详见准备开发环境。
集成依赖
-
参考克隆仓库下载
VideoLoaderAPI
文件夹。将其中的如下文件夹和文件复制到与你的*.xcodeproj
文件同层级的位置:VideoLoaderAPI
文件夹。VideoLoaderAPI.podspec
文件。
注意为方便后续代码升级,请不要修改你添加的这些文件的名称和路径。
-
在终端里进入
*.xcodeproj
文件所在目录,并运行pod init
命令。项目文件夹下会生成一个Podfile
文本文件。 -
打开
Podfile
文件,修改文件为如下内容。注意target
和path
的填写。Rubysource 'https://github.com/CocoaPods/Specs.git'
use_frameworks!
platform :ios, '13.0'
# target 需改为你的项目中实际的 Target 名称
target 'VideoLoaderAPI_Example' do
pod 'VideoLoaderAPI', :path => 'VideoLoaderAPI.podspec'
target 'VideoLoaderAPI_Tests' do
inherit! :search_paths
end
end -
在终端里进入
*.xcodeproj
文件所在目录,执行pod install
命令安装依赖库。依赖库安装成功后,会出现一个
*.xcworkspace
文件。 -
(可选)如果你在使用 VideoLoader API 前,已在项目中集成了 RTC SDK 依赖。你可以修改
VideoLoaderAPI.podspec
这一行内容为你正在使用的 SDK 版本。Shell# 4.1.1.19 为第 4 步默认安装的版本,如果你正在使用其他版本,请修改为当前版本
s.dependency 'AgoraRtcEngine_Special_iOS', '4.1.1.19'
使用 VideoLoader API 前的 API 调用
本节展示在实现秒开、秒切功能前的准备工作。
1. 初始化 RtcEngine 和 VideoLoader
在项目工程文件默认的 ViewController
里,调用声网 RTC SDK 中的 sharedEngineWithConfig
创建并初始化 AgoraRtcEngineKit
对象,并初始化 VideoLoader。
func prepareEngine() {
// 创建 AgoraRtcEngineKit 实例
let engine = _createRtcEngine()
// 获取 VideoLoaderApiImpl 的实例对象
let loader = VideoLoaderApiImpl.shared
loader.addListener(listener: self)
// 创建 VideoLoaderConfig 实例
let config = VideoLoaderConfig()
// 设置 AgoraRtcEngineKit 实例
config.rtcEngine = engine
// 初始化 VideoLoader
loader.setup(config: config)
}
// 初始化 AgoraRtcEngineKit
private func _createRtcEngine() ->AgoraRtcEngineKit {
let config = AgoraRtcEngineConfig()
// 传入你从控制台获取的声网项目的 APP ID
config.appId = KeyCenter.AppId
config.channelProfile = .liveBroadcasting
config.audioScenario = .gameStreaming
config.areaCode = .global
let engine = AgoraRtcEngineKit.sharedEngine(with: config,
delegate: nil)
return engine
}
2. 使用通配 Token
你需要先在服务端生成 Token,再在后续客户端实现步骤中,传入 Token 参数进行鉴权。
为加快用户加入频道的速度,你可以使用通配 Token,详见使用通配 Token 最佳实践。
通配 Token 的使用会带来诸如“炸房”的风险,请结合具体需求决定是否使用通配 Token。
实现秒开
本节介绍如何在直播场景中实现观众观看直播时的丝滑秒开体验。
1. 实现直播间列表 UI 模块
实现一个展示直播间列表的 UI 模块,下面以一个 UICollectionView
举例。
private lazy var listView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 5
layout.minimumInteritemSpacing = 5
layout.sectionInset = .zero
let w = view.bounds.width / 2 - 5
layout.itemSize = CGSize(width: w, height: w * 1.5)
let collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout)
collectionView.backgroundColor = .white
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "videoloader_listcell")
collectionView.scrollsToTop = false
collectionView.delegate = self.delegateHandler
collectionView.dataSource = self
collectionView.contentInsetAdjustmentBehavior = .never
collectionView.showsVerticalScrollIndicator = false
return collectionView
}()
2. 监听直播间列表的滑动事件
创建一个 AGCollectionLoadingDelegateHandler
对象,并将其作为直播间列表滑动事件的代理注册给直播间列表的 UI。创建 AGCollectionLoadingDelegateHandler
对象时,你需要在构造函数的 localUid
参数中传入本地用户的 uid
。
AGCollectionLoadingDelegateHandler
对象监听到直播间列表的滑动事件后,会驱动 AGCollectionLoadingDelegateHandler
类内部封装的最佳实践,并对屏幕内出现的直播间进行频道预加载(preloadChannel
)。
// 代码片段来源于房间列表的 ViewController
class ShowRoomListVC: UIViewController {
// 创建 AGCollectionLoadingDelegateHandler
private lazy var delegateHandler: AGCollectionLoadingDelegateHandler = {
let handler = AGCollectionLoadingDelegateHandler(localUid: localUid)
return handler
}()
// 自己的业务服务模块
private lazy var service: ShowServiceProtocol = AppContext.showServiceImp("")
// 房间列表
private var roomList = [ShowRoomListModel]() {
didSet {
delegateHandler.roomList = AGRoomArray(roomList: roomList)
collectionView.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// 加载视图后可以做其他的额外操作
...
prepareEngine()
listView.delegate = self.delegateHandler
listView.dataSource = self
service.getRoomList(page: 1) { [weak self] error, roomList in
self?.roomList = roomList ?? []
}
}
...
}
3. 监听单个直播间的触碰事件
在原生 listView
的 DataSource
协议中,你可以通过在 cellForItemAt
方法中创建 Cell。在创建 Cell 时,添加 ag_addPreloadTap
方法来实现监听单个直播间的触碰事件。在添加 ag_addPreloadTap
方法时,你需要传入以下参数:
roomInfo
:VideoLoader.RoomInfo
对象。localUid
:本地用户的uid
。enableProcess
:点击事件触发的回调函数。你可以根据回调中的点击状态执行相应的业务逻辑,例如检查是否成功获取到 Token 等。onRequireRenderVideo
:最佳主播画面渲染时机的回调函数。建议你提前创建好主播画面的容器,并在收到onRequireRenderVideo
事件通知后,将容器对象填入该回调函数的返回值中。这样,ag_addPreloadTap
会自动将主播画面添加并渲染在该容器上。completion
:进入直播页面的回调函数。
当触发单个直播间的触碰事件时,ag_addPreloadTap
会自动执行内部封装的最佳实践,并加入点击的直播间频道,这样你就无需在业务调用 joinChannelByToken
或类似的用于加入频道的方法。
// 代码片段来源于直播间列表的 ViewController
extension ShowRoomListVC: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// 返回房间列表的数量
return roomList.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: ShowRoomListCell = collectionView.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(ShowRoomListCell.self), for: indexPath) as! ShowRoomListCell
// 获取对应索引的房间数据
let room = roomList[indexPath.item]
// 设置 Cell 内容
...
// 添加触碰事件
cell.ag_addPreloadTap(roomInfo: room, localUid: delegateHandler.localUid) { [weak self] state in
if AppContext.shared.rtcToken?.count ?? 0 == 0 {
// 如果没有获取到 Token
if state == .began {
// 如果是按下的情况,可以尝试再次获取 Token
self?.preGenerateToken()
} else if state == .ended {
// 如果点击完成,但是仍然没有获取到 Token,禁止执行进入直播间操作
ToastView.show(text: "Token is not exit, try again!")
}
return false
}
return true
} onRequireRenderVideo: { _ in
// 主播画面渲染的最佳时机
return nil
} completion: { [weak self] in
// 进入直播间内
self?.joinRoom(room)
}
return cell
}
}
实现秒切
本节介绍如何在直播场景内实现观众秒速切换直播间观看直播。
1. 实现直播间滑动切换 UI 模块
实现一个直播间列表的滑动切换模块,下面以 UICollectionView
为例。
private lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
layout.sectionInset = .zero
layout.itemSize = self.view.bounds.size
let collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout)
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: NSStringFromClass(UICollectionViewCell.self))
collectionView.scrollsToTop = false
collectionView.isPagingEnabled = true
collectionView.contentInsetAdjustmentBehavior = .never
collectionView.bounces = false
collectionView.showsVerticalScrollIndicator = false
return collectionView
}()
2. 监听直播间的切换事件
创建一个 AGCollectionSlicingDelegateHandler
的对象,并将其注册为直播间 CollectionView 的滑动事件代理。创建 AGCollectionSlicingDelegateHandler
对象时,你需要在构造函数的参数中传入如下:
localUid
:本地用户的uid
。needPreJoin
:设置是否需要提前加入所处直播间的上下两个直播间。如果设为是(true
),则会带来更好的秒切效果,但也会增加费用。videoType
:切换直播间视频显示的时机。audioType
:切换直播间音频显示的时机。onRequireRenderVideo
:对应直播间主播视图的最佳渲染时机。此时你必须传入对应直播间的主播画面的容器,AGCollectionSlicingDelegateHandler
对象将主播画面添加并渲染在该容器上。
AGCollectionSlicingDelegateHandler
对象监听到直播间 CollectionView 的滑动事件后,会驱动 AGCollectionSlicingDelegateHandler
类封内部封装的最佳实践,并在最佳时机对不同直播间的音视频订阅行为进行切换。
同时,在创建 ViewController 时,你需要调用 roomList
方法,将初始的直播间列表信息传递给 AGCollectionSlicingDelegateHandler
对象。
// 代码片段来源于房间列表的 ViewController
class ShowLivePagesViewController: ViewController {
// 创建一个 AGCollectionSlicingDelegateHandler 对象
private lazy var delegateHandler = {
let handler = AGCollectionSlicingDelegateHandler(localUid: localUid, needPrejoin: needPrejoin)
handler.videoSlicingType = videoType
handler.audioSlicingType = audioType
handler.onRequireRenderVideo = { [weak self] info, cell, indexPath in
// 在渲染主播画面的最佳时机,执行相应的操作
return ...
}
return handler
}()
// 传递初始的直播间列表信息给 AGCollectionSlicingDelegateHandler 对象
var roomList: [ShowRoomListModel]? {
didSet {
delegateHandler.roomList = AGRoomArray(roomList: roomList)
}
}
// 将 delegateHandler 对象注册为直播间 CollectionView 的滑动事件代理
override func viewDidLoad() {
super.viewDidLoad()
// 设置代理
collectionView.delegate = delegateHandler
collectionView.dataSource = self
...
}
}
使用 VideoLoader API 后释放资源
使用声网秒开秒切场景化 API(VideoLoader API)后,在离开直播场景时,无需你在业务层主动调用 leaveChannel
,按照如下示例代码释放资源即可:
VideoLoaderApiImpl.shared.cleanCache()
AgoraRtcEngineKit.destroy()