如何在15分钟内将视频呼叫功能添加到您的iOS应用
#ios #webrtc

在本文中,我们将向您展示如何在15分钟内将视频呼叫集成到iOS应用中。您不必从头开始实现整个WebRTC堆栈;您可以只使用现成的SDK。

这是结果的样子:

Image description

您将实现业务逻辑和界面。视频呼叫函数将使用GCoreVideOcallssdk集成,GCoreVideOcallssdk是一个GCORE框架,它可以照顾与插座和WEBRTC进行创建和交互,连接/创建视频呼叫室并与服务器进行交互。

>

注意:本文是有关与iOS合作的系列的一部分。在其他文章中,我们向您展示了如何create a mobile streaming app on iOS,以及如何将add VOD uploadingsmooth scrolling VOD介绍给现有应用程序。

您可以在本指南的帮助下添加什么功能

解决方案包括以下内容:

  1. 带有相机和麦克风的视频通话。
  2. 使用您的设计显示对话参与者。
  3. 将视频流从内置相机发送到服务器。
  4. 从服务器接收视频流。

解决方案体系结构

这是视频调用的解决方案体系结构的样子:

Image description

如何将视频通话功能集成到您的应用中

步骤1:准备

应用程序必须连接到房间,并在屏幕上显示用户和扬声器。为此,我们将使用UICollectionView和UicollectionViewCel来显示参与者,以及Uiview以显示用户。 WEBRTC提供RTCEAGLVideoView来在应用程序中显示视频流。我们还将创建一个小型模型来存储数据并将其链接起来。 SDK将是UiviewController。

最终应用程序将看起来像这样:

Image description

首先,您需要安装所有依赖项,并要求用户录制视频/麦克风的权利。

依赖性

通过在podfile中指定以下内容:

source 'https://github.com/G-Core/ios-video-calls-SDK.git'

... 

pod "mediasoup_ios_client", '1.5.3' 
pod "GCoreVideoCallsSDK", '2.6.0'

权限

要允许应用程序访问相机和麦克风,请在项目信息中指定权限:

  • NSMicrophoneUsageDescription(隐私麦克风用法描述)
  • NSCameraUsageDescription(隐私摄像机用法描述)

Image description

步骤2:创建UI

模型

需要一个模型来存储任何数据,在这种情况下是用户数据。

创建一个模型文件,并将gcorevideocallssdk导入其中。要存储用户数据,请创建将包含用户的ID和名称以及分配给其的RTCEAGLVIDEOVIEW的VideoCallunit结构。这是整个文件的外观:

import GCoreVideoCallsSDK 

final class Model { 
    var localUser: VideoCallUnit? 
    var remoteUsers: [VideoCallUnit] = [] 
} 

struct VideoCallUnit { 
    let peerID: String 
    let name: String 
    let view = RTCEAGLVideoView() 
} 

CollectionCell

CollectionCell将用于显示扬声器。

细胞被重复使用,可以在其一生中显示大量不同的扬声器。为此,我们需要一种机制,该机制将从屏幕上删除上一个线程,将新线程拉到屏幕上,然后在屏幕上设置其位置。要实现该机制,请创建一个CollectionCell类并从UicollectionViewCell设置继承。此类仅包含一个属性: rtcview

import GCoreVideoCallsSDK 

final class CollectionCell: UICollectionViewCell { 
   weak var rtcView: RTCEAGLVideoView? { 
       didSet { 
           oldValue?.removeFromSuperview() 
           guard let rtcView = rtcView else { return } 
           rtcView.frame = self.bounds 
           addSubview(rtcView) 
       } 
   } 
}

ViewController

设置控制器来管理整个过程:

导入gcorevideocallssdk。

   import GCoreVideoCallsSDK

创建模型属性。

   let model = Model()

创建属性牢房以容纳单元格ID,以及懒惰的属性收集视图来管理单元格(懒惰使用CellID,View和Self)。设置集合布局,将控制器分配为 dataSource ,然后注册单元格。

lazy var collectionView: UICollectionView = { 
    let layout = UICollectionViewFlowLayout() 
    layout.itemSize.width = UIScreen.main.bounds.width - 100 
    layout.itemSize.height = layout.itemSize.width 
    layout.minimumInteritemSpacing = 10 

    let collection = UICollectionView(frame: view.bounds, collectionViewLayout: layout) 
    collection.backgroundColor = .white 
    collection.dataSource = self 
    collection.register(CollectionCell.self, forCellWithReuseIdentifier: cellID) 

    return collection 
}()

