WWDC Quick Look 💓 By SwiftGGTeam
Enhance voice communication with Push to Talk

Enhance voice communication with Push to Talk

元の動画を見る

ハイライト

iOS 16 の PushToTalk フレームワークは、システム レベルのインターコム UI、バックグラウンド マイクのアクティブ化、Push to Talk 固有の APNS 通知、チャネル回復メカニズムを提供し、インターコム アプリが独自のオーディオ エンコーディング、ストリーミング メディア、バックエンド伝送リンクを使用し続けながら、ロック画面とシステム インターフェイスにアクセスできるようにします。

主要内容

インターホンアプリの難点はボタンではありません。ユーザーは、ロック画面上、別のアプリ内、Bluetooth アクセサリ上、またはネットワークへの再接続中に話し始めることがあります。アプリがすでに一時停止されている間に、受信機は音声を受信することもあります。開発者は、システム コールと同じくらいリアルタイムのエクスペリエンスを実現するだけでなく、マイク、ネットワーク、およびバックグラウンドの実行時間を長時間消費することを避ける必要があります。

PushToTalk フレームワークは、このリンクをいくつかの明確な境界に分割します。このシステムは、チャネルの入り口、ステータス バーの青いカプセル、ロック画面 UI、マイクのアクティブ化、オーディオ セッションのアクティブ化、および通知受信後のバックグラウンド ウェイクアップを担当します。アプリは独自のビジネス チャネル、サーバー、オーディオ エンコード、オーディオ ストリーミング、メンバー ステータスを管理します。

このセッションの主旨は非常に明確です。まず、チャネルを追加してシステムに現在のインターコム セッションを知らせます。それからパスしますPTChannelManagerスピーチの開始と終了を処理します。最後に、新しいプッシュ トゥ トーク APNS 通知で受信機を起動し、現在の発言者を報告します。システムでは、音声の送信または受信時にのみバックグラウンドでの実行時間が許可され、アイドル状態の場合は電力を節約するためにアプリが一時停止されます。

詳細

チャンネルに参加: システム UI はチャンネルから始まります

(05:33) アクセスする前に、Xcode プロジェクトでプッシュ トゥ トーク バックグラウンド モード、プッシュ トゥ トーク機能、およびプッシュ通知機能をオンにする必要があります。また、アプリは録音の許可を要求し、マイクの目的の説明を Info.plist に提供します。

(06:16) Push to Talk セッションはチャネルと呼ばれます。システム UI が表示される前に、アプリはまずチャネルに参加する必要があります。アプリは、チャネルのライフサイクル中に利用可能な APNS プッシュ トークンも受け取ります。

func setupChannelManager() async throws {
    channelManager = try await PTChannelManager.channelManager(delegate: self,
                                                               restorationDelegate: self)
}

キーポイント:

  • PTChannelManager.channelManagerチャネル マネージャーを作成します。これは、アプリが PushToTalk フレームワークと対話するための主要な入り口です。 -delegate: self参加、退席、発言、音声セッション イベントを受信します。 -restorationDelegate: selfアプリの再起動またはデバイスの再起動後に既存のチャネルを復元するためにシステムによって使用されます。
  • でのスピーチの提案ApplicationDelegatedidFinishLaunchingWithOptionsバックグラウンドの開始時にチャネルが復元されてプッシュ受信できるように、できるだけ早く初期化してください。

(07:33) チャンネルに参加すると、アプリは UUID と説明オブジェクトを提供します。 UUID はすべてのマネージャー呼び出し全体で使用されます。 description オブジェクトは、システム UI 表示の名前と画像を提供します。

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)
}

キーポイント:

  • channelUUIDこの Push to Talk チャネルの安定したロゴです。 -PTChannelDescriptorチャネル名と画像を指定します。システム UI はこれを使用して、ユーザーがチャネルを識別できるようにします。
  • コメントは、チャネルを復元するときに使用されるため、記述子と UUID をディスクに永続化することを思い出させます。 -requestJoinChannel参加をリクエストできるのは、アプリがフォアグラウンドで実行されている場合のみです。これはトランスクリプトによって明示的に与えられた制限です。

