本文介绍如何集成火山引擎 RTC SDK,并实现实时音视频通话。根据如下步骤操作,即可从 0 开始构建一个简单的音视频通话应用。
你也可以参考示例项目,了解更完整的项目实现。
在开始集成 RTC SDK 前,请确保满足以下要求:
macOS 开发电脑,且可以正常访问互联网
Xcode 14.1 或以上版本
Apple 开发者账号
iOS 11.0 或以上版本真机设备,且可以正常访问互联网
打开 Xcode,单击 Create New Project... 新建项目。
在项目模板页选择 iOS > App,单击 Next。
在项目配置页填写 Product Name(本文以 RTCDemo 为例)、Team、Organization Identifier。Interface 选择 Storyboard,Language 选择 Swift。单击 Next。
说明
如果你尚未登录 Apple 账户,单击 Add account… 并按照提示登录。完成后即可选择你的 Apple 账户作为开发团队。
选择项目存储位置,单击 Create。
选中项目,进入 TARGETS > RTCDemo > Signing & Capabilities,勾选 Automatically manage signing。
切换到 Info 页面,单击 + 添加音频和视频设备权限。
添加 Privacy - Microphone Usage Description,并填入使用麦克风的原因。
添加 Privacy - Camera Usage Description,并填入使用摄像头的原因。
在终端窗口执行如下命令安装 CocoaPods。
sudo gem install cocoapods
进入项目根目录,执行如下命令,创建 Podfile 文件。
pod init
打开 Podfile 文件,替换为如下内容并保存。
说明
'3.x.y.z'
替换为具体的版本号,最新版本号请参看下载 SDK。source 'https://github.com/volcengine/volcengine-specs.git' target 'RTCDemo' do pod 'VolcEngineRTC', '3.x.y.z' end
执行 pod install
命令安装 VolcEngineRTC
相关库。安装成功后,项目文件夹中出现 RTCDemo.xcworkspace
文件,使用 Xcode 打开该文件进行后续操作。
下载并解压火山引擎 RTC SDK 文件。
将解压后目录中的 VolcEngineRTC.xcframework
和 RealXBase.xcframework
拖入到项目中,勾选 Copy items if needed
。
选中项目,进入 TARGETS > RTCDemo > General,在 Frameworks, Libraries, and Embedded Content 中将 VolcEngineRTC.xcframework
和 RealXBase.xcframework
的属性设置为 Embed & Sign。
应苹果公司的要求,你的 iOS App 如需要上线 App Store,必须准确描述 App 本身和集成的第三方 SDK 使用指定范围内系统接口的原因。自 2024 年 5 月 1 日起,如果你未提供相关描述,你的 App 将无法通过 App Store Connect 的审核。详见 Describing use of required reason API。
如果你在 App 中集成了 3.58 及之前版本的 RTC SDK,你必须添加相关说明:
获取 RTC SDK 的隐私清单文件:
说明
本章节将先向你提供 API 调用时序图和完整的实现代码,再对具体的实现步骤展开介绍。
下图为使用火山引擎 RTC SDK 实现基础音视频通话的 API 调用时序图。
将以下示例代码替换 ViewController.swift
文件中的全部内容,连接并选择你的 iOS 真机设备,单击 XCode 窗口左上角的运行按钮(或使用 Command ⌘ + R 快捷键),即可快速实现音视频通话。
说明
你需要将代码中的 roomId
、userId
、kAppID
、token
替换为你在控制台上生成临时 Token 时所使用的房间 ID 和用户 ID,以及获取到的 AppID 和临时 Token。
import UIKit import VolcEngineRTC let kAppID = "" // 填写 appId let roomId = "" // 填写房间号 let userId = "" // 填写 userId let token = "" // 填写临时 token class ViewController: UIViewController, ByteRTCVideoDelegate, ByteRTCRoomDelegate { var rtcVideo: ByteRTCVideo? var rtcRoom: ByteRTCRoom? override func viewDidLoad() { super.viewDidLoad() self.createUI() self.buildRTCEngine() self.bindLocalRenderView() } deinit { // 销毁房间 self.rtcRoom?.leaveRoom() self.rtcRoom?.destroy() self.rtcRoom = nil // 销毁引擎 ByteRTCVideo.destroyRTCVideo() self.rtcVideo = nil } // MARK: Private method @objc func joinRoom() { joinButton.isSelected = !joinButton.isSelected if joinButton.isSelected { joinButton.setTitle("离开房间", for: .normal) // 加入房间 self.rtcRoom = self.rtcVideo?.createRTCRoom(roomId) self.rtcRoom?.delegate = self let userInfo = ByteRTCUserInfo.init() userInfo.userId = userId let roomCfg = ByteRTCRoomConfig.init() roomCfg.isAutoPublish = true roomCfg.isAutoSubscribeAudio = true roomCfg.isAutoSubscribeVideo = true self.rtcRoom?.joinRoom(token, userInfo: userInfo, roomConfig: roomCfg) } else { joinButton.setTitle("加入房间", for: .normal) self.rtcRoom?.leaveRoom() } } func buildRTCEngine() { // 创建引擎 self.rtcVideo = ByteRTCVideo.createRTCVideo(kAppID, delegate: self, parameters: [:]) // 开启本地音视频采集 self.rtcVideo?.startVideoCapture() self.rtcVideo?.startAudioCapture() } func bindLocalRenderView() { // 设置本地渲染视图 let canvas = ByteRTCVideoCanvas.init() canvas.view = self.localView canvas.renderMode = .hidden self.rtcVideo?.setLocalVideoCanvas(.main, withCanvas: canvas); } func bindRemoteRenderView(roomId: String, userId: String) { // 设置远端用户视频渲染视图 let canvas = ByteRTCVideoCanvas.init() canvas.view = remoteView canvas.renderMode = .hidden let streamKey = ByteRTCRemoteStreamKey.init() streamKey.userId = userId; streamKey.roomId = roomId; streamKey.streamIndex = .main; self.rtcVideo?.setRemoteVideoCanvas(streamKey, withCanvas: canvas) } func removeRemoteRenderView(roomId: String, userId: String) { // 移除远端用户视频渲染视图 let canvas = ByteRTCVideoCanvas.init() canvas.view = nil // 置为空 canvas.renderMode = .hidden let streamKey = ByteRTCRemoteStreamKey.init() streamKey.userId = userId; streamKey.roomId = roomId; streamKey.streamIndex = .main; self.rtcVideo?.setRemoteVideoCanvas(streamKey, withCanvas: canvas) } // 添加视图 func createUI() -> Void { let width = self.view.bounds.size.width*0.5 let height = self.view.bounds.size.height*0.5 // 本地预览 localView.frame = CGRect(x: 0, y: 0, width: width, height: height) self.view.addSubview(localView) // 远端预览 remoteView.frame = CGRect(x: width, y: 0, width: width, height: height) self.view.addSubview(remoteView) // 加入房间按钮 joinButton.frame = CGRect(x: 10, y: height + 30, width: width*2 - 20, height: 44) self.view.addSubview(joinButton) } // MARK: Lazy load lazy var joinButton: UIButton = { let button = UIButton(type: .custom) button.backgroundColor = .blue button.setTitle("加入房间", for: .normal) button.addTarget(self, action: #selector(joinRoom), for: .touchUpInside) return button }() lazy var remoteView: UIView = { let view = UIView.init() view.backgroundColor = .lightGray return view }() lazy var localView: UIView = { let view = UIView.init() view.backgroundColor = .lightGray return view }() // MARK: ByteRTCVideoDelegate & ByteRTCRoomDelegate //进房状态 func rtcRoom(_ rtcRoom: ByteRTCRoom, onRoomStateChanged roomId: String, withUid uid: String, state: Int, extraInfo: String) { } // 远端用户发流 func rtcRoom(_ rtcRoom: ByteRTCRoom, onUserPublishStream userId: String, type: ByteRTCMediaStreamType) { if type == .video || type == .both { DispatchQueue.main.async { self .bindRemoteRenderView(roomId: rtcRoom.getId(), userId: userId) } } } func rtcRoom(_ rtcRoom: ByteRTCRoom, onUserUnpublishStream userId: String, type: ByteRTCMediaStreamType, reason: ByteRTCStreamRemoveReason) { if type == .video || type == .both { DispatchQueue.main.async { self .removeRemoteRenderView(roomId: rtcRoom.getId(), userId: userId) } } } }
在 ViewController.swift
引入以下头文件。
import UIKit import VolcEngineRTC
将 ViewController.swift
中的 roomId
、userId
、kAppID
、token
替换为你在控制台上生成临时 Token 时所使用的房间 ID 和用户 ID,以及获取到的 AppID 和临时 Token。
let kAppID = "" // 填写 appId let roomId = "" // 填写房间号 let userId = "" // 填写 userId let token = "" // 填写临时 token
这里为了演示,我们创建两个 View 分别用于渲染本端视频和远端视频。界面左上角显示本端视频,右上角显示远端视频,下方为加入房间的按钮。
// 添加视图 func createUI() -> Void { let width = self.view.bounds.size.width*0.5 let height = self.view.bounds.size.height*0.5 // 本地预览 localView.frame = CGRect(x: 0, y: 0, width: width, height: height) self.view.addSubview(localView) // 远端预览 remoteView.frame = CGRect(x: width, y: 0, width: width, height: height) self.view.addSubview(remoteView) // 加入房间按钮 joinButton.frame = CGRect(x: 10, y: height + 30, width: width*2 - 20, height: 44) self.view.addSubview(joinButton) } // MARK: Lazy load lazy var joinButton: UIButton = { let button = UIButton(type: .custom) button.backgroundColor = .blue button.setTitle("加入房间", for: .normal) button.addTarget(self, action: #selector(joinRoom), for: .touchUpInside) return button }() lazy var remoteView: UIView = { let view = UIView.init() view.backgroundColor = .lightGray return view }() lazy var localView: UIView = { let view = UIView.init() view.backgroundColor = .lightGray return view }()
调用 createRTCVideo 创建引擎,所有 RTC 相关的 API 调用都要在创建引擎之后。
self.rtcVideo = ByteRTCVideo.createRTCVideo(kAppID, delegate: self, parameters: [:])
创建引擎后,调用 startVideoCapture 开启视频采集,调用 startAudioCapture 开启音频采集。
self.rtcVideo?.startVideoCapture() self.rtcVideo?.startAudioCapture()
调用 createRTCRoom 创建 RTC 房间,所有和房间相关的 API 都在 ByteRTCRoom 类。
joinRoom 表示进房,进房状态可以通过 ByteRTCRoomDelegate 中 onRoomStateChanged 回调。
self.rtcRoom = self.rtcVideo?.createRTCRoom(roomId) self.rtcRoom?.delegate = self let userInfo = ByteRTCUserInfo.init() userInfo.userId = userId let roomCfg = ByteRTCRoomConfig.init() roomCfg.isAutoPublish = true roomCfg.isAutoSubscribeAudio = true roomCfg.isAutoSubscribeVideo = true self.rtcRoom?.joinRoom(token, userInfo: userInfo, roomConfig: roomCfg)
调用 setLocalVideoCanvas 设置本端渲染窗口。
func bindLocalRenderView() { // 设置本地渲染视图 let canvas = ByteRTCVideoCanvas.init() canvas.view = self.localView canvas.renderMode = .hidden self.rtcVideo?.setLocalVideoCanvas(.main, withCanvas: canvas); }
在收到远端用户的 onUserPublishStream 回调后,你需要调用 setRemoteVideoCanvas 设置远端视图以在通话中查看远端视频。
func bindRemoteRenderView(roomId: String, userId: String) { // 设置远端用户视频渲染视图 let canvas = ByteRTCVideoCanvas.init() canvas.view = remoteView canvas.renderMode = .hidden let streamKey = ByteRTCRemoteStreamKey.init() streamKey.userId = userId; streamKey.roomId = roomId; streamKey.streamIndex = .main; self.rtcVideo?.setRemoteVideoCanvas(streamKey, withCanvas: canvas) }
在收到远端用户的 onUserUnpublishStream 回调后,你需要停止渲染远端视频。
func removeRemoteRenderView(roomId: String, userId: String) { // 移除远端用户视频渲染视图 let canvas = ByteRTCVideoCanvas.init() canvas.view = nil // 置为空 canvas.renderMode = .hidden let streamKey = ByteRTCRemoteStreamKey.init() streamKey.userId = userId; streamKey.roomId = roomId; streamKey.streamIndex = .main; self.rtcVideo?.setRemoteVideoCanvas(streamKey, withCanvas: canvas) }
以下代码在 ViewController.swift
的析构函数中执行。调用 leaveRoom 离开房间,destroy 销毁房间;调用 destroyRTCVideo 销毁 RTC 引擎。
deinit { // 销毁房间 self.rtcRoom?.leaveRoom() self.rtcRoom?.destroy() self.rtcRoom = nil // 销毁引擎 ByteRTCVideo.destroyRTCVideo() self.rtcVideo = nil }
在 Xcode 中连接并选择你的 iOS 真机设备,单击 XCode 窗口左上角的运行按钮(或使用 Command ⌘ + R 快捷键)。
说明
如果你尚未信任开发者,请根据 Xcode 提示,在 iOS 设备上打开设置,选择通用 > VPN 与设备管理,在开发者 APP 中单击信任开发者。
在 iOS 设备上打开 Demo 应用时,在弹窗中选择开启摄像头和麦克风权限。
(可选)在第二台设备上使用相同的 AppID 和 RoomID,更换 UserID 并生成新的临时 Token,即可加入同一个房间体验双端通话。
双端通话效果如下:
使用模拟器编译报错 No such module 'VolcEngineRTC'
?
解决方案:如果你使用的是搭载 Apple 芯片的 Mac 电脑,该问题可能是编译的架构(ARM64)和模拟器的架构(x86_64)不匹配导致的,请使用真机编译。
Xcode 15 编译报错 Sandbox: rsync.samba(xxxxx) deny(1)
?
解决方案:选中项目,进入 TARGETS > 项目名称 > Build Settings,在 Build Options 中将 User Script Sandboxing 修改为 No。