ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Push to Talkで音声通信を強化する
音声が明瞭になり、Appにてトランシーバー通信ができるようになりました(どうぞ!)。Push to Talk Appに優れたシステムUIを追加して、ボタンのタップで高速通信を実現する方法をご覧ください。PushToTalkフレームワークについて解説し、Appを設定してバックグラウンドからでも音声を送受信できるようにする方法を紹介します。 このセッションを最大限に活用するには、Appのバックエンドでの音声通信の処理に関する知識を習得しておくとよいでしょう。また、APNの基礎知識も習得しておくことをお勧めします。
リソース
関連ビデオ
Tech Talks
WWDC22
-
ダウンロード
♪ メロウなインスト曲 ヒップホップ ♪ ♪ こんにちは Kevinです PushToTalk(PTT)フレームワーク 担当のエンジニアです PTTを使うと iOSのAppで トランシーバーのような システム体験を提供できます 後で同僚のTrevorにも 参加してもらって あなたのAppで 音声通信を向上させる方法を 説明してもらいます PTTフレームワークを 最初に紹介し それをAppに組み込む 方法について説明します 続いて PTT用に Appを設定する方法を 紹介し その後はTrevorが フレームワークを使用して 音声を送受信する 方法について説明します 最後のまとめではTrevorが PTTユーザー体験を 向上させながら バッテリーを長持ちさせる ベストプラクティスを ご紹介します では新しいPTT フレームワークの 主な機能の紹介から始めます PTTフレームワークを使うと トランシーバーのような 機能を提供する 新しい音声通信Appを iOS上に構築できます PTTは医療や救急など 迅速なコミュニケーションが 不可欠な分野で 多くの用途があります 優れたPTT体験を 提供するには ユーザーが音声通信機能に 素早くアクセスできて 誰に応答しているのかも 同時に確認できる 方法が必要です 同時に PTT Appには Appを使用しながらも バッテリーが1日持つような 優れた電力効率が 必要です PTTフレームワークには 直接Appを起動しなくても ユーザーがシステムの どこからでもアクセスできる システムUIを利用する APIがあります このシステムUIでは ユーザーが音声通信を 作動させると Appが バックグラウンドで起動し 音声を録音して サーバーにストリームします Appがサーバーからの 音声を再生すると 誰が話しているかが 表示されて 透明性が確保されます PTTフレームワークでは 新しい音声が 再生可能になるとAppに 通知するプッシュタイプ 通知が導入されています Appはこの通知を受信すると バックグラウンドで起動して 音声を配信・再生します 既存のエンドツーエンド 通信ソリューションや バックエンドのインフラとも 互換性のある 設計となっています PTTワークフローを 実装した経験がある方なら PTTフレームワークを 既存のコードに統合するのは 簡単なはずです このフレームワークを使うと あなたのAppで独自の オーディオエンコーディングと ストリーミングプロセスを 実装し ユーザー間で 音声を送受信できます これにより音声の通信方法を 変更する柔軟性と 他のプラットフォームとの 互換性も確保されます 最後に 多くのPTT Appは 音声の録音と通信を 発動させる際に ワイヤレスBluetooth アクセサリを使用します CoreBluetooth フレームワークを 使うことで 引き続き これらのアクセサリと 連動し 録音を 開始することができます 初めてPTT Appを作成する場合 コードの作成を 始める時には この注意事項を 覚えておいてください 新しいPTT フレームワークの コードの説明を 始める前に AppでのPTT体験が どんなものか デモをお見せしましょう Trevorと私は PTTの機能を 紹介するデモを作りました まずは「」ボタンを タップして PTTセッションに 接続します これをチャネルと呼びます チャネルに参加すると チャネルの他のメンバーと 音声を送受信できます Trevorと数名の同僚が 一日中 連絡が取れるように 同じチャネルに 参加しました マイクのボタンを使って Appから直接音声を 送信できますが PTTフレームワークを 使えばシステムの どこからでも 送信機能にアクセスできます PTTチャネルが アクティブな時は 青い〇印がステータス バーに表示されます この〇印をタップすると システムUIが表示されます システムUIには参加した PTTチャネル名と そのAppが提供する画像が 表示されるので ユーザーはチャネルを すぐに特定できます チャネルに音声を 送信するには 「Talk」ボタンを長押しして システムのチャイム音が 通話を開始できると 知らせてくれるのを待ちます
やあ Trevor WWDCスライドのプレゼン 準備はできた?どうぞ Trevor:Kevinの メッセージを受信すると 彼の名前と画像入りの 通知が表示されるので 誰からのメッセージか はっきりとわかります システムUIを起動すると すぐにKevinのメッセージに 応答することも あるいは作業を止めずに 退出することもできます Kevinが待っているので 応答しましょう やあ Kevin すぐに始めるよ どうぞ Kevin:PTT システムUIには ロック画面からも アクセスできるので ロックを解除せずに メッセージを送受信できます
OK じゃ またな! どうぞ PTTの機能の 説明は以上です 次はフレームワークのAppへの 統合方法を確認します PTTフレームワークを サポートするには Xcodeプロジェクトを数か所 変更する必要があります まず新しいバックグラウンド モードを追加します これによりPTT イベントに応答する時に Appをバックグラウンドで 実行できます 次にPTT機能を あなたのAppに追加して フレームワーク機能を 有効にします APNSがバックグラウンドで Appを立ち上げ 受信した音声を再生するには プッシュ通知機能が必要です 最後にAppはユーザーから 録音許可をリクエストして Info.plistファイルに マイク目的の文字列を 含める必要があります では コードの統合を 始めましょう PTTワークフローの 最初のステップは チャネルに参加する事です チャネルはPTT セッションを表現し システムに記述します Appとチャネルの対話に使うのは チャネル・マネージャ これはAppがチャネルに 参加したり 音声の送受信を行う際の 主なインターフェースです チャネルに参加すると PTTシステムUIが 使用可能になりAppは チャネルの使用期間全体で 使用できるAPNSデバイス トークンを受け取ります 音声の送受信を開始する前に チャネルに 参加しておく必要があります 最初のステップでは クラス イニシャライザを使用して チャネル・マネージャを 作成します このイニシャライザでは チャネル・マネージャ デリゲートとチャネル復元 デリゲートの指定が必要です イニシャライザを複数回 呼び出すと 同じ共有インスタンスが 返されますが チャネル・マネージャは インスタンス変数に 保存することをお勧めします Appの起動中に早急に ApplicationDelegateの didFinishLaunchingWithOptions メソッドで チャネル・マネージャを 初期化することが大切です こうするとチャネル・ マネージャーがすぐに 初期化されるので 既存の チャネルを復元し バックグラウンドで 起動したAppに プッシュ通知を 配信することができます これでチャネルに参加する 準備ができました あなたのAppから誰かが チャネルに参加したら チャネルを識別する UUIDと チャネルについて 記述した記述子を 提供する必要があります このチャネルの存続期間中 マネージャーとの対話で 同じUUIDを使います 記述子には名前と画像が 含まれています チャネルを表す一意の 画像を指定すると ユーザーがシステムと 対話する際にチャネルの 見分けがつきやすくなります Appはチャネル参加時に チャネル・マネージャで requestJoinメソッドを 呼び出します チャネルに参加できるのは Appがフォアグラウンドで 実行されている時だけ なのでご注意下さい チャネルに参加すると チャネル・マネージャ デリゲートのdidJoinChannel メソッドが呼び出されます このデリゲート・メソッドは Appがチャネルに 参加したことを 示すものです さらにデリゲートの receivedEphemeralPushToken メソッドもAPNSプッシュ トークンと一緒に呼び出され このデバイスに PTT通知を 送信するのに使用されます このトークンはPTT チャネルの 存続期間中のみ有効です APNSプッシュトークンは 可変長なので その長さを Appにハードコード化 しないようにして下さい 他のチャネルがすでに アクティブな状態で チャネルに参加しようと したりすると チャネル参加リクエストが 失敗することもあります そのような場合は エラーハンドラを呼び出され エラーに失敗の理由が 記載されます ユーザーがチャネルを退出すると デリゲートのdidLeaveChannel メソッドが呼び出されます ユーザーのチャネル退出が 起きるのは 退出をリクエストするように プログラミングした時か ユーザーがシステムUIで 「Leave Channel」ボタンを タップした時です チャネル・マネージャ ・ デリゲートには LeaveChannelエラー処理 メソッドが関連付けられ チャネル退出リクエストが 失敗すると呼び出されます PTTではApp終了後や デバイス再起動後 Appが再起動するたびに 以前のチャネルを 復元することができます システムでこれを行うには システムの更新のために チャネル記述子の提供が必要です こちらは復元デリゲートで キャッシュ済みのチャネル 記述子を取得する ヘルパーメソッドです システムの応答性を 保つためにも 早急にこのメソッドから戻り チャネル記述子を 取得するために ネットワークリクエストなど 長時間実行されるタスクや ブロックされるタスクを 行わないで下さい PTTセッションの ライフサイクルにおいて チャネルに関する情報が 変更されるたびに 記述子を更新してください またネットワーク接続や サーバー可用性などの変更は サービスステータス オブジェクトを使用して システムに知らせます ここではチャネルの 記述子を更新しています このメソッドはチャネル名や 画像の更新が必要な場合に 呼び出すことができます この例では システムに対して Appのサーバーへの 接続が 再接続中の状態で あることを知らせます こうするとシステムUIが そのように更新され サービス状態が接続中か 切断の場合にユーザーは 音声を送信できなくなります 接続が再確立されたら サービス状態を 「Ready」に更新して下さい 次はPTTで 音声を送受信する方法を 確認しましょう Trevor APIの残りの説明を お願いできる? どうぞ
Trevor:ああ 任せて下さい どうぞ
PTTフレームワークの 設定方法を 見てきましたが 次は音声の送受信方法を 見ていきましょう PTTフレームワークの 中核となる機能は ユーザーが素早く音声を 送信できるようにするものです 音声通信はApp内から またはシステムの PTTのUIから 開始する事ができます CoreBluetoothから Bluetoothアクセサリを サポートできるAppなら 周辺機器の特性変化に応じて バックグラウンドで 通信を開始することも 可能です PTTフレームワークは 送信時にデバイスの マイクのロックを解除し バックグラウンドで録音が できるように Appの オーディオセッションを 起動します このプロセスを詳しく 見ていきましょう App内から通信を開始するには requestBeginTransmitting 関数を呼び出します Appがフォアグラウンドで 実行される時や Bluetooth周辺機器の 特性変化に反応した時に この関数を 呼び出すことができます システムが通信を 開始できない場合は デリゲートの failedToBeginTransmitting InChannelメソッドが 失敗の理由と共に 呼び出されます 例えば携帯電話で通話が 続いている場合は PTT通信を 開始できません 通信を停止するには マネージャの stopTransmittingメソッドを 呼び出します ユーザーが通信状態で なかった場合など 通信を 停止しようとした時の 失敗を処理するために チャネル・マネージャ デリゲートには関連した failedToStopTransmitting InChannelメソッドがあります App内から通信が始まった場合 または ユーザーがシステムUIから 開始した場合のどちらであっても チャネル・マネージャ デリゲートは 「Did begin transmitting」という コールバックを受け取ります 通信元がメソッドに渡され その通信が開始されたのが システムUIかプログラムAPIか ハードウェアボタン イベントかが示されます 通信が開始されると Appのオーディオ セッションが起動します これは録音を開始できる 合図です オーディオセッションは 自分で開始・停止しないでください チャネル・マネージャ デリゲートは通信が終了すると 通信終了イベントと オーディオ セッション無効化イベントを 受け取ります 通信がアクティブに なっている間に 電話やFaceTime通話など 他のソースによって オーディオセッションが 中断されることがあり その場合はApp内で処理する 必要があります PTTフレームワークでは Appを使用して バックグラウンドで 他のユーザーからの音声を 受信・再生することができます これにはPTT Appに 固有のAppleプッシュ 通知タイプを使います PTTサーバーは 新しい音声を受信すると チャネル参加時に ユーザーが受け取った デバイスプッシュトークンを 使用して PTT通知を送信します Appがプッシュ通知を 受信すると アクティブなスピーカーが フレームワークに報告されます するとAppのオーディオ セッションが起動し 再生が開始されます 新しいPTT通知は iOSの他の通知タイプと 似ており PTT Appに 配信できるようにするために 特定の属性を設定する 必要があります まず リクエストヘッダーで APNSプッシュタイプを "pushtotalk"に設定します 次にAPNSトピックヘッダーに Appのバンドル識別子を設定し ".voip-ptt"サフィックスを 末尾に追加します プッシュペイロードには アクティブなスピーカー名や セッションが終了したので AppがPTTチャネルを 出るべきであることを 示す表示など Appに関連した カスタムキーを含める ことができます "aps"プロパティの本文は 空白でも構いません さらに他の通信関連の プッシュタイプと同様 PTTペイロードは APNS優先順位を10に設定して 即時配信をリクエストし APNS有効期限を0に設定して 関連性のなくなった 古いプッシュが後から配信 されないようにします サーバーがPTT通知を 送信すると バックグラウンドで Appが起動し 受信したプッシュデリゲート メソッドが呼び出されます プッシュペイロードを 受信する場合 プッシュ結果タイプを 構築して プッシュ通知の結果として 取るべきアクションを 指定する必要があります リモートユーザーが 話していることを示すには アクティブな参加者の 名前や画像(任意)など 参加者の情報を含む プッシュ結果を返します これによりシステムは アクティブな参加者を チャネルに設定し チャネルが受信モードで あることを示します その後システムはオーディオ セッションを起動し didActivateaudioSession デリゲートメソッドを 呼び出します 再生を開始する前に このメソッドが 呼び出されるのを待ちます ユーザーがチャネルに 参加すべきではないと サーバーが判断すると プッシュペイロードに この事が示される 可能性があり その場合は leaveChannelプッシュ結果を 返すことができます 重要な点はこのメソッドから できるだけ早く PTPushResultを返し スレッドを ブロックしないことです アクティブなリモート 参加者を設定しようとしても 参加者の画像がローカルに 保存されていない場合は activeRemoteParticipantを 返し スピーカーの名前だけを 表示します その後別のスレッドに 画像をダウンロードし 画像を取得したら チャネル・マネージャで setActiveRemoteParticipantを 呼び出して activeRemoteParticipantを 更新します リモート参加者が 話し終えたら activeRemoteParticipantを nilに設定します これによりチャネルでの 音声受信が終わったこと またオーディオセッションを 無効化すべきであることを システムに知らせる ことができます これでシステムのPTT UIが更新され 再度通信が行える ようになります PTTをAppに 組み込む方法の基礎を 一通りお話ししましたが 次はユーザー体験を最適化し バッテリー寿命を 持たせるための ベストプラクティスを ご紹介します
PTTフレームワークの システムUIでは ユーザーはシステムの どこからでも 通信の開始やチャネルの 退出ができます Appがフォアグラウンドに ある時には 独自の カスタムPTT UIを 実装できる 柔軟性も備えています PTTフレームワークは 共有システムリソースを 使用するため システム上では一度に1つの PTT Appしか アクティブにできず 携帯電話 FaceTime VoIP通話を 使用すると PTT通信よりも 優先されます AppはPTTの失敗を 正常に処理し 相応に 応答する必要があります 先ほどもお話ししたとおり PTTフレームワークは オーディオセッションの 有効化と無効化を 自動的に行います ただし App起動時に 再生・録音する オーディオセッションの カテゴリは やはり設定すべきです システムには通信中に マイクが作動中か 停止中かをユーザーに 知らせるための 効果音が内蔵されています これらのイベントには 独自の効果音を 割り当てないで下さい またセッション中断や ルート変更 障害などの AVAudioSession通知を Appでモニターし 対応することも重要です システム上の他のオーディオ Appと同様 PTT Appもこれらの オーディオセッション イベントの影響を受けます Appを最適化して バッテリー寿命を 持たせることも重要です PTTフレームワークは 音声の送受信など 必要に応じて バックグラウンドの ランタイムを提供しています ユーザーが使用していない時 バッテリーを持たせるために Appは停止されます 自分のオーディオ セッションの有効・無効を 自分で切り替えないで下さい オーディオセッションの 有効化は適宜 システムが行います これによりシステムでの オーディオセッションの 適切な優先順位が保たれ 使用していない時には 停止することができます PTTサーバーは 新しいプッシュ通知タイプを 使用して 新しい音声の再生や PTTセッションの 終了をAppに知らせます Appでのバッテリー 寿命の 改善に関する詳細は 「電力の制限:バッテリー消費の改善」 セッションをご覧下さい PTT Appが バックグラウンドにあり Appで音声の送受信が 行われていない場合 Appはシステムによって 停止されます Appが停止すると ネットワーク接続は 全て切断されます セキュアなTLS接続を確立し 初期接続速度を 向上させるのに必要な 手順を減らすには Network.frameworkと QUICの導入を 検討して下さい Network.frameworkは QUICのビルトインサポート付きです QUICの使い方については 「ネットワーク遅延を減らしてAppの 応答性を向上させる」セッションで 詳細をご確認下さい PTTフレームワークを 使用すると 堅牢で電力効率に優れた トランシーバースタイルの 通信スタイルを App内に構築できます すでにiOSのAppで トランシーバースタイルの 体験を実装済みの場合は 新しいAPIを使用するように 既存のAppを更新して下さい 新たにトランシーバーAppを 実装する場合は PTTフレームワークを お使い下さい 最後に 新しいフレームワークの テストを開始して Appに組み込んだら フィードバックを お寄せ下さい ありがとうございました 素晴らしいWWDCを! どうぞ 以上です! ♪
-
-
6:52 - Creating a Channel Manager
func setupChannelManager() async throws { channelManager = try await PTChannelManager.channelManager(delegate: self, restorationDelegate: self) }
-
7:33 - Joining a Channel
func joinChannel(channelUUID: UUID) { let channelImage = UIImage(named: "ChannelIcon") channelDescriptor = PTChannelDescriptor(name: "Awesome Crew", image: channelImage) // Ensure that your channel descriptor and UUID are persisted to disk for later use. channelManager.requestJoinChannel(channelUUID: channelUUID, descriptor: channelDescriptor) }
-
8:11 - PTChannelManagerDelegate didJoinChannel
func channelManager(_ channelManager: PTChannelManager, didJoinChannel channelUUID: UUID, reason: PTChannelJoinReason) { // Process joining the channel print("Joined channel with UUID: \(channelUUID)") } func channelManager(_ channelManager: PTChannelManager, receivedEphemeralPushToken pushToken: Data) { // Send the variable length push token to the server print("Received push token") }
-
8:45 - PTChannelManagerDelegate failedToJoinChannel
func channelManager(_ channelManager: PTChannelManager, failedToJoinChannel channelUUID: UUID, error: Error) { let error = error as NSError switch error.code { case PTChannelError.channelLimitReached.rawValue: print("The user has already joined a channel") default: break } }
-
9:00 - PTChannelManagerDelegate didLeaveChannel
func channelManager(_ channelManager: PTChannelManager, didLeaveChannel channelUUID: UUID, reason: PTChannelLeaveReason) { // Process leaving the channel print("Left channel with UUID: \(channelUUID)") }
-
9:22 - PTChannelRestorationDelegate
func channelDescriptor(restoredChannelUUID channelUUID: UUID) -> PTChannelDescriptor { return getCachedChannelDescriptor(channelUUID) }
-
10:12 - Provide channel descriptor updates
func updateChannel(_ channelDescriptor: PTChannelDescriptor) async throws { try await channelManager.setChannelDescriptor(channelDescriptor, channelUUID: channelUUID) }
-
10:20 - Provide service status updates
func reportServiceIsReconnecting() async throws { try await channelManager.setServiceStatus(.connecting, channelUUID: channelUUID) } func reportServiceIsConnected() async throws { try await channelManager.setServiceStatus(.ready, channelUUID: channelUUID) }
-
11:48 - Start transmission from within your app
func startTransmitting() { channelManager.requestBeginTransmitting(channelUUID: channelUUID) } // PTChannelManagerDelegate func channelManager(_ channelManager: PTChannelManager, failedToBeginTransmittingInChannel channelUUID: UUID, error: Error) { let error = error as NSError switch error.code { case PTChannelError.callIsActive.rawValue: print("The system has another ongoing call that is preventing transmission.") default: break } }
-
12:22 - Stop transmission from within your app
func stopTransmitting() { channelManager.stopTransmitting(channelUUID: channelUUID) } func channelManager(_ channelManager: PTChannelManager, failedToStopTransmittingInChannel channelUUID: UUID, error: Error) { let error = error as NSError switch error.code { case PTChannelError.transmissionNotFound.rawValue: print("The user was not in a transmitting state") default: break } }
-
12:41 - Responding to begin transmission delegate events
func channelManager(_ channelManager: PTChannelManager, channelUUID: UUID, didBeginTransmittingFrom source: PTChannelTransmitRequestSource) { print("Did begin transmission from: \(source)") } func channelManager(_ channelManager: PTChannelManager, didActivate audioSession: AVAudioSession) { print("Did activate audio session") // Configure your audio session and begin recording }
-
13:19 - Responding to end transmission delegate events
func channelManager(_ channelManager: PTChannelManager, channelUUID: UUID, didEndTransmittingFrom source: PTChannelTransmitRequestSource) { print("Did end transmission from: \(source)") } func channelManager(_ channelManager: PTChannelManager, didDeactivate audioSession: AVAudioSession) { print("Did deactivate audio session") // Stop recording and clean up resources }
-
15:29 - Receiving Push to Talk Pushes
func incomingPushResult(channelManager: PTChannelManager, channelUUID: UUID, pushPayload: [String : Any]) -> PTPushResult { guard let activeSpeaker = pushPayload["activeSpeaker"] as? String else { // If no active speaker is set, the only other valid operation // is to leave the channel return .leaveChannel } let activeSpeakerImage = getActiveSpeakerImage(activeSpeaker) let participant = PTParticipant(name: activeSpeaker, image: activeSpeakerImage) return .activeRemoteParticipant(participant) }
-
17:03 - Stop receiving audio
func stopReceivingAudio() { channelManager.setActiveRemoteParticipant(nil, channelUUID: channelUUID) }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。