(08:11) 参加に成功すると、デリゲートは参加結果と一時的なプッシュ トークンを受け取ります。このトークンは、デバイスに Push to Talk 通知を送信するためにサーバーに送信されます。

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")
}

キーポイント:

  • didJoinChannelアプリがチャンネルに参加したことの確認ポイントです。 -receivedEphemeralPushTokenこのチャネルのライフサイクル中に利用可能な APNS トークンを返します。
  • コードのコメントは、プッシュ トークンが可変長データであり、サーバーもクライアントも長さをハードコードすべきではないことを強調しています。
  • サーバーは後でこのトークンを使用して、新しいオーディオが再生可能であることを受信側に通知します。

リカバリとステータスの更新: システム UI に古い情報を表示させないでください。

(09:22) PushToTalk は、アプリの終了後またはデバイスの再起動後の前のチャネルの復元をサポートしています。復元時に、システムは復元デリゲートにチャネル記述子を要求します。

func channelDescriptor(restoredChannelUUID channelUUID: UUID) -> PTChannelDescriptor {
    return getCachedChannelDescriptor(channelUUID)
}

キーポイント:

  • restoredChannelUUIDシステムが回復しているチャネルです。 -getCachedChannelDescriptorローカル キャッシュから記述子をフェッチすることを示します。
  • トランスクリプトでは、このメソッドができるだけ早く戻ることを明示的に要求し、ここでネットワーク要求やその他のブロックタスクを行わないようにします。
  • これは、UUID と記述子が以前に永続化された理由でもあります。

(10:12) チャンネル名、画像、またはネットワーク接続ステータスが変更された場合、アプリはシステムに積極的に通知する必要があります。システム UI はそれに応じて表示を更新し、サービス接続が利用できない場合はユーザーが話すことを防ぎます。

func updateChannel(_ channelDescriptor: PTChannelDescriptor) async throws {
    try await channelManager.setChannelDescriptor(channelDescriptor, 
                                                  channelUUID: channelUUID)
}

キーポイント:

  • setChannelDescriptorシステムによって保存されたチャネル情報を新しい記述子で更新します。 -channelUUID更新するチャネルを指定します。
  • 該当するシナリオには、チャンネル名の変更、チャンネルのアバターの変更、またはローカル キャッシュの更新が含まれます。
func reportServiceIsReconnecting() async throws {
    try await channelManager.setServiceStatus(.connecting, channelUUID: channelUUID)
}

func reportServiceIsConnected() async throws {
    try await channelManager.setServiceStatus(.ready, channelUUID: channelUUID)
}

キーポイント:

  • .connectingサービスが再接続中であることをシステムに伝えます。
  • トランスクリプト 説明 このステータスはシステム UI を更新し、接続時または切断時にユーザーが発言できないようにします。 -.ready接続が復元され、ユーザーが音声の送信を続行できることを示します。
  • PushToTalk はアプリのネットワーク層を置き換えるものではなく、サービスのステータスは依然としてアプリ自身のバックエンド接続に基づいてアプリによって決定されます。

話す: 録音する前に、システムがオーディオ セッションをアクティブにするのを待ちます。

(11:09) ユーザーはアプリ内またはシステム UI から話し始めることができます。アプリが CoreBluetooth を介してハードウェア ボタンをサポートしている場合、バックグラウンドで周辺機器の特性の変化に応答した後、送信の開始を要求することもできます。

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
    }
}

キーポイント:

  • requestBeginTransmitting指定されたチャネルでの発言の開始を要求します。
  • このリクエストは、フォアグラウンド アプリまたはサポートされている Bluetooth 周辺機器イベントから送信される可能性があります。 -failedToBeginTransmittingInChannelシステムが音声の開始を拒否する状況に対処します。
  • 例ではPTChannelError.callIsActiveトランスクリプトのシナリオに対応: 進行中の携帯電話通話がプッシュ トゥ トーク送信をブロックします。

(12:41) 発言の開始をリクエストしても、すぐに録音されるわけではありません。実際の記録時間は、デリゲートが送信開始イベントを受信し、システムがアクティブ化されたときです。AVAudioSession

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
}

