はじめに
SharePlayはWWDC 2021でiOS 15に導入された新技術です。
この記事は、SharePlayを使ったデータ共有機能の実装についてご説明します。
手順に従って、SharePlayのセッションを開始し、FaceTime参加者とデータをやり取りするアプリを作ります。
プロセスを詳しく説明するため、図をいくつか作成して記事に追加しました。画像をクリックすると拡大できます。
SharePlayについて
ユーザーの立場で言うと、このフレームワークを使った場合、FaceTimeの参加者がオンラインの活動を一緒にやる(例えば映画の鑑賞など)ことができるようになります。
開発者の視点で言うと、FaceTimeの参加者の間のデータ交換が可能になります。
そのため、一緒に映画を見る場合、FaceTime参加者間では再生時間のみが同期され、映画は各端末からストリーミング再生されることになります。
動画配信: Internet -> ユーザーの端末
再生時間: 他のFaceTimeの参加者 -> SharePlay -> ユーザーの端末
SharePlayセッションのフロー図
セッションのホストの場合:
既存のセッションに参加するの場合:
実装
エンタイトルメントの追加
まず、Signing & Capabilities
タブで、Group Activities
をエンタイトルメントファイルに追加します。
この記事に関連するファイル
File name | Purposes |
---|---|
DemoGroupActivityType.swift | グループ活動の種類を定義する。 |
MessageDataType.swift | FaceTime参加者間で転送されるデータの種類を定義する。 |
GroupActivityManager.swift | 新しいグループセッションをホストする機能、グループセッションを設定する機能(データを受信するハンドラの設定、参加者リストの変更を処理)、データを送信する機能を提供。 |
ContentsView.swift | これはSwiftUIビューで、そこではグループ・アクティビティーを開始するためのボタン、他のFaceTime参加者にランダムUUIDを送信するためのボタンが提供されるとともに、アクティブなグループ・セッションの監視が可能です。この例ではSwiftUIが使われていますが、UIKitを使用して同じ機能を提供することもできます。 |
データタイプの定義
まず、転送したいデータのタイプを定義する必要があります。データタイプは Codable
プロトコルに準拠する必要があります。
この例では、UUIDのみを転送します。
// MessageDataType.swift
struct DemoMessage: Codable {
let id: UUID
}
SharePlayのアクティビティの種類 GroupActivity
あなたのアプリが提供するグループアクティビティのメタデータを提供する必要があります。このメタデータには、アクティビティの名前、種類、フォールバックURLが含まれます。
フォールバックURLは、アプリをダウンロードするためのリンクが含まれているウェブページに設定します。
これらの情報は、FaceTimeのチャットウィンドウに表示されます。
// DemoGroupActivityType.swift
import Foundation
import GroupActivities
struct DemoGroupActivityType: GroupActivity {
var metadata: GroupActivityMetadata {
var metadata = GroupActivityMetadata()
metadata.title = "Demo Activity"
metadata.type = .generic
metadata.fallbackURL = URL(string: "https://example.com")
return metadata
}
}
このアクティビティタイプのオブジェクトはグループアクティビティをスタートするため、また現在進行中の全てのグループアクティビティのセッションをフェッチするためにも使用されます。
セッション・マネージャーを定義
ここで、データを保存してグループアクティビティセッションを制御するためにセッションマネージャー設定を作成する必要があります。このセッションマネージャの使用法は以下の通りです。
- グループ活動で共有したデータを保存する
- グループ活動を開催する
- 他の人にデータを送る
- 受信したデータを処理する
- 参加者リストの変更を処理する
// GroupActivityManager.swift
import Foundation
import Combine
import GroupActivities
@MainActor
class GroupActivityManager: ObservableObject {
// SharePlay session
@Published var groupSession: GroupSession<DemoGroupActivityType>?
var messenger: GroupSessionMessenger?
// Combine related
var subscriptions = Set<AnyCancellable>()
var tasks = Set<Task<Void, Never>>()
}
groupSession
は、グループセッションの状態や参加者のリストを管理します。
messenger
は、データの送受信に使用します。
subscriptions
変数は、グループ・セッションの状態や参加者リストの変更を監視するのに役立ちます。
tasks
は、データを受信するためのタスクを格納します。
グループセッションの初期化ロジックを設定する
ユーザーがグループセッションに参加したり、グループセッションをホストしたりするたびに、セッションデータを設定する必要があります。
そのためには、DemoGroupActivityType.sessions()
が変更されたときに通知するようシステムに依頼できます。ユーザーがグループセッションをホストしたり、グループセッションに参加したりしたことがわかるのです。
struct ContentView: View {
@StateObject var manager = GroupActivityManager()
var body: some View {
VStack {
Text("Hello world!")
}
.task {
for await session in DemoGroupActivityType.sessions() {
manager.configureGroupSession(session)
}
}
}
}
上記の例はSwiftUI用に書かれています。しかしながら、UIKitでも(セッションのリストの変更を監視するために)同じ待機ロジックを簡単に実装できるはずです。
configureGroupSession
関数
グループセッションの設定段階では、FaceTime参加者間でデータを転送するためのGroupSessionMessenger
オブジェクトを設定する必要があります。また、受信データのハンドラも設定する必要があります。
GroupActivityManager
クラスに新しい関数configureGroupSession
を追加します。
class GroupActivityManager: ObservableObject {
/* ... Other Codes ... */
func configureGroupSession(_ groupSession: GroupSession<DemoGroupActivityType>) {
// セッションオブジェクトをローカル変数に格納し、(データの送受信に使用される)メッセンジャーオブジェクトを生成および格納します。
self.groupSession = groupSession
let messenger = GroupSessionMessenger(session: groupSession)
self.messenger = messenger
// セッション無効化の状態を処理
groupSession.$state
.sink { state in
if case .invalidated = state {
self.groupSession = nil
self.reset()
}
}
.store(in: &subscriptions)
// 参加者リストの変更を処理
groupSession.$activeParticipants
.sink { activeParticipants in
print(activeParticipants)
}
.store(in: &subscriptions)
// タイプDemoMessageの入力データを処理
let task = Task {
for await (message, _) in messenger.messages(of: DemoMessage.self) {
handle(message)
}
}
tasks.insert(task)
groupSession.join()
}
func handle(_ message: DemoMessage) {
print("Received message: \(message)")
latestUUID = message.id
}
/* ... Other Codes ... */
}
これで、データをグループアクティビティから受信すると、handle(message)
関数が呼び出されます。
上の例では、タイプDemoMessage
のオブジェクトを転送しています。
SharePlayセッションの開始
一般的なグループアクティビティのセッションでは、誰かがグループアクティビティ(例:映画を視聴する)を開始(ホスト)します。
既存のアクティビティがある状態でアクティビティの開始を選択した場合は、既存のアクティビティを置き換える必要があるかどうかを尋ねられます。
以下のコードをGroupActivityManager
クラスに追加します。
func startSharing() {
Task {
do {
_ = try await DemoGroupActivityType().activate()
} catch {
print("Failed to activate DrawTogether activity: \(error)")
}
}
}
ご注意:activate()
関数を呼び出してグループセッションを開始すると、アプリはグループセッションの開始をリクエストします。
このようなリクエストが成功すると、アプリのDemoGroupActivityType.sessions()
変数がシステムによって変更されます。
この変数は、新しく作成されたグループセッションのオブジェクトを取得するための方法です。ですから、アプリはこの変数に対する変更を監視しなければなりません(例えば、await
を使用するなどして)。
グループセッションへの参加
アプリでは、グループセッションに参加するためのロジックを実装する必要はありません。これは、ユーザーがFaceTime通話ウィンドウの参加ボタンをタップするからです。
ユーザーがグループセッションに参加すると、アプリのDemoGroupActivityType.sessions()
にそのグループセッションの情報が格納されます。
データ送信
他の人にデータを送信するには、上記で設定したmessenger
オブジェクトを使用する。
以下のコードをGroupActivityManager
クラスに追加する。
func send(_ message: DemoMessage) {
if let messenger = messenger {
Task {
try? await messenger.send(message)
}
}
}
これで、オブジェクトのsend
関数を呼び出し、データを送信することができます。
Button("Send message") {
manager.send(DemoMessage(id: UUID()))
}
セッションデータの同期
新しいFaceTime参加者が通話に参加すると、groupSession.$activeParticipants
ハンドラが呼び出されます(configureGroupSession
関数で設定されます)。
通常、新しい参加者に以前のセッションデータを通知する必要があります(例えば、現在の再生時間を送信するなど)。
データの受信
受信データの処理はconfigureGroupSession
機能に実装されています。この機能では、受信データのハンドラと新しい参加者が加わる際のハンドラを設定します。
プロジェクトの完成
これで、グループアクティビティーのホスト、グループセッションの構成、データの送受信が可能なプロジェクトが完成するはずです。
iOS 15.4から、グループFaceTime通話を始めたい場合にもし進行中のFaceTime通話が無ければ、ユーザーを招待して通話を開始することができます。