创建LocalView属性。该视图的布局需要从代码中制定;为此,创建方法initconstraints。

let localView: UIView = { 
    let view = UIView(frame: .zero) 
    view.translatesAutoresizingMaskIntoConstraints = false 
    view.layer.cornerRadius = 10  
    view.backgroundColor = .black 
    view.clipsToBounds = true  

    return view 
}() 

func initConstraints() { 
     NSLayoutConstraint.activate([ 
      localView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width / 3), 
      localView.heightAnchor.constraint(equalTo: localView.widthAnchor, multiplier: 4/3), 
      localView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 5), 
      localView.bottomAnchor.constraint(equalTo:  view.bottomAnchor, constant: -5) 
    ]) 
}

创建gcmeet属性。

var gcMeet = gcMeet.shared

viewDidload 方法中

override func viewDidLoad() { 
    super.viewDidLoad() 

    view.backgroundColor = .white 
    view.addSubview(collectonView) 
    view.addSubview(localView) 

    initConstraints() 
})

创建一个扩展程序,将控制器订阅到UicollectionViewDataSource。这是为了将UI与模型链接并在出现时设置单元格是必要的。

extension ViewController: UICollectionViewDataSource { 
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 
        // Get remote peers count
        return model.remoteUsers.count
    } 

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as! CollectionCell 
        cell.rtcView = model.remoteUsers[indexPath.row].view 
        cell.layer.cornerRadius = 10 
        cell.backgroundColor = .black 
        cell.clipsToBounds = true 

        return cell 
    } 
}

UI现在准备显示呼叫。现在我们需要与SDK建立关系。

初始化gcoremeet

通过 gcoremeet.shared 建立了与服务器的连接。本地用户,房间和相机的参数将传递给它。您还需要调用该方法激活音频会话,这将使您可以捕获连接的耳机。 SDK通过听众将数据从服务器传递到应用程序: roomListener em> epreteratorListener 。。

将所有这些添加到ViewDidload方法:

override func viewDidLoad() { 
    super.viewDidLoad() 

    view.backgroundColor = .white 
    view.addSubview(collectonView) 
    view.addSubview(localView) 

    let userParams = GCoreLocalUserParams(name: "EvgenMob", role: .moderator) 
    let roomParams = GCoreRoomParams(id: "serv1z3snbnoq") 
    gcMeet.connectionParams = (userParams, roomParams) 

    gcMeet.audioSessionActivate() 

    gcMeet.moderatorListener = self 
    gcMeet.roomListener = self 

    try? gcMeet.startConnection() 

    initConstraints() 
}

可以通过单击创建一个免费的按钮并选择会议的空间来从https://meet.gcore.com获取 Roomid

Image description

在室内列表方法中,您还将收到视频流(您自己和远程用户),音频流以及有关用户和主持人操作的信息,这将使您能够呈现必要的UI。这将在文章后面进一步讨论。

通过主持人路标,您将收到其他用户的请求以启用流,并在新用户加入候诊室时通知。 MeetRoomParameters也可以使用以下参数:

  • clientHostName:您可以离开此零,在这种情况下,默认值将被满足.gcorelabs.com
  • peerId:如果您离开此零,ID将由SDK自动生成。

步骤3:与GCORE服务器进行交互

与服务器的互动是通过订阅室列表协议的对象进行的。它有多种方法。您可以在SDK readme上找到有关这些以及有关SDK的更多详细信息。下面是这种互动过程的图像:

Image description

对于最简单的实现,您只需要几种方法。
首先,将控制器订阅室列表协议:

extension ViewController: RoomListener { 

}

将所需方法添加到扩展。

加入房间并发送与房间权限,参与者列表以及有关本地用户的信息有关的第一种方法。

func roomClientHandle(_ client: GCoreRoomClient, forAllRoles joinData: GCoreJoinData)

我们对用户列表感兴趣:

// To get data at the moment of entering the room
func roomClientHandle(_ client: GCoreRoomClient, forAllRoles joinData: GCoreJoinData) { 
    switch joinData { 
    case othersInRoom(remoteUsers: [GCoreRemoteUser]): 
        remoteUsers.forEach { 
            model.remoteUsers += [ .init(peerID: $0.id, name: $0.displayName ?? "") ] 
        } 
        collectonView.reloadData() 
    default: 
        break 
    } 
 }