キーポイント:

  • didBeginTransmittingFrom音声がシステム UI、プログラム API、またはハードウェア ボタン イベントから開始されたかどうかをアプリに伝えます。 -didActivate audioSession録音開始の合図です。
  • トランスクリプトには、音声セッションを自分で開始または停止しないでください、システムが適切なタイミングで音声セッションをアクティブ化することが明確に記載されています。
  • アプリは録音を設定し、オーディオセッションがアクティブ化された後にオーディオストリームを独自のサーバーに送信できます。

(13:19) 最後のスピーチも管理者と代表者の協力で完了します。システムがオーディオセッションを非アクティブ化した後、アプリは録音リソースをクリーンアップします。

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
}

キーポイント:

  • didEndTransmittingFromステートメントの終わりと終わりのソースを記録します。 -didDeactivate audioSessionオーディオ セッションがシステムによって無効になっていることを示します。
  • アプリはここで記録を停止し、リソースを解放します。
  • 電話や FaceTime 通話などの音声中断は、送信中にも処理する必要があります。

受信: Push to Talk 専用 APNS 通知ウェイクアップ アプリ

(13:42) 音声の受信は、新しい Push to Talk プッシュ通知タイプに依存します。サーバー上に新しい音声がある場合、チャンネルに参加するときに取得したデバイス プッシュ トークンを使用して通知が送信されます。 APNS ヘッダーのプッシュ タイプは次のように設定する必要があります。pushtotalk、トピックではバンドル識別子とプラスを使用する必要があります.voip-pttサフィックスの場合、優先度は 10、有効期限は 0 にすることをお勧めします。

(15:29) アプリがバックグラウンドで開始された後、できるだけ早く戻る必要がありますPTPushResult。リモート スピーカーがいる場合は、アクティブな参加者が返されます。サーバーがチャネルからの離脱を要求した場合、チャネルからの離脱が返されます。

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)
}

キーポイント:

  • pushPayloadアプリのカスタム キーを含めることができます。使用例activeSpeaker現在のスピーカーを示します。
  • アクティブな発言者がいない場合、例は戻ります。.leaveChannel
  • PTParticipant発言者の名前とオプションの画像が含まれます。システム UI はこれを使用して、誰が発言しているかを示します。
  • 戻る.activeRemoteParticipantその後、システムはチャネルを受信モードに設定し、オーディオ セッションをアクティブ化し、オーディオ セッションを呼び出してデリゲートをアクティブにします。
  • トランスクリプトでは、このメソッドができるだけ早く返される必要があります。アバターがローカルでない場合は、最初に名前のみを返し、次に画像を非同期でダウンロードしてアクティブな参加者を更新できます。

(17:03) リモート会話が終了すると、アプリはアクティブなリモート参加者をクリアする必要があります。その後、システムはプッシュ トゥ トーク UI を更新し、ユーザーが再び話すことができるようになります。

func stopReceivingAudio() {
    channelManager.setActiveRemoteParticipant(nil, channelUUID: channelUUID)
}

キーポイント:

  • setActiveRemoteParticipant(nil, channelUUID:)リモート音声が受信されなくなったことを示します。
  • アプリのオーディオセッションは無効になります。
  • UI は受信モードを終了し、ユーザーはもう一度 Talk を押すことができます。
  • このステップはサーバー側のオーディオ ストリーム終了イベントと一致する必要があります。そうしないと、システム UI がまだ受信中であると誤って認識します。

バッテリーとネットワーク: バックグラウンド実行時間は必要な場合にのみ表示されます

(17:37) PushToTalk は共有システム リソースを使用します。システム上で同時にアクティブにできる Push To Talk アプリは 1 つだけであり、携帯電話通話、FaceTime、および VoIP 通話が Push To Talk よりも優先されます。アプリは失敗時のコールバックを適切に処理する必要があります。

(18:18) オーディオ セッションのアクティブ化と非アクティブ化はシステムが担当しますが、アプリは起動時にオーディオ セッション カテゴリを再生および録音するように設定する必要があります。このシステムはマイクのアクティブ化および非アクティブ化の音も提供し、アプリは独自の音を再生しなくなりました。

