📲

iOSアプリに着信機能を追加する(VoIP通知)

に公開

はじめに

先日ランサーズのiOSアプリにビデオ通話の着信機能が追加されました。
今回は着信機能を実装する際に気をつけたい点などを紹介したいと思います。

通知の送信

通常のプッシュ通知については、Firebase Cloud Messaging(以下FCM)を使用していました。着信機能の通知は通常のプッシュ通知とは異なる、Voice over Internet Protocol(以下VoIP)用の通知を送信する必要があります。
このVoIP用の通知についてもFCMから簡単に送信出来るものと考えていましたが、APNsヘッダーの制御などが複雑な実装が必要になるため断念しました。
そのため、他のサービスを探したのですが、AWSのSimple Notification Service(以下SNS)が簡単に利用できそうだったので、SNSを利用することにしました。基本的には以下の流れで、通知を送信できます。

  • プラットフォームアプリケーションを作成
  • ユーザーのデバイストークンごとにエンドポイントを作成・保持
  • 送りたいエンドポイントに対して通知を送信

https://docs.aws.amazon.com/ja_jp/sns/latest/dg/sns-mobile-application-as-subscriber.html

iOSアプリ側の着信機能の実装

iOSアプリ側の着信機能の実装については、PushKitとCallKitという二つのAppleのフレームワークを使用します。
PushKitはVoIP用の通知をハンドリングして、CallKitは通話のUIを提供します。着信機能を実装する際には、この二つのフレームワークを組み合わせて実装する必要があり、どちらか一つのみ使用することはできません。例えば、PushKitのみ使用して、CallKitの着信機能を使用しなかったり、CallKitのみ使用して、VoIP用の通知が届いたタイミング以外で着信機能を使用するなどはガイドライン違反になります。

https://developer.apple.com/documentation/pushkit/pkpushtype/voip

PushKit

PushKitはデバイストークンの取得、通知のハンドリングを行います。ここで取得できるデバイストークンは通常のプッシュ通知とは別のものになります。
AppDelegateのdidFinishLaunchingWithOptionsなどで、以下のようにdelegateを設定します。

let voipRegistry = PKPushRegistry(queue: .main)
voipRegistry.delegate = self
voipRegistry.desiredPushTypes = [.voIP]

delegateの以下の関数で、デバイストークンの取得、通知のハンドリングを行います。

func pushRegistry(
    _ registry: PKPushRegistry,
    didUpdate credentials: PKPushCredentials,
    for type: PKPushType
) {
    // デバイストークンをサーバーに送信する
}

func pushRegistry(
    _ registry: PKPushRegistry,
    didReceiveIncomingPushWith payload: PKPushPayload,
    for type: PKPushType,
    completion: @escaping () -> Void
) {
    // VoIP用の通知を受信した場合、この関数が呼ばれるので、CallKitの着信機能を実装する
    completion()
}

https://developer.apple.com/library/archive/documentation/Performance/Conceptual/EnergyGuide-iOS/OptimizeVoIP.html

CallKit

CallKitは通話のUIを提供するフレームワークで、今回実装した着信機能については、以下のようになり、こちらをVoIP用の通知を受信したタイミングで行います。

var callProvider: CXProvider?

func incomingCall() {
    let configuration = CXProviderConfiguration()
    callProvider = CXProvider(configuration: configuration)
    callProvider?.setDelegate(self, queue: nil)

    let callID = UUID()
    let update = CXCallUpdate()
    update.remoteHandle = CXHandle(type: .generic, value: "名前")

    callProvider?.reportNewIncomingCall(with: callID, update: update) { error in
        if let error = error {
            print(error.localizedDescription)
        }
    }
}

通話に応答した場合など、アクションが起きた場合に、それぞれ以下のdelegeteメソッドが呼ばれるので、そこで必要な処理を実装します。

func providerDidReset(_ provider: CXProvider) {
}

func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
}

func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
}

func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
}

func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
}

func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
}

https://developer.apple.com/documentation/callkit/

リジェクト対応

CallKit機能を含むアプリを中国で利用できる状態だと、Appleの審査でリジェクトされてしまうようです。そのため、端末の地域設定が中国本土の場合、CallKitの処理を行わないようにするなどの対応が必要になります。

まとめ

最初はiOSアプリに着信機能を追加するのは、FCMを使用して簡単にできそうだなと考えていたのですが、FCMを使用することは難しく、アプリ側の実装も色々考慮することがあり、案外大変だなと感じました。ただOS標準の通話と同じようなUIが使用できるので、使用感としては凄く良いなと思います。

ランサーズ株式会社

Discussion