WWDC Quick Look 💓 By SwiftGGTeam
Coordinate media experiences with Group Activities

Coordinate media experiences with Group Activities

元の動画を見る

ハイライト

Apple は、SharePlay メディア同期機能をグループ アクティビティ フレームワークに追加し、AVPlaybackCoordinator を使用して、開発者が AVPlayer の再生、一時停止、およびジャンプ状態を FaceTime 通話内のすべてのデバイスに同期できるようにしました。

主要内容

リモートで一緒にビデオを視聴する際に最も難しいのは、ビデオそのものを再生しないことです。

問題は同期にあります。 1 人が再生を押しても、もう 1 人はまだバッファリング中です。 1 人が話をするために一時停止し、もう 1 人が前進を続けます。誰かが進行状況バーをドラッグすると、グループ内のすべてのデバイスが同じ位置に追いつく必要があります。

以前は、開発者がこの状態を自分で処理する必要がありました。アプリは、ユーザーが通話中かどうかを認識し、再生コマンドをリモート デバイスに送信し、ネットワーク遅延、バッファリング、セッション途中での参加、セッションからの退出などの詳細も処理する必要があります。 Media App は最終的に、小規模なリアルタイム コラボレーション システムを作成する予定です。

AppleがWWDC21で提示したソリューションはグループアクティビティとSharePlayだった。アプリは「全員が一緒に何をしているか」を定義するだけでよく、システムは FaceTime 通話の他の参加者にアクティビティを送信し、GroupSession を各デバイスに渡します。

メディア再生には別の特別な処理層があります。 AVFoundation は AVPlaybackCoordinator (再生コーディネーター) を追加します。開発者は置いたAVPlayerplaybackCoordinatorに接続しますGroupSession、再生、一時停止、シークなどの送信制御コマンドは、同じグループ状態に調整されます。

ローカル再生から SharePlay に切り替える

ユーザーが再生ボタンをクリックしたとき、アプリはユーザーが共有したいと直接想定すべきではありません。

prepareForActivation()これにより、システムは現在の環境とユーザーの意図を判断できるようになります。ユーザーが FaceTime 通話に参加していない場合、アプリはローカル再生を続行します。ユーザーが通話中に共有を選択した場合、アプリは通話を行いますactivate()を実行すると、システムはセッションを作成し、それをローカル デバイスとリモート デバイスに送信します。

このステップにより、入口の問題が解決されます。再生ボタンは再生ボタンのままであり、ユーザーは新しい操作方法を学ぶ必要はありません。

GroupSession から再生ステータスを引き継ぐ

セッションが作成された後、アプリは渡されますMovieWatchingActivity.sessions()監視システムから送信されるGroupSession

開始されたアクティビティはネイティブにセッションを受け取ります。リモート ユーザーも、アクティビティを受け入れた後にセッションを受け取ります。アプリはセッションを取得した後、対応するコンテンツをロードし、AVPlayerこのセッションに接続し、最後に呼び出しますjoin()ライブ接続に参加します。

このステップにより、配布の問題が解決されます。アプリは直接作成されませんGroupSessionまた、リモート デバイス リストを独自に管理することもありません。

同期の詳細は AVPlayer に任せましょう

AVPlaybackCoordinatorレートまたは現在の時間を変更する AVPlayer API はインターセプトされます。

参加者が再生を押すと、コーディネーターはローカル デバイスを待機させてから、他のデバイスにコマンドを送信します。すべてのデバイスの準備ができたら、一緒に遊び始めてください。同じプロセスがシークにも適用されます。各デバイスがジャンプを完了した後、一緒に再開します。

この手順により、同期の問題が解決されます。開発者は今でも使い慣れたものをこう呼んでいますAVPlayerAPI コーディネーターは、これらのコマンドをグループ アクションに変換する役割を果たします。

全員の一時的な停止に対処する

グループ再生は、すべての一時停止が全員に影響を与えるという意味ではありません。

アラームが鳴ると、システム オーディオがローカル再生を中断します。ネットワークの遅延は 1 台のデバイスのみで発生する場合もあります。 AVPlayerPlaybackCoordinator は、そのような状況に備えて一時停止を作成し、デバイスをグループ タイミングから一時的に切り離します。

割り込みが終了すると、ユニットはグループの現在時刻にジャンプし、再同期します。他のユーザーは、このローカルな停止を待つ必要はありません。

詳細

グループアクティビティを定義する

(03:06) GroupActivity は、ユーザーが一緒に体験するコンテンツです。動画アプリでは「一緒に見ている映画」として理解できます。フレームワークはアクティビティ データをネットワーク経由で他のデバイスに送信したいため、プロトコルは次のものを継承します。Codable