(19:05) PushToTalk は、音声の送受信に必要な場合にのみバックグラウンド ランタイムを提供します。アイドル状態になると、アプリはシステムによって一時停止され、ネットワーク接続が切断されます。この講演では、安全な TLS 接続を再確立し、初期接続速度を向上させるために必要な手順を削減するために、Network.framework と QUIC を検討することを推奨しています。

func reportServiceIsReconnecting() async throws {
    try await channelManager.setServiceStatus(.connecting, channelUUID: channelUUID)
}

func reportServiceIsConnected() async throws {
    try await channelManager.setServiceStatus(.ready, channelUUID: channelUUID)
}

キーポイント:

  • バックグラウンドサスペンド後のネットワーク接続の切断は、システムの省電力戦略の一部です。
  • 再接続時に使用.connectingシステム UI を更新して、ユーザーがサービスが利用できないことを知るためにトークを押すことを防ぎます。
  • 接続が回復したら報告します.ready
  • リアルタイム オーディオ リンクが回復速度の影響を受けやすい場合は、Network.framework と QUIC を組み合わせて接続の確立を最適化できます。

重要ポイント

  • やるべきこと: チーム コラボレーション アプリに「チャネル スタイルの音声通話」を追加します。 実行する価値がある理由: PushToTalk はシステム UI とロック画面へのアクセスを提供するため、ユーザーはアプリを開かずに返信できます。 開始方法: 最初に使用しますPTChannelManager.channelManagerマネージャーを初期化して使用しますrequestJoinChannelチームチャンネルに参加してreceivedEphemeralPushTokenそれをサーバーに送信します。

  • 対処法: Bluetooth ボタン音声を倉庫、医療、または現場検査アプリに追加します。 実行する価値がある理由: このセッションでは、アプリが引き続き CoreBluetooth を使用してワイヤレス Bluetooth アクセサリを統合し、周辺機器の特性が変化したときに通話を要求できることが明確に述べられています。 開始方法: 周辺ボタン イベントをマップするrequestBeginTransmitting(channelUUID:)、存在するdidActivate audioSession次に、オーディオ ストリームの録音とアップロードを開始します。

  • 内容: 受信側で「誰が話しているのか」を明確に通知します。 実行する価値がある理由: システム UI にはアクティブな参加者の名前と写真が表示されるため、ユーザーは現在誰の音声を受信して​​いるかを知ることができます。 開始方法: サーバーはスピーカー ID を Push to Talk ペイロードに入れ、アプリをincomingPushResult中程度の構造PTParticipantそして戻る.activeRemoteParticipant

  • 対処方法: インターコム サービスにネットワーク ステータス保護を追加します。 実行する価値がある理由: システム UI により、サービスのステータスに基づいてサービスの接続中または切断中にユーザーが発言するのを防ぐことができ、失敗の経験が軽減されます。 開始方法: バックエンド接続ステータスを同期します。setServiceStatus(.connecting, channelUUID:)そしてsetServiceStatus(.ready, channelUUID:)

  • 何をすべきか: プッシュを受信するためのクリティカル パスからアバターの読み込みを移動します。 実行する価値がある理由: トランスクリプトでは、受信プッシュ デリゲートができるだけ早く戻る必要があり、ブロックすると受信リンクに影響します。 開始方法: ローカル アバターがない場合は、最初に名前のみを返します。PTParticipant、バックグラウンドで画像をダウンロードしてから呼び出しますsetActiveRemoteParticipant表示を更新します。

関連セッション

  • アプリの応答性を高めるためにネットワークの遅延を軽減する — Push to Talk アプリ アイドル状態になるとネットワークが切断されます。このセッションでは、Network.framework、HTTP/3、QUIC が再接続とリクエストの遅延をどのように削減できるかについて説明します。
  • パワーダウン: バッテリー消費の改善 - PushToTalk のバックグラウンド モデルは省電力に重点を置いています。このセッション システムでは、バックグラウンド データ処理、長時間のタスク、電力消費について説明します。
  • Meet Web Push for Safari — どちらのセッションにも、サーバーが Apple プッシュ リンクを介してクライアントに到達することが含まれます。プッシュのサブスクライブ、送信、デバッグの完全なプロセスを比較して理解できます。

コメント

GitHub Issues · utterances