Slide 1

Slide 1 text

© GO Inc. 実践 Advanced CallKit 〜快適な通話の実現に向けて〜 2024.08.22 17:20 ~ / Track C 開発本部 ソフトウェア開発統括部 ユーザーシステム開発部 ユーザーシステム1グループ/ 髙橋秀宗 GO株式会社

Slide 2

Slide 2 text

© GO Inc. 2 自己紹介 GO株式会社 iOSエンジニア / 髙橋秀宗 2022年12月に入社後、タクシーアプリ『GO』のユーザーアプ リの開発を担当 直近はiOSアプリ開発に必要なBackend APIの開発も担当し、仕 事の幅を広げている 最近AirPods Proをお洗濯する実績を解除。ぐぬぬ。 @h1d3mun3

Slide 3

Slide 3 text

© GO Inc. 3 突然ですが...

Slide 4

Slide 4 text

© GO Inc. 4 iOSアプリで通話したことがある人?🙋

Slide 5

Slide 5 text

© GO Inc. 5 アプリで通話機能を実装したことのある人?🙋

Slide 6

Slide 6 text

© GO Inc. 本日はiOSアプリの通話を支える CallKitと その周辺技術についてお話しします 6

Slide 7

Slide 7 text

© GO Inc. タクシーアプリ『GO』でのCallKitの活用事例 7 ● タクシー乗車前のお客様と乗務員との通話に活用 📞 お客様に電話をかけ円滑な コミュニケーションを実現

Slide 8

Slide 8 text

© GO Inc. タクシーアプリ『GO』でのCallKitの活用事例 8 ● 音声通話の基盤として、Apple社が提供するCallKitを利用 ○ APNsからのVoIP Push通知を受け取るためにPushKitも利用 ● 複数プラットフォーム間で円滑な通話を実現するためにTwilio Voiceを 採用

Slide 9

Slide 9 text

Index © GO Inc. 1. CallKit + Twilio Voiceでの通話 2. CallKitの紹介 3. Twilio Voiceの紹介 4. 『GO』での活用事例 5. Advanced CallKit 6. おわりに 9

Slide 10

Slide 10 text

© GO Inc. CallKit + Twilio Voiceで の通話 01

Slide 11

Slide 11 text

© GO Inc. CallKit + Twilio Voiceで着信に応答するまでのフロー 11

Slide 12

Slide 12 text

© GO Inc. CallKit + Twilio Voiceで着信に応答するまでのフロー 12 CallKitが担当

Slide 13

Slide 13 text

© GO Inc. CallKit + Twilio Voiceで着信に応答するまでのフロー 13 Twilio Voiceが担当

Slide 14

Slide 14 text

© GO Inc. 着信に応答する時のCallKitとTwilio Voiceの責務 14 ● CallKit ○ 新規着信の登録 ○ 通話を接続して良いかの問い合わせ ○ 通話に接続して良いと返却 ● Twilio Voice ○ VoIP通知受信を通知 ○ 着信を通知

Slide 15

Slide 15 text

© GO Inc. CallKitの紹介 02

Slide 16

Slide 16 text

© GO Inc. CallKitとは 16 ● VoIPアプリをより標準の通話アプリのように実装するためのFramework ○ iOS10より利用可能 ○ 下記が実現可能 ■ 標準電話アプリのようなUIでの着信の応答 ■ 通話履歴に着信を残し標準アプリ経由で発信 ● 通話の発信・応答・終話・ミュート/アンミュート・保留・マージなど の音声通話操作の責務を担当

Slide 17

Slide 17 text

© GO Inc. 着信に応答する時に必要になる情報 17 ● UUID ○ 個別の通話をUUIDで表現 ● CXProvider ○ システムへの通話登録用クラス ■ 新規の通話や通話の終了をシステムにリクエスト

Slide 18

Slide 18 text

