ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Group Activitiesによるメディアエクスペリエンス連携
SharePlayとGroup Activitiesフレームワークによって、コンテンツを完全に同期した状態でユーザが見たり聞いたりできるようにする方法について確認します。メディアAppを同期し、大勢の人々のためにSharePlay対応のエクスペリエンスを提供する方法について解説します。AppにGroup Activitiesを追加する方法、ピクチャ・イン・ピクチャのレイアウト、Playback coordinatorオブジェクトによって複数デバイス間での再生を微調整する方法を紹介します。
リソース
関連ビデオ
WWDC22
WWDC21
-
ダウンロード
こんにちは ”Group Activitiesによる メディアエクスペリエンス連携”の ―セッションへようこそ 私ヘイデンはAppleの ―Group Activitiesチームの エンジニアです 今日は ユーザーが各デバイスで ―コンテンツを一緒に 視聴することができる ―同期メディアAppの 作成方法を紹介します 目標は ユーザーがどこにいても ―まるで物理的に一緒にいる ように感じられることです GroupActivities APIは 共有体験を生むための ―Swiftフレームワークです 再生調整や グループセッション管理を ―APIを介して行い メディアを調整します これは iOS iPadOS macOS tvOSなど ―幅広いプラットフォームに 対応しています Group Activities APIの デモから始めましょう 同僚のモーリッツに電話し このセッションの ―サンプルAppから 動画を共有しましょう モーリッツは 出るでしょうか
やあ モーリッツ! やあ ヘイデン! ホーム画面から サンプルAppを開きます Apple Parkの ドローン動画の一覧です モーリッツに シアターの動画を見せます これはどう 見たいかな? いいね 気に入ったよ ただ 先週アップロードした ―リングの新しい動画は 見たかい? これは素敵だね! 再生しましょう 動画はデバイス間で 同期されます 私が一時停止すると モーリッツの動画も停止し ―彼が早送りすると 私の動画も同様になります
素晴らしい ありがとう モーリッツ! いい動画だったね ではまた ヘイデン メディアの 同期再生を行うAppを ―作成するための要点は 3つあります 1つ目が 新しい GroupActivities APIの実装 2つ目が 自分の体験を シームレスに共有するために ―いかにピクチャー・イン・ ピクチャーを活用するか 3つ目は 同期再生の仕組みと ―新しい同期再生 オブジェクトの機能で ―これはモーリッツが 詳しく説明します GroupActivities APIの ライフサイクルを見ながら ―理解を深めましょう 最初のデモと同様に FaceTimeで通話中の ―2台のiPhoneがあります 左が私のデバイスで ―右がモーリッツの デバイスです 私のデバイスで 共有Appを開きます このAppで グループに アクティビティを共有します GroupActivitiesフレーム ワークはアクティビティの ―セッションオブジェクトを 作成し Appに配信します セッションオブジェクトが モーリッツに共有され ―同じAppを開くよう フレームワークが処理して ―GroupSessionを 渡します これで 2つのAppが 同じGroupSessionで ―通信できるようになりました では GroupActivities フレームワークを ―導入するために 必要なステップを紹介します 最初のステップは GroupActivityを ―メタデータとともに 作成することです では GroupActivityとは何か ―定義の仕方を説明します GroupActivityは ユーザーが共有体験している ―アイテムを表す Swiftプロトコルです これは映画や歌のような ―1つのコンテンツに あたります このタイプには 体験を設定するために必要な ―プロパティを含めないと いけません 例えば ここに 動画のURLを含めると ―セッションの準備として 動画が読み込めます フレームワークが データをネットワークで ―送信するために Codableに準拠します つまり プロパティも Codableに準拠します 2つのプロパティが必須だと 気づきますね ActivityIdentifierは システムがアクティビティを ―参照するための ユニークな識別子です GroupActivityMetadataは リモートデバイスのシステムUIで ―アクティビティを表示するための 情報を含みます これは 今年 Swift Concurrencyとして ―導入される 新しい非同期の 効果的な読み取り専用 ―プロパティ機能の一例です 詳しくは今年度のWWDCの ”Swift の async / await について” をご覧ください GroupActivityが 定義できましたね 次に GroupActivityの ―activateメソッドを呼び出し 通話で共有します GroupActivityを有効に すると フレームワークが ―GroupSessionオブジェクトを 作成し ローカルデバイスと リモートデバイスに配信します システムはAppの起動を 担当し システムUIが ―アクティビティの メタデータを表示します その前に 伝えきれて いなかったことがあります ―Appが FaceTimeが 通話中だと知る方法とは? ユーザーがコンテンツを 選択したときに ―グループで共有するのでなく ―ローカルで見たいとしたら? PrepareForActivation関数は システムにユーザーの意図を ―聞き取らせることで これらの問題を解決します これはprepareForActivation APIの例です switchステートメントでは 3つのケースを ―処理します activationDisabledは FaceTimeが通話中ではない ―またはユーザーはローカルで再生したいと システムが判断した場合 ―選択されます activationPreferredは ユーザーが ―FaceTimeで通話中に それをグループで ―共有したいときに 選択されます Canceledが選択されるのは 共有を中止したときです GroupActivities Appを 作成するにはこれで ―知識は十分です Xcodeの話に 移りましょう これは実践でお見せします GroupActivitiesの サポートを追加する前に ―プロジェクトの概要を 説明します これは映画を 選んで観るだけの ―シンプルな 映画鑑賞Appです 観たい映画のリストページと 動画プレーヤーを表示する ―映画の詳細ページで 構成されています 現在Appでは映画を 一人でしか観ることが ―できませんが 変えていきましょう
Group Activity entitlement が 設定されていますね さらに Movie.swift に GroupActivityを追加します 最初に GroupActivities を インポートします
そして GroupActivity ―MovieWatchingActivityを 追加します ここあるmovieプロパティを 使って ―メタデータを入力している ことがわかりますね アクティビティを 今 共有しましょう CoordinationManagerに 進み ―prepareToPlay機能を 見つけます 現在 この関数は movie が ―エンキューされると すぐに再生を開始しますが ―代わりにスライドで紹介した ―prepareForActivation関数に ―置き換えます
activationDisabledの ケースでは ―映画をすぐに エンキューします
activate()は ユーザーがFaceTime通話で ―共有したいときに 呼び出されます 後ほど映画をエンキューして 再生を開始しますが ―今回は Appをこのまま 実行してみましょう 私はモーリッツのデバイスを 持っており ―2つのiPhones間で FaceTimeを開始します ホーム画面から サンプルAppを起動します ―先ほど見た リングのビデオを共有し ―モーリッツのデバイスで Appを起動します GroupActivityを 受信しましたね しかし 正しい動画が 読み込まれず ―再生の同期が 起こりませんでした Appにこの機能を 追加する方法を紹介します Group Activitiesの受信に 関して学ぶべきは ―GroupSession オブジェクトと ―GroupSessionを 受信する方法 ―そして GroupSessionの 非同期シーケンスです これは GroupSessionの ―典型的なライフサイクルを 示す 大まかな図です 両デバイスとも グループセッションを受信し ―それから Appが 再生準備を始めます 準備ができたら グループ セッションに参加します GroupSessionはグループ アクティビティのデバイス間の ―リアルタイムセッションを 表すオブジェクトで ―最新のグループアクティビティ 接続状態 セッションに ―接続しているアクティブな 参加者のグループなど ―セッションに関する 状態を提供します 後で説明しますが ―これは再生の同期にも 使用されます GroupSession AsyncSequenceは ―GroupSessionsを Appに配信します Appは直接GroupSession オブジェクトのインスタンスを ―作成しないので これが GroupSessionsを受け取る ―唯一の方法です 重要なのは ―この流れで ローカルと リモートの両デバイスで ―最新のGroupSessionを 取得することです AsyncSequenceは Swift の Concurrency の ―セッションで 取り上げられています GroupSessions AsyncSequenceの ―待ち受けは コード上では このようになります GroupActivityが アクティブになると ―システムは GroupSessionを ―AsyncSequenceから awaitループに戻します さて GroupSessionを 受け取ったので ―それを使った同期再生の 設定方法を学びましょう AVFoundationの最新版では AVPlaybackCoordinator ―という新しいタイプが 導入されました このオブジェクトについて 後でモーリッツが ―詳しく説明しますが ここでは ―再生コーディネーターに GroupSessionを渡し ―同期再生を可能にする 方法を紹介します 再生コーディネーターへは AVPlayerの ―playbackCoordinator プロパティからアクセスし ―コーディネーターに GroupSessionを添付するため ―coordinateWithSessionを 呼び出しGroupSessionを ―渡します これだけです 内部ではフレームワークが 再生調整や ―リアルタイム配信など 複雑な作業を全て行います セッション管理の 最後のステップは ―セッションへの参加です 初期状態では GroupSessionは ―接続されておらず ”待機”状態です ”join()”を呼び出すと GroupSessionがグループに ―接続され リアルタイム 接続を開始し グループ内の ―デバイス間でメッセージの 送受信が可能になります GroupSessionが正常に 接続されると ―同期再生が始まります セッション管理コードを サンプルAppに ―追加してみましょう CoordinationManager で セッションの ―非同期シーケンスを awaitループに追加します
これによりローカルまたは リモートデバイスから ―アクティベートされたときに AppにGroupSessionsを ―送ります このgroupSessionを ―保存して ―MoviePlayerViewController に変更を反映させます
完了したら セッションオブジェクトを ―playbackCoordinator .coordinateWithSession で
AVPlayerに添付し awaitループに戻ります movieをセッション オブジェクトから取得します
アクティビティはセッション中に 変わることもあるので ―Combineパブリッシャーで アクティビティを取得します 続いて再生を開始するため movieをエンキューします 最後に セッションに参加します これでGroupSessionを 受け取り 再生を同期する ―コード設定が完了しました Appを端末で 起動してみましょう FaceTimeで通話中の ―2つのデバイスのうち 片方でサンプルAppを開き ―リングの動画を共有します
モーリッツのデバイスが Group Activitiesを ―受信しましたね Appを起動します 今回は両方のデバイスで 正しい動画が表示されました
私が”再生”を押せば ―同期再生が機能している ことがわかります 停止を押せば もう片方の デバイスでも停止し
―早送りすれば 両方で早送りになります
再生は完全に 同期しています
GroupSessionについて 最後に知っておくことは ―セッションの終了方法です GroupSessionの終了方法は 2つあります 1つめがleave()です ローカルユーザーは セッションから ―切断されますが ―残った通話参加者に対しては セッションは有効なままです 2つめはend()です ローカルの参加者だけでなく ―通話全体のセッションが 終了します 高度なGroupActivities Appの作成に関する詳細は ―WWDCのセッションから ”Group Activitiesによる カスタムエクスペリエンスの構築”で ―GroupSessionアクティビティ の変更方法や ―GroupSessionの状態を 観察する方法 グループ間で任意の メッセージが送信できる ―GroupSessionMessengerなど 高度な機能の使用方法も ―紹介しています ここで ピクチャー・イン・ ピクチャーを活用して ―動画の GroupActivityを シームレスに共有する ―方法を見てみましょう なぜ ピクチャー・イン・ ピクチャーが ―GroupActivities Appに 適しているのでしょう? ピクチャー・イン・ ピクチャーに対応すると ―共有されたコンテンツが すぐに再生可能になります ユーザーは現状の コンテクストのままで ―明示的な操作なしに 再生を開始できます これによって コンテンツを共有するときに ―余計なステップを踏まず シームレスな体験ができます ピクチャー・イン・ ピクチャーの設定に関しては ―詳細は2019年のWWDCより ―”AVKitで直感的なメディア 再生を提供”をご覧ください しかし GroupActivititiesと ピクチャー・イン・ピクチャーの ―連動には微妙な 違いがあります GroupActivities フレームワークは ―バックグラウンドでメディア Appにセッションを配信し ―Appにピクチャー・イン・ ピクチャーを設定させます GroupSessionが バックグラウンドで ―再生開始を 指示された場合 ―ピクチャー・イン・ ピクチャーを設定して ―通常のGroupActivities フローを実行します このシステムでは Appをフルスクリーンで ―起動することなくピクチャー ・イン・ピクチャーで ―コンテンツを再生するため ユーザーは現状を ―保持したまま シームレスな体験ができます しかし Appへのサインインが ―必要であったり 何らかの手順を踏まないと ―コンテンツを利用できない などの理由で ―ピクチャー・イン・ ピクチャーを設定できない ―ときもあります その場合 GroupSessionはAppに ―フォアグラウンド化を要求 するためのAPIを提供します ピクチャー・イン・ ピクチャーにはもう1つ ―GroupSessionの離脱と終了を システムダイアログで ―処理する便利な機能が あるので 作動中は ―セッションの終了と離脱を 心配しなくて済みます 続いて モーリッツが ―再生コーディネーター オブジェクトについて ―詳しく説明します
こんにちは 私モーリッツ・ ウィッテンハーゲンは ―AVFoundationチームの エンジニアです ヘイデンは複数のデバイスで 再生を自動的に同期する ―オブジェクトとして AVPlaybackCoordinatorを ―紹介し その動作を デモで確認しました このパートでは コーディネーターが ―内部で実際に 何をしているかと ―コードにどのように 作用するのかを説明します 主に コーディネーターが どのように ―AVPlayerに作用するかや ―再生調整のための アセットの選択の仕方 ―個々の参加者がどうやって 一時的に調整をサスペンドするかや ―AVPlayerを使用しない場合の 調整の ―実装方法についても 簡単に紹介します AVPlaybackCoordinatorは デバイス間で ―再生状態を 共有するオブジェクトで ―各デバイス間での 再生開始を調整し コンテンツを 見逃さないようにします コーディネーターには 2つのサブクラスがあります AVPlayerPlaybackCoordinator の ―インスタンスは 常に 特定のAVPlayerに紐づき ―リモート状態の管理を 全て担います これは協調再生への 最も簡単な方法で ―まずはここから始める ことをお勧めします ここでは AVDelegatingPlaybackCoordinator に ―関する詳細は 割愛しますが このサブクラスを使用すると AVPlayer以外の ―再生オブジェクトを 柔軟に制御できます 再度 デバイスの設定を 見てみます このあとは GroupSessionを通して ―GroupActivities オブジェクトを説明します また UIはAVPlayerItemを 再生するAVPlayerを ―提示することを 想定しており ここで ―新しいAVPlayerPlayback Coordinatorが登場します coordinatorの coordinateWithSessionを ―呼び出すと 先ほどと同様に 2つのAVPlayerが ―効果的に接続され 相互作用するようになります 基本的に コーディネーターは レートまたは現在時間を変更する APIなど どんな再生制御APIも 転送します そしてコマンドを受け取り 他者との調整が ―必要かを判断して 適切なタイミングで ―AVPlayerに反映させます 例をご覧ください ヘイデンと私が GroupSessionしています GroupActivityが どの URLをロードすべきか教え ―同じAVPlayerItemを エンキューしたからです もしデバイスのAVPlayerの レートプロパティを変えたら ―再生コーディネーターは そのコマンドを傍受し すぐにはプレーヤーの 再生開始を許可しません ―代わりに プレーヤーに timeControlStatusを WaitingToPlayAtSpecifiedRateに 設定するよう指示します UIは通常この timeControlStatusを ―回転する矢印で 待機中である事を示します そしてコーディネーターが コマンドを ―ヘイデンのiPadに送ります AVPlayerPlaybackCoordinatorが コマンドを ―受け取り ヘイデンの AVPlayerにレートを変更して ―待機状態になるよう 指示します コーディネーターは全員に 再生準備の時間を与え ―全員が同じ時間に 再生を開始し ―コンテンツを見逃さない ようにします 全員の準備が整ったら全ての デバイスの再生が始まります セッション内のすべての コーディネーターは ―平等なので ヘイデンも コマンドを開始できます ―今度は彼にお願いしましょう ここでも シークAPIは 傍受され ―コーディネーターは接続 されたコーディネーターと ―コマンドを共有する間 AVPlayerを待機させます 全員にシークを完了する 時間が与えられ ―全てのデバイスの 準備が整うと ―一斉に再生を再開します ここで AVPlayersが 異なるアイテムを ―再生している場合は? という疑問に答えます コーディネーターは 両プレイヤーが同一の ―コンテンツを 再生しているときのみ ―相手プレイヤーに 状態を適用します 同一の概念については 後で詳しく述べます ひとまず 同じURLから アイテムを作成した場合 ―コンテンツは同一だと 考えてください つまり アイテムAに送る コマンドは 受信側が ―アイテムAを再生している ときだけ適用されます アイテムをBに変更すると アイテムAの状態を完全に ―無視します アイテムごとにコマンドを ―区別することで 参加者の合流や ―アイテム間の移行を 安全に行えるからです ご説明します 再度例を挙げますが 今回はヘイデンが ―すでに再生している状態から スタートします 私が同じセッションに参加し 再生コーディネーターを接続しても ―ヘイデンとは 現在同じアイテムを再生していないので ―私のAVPlayerには 何も起こりません 同じアイテムを作成しても 私の現在のプレーヤー内には ―存在しないため 影響されることはありません つまり 誰にも影響を与えずに シークを行うことが できるのです そのアイテムが AVPlayerの中で ―進行中になって初めて コーディネーターは作動し ―正しい状態を 適用しようとします コーディネーターは グループの状態が存在すれば ―常にそこからスタートする 決まりです ヘイデンはすでに GroupSessionで再生中なので ―コーディネーターは 私がアイテムを ―エンキューする前の設定より ヘイデンの状態を優先します 私のコーディネーターは ヘイデンのデバイスと同期するため ―AVPlayerとAVPlayerItemの 設定を上書きします それによって同じ状態になり 一緒に再生できるようになります これをアイテムの移行で 振り返ってみましょう 2人ともアイテムAの 終わりが近づいたため ―私のAVQueuePlayerは 次のアイテムの再生準備を ―しています 通常は ヘイデンも同様だと ―思われますが ここでは ヘイデンのiPadの接続が悪く ―次のアイテムをまだ 読み込めないと仮定します アイテムAが終わって すでにアイテムBが ―エンキューされて 再生準備できています しかし アイテムが 再生中のものでないため ―コーディネーターは まだ何もしません プレイヤーが新しい アイテムに移行して初めて ―コーディネーターは 再び作動します 今回は アイテムBの既存の状態が ―見つからないので 私のプレーヤーが ―提案する状態を そのまま継続します 例え コーディネーターが ―この新しい状態を共有しても アイテムAを ―表示したままのヘイデンに 影響はありません しかし 私のアイテムに 合わせて移行すると ―彼のコーディネーターは 再び彼のプレーヤーに ―既存の状態を適用します そして再び 再生同期します これが私の最初の 行動喚起となるものです アイテム変更と 再生制御コマンドの順序に ―気を付けてください アイテムをエンキューして 自動的に再生開始する ―関数 beginPlaybackが あるとします このコードは ユーザーが 何か再生するものを ―選択したときや GroupSessionが新しい ―アクティビティを通知した ときに呼び出されます 始めに スタート時間を 要求することが重要です 同様に アイテムが エンキューされる前に ―初期設定が 他の人に影響しないように ―再生レートを変更する 必要があります この順序で行うと 再生コーディネーターは ―自分たちが最初で 状態を全員に共有すべきか ―あるいは別の状態が既に 存在しており その状態に自分たちを 上書きすべきか判断できます また 再生制御コマンドを 監査し それをグループ内の ―全員に影響させるべきか 検討します 通常は APIコールが 再生UIから発信された場合 ―それはグループに 影響します もしユーザーが一時停止 すれば 他も停止します このケースではただ AVPlayer APIを呼び出します では 再生UIから 発信されたのでない ―他のAPIが呼び出された 場合はどうでしょう? 良くあるのは Appが何らかの システムイベントに ―遭遇したことによる 自動停止です このような自動停止は 通常 ―他の参加者に影響することは ありません ユーザーが誰かと一緒に 再生しているときは ―まず 一時停止しないことを 心掛けてください 一時的にコンテンツが 見られないなどの不都合があっても ―他の人たちは 再生を続けているため ―ユーザーはそのグループに 留まろうとします 一時停止せざるを得ない場合 2つの選択肢があります アイテムを先に削除するか 同期再生をサスペンドするかです これについては 後ほどお話しします 再生コーディネーターの 仕組みがわかったところで ―協調再生のための コンテンツを説明します 先に述べたように アセットが同じURLから ―作成されている場合 異なるデバイス上の ―2つのアイテムは 同一あるとみなします このデフォルト状態でも 問題ありませんが ―この動作を変更したくなる 場合もあります 例えばコンテンツを事前に ダウンロードするオプションを 考えてみます 私はアセットをダウンロードして ローカルキャッシュから再生 ―ヘイデンはクラウドから コンテンツをストリーミング ―している場合 私たちは 同じURLを使用しないので ―再生コーディネーターは 再生状態を同期しません 同様に AVMutableMoviesや AVCompositionsの状態を ―調整するような 面白い使用例をする場合も ―URL表示を全く持たないので ―再生コーディネーターは 何をすべきか判断できません これを解決するために AVPlayerItemの識別子として ―カスタム文字列を 提供します この文字列が存在する場合 コーディネーターはこれを ―2つのアイテムが同じ コンテンツを表すかどうかを ―判断するために使用し URLは無視します これを行うには AVPlayerPlaybackCoordinator ―Delegateプロトコルと そのplaybackCoordinator ―identifierFor playerItem 関数を実装します プレーヤーでアイテムを エンキューするたびに ―コーディネーターは識別子を デリゲートに要求します 再生コーディネーターが 2つのアイテムを同一と ―見なす場合 1つのデバイスのタイムが ―他のデバイスのタイムと 一致することが重要です つまり サーバーから 再生ストリームに ―自動的に投入される ―コンテンツには 注意が必要です もしヘイデンと私が 同じURLをリクエストしても ―サーバーが私のストリームに だけ広告を投入すると ―デバイスは同期されなくなり 私が広告を再生していても ―ヘイデンはメインコンテンツを 再生し続けます この問題を解決するには インターステイシャル ―広告などを 別のプレーヤーに移します そうするとメインアセットは 影響を受けません 広告を再生すると その間 私の携帯電話は ―別のプレーヤーに 切り替わります 広告が終了すると コーディネーターが ―他の人の時点に合わせて 同期状態に戻ります HLSコンテンツの再生中は AVPlayerが 新しい ―AVPlayerInterstitialEvent APIを通してサポートします 詳細は ”HLSにおけるダイナミックな プレロールとミッドロール” ―をご覧ください 要約すると URLが正しい 情報を対象にしていない ―場合は カスタム識別子で コンテンツを照合します 時間が全員同じであることを 確認してください 個別の インターステイシャルを ―再生する場合は メインコンテンツに ―影響しないように 別のプレーヤーで再生します ライブコンテンツを 同期再生する場合は ―DATEタグが使われていれば コーディネーターが ―正しいタイミングを 全員で共有します ここまでは 全員が 常に同期した状態のままで ―いられる 完璧な再生状況 のみ扱ってきました 残念なことに いつもこうだとは限りません ヘイデンと私が一緒に 再生しているときに ―猫の餌やりの時間を知らせる アラームが鳴ったとします AVAudioSessionのルールで 私のAppは一時停止します このルールはグループで 再生している時も適用されます 私の一時停止が他の人に 影響してほしくありません みんなのちょっとした 一時停止を ―グループ全体に反映しても 迷惑なだけです そこで ヘイデンのiPadには 再生を継続してもらいます 私がアラームを解除すると 再生が追いつき ―再び同期するように なります この動作の実装方法を 説明します AVCoordinatedPlaybackSuspension という ―新しいオブジェクトを 使います このようなサスペンドでは ―該当の参加者の コーディネーターと ―他の参加者の コーディネーターを ―分離することにより 該当参加者の ―プレーヤーのレート変更や シークは誰にも影響しません 同様に グループからの レート変更があっても該当者の ―AVPlayerのレートや時間が 変わることはありません この例では アラームが鳴っている間は ―ヘイデンが 私のプレーヤーを ―スタートすることは できないということです サスペンドには 自動サスペンドと ―自分で行ったサスペンドの 2種類あります 自動サスペンドはプレーヤーが 自動的に一時停止した時に ―AVPlayerPlaybackCoordinator が設定します すでにオーディオセッションの 停止の例を ―見ましたが これは ネットワークの停止や ―新しいAVPlayer interstitial APIによる ―インターステイシャルの 再生にも当てはまります 再生コーディネーターが 行ったサスペンドは ―プレーヤーが再生を 再開すると終了し ―タイミングを 現在の グループの状態に合わせます この例では Appが ―アラーム解除による ポーズの解除通知を処理した後 ―自分のプレイヤーのレートが 1に戻ると ―自動的にグループに 復帰するということです システムがどのように調整された 再生のサスペンドを ―使用するか 2つ例を見ていきます
リングのドローン動画を Appで再生しています 先ほどの停止の例を 見てみましょう 数秒後に鳴るタイマーを 設定しています 待ちましょう
鳴りましたね これで左側のiPadが 一時停止しましたが ―もう片方は まだ楽しそうに再生中です タイマーを解除すると ―左のデバイスの再生が とばされ ―もう片方のデバイスと合流し 完全に同期します システムがサスペンドを 利用するのは ―これだけではありません AVKitは誰かがスクラブ動作をした時に ランダムなビデオフレームが ―停止中の他の人に表示されるのを 防ぐために使用しています お気づきかもしれませんが 停止中は ―私が操作している デバイス上でのみ ―中間の映像を 表示します そのため スクラバーをタッチすると ―右のデバイスは再生を続け ―左のデバイスは 停止中の映像を表示します スクラバーから手を離すと ―新しい時間が 他のデバイスに共有され ―全員で再生を再開します これらがシステムによる サスペンドの使用例です では 自身で導入する方法を 見ていきます Xcodeに移り 実際にやってみましょう 例えば 参加者の1人が ―見逃した内容を 再視聴できる機能を ―実装したいとします 他の人がすでに見た ドローンの映像の中に ―特に興奮する瞬間が あったのかもしれません これまで述べた コーディネーターの動作だと ―戻って再生を再開することは 全員に影響します 強調したいのは これが ―非常に正しいことであろう という点です なるべくユーザー同士が 一緒になるようにします しかし 全員に戻るよう 求めることはできません そこで 私たちが 構築するのが ―数秒前に戻って みんなと合流するまで ―2倍の速さで コンテンツを 再生する方法です 既に ユーザーが何か 見逃したことを示すために ―使用できる ボタンをUIに追加しました これは MoviePlayerViewControllerで ―この関数に接続されています それを埋めていきましょう
ここではプレイヤーの ロジックだけ紹介します 何時にシークするか シークバックするかを見つけ ―再生速度を2に設定します 追いついたら レート1で再生を再開します ―再生コーディネーターが APIコールを傍受するので これらは全て 誰に対しても起こります ここでサスペンドの出番です シークバックを行う直前に ―私のプレーヤーを 他の人たちと分離します それを コーディネーターの ―beginSuspension関数で 行います これには理由が必要で その理由を提供するため ―Reason構造体を新しい 文字列定数で拡張します
今回は What-happenedです これをbeginSuspensionの 呼び出しに使用できます
コーディネーターがサスペンドして いるので 安心して自身の ―プレイヤー用にシークおよび レートを設定できます 他のメンバーと 合流する準備ができたら ―再びグループのステートに 従うために ―コーディネーターに 信号を送る必要があるので ―suspension.end()を 呼び出します 該当プレイヤーのレート変更は もう必要ありません サスペンドを終了すると必ず ―他のメンバーと合流するので プレイヤーレートも ―現在のグループレートに 戻ります
実際に試してみましょう
一緒に再生しています リングへアプローチする 瞬間を見逃したため ―新しいWhat-happened ボタンをタップします 私の操作するデバイスは コンテンツを繰り返すため ―ジャンプして戻っている ことがわかります 追いつくために 再生速度が速くなり ―その間 他のデバイスは 影響を受けません 今はもう 完全な 同期再生に戻っています AVPlaybackCoordinator上で 停サスペンドを開始すると ―プレーヤーは グループから切り離され ―他のプレーヤーに 影響することなく ―レート変更やシークを 行うことができます サスペンドを終了すると ―グループの現在の時間と レートに復帰します このスライドでは 新たにサスペンドの終了時に ―新しい時間をグループに 提案できるようになります これは 再生コントロールを 放したときにだけ ―全員の時間変更を行う ―スクラブ時のサスペンドを 実装する方法です ここで AVPlayerの 再生制御コマンドが ―他の参加者と共有された 場合について要約します まず 冒頭で ヘイデンが紹介したように AVPlayerPlaybackCoordinatorを 他の参加者と ―グループセッションで 接続する必要があります 次に AVPlayerの現在の AVPlayerItemは ―他のプレーヤーアイテムと 同じURLでないといけません また 独自の識別子を 提供している場合は ―同じカスタム識別子を 持つ必要があります アイテムがエンキューされると シークやレートの ―変更は グループ全員に 影響しますが ―同期再生のサスペンドを 始めた場合は例外です AVPlayerPlaybackCoordinator に関して最後に ―同期再生に関連する 他のAPIを紹介します あるユーザーのサスペンド中に ―他の参加者を 待機させたい場合 ―コーディネーターの suspensionReasonsThatTriggerWaiting ―プロパティを設定します このように 参加者の 広告の長さが違っていても ―誰もコンテンツを 見逃さないようできます 他の参加者の状態について 詳しく知るために ―コーディネーターの otherParticipantsプロパティと ―それに対応する通知を 見てみましょう AVCoordinatedPlaybackParticipant は ―suspensionReasonsの リストを提供しており ―特に suspensionReasonsThatTriggerWaitingを ―使用している場合は UIの更新に便利です コーディネーターが AVPlayerに待機を要求するたびに ―waitingForCoordinatedPlayback に新しい理由が設定され ―プレーヤーのreasonForWaitingToPlay に反映されます 他の参加者の状態に関わらず 待機状態を無効にして ―直ちに再生開始するには プレーヤーの ―playImmediately(atRate:) 関数を使用します このAPIを使用する場合 他の参加者がコンテンツを ―見逃す可能性があることを 留意してください AVPlayerには新しい rateDidChangeNotificationがあり ―他の参加者がいつ レートを変更したかなど ―レート変更に関する 詳細情報を提供しています 同様に ―AVPlayerItemの timeJumpedNotificationでは ―タイムジャンプが 他の参加者から ―発生した場合にも 知らせてくれます AVPlayerPlaybackCoordinatorと ―一緒に使用できない AVPlayer APIもあります iOSのデフォルトの タイムピッチアルゴリズムは ―lowQualityZeroLatency でしたが この値はiOS 15では 非推奨となっています lowQualityZeroLatencyは 同期再生では ―サポートされていないので 現在非推奨であるこの値に ―コードがリセットされない ように注意してください 代わりに別の アルゴリズムを使用します また AVPlayerの setRate(time:atHostTime:)関数を ―AVPlayerPlaybackCoordinatorと 一緒に ―使用しないでください 再生コーディネーターが プレーヤーのタイミングを ―担うことが重要であるからです これは他で開始された同期と 互換性がありません AVPlayerの話が 出たところで ―AVPlaybackCoordinatorの 2つ目のサブクラス ―AVDelegatingPlaybackCoordinator を紹介します ここまでのコンセプトの 多くは ―この委任コーディネーターにも ―当てはまり プレーヤーを観察する代わりに 再生状態に関する情報を提供し ―再生オブジェクトに受け取った 状態を適用することが求められます カスタムプレイバック オブジェクトの設定です ―UIがプレーヤーを制御し プレーヤーは ―システムAPIを使って レンダリングします 委任再生コーディネーターは ―UIとプレーヤー実装の間に 位置します AVDelegatingPlaybackCoordinatorは 名前の通り ―再生 一時停止 シーク バッファリングなどの ―再生コマンドを受け取る ―デリゲートプロトコルを 実装する必要があります プレイヤーに直接プレイ コマンドを送るのでなく ―UIはまずコーディネーターに 伝えるのです コーディネーターは 再生オブジェクトの ―時間やレートを変更する前に 接続されている ―他のプレーヤーと交渉する 必要があるか判断します プレーヤーが新しい現在の アイテムに移行するたび ―コーディネーターに伝え どのコマンドをあなたに ―送るべきか 知らせないといけません 従来通り アイテムは 任意の文字列で識別されます コーディネーターが コマンドを送信する際は ―受信側と発信側の両UIに 影響するはずです そして プレイ時は ―すべてのプレイヤーに 同様に影響します 再生オブジェクトを コマンドに追従させることと ―UIを適切に 更新することは ―あなたの責任です 特に注意したいのは デバイスの準備が ―完了していても 委任したコーディネーターが ―バッファリング開始の指示を 出すことが多い点です これは コーディネーターが まだ他の接続された参加者を ―待っていることを意味し あなた側で何も ―していなくても UIがそれを 反映させる必要があります 最後の警告です ルート変更のように 一時的にプレーヤーを ―停止 再開する イベントに注意してください 要求されたタイミングを守り 必要に応じてグループの ―タイミングを再設定するのは あなたの責任です それが維持できない場合は ―サスペンションを使って コーディネーターに ―その旨を伝えてください 委任再生コーディネーターが ―自動サスペンドを 追加することはありません よって 自身で 関連するシステムイベントの ―サスペンドの開始終了を 行う必要があります 必要に応じて AVFoundationが提供する ―reasonsを設定してください あるデバイス上の 委任再生コーディネーターは ―別のデバイス上のプレーヤー 再生コーディネーターに ―接続可能です ただし プレーヤー再生 ―コーディネーター側で 必ず カスタム識別子を使用します 締めくくりましょう 同期されたメディア再生Appを 構築するには ―GroupActivityを使用して 再生コンテンツを ―定義して それをグループに提案します Appが起動したらすぐに GroupSessionsを監視して ―ユーザーがいつ FaceTimeで着信し ―一緒に再生したいと 希望したのかを把握します 最後に 再生コーディネーターを ―GroupSessionに接続すると 全員で コンテンツの ―同期状態が保たれます ありがとうございました 残りのWWDCも楽しんで [音楽]
-
-
3:06 - Define a GroupActivity
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 } }
-
4:42 - Making your play buttons automatically start a group session when appropriate
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 } } }
-
8:31 - Receiving a GroupSession from the GroupSession AsyncSequence
// Receiving a GroupSession from the GroupSession AsyncSequence func listenForGroupSession() { Task { for await session in MovieWatchingActivity.sessions() { ... } } }
-
9:03 - Attaching an AVPlayer to the GroupSession
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) ... } } }
-
31:26 - Custom suspensions
class AVPlaybackCoordinator { func beginSuspension(for reason: AVCoordinatedPlaybackSuspension.Reason) -> AVCoordinatedPlaybackSuspension } class AVCoordinatedPlaybackSuspension { func end() func end(proposingNewTime newTime: CMTime) }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。