iDoorPhoneの初期設定をお探しの方は、以下のトピック1〜2をご参照ください。
はじめに
iOS向けにTencentCloudを利用した通話アプリを作る機会があったのですが、いくつか参考になる記事があったものの、最初に動くまでいくつかハマりポイントもあったので、備忘録として記事化しようと思います。
- TencentCloudの会員登録
- アプリに必要な情報取得
- Swiftでのアプリ実装
1. TencentCloudの会員登録
TencentCloudは、AWSやGCPのように様々なクラウドサービスを提供しており、通話アプリを創る場合、この中のTencent Real-Time Communication (TRTC)を利用します。TencentCloudは会員登録することで、サービスの無料利用枠が利用できます。(執筆時点で、無料会員でも毎月10000分の無料通話が可能)
まずはTencentCloudのページに行き、右上から無料の会員登録を行います。
必要情報を入力してサインアップしましょう。私は左下のGoogleアカウントから登録しました。
Googleアカウントを利用する場合でも、国、設定したいパスワード、誕生日の入力が必要です。
上のページでサインアップを押すと、以下のように追加情報の入力を求められるので入力しておきます。
このあとのページでクレカ登録を求められますが、一旦無視してページを閉じてしまっても大丈夫です。
2. アプリに必要な情報取得
通話アプリを作成するには、以下の事が必要になりますので、順を追って説明します。
- TencentCloud内でアプリケーションを作成する
- 作成したアプリケーションのSDKAppID / SDKSecretKeyを取得する
まずは、こちらのページに戻り、上のナビゲーションメニューの「製品」から、「Tencent Real-Time Communication (TRTC)」を選択しましょう。
以下のページ中段にある「無料ではじめる」を押す。
その後、以下のアプリケーションの作成画面に行くので、以下を入力してGetStartedを押す。
- Application name : 好きなアプリケーション名を入力(デフォルトでも可)
- Select product : 製品は「Call」を選択
- Region : 適宜近いところを選ぶ(私はソウルを選びました)
このような画面が表示されれば無事にアプリケーション作成は完了です。
この段階では無料体験版扱いなので、7日の期限付きとなります。
このページの中段「Basic Information」のところに、SDKAppID / SDKSecretKeyがありますので、こちらがアプリ作成に必要な情報になります。
3. Swiftでのアプリ実装
ここからは、Swiftで最小限の実装をしていくまでの解説になります。
環境:
- MacBook Air (M2, 2022)
- macOS Sonoma 14.5
- Xcode 15.0.1
STEP1:プロジェクト作成
まずは新規プロジェクトを作成します。
インターフェースはStoryboardを選択してください。
STEP2:SDKの追加
次にSDKを入れていきます。
こちらのページの「iOS SDKのダウンロード」の箇所からZIPのダウンロードを選び、SDKをダウンロードしてください。ダウンロードしたZIPは解凍しておきます。
https://www.tencentcloud.com/jp/document/product/647/34615
公式ではCocoaPodsによる入れ方も書いてありますが、私の場合はうまくコンパイルが通らなかったので、手動でいれるのをおすすめしておきます。
次に、Xcode画面左のNavigatorで「SDK」フォルダを作成し、ここに先ほど解凍したZIPの中にある4つのFrameworkを追加します。
Frameworkを選択するときは、念のため以下の画面で「Copy items if needed」にチェックしておきましょう。
次に、必要なライブラリを追加します。プロジェクトの設定の「General->Frameworks,Libraries, and Embedded Content」を開き、以下の一覧にある必要なフレームワークとライブラリを追加してください。
また、さきほど追加した4つのライブラリが青で示されていますが、このうち「TXFFmpeg」「TXSoundTouch」の2つについては、Embedを「Embed&Sign」に変更しておきます。
ここが一番のハマりどころです。公式の解説や、他の記事などでは含まれていませんが、実際にビルドしようとすると、ReplayKit、CoreMotion、AVKitもないとエラーになりますので必ず入れておきましょう。
STEP3:プライバシー設定
ここまでできたら、プライバシーの設定をします。
Info.plistを開いて、以下のようにカメラとマイクのアクセスを追加してください。
- Privacy - Microphone Usage Description
- Privacy - Camera Usage Description
STEP4:コード作成
ソースファイルに含まれているViewControllerに、最小限の通話サンプルとして以下のコードをコピペします。こちらはyuppejp(yuppe)さんの記事のものがすごく使いやすかったので、そのまま載せさせていただきます。
ViewController.swift
import Foundation
import UIKit
import TXLiteAVSDK_TRTC
class ViewController: UIViewController {
@IBOutlet weak var remoteVideoView: UIView!
@IBOutlet weak var remoteUserLabel: UILabel!
@IBOutlet weak var joinButton: UIButton!
private var trtcCloud: TRTCCloud = TRTCCloud.sharedInstance()
private var joined = false
override func viewDidLoad() {
super.viewDidLoad()
remoteUserLabel.text = ""
remoteVideoView.layer.opacity = 0.3
joinButton.tintColor = UIColor.systemGreen
}
@IBAction func joinButtonTouched(_ sender: Any) {
if joined {
exitRoom()
} else {
enterRoom()
}
}
private func enterRoom() {
let roomId: Int = 1
let userId: String = "iOS demo1"
let isFrontCamera = true
trtcCloud.delegate = self
trtcCloud.startLocalPreview(isFrontCamera, view: view)
let params = TRTCParams()
params.sdkAppId = UInt32(SDKAPPID)
params.roomId = UInt32(roomId)
params.userId = userId
params.role = .anchor
params.userSig = TrtcUserSig.genTestUserSig(identifier: userId) as String
trtcCloud.enterRoom(params, appScene: .videoCall)
let encParams = TRTCVideoEncParam()
encParams.videoResolution = ._640_360
encParams.videoBitrate = 550
encParams.videoFps = 15
trtcCloud.setVideoEncoderParam(encParams)
trtcCloud.startLocalPreview(isFrontCamera, view: view)
trtcCloud.startLocalAudio(.music)
}
private func exitRoom() {
trtcCloud.exitRoom()
trtcCloud.stopLocalPreview()
trtcCloud.stopLocalAudio()
trtcCloud.delegate = self
}
}
extension ViewController: TRTCCloudDelegate {
func onEnterRoom(_ result: Int) {
print("*** onEnterRoom: result: \(result)")
joined = true
joinButton.setTitle("Leave", for: .normal)
joinButton.tintColor = UIColor.red
}
func onExitRoom(_ reason: Int) {
print("*** onExitRoom: reason: \(reason)")
joined = false
joinButton.setTitle("Join", for: .normal)
joinButton.tintColor = UIColor.systemGreen
}
func onUserVideoAvailable(_ userId: String, available: Bool) {
print("*** onUserAudioAvailable: userId: \(userId), available: \(available)")
if available {
trtcCloud.startRemoteView(userId, streamType:.small, view: remoteVideoView)
remoteVideoView.layer.opacity = 1
remoteUserLabel.text = userId
} else {
trtcCloud.stopRemoteView(userId, streamType: .small)
remoteVideoView.layer.opacity = 0.3
remoteUserLabel.text = ""
}
}
func onError(_ errCode: TXLiteAVError, errMsg: String?, extInfo: [AnyHashable : Any]?) {
if let errMsg = errMsg {
print("*** onError: \(errCode): \(errMsg)")
} else {
print("*** onError: \(errCode): ?")
}
}
}
ただ、これだけだと、「Cannot find 'SDKAPPID' in scope」などのビルドエラーが出ると思いますので、新規Swiftファイル「TrtcUserSig.swift」をプロジェクトに追加し、以下のコードをコピペします。
TrtcUserSig.swift
import Foundation
import CommonCrypto
import zlib
let SDKAPPID: Int = MY_SDKAPPID
let SECRETKEY:String = MY_SECRETKEY
let EXPIRETIME: Int = 10 * 60 // seconds
class TrtcUserSig {
class func genTestUserSig(identifier: String) -> String {
let current = CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970
let TLSTime: CLong = CLong(floor(current))
var obj: [String: Any] = [
"TLS.ver": "2.0",
"TLS.identifier": identifier,
"TLS.sdkappid": SDKAPPID,
"TLS.expire": EXPIRETIME,
"TLS.time": TLSTime
]
let keyOrder = [
"TLS.identifier",
"TLS.sdkappid",
"TLS.time",
"TLS.expire"
]
var stringToSign = ""
keyOrder.forEach { (key) in
if let value = obj[key] {
stringToSign += "\(key):\(value)\n"
}
}
print("string to sign: \(stringToSign)")
let sig = hmac(stringToSign)
obj["TLS.sig"] = sig!
print("sig: \(String(describing: sig))")
guard let jsonData = try? JSONSerialization.data(withJSONObject: obj, options: .sortedKeys) else { return "" }
let bytes = jsonData.withUnsafeBytes { (result) -> UnsafePointer<Bytef> in
return result.bindMemory(to: Bytef.self).baseAddress!
}
let srcLen: uLongf = uLongf(jsonData.count)
let upperBound: uLong = compressBound(srcLen)
let capacity: Int = Int(upperBound)
let dest: UnsafeMutablePointer<Bytef> = UnsafeMutablePointer<Bytef>.allocate(capacity: capacity)
var destLen = upperBound
let ret = compress2(dest, &destLen, bytes, srcLen, Z_BEST_SPEED)
if ret != Z_OK {
print("[Error] Compress Error \(ret), upper bound: \(upperBound)")
dest.deallocate()
return ""
}
let count = Int(destLen)
let result = self.base64URL(data: Data.init(bytesNoCopy: dest, count: count, deallocator: .free))
return result
}
class func hmac(_ plainText: String) -> String? {
let cKey = SECRETKEY.cString(using: String.Encoding.ascii)
let cData = plainText.cString(using: String.Encoding.ascii)
let cKeyLen = SECRETKEY.lengthOfBytes(using: .ascii)
let cDataLen = plainText.lengthOfBytes(using: .ascii)
var cHMAC = [CUnsignedChar].init(repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
let pointer = cHMAC.withUnsafeMutableBufferPointer { (unsafeBufferPointer) in
return unsafeBufferPointer
}
CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), cKey!, cKeyLen, cData, cDataLen, pointer.baseAddress)
let data = Data.init(bytes: pointer.baseAddress!, count: cHMAC.count)
return data.base64EncodedString(options: [])
}
class func base64URL(data: Data) -> String {
let result = data.base64EncodedString(options: Data.Base64EncodingOptions.init(rawValue: 0))
var final = ""
result.forEach { (char) in
switch char {
case "+":
final += "*"
case "/":
final += "-"
case "=":
final += "_"
default:
final += "\(char)"
}
}
return final
}
}
コピペができたら、TrtcUserSig.swift内でSDKAPPID/SECRETKEYが見つからないエラーが出るので、ここに、最初にTencentCloudで取得したSDKAPPID/SECRETKEYを入力してください。(SECRETKEYは””で囲む)
最後に、Storyboardに以下のような画面を作成して、ViewControllerのアウトレットと紐づけたら完成です。
- Remote video view : UIView
- Remote user label : UILabel
ビルドをして実行できるのを確認してみましょう。