You need to enable JavaScript to run this app.
导航

iOS (Swift)

最近更新时间2024.04.12 17:03:46

首次发布时间2023.12.12 21:59:22

本文介绍如何集成火山引擎 RTC SDK,并实现实时音视频通话。根据如下步骤操作,即可从 0 开始构建一个简单的音视频通话应用。
你也可以参考示例项目,了解更完整的项目实现。

前提条件

在开始集成 RTC SDK 前,请确保满足以下要求:

  • macOS 开发电脑,且可以正常访问互联网

  • Xcode 14.1 或以上版本

  • Apple 开发者账号

  • iOS 11.0 或以上版本真机设备,且可以正常访问互联网

  • 火山引擎控制台开通实时音视频服务,你需要从控制台获取 AppID 和临时 Token 用于项目跑通

创建和配置项目

新建项目

  1. 打开 Xcode,单击 Create New Project... 新建项目。

  2. 在项目模板页选择 iOS > App,单击 Next

  3. 在项目配置页填写 Product Name(本文以 RTCDemo 为例)、TeamOrganization IdentifierInterface 选择 StoryboardLanguage 选择 Swift。单击 Next

    说明

    如果你尚未登录 Apple 账户,单击 Add account… 并按照提示登录。完成后即可选择你的 Apple 账户作为开发团队。

  4. 选择项目存储位置,单击 Create

配置签名

选中项目,进入 TARGETS > RTCDemo > Signing & Capabilities,勾选 Automatically manage signing

配置权限

切换到 Info 页面,单击 + 添加音频和视频设备权限。

  • 添加 Privacy - Microphone Usage Description,并填入使用麦克风的原因。

  • 添加 Privacy - Camera Usage Description,并填入使用摄像头的原因。

集成 SDK

通过 CocoaPods 集成

  1. 在终端窗口执行如下命令安装 CocoaPods

    sudo gem install cocoapods
    
  2. 进入项目根目录,执行如下命令,创建 Podfile 文件。

    pod init
    
  3. 打开 Podfile 文件,替换为如下内容并保存。

    说明

    • 如果你的项目名称不是 RTCDemo,请注意替换。
    • 你需要将 '3.x.y.z' 替换为具体的版本号,最新版本号请参看下载 SDK
    source 'https://github.com/volcengine/volcengine-specs.git'
    target 'RTCDemo' do
      pod 'VolcEngineRTC', '3.x.y.z'
    
    end
    
  4. 执行 pod install 命令安装 VolcEngineRTC 相关库。安装成功后,项目文件夹中出现 RTCDemo.xcworkspace 文件,使用 Xcode 打开该文件进行后续操作。

手动集成

  1. 下载并解压火山引擎 RTC SDK 文件。

  2. 将解压后目录中的 VolcEngineRTC.xcframeworkRealXBase.xcframework 拖入到项目中,勾选 Copy items if needed

  3. 选中项目,进入 TARGETS > RTCDemo > General,在 Frameworks, Libraries, and Embedded Content 中将 VolcEngineRTC.xcframeworkRealXBase.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,你必须添加相关说明:

  • 如果你的 App 中没有隐私清单文件,你可以直接将 RTC SDK 的隐私清单文件添加项目根目录下。如图:
    alt
  • 如果你的 App 中已有隐私清单文件,请将 RTC SDK 的隐私清单文件中的内容添加到 App 的隐私清单文件中。

获取 RTC SDK 的隐私清单文件:

PrivacyInfo.xcprivacy.zip
933.00Bytes
实现音视频通话

说明

本章节将先向你提供 API 调用时序图和完整的实现代码,再对具体的实现步骤展开介绍。

时序图

下图为使用火山引擎 RTC SDK 实现基础音视频通话的 API 调用时序图。

alt

完整示例代码

将以下示例代码替换 ViewController.swift 文件中的全部内容,连接并选择你的 iOS 真机设备,单击 XCode 窗口左上角的运行按钮(或使用 Command ⌘ + R 快捷键),即可快速实现音视频通话。

说明

你需要将代码中的 roomIduserIdkAppIDtoken 替换为你在控制台上生成临时 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 中的 roomIduserIdkAppIDtoken 替换为你在控制台上生成临时 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
}
编译与运行
  1. 在 Xcode 中连接并选择你的 iOS 真机设备,单击 XCode 窗口左上角的运行按钮(或使用 Command ⌘ + R 快捷键)。

    说明

    如果你尚未信任开发者,请根据 Xcode 提示,在 iOS 设备上打开设置,选择通用 > VPN 与设备管理,在开发者 APP 中单击信任开发者。

  2. 在 iOS 设备上打开 Demo 应用时,在弹窗中选择开启摄像头和麦克风权限。

  3. (可选)在第二台设备上使用相同的 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