protocol GroupActivity: Codable {

    /// An identifier so the system knows how to reference this activity
    static var activityIdentifier: String { get }

    /// Information that the system uses to show this activity, such as title and a preview image
    var metadata: GroupActivityMetadata { get async }
}

キーポイント:

  • GroupActivity映画や歌など、共有エクスペリエンス内の 1 つのコンテンツを表します。
  • Codableフレームワークがアクティビティ内のプロパティをネットワークのもう一方の端に送信できるようにします。
  • activityIdentifierシステムによって参照されるこのアクティビティ タイプの一意の識別子です。
  • metadataシステム UI が表示する必要があるタイトル、プレビュー、その他の情報を提供します。
  • get asyncSwift Concurrency の非同期読み取り専用プロパティ フォームを使用します。

再生ボタンで共有を開始するかどうかを決定します

(04:42) ユーザーがコンテンツを選択した後、アプリは最初にアクティビティを構築してから、prepareForActivation()。結果は、FaceTime 通話ステータスとユーザーの選択に基づいて返されます。

func playButtonTapped() {
    let activity = MovieWatchingActivity(movie: movie)

    Task {
        switch await activity.prepareForActivation() {
        case .activationDisabled:
            // Playback coordination isn't active. Queue movie
            // for local playback.
            self.enqueuedMovie = movie
        case .activationPreferred:
            // Activate the activity. The system enqueues the movie
            // when the activity starts.
            activity.activate()
        case .cancelled:
            // The user cancelled the operation. Nothing to perform.
            break
        default:
            break
        }
    }
}

キーポイント:

  • MovieWatchingActivity(movie: movie)現在のムービーをグループ アクティビティにパッケージ化します。
  • Task非同期呼び出しを実行するために使用されます。
  • await activity.prepareForActivation()FaceTime に共有する必要があるかどうかをシステムに判断させます。
  • .activationDisabled再生調整が有効になっていない、またはユーザーがローカルで視聴することを選択したことを示します。コードは直接設定されますenqueuedMovie
  • .activationPreferredシステムが共有の開始を推奨することを示します。コード呼び出しactivity.activate()
  • .cancelledユーザーが共有プロセスをキャンセルし、コードが追加の操作を実行しないことを示します。

GroupSessionを受信する

(08:31) アプリは直接インスタンス化されませんGroupSession。システム合格GroupActivity.sessions()返された AsyncSequence はセッションを配信します。

// Receiving a GroupSession from the GroupSession AsyncSequence

func listenForGroupSession() {
    Task {
        for await session in MovieWatchingActivity.sessions() {
            ...
        }
    }
}

キーポイント:

  • listenForGroupSession()アプリの起動後、できるだけ早くリスニングを開始する必要があります。
  • Task非同期ループを開始します。
  • for awaitシステムが新しいセッションを配信するのを継続的に待ちます。
  • MovieWatchingActivity.sessions()はい、受け取りますGroupSession入り口。
  • session複数のデバイスにわたるリアルタイムの会話における現在のアクティビティを表します。

AVPlayer を GroupSession に接続する

(09:03) 入手GroupSession最後に、メディア アプリの重要なステップは次のとおりです。AVPlayerplaybackCoordinatorこのセッションに接続します。

let player = AVPlayer()

...

func listenForGroupSession() {
    Task {
        for await groupSession in MovieWatchingActivity.sessions() {

            // Verify content is available, prepare for playback to begin

            player.playbackCoordinator.coordinateWithSession(groupSession)

            ...
        }
    }
}

キーポイント:

  • AVPlayer()実際にメディア コンテンツを再生するプレーヤーです。
  • for await groupSessionシステムから送信されたグループ会話を受信します。
  • コメントは、コンテンツが利用可能であることを最初に確認し、再生する前に準備を完了することを開発者に思い出させます。
  • player.playbackCoordinatorAVPlayer にバインドされた再生コーディネーターにアクセスします。
  • coordinateWithSession(groupSession)プレーヤーをグループ セッションに接続します。
  • 接続が完了すると、レートや時間を変更する再生、一時停止、シークなどのコマンドが調整プロセスに入ります。

(09:24) セッションは最初は待機状態です。アプリはまだ呼び出しが必要ですjoin()をクリックして、ライブ接続の動作を開始します。このスピーチでは、接続が成功するまで再生の同期は開始されないことが説明されています。

groupSession.join()

キーポイント:

  • join()現在のデバイスをリアルタイム接続のグループに参加させます。
  • 参加後、デバイスはデバイス間でセッション メッセージを送受信できるようになります。
  • メディア再生の場合、再生調整はセッションに参加するまで有効になりません。