© GO Inc. let configuration = CXProviderConfiguration() // 標準通話の最近の通話のリストに通話を含めるかどうか configuration.includesCallsInRecents = false let provider = CXProvider(configuration: configuration) CXProviderの初期化 18

Slide 19

Slide 19 text

© GO Inc. // 新規の着信通話を非同期で登録 func reportNewIncomingCall(with: UUID, update: CXCallUpdate, completion: ((any Error)?) -> Void) 新規着信の登録で使うCXProviderのメソッド 19

Slide 20

Slide 20 text

© GO Inc. ● 通話のメタ情報を保持 ○ 発信者の名前 ○ ビデオはあるか ○ 保留をサポートするか ○ 通話のグループ化・グループ化解除をサポートするか ○ …etc CXCallUpdateとは 20

Slide 21

Slide 21 text

© GO Inc. let uuid = call.uuid let update = CXCallUpdate() 〜〜〜updateを設定〜〜〜 provider.reportNewIncomingCall( with: uuid, update: callUpdate) { [weak self] error in 〜〜〜 } 新規着信の登録のコード 21

Slide 22

Slide 22 text

© GO Inc. func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { guard isReady else { // 処理を失敗させたい時はfail()を呼び出す action.fail() return } action.fulfill() // 処理を進める時はfulfill()を呼び出す registCall(of: action.callUUID) } 通話を接続する場合のコード 22 「特定の通話に応答する」と いう行為を表すCXActionと いう抽象クラスの具象クラス

Slide 23

Slide 23 text

© GO Inc. CXActionとは 23 ● CXActionとは通話に対する操作を抽象化したクラス ○ CXAnswerCallActionの他にも複数種類あり、代表的なものとして 下記がある ■ CXStartCallAction: 通話を発信 ■ CXEndCallAction: 通話を終話 ● これらの更新処理を1つにまとめたCXTransactionというクラスも存在

Slide 24

Slide 24 text

© GO Inc. ● 登録済み通話の通話状態の更新をリクエスト ○ ミュート・保留...etc // CXTransaction(≒通話の状態更新処理)を非同期で行うように要求 func request(CXTransaction, completion: ((any Error)?) -> Void) CXCallController 24

Slide 25

Slide 25 text

© GO Inc. Twilio Voiceの 紹介 03

Slide 26

Slide 26 text

© GO Inc. Twilio Voiceとは 26 ● 米Twilio社が提供する、VoIPサービスを実装するためにクラウド上に構 築されたコミュニケーションプラットフォーム ○ Swift Package ManagerでTwilioVoiceSDKを導入可能 ○ 開発者はより簡単にVoIP通話を実装可能

Slide 27

Slide 27 text

© GO Inc. TwilioVoiceSDKでの登場人物 27 ● Call ○ 通話を表す ● CallInvite ○ 通話になる前の着信状態を表す → CallKitだとUUIDで管理していたものに具象クラスがある

Slide 28

Slide 28 text

© GO Inc. VoIP通知受信を通知する時に使う関数 28 class func handleNotification( _ payload: [AnyHashable : Any], delegate: NotificationDelegate, delegateQueue: dispatch_queue_t? ) -> Bool

Slide 29

Slide 29 text

© GO Inc. NotificationDelegateとは 29 ● Twilio Voice SDKで管理している通話の状態が更新されたことを伝える 責務を持つDelegate

Slide 30

Slide 30 text

© GO Inc. VoIP通知受信を通知する時のコード 30 // PushKit経由で通知されたVoIP通知のPayloadをSDKに投げる let payload = payload.dictionaryPayload _ = TwilioVoiceSDK.handleNotification( payload.dictionaryPayload, delegate: self, delegateQueue: nil)

Slide 31

Slide 31 text

© GO Inc. // CallInviteが届いたことを通知 func callInviteReceived(callInvite: CallInvite) NotificationDelegateで使うメソッド 31

Slide 32

Slide 32 text

© GO Inc. 着信を通知してもらい、システムに登録するコード 32 func callInviteReceived(callInvite: CallInvite) { let uuid = callInvite.callSid let callUpdate = CXCallUpdate() provider.reportNewIncomingCall( with: callInvite.uuid, update: callUpdate) { [weak self] error in // システムへの登録成功🎉 } }

Slide 33

Slide 33 text

© GO Inc. 『GO』での活用 事例 04

Slide 34

Slide 34 text

© GO Inc. 『GO』での通話特有のコンテキスト 34 ● 1つ1つの通話には上限の通話時間が存在 ● タクシーを複数台呼べるため、通話が複数同時に着信する可能性 -> 複数の着信と通話を同時に管理する必要

Slide 35

Slide 35 text

© GO Inc. 通話の上限時間について ● お客様と乗務員間でやり取りする際、5分あれば問題なくやり取りでき ると思われたため ● 5分を超えると自動的に通話は切断 35

Slide 36

Slide 36 text

© GO Inc. 通話の上限の通話時間設定 36

Slide 37

Slide 37 text

© GO Inc. ● 『GO』には下記利用シーンを想定してアプリから複数台のタクシーを 配車することができる複数台配車機能がある ○ イベントでの大人数での移動 ○ 接待時の代理手配 ● ほとんど同じタイミングで複数の車両がお客様のもとに向かうケースが ある ○ 1つの『GO』アプリに複数の着信が来る可能性がある 複数の着信と通話を同時に管理する必要 37

Slide 38

Slide 38 text

© GO Inc. ● A乗務員との通話中にB乗務員から着信が来た場合、ユーザーは下記のう ちどちらかを選択可能 ○ A乗務員との通話を優先し、B乗務員との通話を拒否 ○ A乗務員との通話を終話し、B乗務員との通話を開始 乗務員との通話中に別乗務員から着信が来たら... 38

Slide 39

Slide 39 text

© GO Inc. 下記のような状況においてもCallInviteとCallを適切に管理できなければな らない 1. 乗務員Aから着信 2. ユーザーが着信に応答 3. 乗務員Aとの通話中に乗務員Bから着信 4. 乗務員Bの着信に応答 複数の着信と通話を同時に管理する必要 39

Slide 40

Slide 40 text

© GO Inc. 下記のような状況においてもCallInviteとCallを適切に管理できなければな らない 1. 乗務員Aから着信 ←CallInviteとCallの状況は? 2. ユーザーが着信に応答 ←CallInviteとCallの状況は? 3. 乗務員Aとの通話中に乗務員Bから着信 ←CallInviteとCallの状況は? 4. 乗務員Bの着信に応答 ←CallInviteとCallの状況は? 複数の着信と通話を同時に管理する必要 40

Slide 41

Slide 41 text

© GO Inc. 下記のような状況においてもCallInviteとCallを適切に管理できなければな らない 1. 乗務員Aから着信 ←CallInviteとCallの状況は? 2. ユーザーが着信に応答 ←CallInviteとCallの状況は? 3. 乗務員Aとの通話中に乗務員Bから着信 ←CallInviteとCallの状況は? 4. 乗務員Bの着信に応答 ←CallInviteとCallの状況は? 複数の着信と通話を同時に管理する必要 41 各種Delegate等の通知をもとにCallInviteとCallの情報を常に正しい状態 に保つ必要がある

Slide 42

Slide 42 text

© GO Inc. CallInviteとCallを正しい状態に保てないとどうなるか... 42 ● CallInviteとCallを最新状態に保てないと下記問題が発生 ○ システムに登録されている着信に応答したいができない ○ システムに登録されている通話を終話したいができない → システムに登録されている着信 / 通話に対して操作ができなくなってし まう

Slide 43

Slide 43 text

© GO Inc. ● CallKit / TwilioVoiceSDKともに通話の更新処理は、全て非同期で通知 ○ 複数スレッドから同時に通知される可能性あり ■ CallInfoStoreを実装し、問題の解決を図る 更に通話特有の考慮 43

Slide 44

Slide 44 text

© GO Inc. CallInfoStore 44

Slide 45

Slide 45 text

© GO Inc. ● 着信や通話を特定するためのCallIDという構造体を宣言 ● CallIDをもとにCallInviteとCallを管理 ● 複数スレッドから同時にアクセスされてもいいように、NSLockで排他 制御を実施 CallInfoStore 45

Slide 46

Slide 46 text

© GO Inc. Advanced CallKit 05

Slide 47

Slide 47 text

© GO Inc. ● 保留 ● 通話の入れ替え ● 通話と集中モードの関連性 このセクションで話すこと 47

Slide 48

Slide 48 text

© GO Inc. ● 保留 ● 通話の入れ替え ● 通話と集中モードの関連性 このセクションで話すこと 48

Slide 49

Slide 49 text

© GO Inc. ● よくある通話の保留と同じく、現在成立している通話を保留する ○ 自分の声は相手には伝わらない ○ 相手の声は自分には伝わらない ● CallKitのデフォルト機能だと相互に無音になってしまい、保留状態に 入ったことがわかりにくい 保留 49

Slide 50

Slide 50 text

© GO Inc. ● プログラム上の実現フロー ○ システムに通話を登録する時に保留を有効にする ○ 保留を実施する 保留 50

Slide 51

Slide 51 text

© GO Inc. let callUpdate = CXCallUpdate() 〜〜〜中略〜〜〜 callUpdate.supportsHolding = true 〜〜〜中略〜〜〜 provider.reportNewIncomingCall(with: callInvite.uuid, update: callUpdate) { error in 〜〜〜中略〜〜〜 } システムに通話を登録する時に保留を有効にする 51

Slide 52

Slide 52 text

© GO Inc. let callUUID = UUID() let desiredState: Bool = true let action = CXSetHeldCallAction(call: callUUID, onHold: desiredState) callController.request(CXTransaction(action: action), completion: { error in // このClosure内部で結果が取得可能 print(error) } 保留 52

Slide 53

Slide 53 text

© GO Inc. ● 保留 ● 通話の入れ替え ● 通話と集中モードの関連性 このセクションで話すこと 53

Slide 54

Slide 54 text

© GO Inc. 2つの通話を入れ変えて通話する 54 ● すでに成立している2つの通話を切り替えて通話 ○ CXProviderで複数の通話を同時に成立させられるように設定 ○ 通話の登録の時に保留を有効にする ○ 通話を入れ替える

Slide 55

Slide 55 text

© GO Inc. CXProviderで複数の通話を同時に成立させられるように設定 55 let configuration = CXProviderConfiguration() // maximumCallGroupsを2以上に設定すると通話を複数成立させられる configuration.maximumCallGroups = 2 configuration.maximumCallsPerCallGroup = 2 let provider = CXProvider(configuration: configuration)

Slide 56

Slide 56 text

© GO Inc. 通話を入れ替える 56 let holdAction = CXSetHeldCallAction(call: talkingCall.uuid!, onHold: true) let unHoldAction = CXSetHeldCallAction(call: holdingCall.uuid!, onHold: false) callController.request(CXTransaction(actions: [holdAction, unHoldAction]), completion: { error in // 〜〜〜中略〜〜〜 })

Slide 57

Slide 57 text

© GO Inc. ● 保留 ● 通話の入れ替え ● 通話と集中モードの関連性 このセクションで話すこと 57

Slide 58

Slide 58 text

© GO Inc. ● 集中モード有効化時にユーザーに着信 画面が表示されるかどうかは集中モー ドの下記設定で管理 ○ 通知を消音 ○ 連絡先の設定 通話と集中モードの関係性 58 ※テスト画面になります

Slide 59

Slide 59 text

© GO Inc. ● 集中モードの設定をどの時に有効にするかを決める ○ 常に知らせない ■ iPhoneのロック状態に関わらず集中モードの 設定を適用 ○ ロック中 ■ iPhoneがロック状態の時だけ集中モードの設 定を適用 通知を消音について 59

Slide 60

Slide 60 text

© GO Inc. 連絡先の設定について 60 ● 特定の連絡先の通知を管理する設定 ○ アプリを許可する設定は不可 ● 2モードある ○ 通知を知らせない ○ 通知を許可

Slide 61

Slide 61 text

© GO Inc. ● いわゆるブラックリスト ○ ここで設定した連絡先は通知が表示されない 連絡先の通知を知らせないについて 61

Slide 62

Slide 62 text

© GO Inc. 連絡先の通知を許可するについて 62 ● いわゆるホワイトリスト ○ 追加した連絡先にだけ通知を許可 ● 着信を許可するパターンは4モード ○ 全ての人 ○ 通知あり連絡先のみ ○ よく使う項目 ○ 連絡先のみ ● 繰り返しの着信を許可 ○ 3分以内に2度目の着信があったら通知

Slide 63

Slide 63 text

© GO Inc. 着信を許可するパターンについて 63 ● 全ての人 ○ 全ての通話が許可される ● 通知される連絡先のみ ○ 集中モードで登録した連絡先と、緊急時は鳴らすに追加した連絡先からのみの着 信を許可する ● よく使う項目 ○ 通知される連絡先のみに加えて、よく使う項目に追加した連絡先からのみの着信 を許可する ● 連絡先のみ ○ 連絡先からの着信を許可する

Slide 64

Slide 64 text

© GO Inc. 緊急時は鳴らすについて 64 ● 下記手順で設定可能 ○ 登録した連絡先を開き編集 ○ 着信音をタップ ○ 画面上部の「緊急時は鳴らす」のスイッチをOn

Slide 65

Slide 65 text

© GO Inc. ● 集中モードでは通知をさまざまな条件でブロックすることができる ● 集中モードの拒否リストに該当した場合、着信画面が表示されない ● 着信画面が表示できなかった時のケアが大切 ○ CXErrorCodeIncomingCallError というエラーが返ってくる ○ GOでの取り組み ■ 着信画面の表示エラーを検知した場合はログとして保存 ● お客様から「乗務員と通話できなかった」という問い合わせ が来た時の調査目的 集中モードと着信画面の関連性まとめ 65

Slide 66

Slide 66 text

© GO Inc. おわりに 06

Slide 67

Slide 67 text

© GO Inc. おわりに 67 ● CallKit + Twilio Voiceでの通話への応答のフローを紹介 ● VoIP通話を行うために必要なCallKit、Twilioについて紹介 ● 『GO』での活用事例を紹介 ● 通話の保留・入れ替えやおやすみモードと通話の関係性について紹介

Slide 68

Slide 68 text

© GO Inc. おしらせ! 07

Slide 69

Slide 69 text

© GO Inc. 69 After iOSDC & DroidKaigi 2024 Mobile LT Nightやります! 2023年09月26日 19:00 ~ 20:30 https://reiwatravel.connpass.com/event/327079/

Slide 70

Slide 70 text

© GO Inc. 参考資料 08

Slide 71

Slide 71 text

© GO Inc. 参考資料 71 ● CallKit | Apple Developer Documentation ○ https://developer.apple.com/documentation/callkit/ ● CXProvider | Apple Developer Documentation ○ https://developer.apple.com/documentation/callkit/cxprovider ● CXCallController | Apple Developer Documentation ○ https://developer.apple.com/documentation/callkit/cxcallcontroller ● Voice SDKs | Twilio ○ https://www.twilio.com/docs/voice/sdks ● Twilio Voice Quickstart for iOS with Swift - GitHub ○ https://github.com/twilio/voice-quickstart-ios ● 実践 CallKit/PushKit ときどき🐛退治 / iOSDC 2019 ○ https://speakerdeck.com/monoqlo/iosdc-2019

Slide 72

Slide 72 text

文章・画像等の内容の無断転載及び複製等の行為はご遠慮ください。 © GO Inc.