当房间的连接状态更改时,下一个方法是调用的。

func roomClientHandle(_ client: GCoreRoomClient, connectionEvent: GCoreRoomConnectionEvent)

设备的摄像头和麦克风成功连接后将打开。

// To update the status of the connection to the room
func roomClientHandle(_ client: GCoreRoomClient, forAllRoles joinData: GCoreJoinData) { 
    switch joinData { 
    case othersInRoom(remoteUsers: [GCoreRemoteUser]): 
        remoteUsers.forEach { 
            model.remoteUsers += [ .init(peerID: $0.id, name: $0.displayName ?? "") ] 
        } 
        collectonView.reloadData() 
    default: 
        break 
    } 
 }

对于与远程用户数据有关的事件,下一个方法被调用,但与媒体无关。

func roomClientHandle(_ client: GCoreRoomClient, remoteUsersEvent: GCoreRemoteUsersEvent) 

您将使用它在通话过程中添加和删除用户。

// To respond to actions related to remote users, not related to video/audio streams
func roomClientHandle(_ client: GCoreRoomClient, remoteUsersEvent: GCoreRemoteUsersEvent) { 
    switch remoteUsersEvent { 
    case handleRemote(user: GCoreRemoteUser): 
        model.remoteUsers += [.init(peerID: handlePeer.id, name: handlePeer.displayName ?? "")] 
        collectonView.reloadData() 

    case closedRemote(userId: String): 
        model.remoteUsers.removeAll(where: { $0.peerID == peerClosed }) 
        collectonView.reloadData() 

    default: 
        break 
    } 
}

当SDK准备提供用户的视频流以及同行的视频到达时,最后一个方法是调用的。

func roomClientHandle(_ client: GCoreRoomClient, mediaEvent: GCoreMediaEvent)

您将使用它来渲染UI。

// To respond to receiving and disabling video/audio streams
func roomClientHandle(_ client: GCoreRoomClient, mediaEvent: GCoreMediaEvent) { 
    switch mediaEvent { 
    case produceLocalVideo(track: RTCVideoTrack): 
        guard let localUser = model.localUser else { return } 
        videoTrack.add(localUser.view) 
        localUser.view.frame = self.localView.bounds 
        localView.addSubview(localUser.view) 

    case handledRemoteVideo(videoObject: GCoreVideoStream):  
        guard let user = model.remoteUsers.first(where: { $0.peerID == videoObject.peerId }) else { return } 
        videoObject.rtcVideoTrack.add(user.view) 

    default: 
        break 
    } 
}

xcode 填充其余方法而无需提供功能。

设置已完成!该项目准备在真实设备上运行(不建议在模拟器上运行)。

结果

您的应用程序现在具有视频通话功能。这是我们的demo application中此功能的实现。

Image description

开发人员注意

您可以从SDK中获得PixelBuffer,其中包含一个图像框架链接(从相机接收),因此您可以对此做任何喜欢的事情。为此,您需要在MediaCapturerBufferDelegate下订阅控制器并实现mediaCapturerDidBuffer方法。下面的示例在将框架发送到服务器之前添加了模糊:

extension ViewController: MediaCapturerBufferDelegate { 
    func mediaCapturerDidBuffer(_ pixelBuffer: CVPixelBuffer) { 
        let image = CIImage(cvPixelBuffer: pixelBuffer).applyingGaussianBlur(sigma: 10) 
        CIContext().render(image, to: pixelBuffer) 
    } 
}

结论

使用SDK和GCORE服务,您可以轻松,快速地将视频通话功能集成到应用程序中。用户会很高兴;他们曾经在Instagram,WhatsApp和Facebook等流行服务中进行视频通话,现在他们将在您的应用程序中看到一个熟悉的功能。

您可以在此处查看我们项目的源代码:ios-demo-video-calls。在那里,您还可以窥视其他方法,主持人模式,屏幕预览等。

Mediasoup iOS Client用于在iOS上实现WEBRTC。

GcoreVideoCallsSDK用于与房间连接和互动,以及创建插座。

本文基于GcoreVideoCalls应用程序。