AVPlaybackCoordinator が再生コマンドをどのようにインターセプトするかを理解する

(16:17) AVPlaybackCoordinator はトランスポート制御 API (伝送制御 API) をインターセプトします。rateまたはcurrentTime電話。

player.play()
player.pause()
player.seek(to: time)

キーポイント:

  • player.play()プレーヤーのレートが変更され、コーディネーターがこのインテントを他の参加者に送信します。
  • player.pause()これはレートの変更でもあり、同じコンテンツを再生するグループ内のデバイスに影響します。
  • player.seek(to: time)現在時刻を変更すると、コーディネーターによって他のデバイスが一貫した場所にジャンプします。
  • この講演では、参加者が同じコンテンツを再生する場合にのみコーディネーターがこれらの状態を適用することが強調されています。

(18:03) デフォルトでは、コーディネーターは AVPlayerItem の URL を使用して、2 つのデバイスが同じコンテンツを再生しているかどうかを判断します。 URL が同じ場合のみ、状態は相互に影響します。 URL が異なる場合、コマンドはリモート プレーヤーに適用されません。

let item = AVPlayerItem(url: movieURL)
player.replaceCurrentItem(with: item)

キーポイント:

  • AVPlayerItem(url:)URLを使用してプレイアイテムを作成します。
  • コーディネーターは、デフォルトで同じ URL を同じコンテンツとして扱います。
  • replaceCurrentItem(with:)現在再生中のアイテムを作成します。
  • コーディネーターは、アイテムが現在のアイテムになるまで、グループ ステータスの適用を開始しません。

ローカル キャッシュと合成コンテンツにカスタム ID を提供する

(23:20) あるユーザーがローカルキャッシュファイルを再生し、別のユーザーがクラウドURLを再生した場合、デフォルトのURL判定は無効となります。 AVFoundation はデリゲート エントリを提供し、アプリが AVPlayerItem のカスタム文字列識別子を返すことを可能にします。

func playbackCoordinator(_ playbackCoordinator: AVPlayerPlaybackCoordinator,
                         identifierFor playerItem: AVPlayerItem) -> String? {
    return movieIdentifier
}

キーポイント:

  • playbackCoordinator(_:identifierFor:)AVPlayerPlaybackCoordinator デリゲートから。
  • playerItemコーディネーターがアイデンティティを求めているプレイ アイテムです。
  • 返された文字列は URL を置き換え、2 つのデバイスが同じコンテンツを再生しているかどうかを判断するために使用されます。
  • movieIdentifierローカル ファイル パスではなく、コンテンツ ID を表す必要があります。
  • スピーチのリマインダー: 2 つのプロジェクトが同じであるとみなされる場合、それらは同じ時点の同じコンテンツに対応している必要があります。

(24:40) パーソナライズされた広告とインタースティシャルをメイン コンテンツのタイムラインに混ぜてはなりません。講演では、メインアセットのタイミングを一貫させるために、広告とその他のインタースティシャルを別のプレーヤーに配置することが提案されました。 HLS コンテンツは AVPlayerInterstitialEvent API と組み合わせることができます。

let event: AVPlayerInterstitialEvent

キーポイント:

  • AVPlayerInterstitialEvent講演の中で言及されていたHLSプラグイン関連のAPIです。
  • インタースティシャルコンテンツには独自の再生期間があり、メインコンテンツの時間対応を変更しないでください。
  • メイン コンテンツの時間が一貫している場合にのみ、コーディネーターはさまざまなデバイスを同じ進行状況に戻すことができます。

個人的な一時的な離別のための利用停止

(31:26) 一部の操作はこのマシンにのみ影響します。スピーチの例は、「ほんの数秒間見逃しました」です。ローカル デバイスは数秒前に戻り、2 倍の速度でグループに追いつき、他のデバイスは再生を続けます。

class AVPlaybackCoordinator {
    func beginSuspension(for reason: AVCoordinatedPlaybackSuspension.Reason) -> AVCoordinatedPlaybackSuspension
}

class AVCoordinatedPlaybackSuspension {
    func end()
    func end(proposingNewTime newTime: CMTime)
}

キーポイント:

  • beginSuspension(for:)現在の参加者のコーディネーターをグループから一時的に削除します。
  • reasonこの離脱の理由を記録するには、カスタム文字列定数を使用してアプリを拡張できます。
  • 返されましたAVCoordinatedPlaybackSuspensionこの離脱状態を表します。
  • 一時停止が存在する間、ローカルのシークとレートの変更は他に影響を与えません。
  • end()切断が終了すると、マシンは現在の時刻とレートでグループに再参加します。
  • end(proposingNewTime:)退出が終了したら、進行状況バーのドラッグなどの対話に適した新しい時間をグループに提案できます。

(32:40) AVPlaybackCoordinator にはいくつかの補助入口もあります。特定の一時停止理由によって他の参加者が待機することを許可したり、他の参加者のステータスを公開したり、AVPlayer 通知を通じてリモート参加者によって引き起こされたレートの変更やタイムジャンプをマークしたりすることができます。

coordinator.suspensionReasonsThatTriggerWaiting
coordinator.otherParticipants
player.reasonForWaitingToPlay
player.playImmediately(atRate: rate)

キーポイント:

  • suspensionReasonsThatTriggerWaiting他の参加者を待機させる一時停止理由を設定するために使用されます。
  • otherParticipants一時停止の理由を含む、他の参加者のステータスを表示できます。
  • reasonForWaitingToPlay新しい調整された再生の待機理由が表示されます。
  • playImmediately(atRate:)講義リマインダーはすぐに再生できるため、他の参加者がコンテンツを見逃してしまう可能性がある待ち時間を回避できます。

AVDelegatingPlaybackCoordinator を使用したカスタム プレーヤー

(34:46) アプリが AVPlayer を使用しない場合は、AVDelegatingPlaybackCoordinator を使用できます。プレイヤーのステータスを監視することはありません。開発者は、デリゲートへの再生、一時停止、シーク、バッファリングのコマンドを自分で受け取る必要があります。

let coordinator = AVDelegatingPlaybackCoordinator()

キーポイント:

  • AVDelegatingPlaybackCoordinator非 AVPlayer の再生実装。
  • UI はカスタム プレーヤーに直接命令するべきではなく、最初にインテントをコーディネーターに渡す必要があります。
  • コーディネーターは、再生オブジェクトに時間またはレートを変更させる前に、他の参加者を待つ必要があるかどうかを決定します。
  • 現在再生中のアイテムは、開発者が文字列で識別する必要があります。
  • 自動一時停止は委任コーディネーターによって追加されず、システム イベントにより開発者自身が一時停止を開始および終了する必要があります。

重要ポイント

  1. やるべきこと: ビデオ アプリに「一緒に見る」ボタンを追加します。
  • 実行する価値がある理由:prepareForActivation()元の再生入口を保持し、FaceTime 通話中に再生を SharePlay にアップグレードできます。
  • 開始方法: 定義MovieWatchingActivity、再生ボタンで呼び出されますprepareForActivation()、ローカル エンキューを選択するか、結果に基づいて選択します。activate()
  1. やるべきこと: オンラインコースの同期視聴機能を作成します。
  • 実行する価値がある理由:AVPlaybackCoordinator再生、一時停止、シークが同時に行われるため、講師と生徒が同じコースのビデオを一緒に視聴するのに適しています。
  • 開始方法: コースビデオの URL を次のようにします。GroupActivityコンテンツ データ、モニタリングCourseWatchingActivity.sessions()、その後電話しますplayer.playbackCoordinator.coordinateWithSession(groupSession)
  1. やるべきこと: スポーツのリプレイに「見逃した」個人的なキャッチアップ ボタンを追加します。
  • 実行する価値がある理由:AVCoordinatedPlaybackSuspension1 人の参加者が、他の参加者の視聴を中断することなく、短時間後退したり速度を上げたりできるようにします。
  • 起動方法: ボタンをクリックすると呼び出されますbeginSuspension(for:)、マシンは数秒前をシークしてより高いレートを設定し、追いついた後に呼び出します。suspension.end()
  1. 内容: ローカルにキャッシュされたビデオの SharePlay 再生をサポートします。
  • 実行する価値がある理由: キャッシュされたファイルとクラウド ファイルの URL は異なり、デフォルトの URL ID は同期できません。カスタム識別子は、それらを同じコンテンツにマップできます。
  • 開始方法: AVPlayerPlaybackCoordinator デリゲートを実装するidentifierFor playerItem、サーバーのコンテンツ ID を返します。
  1. 対処法: 広告付きのビデオを安定したグループ同期にします。
  • 価値がある理由: パーソナライズされたインロールはメイン コンテンツのタイムラインを混乱させ、デバイスが別のクリップで同期できなくなる可能性があります。
  • 開始方法: 広告を別のプレーヤーに配置し、メイン コンテンツの一貫性を保ちます。 HLS シナリオの研究を続けるAVPlayerInterstitialEvent

関連セッション

コメント

GitHub Issues · utterances