「2台のiPhoneを近づけてSharePlay開始」について調べたメモ
SharePlayはiOS 17からは「2台のiPhoneを近づけてSharePlay開始」が可能になった。
こんな感じでNameDropと同様のエフェクトでSharePlayの開始がトリガーされる:
デバイスを近づけてSharePlay(WWDC23 "Add SharePlay to your app" より)
以前はFaceTimeで接続した上での利用が必須だった。「アプリの体験を共有する」という限りなく可能性のある機能なので、FaceTimeなしで近くの人にシュッと共有できるようになった今、もっと使われてもいいのでは、と思う。
なおSharePlayはiOSだけでなくvisionOSやmacOS, tvOSでも利用可能。フレームワークとしては Group Activities を使用する。
どういう体験が共有できるのか?
当初から想定されていた代表的なユースケースは「動画視聴体験の共有」だ。要は動画を一緒に見れる。
ただそれだけじゃなく、基本的にデータを送り合って実現する体験なら何でもSharePlayで共有できる。
たとえばAppleが公開しているサンプル DrawTogether では、お絵描きを共有できる:
後述するが、このアプリではストロークをSharePlayで送り合うことでお絵かき体験の共有を実現している。(iOS 17から可能になったSharePlayでのファイル送信を利用した、画像追加機能もある)
「2台のiPhoneを近づけてSharePlay開始」の実装
「2台のiPhoneを近づけてSharePlay開始」には特別な実装は必要ない。普通にSharePlayに対応すれば、2台のiPhoneを近づけた際にSharePlay開始がトリガーされる。
以下、サンプルがどういう実装をしているのかコードを読んだメモ。
「動画視聴体験の共有」の実装
日本のiOSエンジニアにおけるSharePlay伝道師である @tokorom さんが公開しているサンプルが参考になる:
「動画視聴体験の共有」における実装の肝はここ:
playerViewController?.player?.playbackCoordinator.coordinateWithSession(session)
解説すると、AVPlayer
が AVPlayerPlaybackCoordinator
型の playbackCoordinator
プロパティを持っていて、
var playbackCoordinator: AVPlayerPlaybackCoordinator { get }
その AVPlayerPlaybackCoordinator
の coordinateWithSession(_:)
メソッドを呼ぶことでグループ視聴セッションを開始している。
coordinateWithSession(_:)
メソッドの定義はこちら:
func coordinateWithSession<T>(_ session: GroupSession<T>) where T : GroupActivity
Begins coordination of a player with a group session.
(グループセッションとプレーヤーの調整を開始します。)
「カスタムな体験の共有」の実装
以下はDrawTogetherサンプルのコードを読んだメモ。
お絵かきの実装
Canvas
という ObservableObject
に準拠するクラスで、Stroke
の配列やアクティブなストロークを管理している
class Canvas: ObservableObject {
@Published var strokes = [Stroke]()
@Published var activeStroke: Stroke?
...
let strokeColor = Stroke.Color.random
...
func addPointToActiveStroke(_ point: CGPoint) {
let stroke: Stroke
if let activeStroke = activeStroke {
stroke = activeStroke
} else {
stroke = Stroke(color: strokeColor)
activeStroke = stroke
}
stroke.points.append(point)
if let messenger = messenger {
Task {
try? await messenger.send(UpsertStrokeMessage(id: stroke.id, color: stroke.color, point: point))
}
}
}
func finishStroke() {
guard let activeStroke = activeStroke else {
return
}
strokes.append(activeStroke)
self.activeStroke = nil
}
Stroke
は StrokeView
を用いてSwiftUIビューとして描画できる
struct StrokeView: View {
@ObservedObject var stroke: Stroke
var body: some View {
stroke.path
.stroke(stroke.color.uiColor, style: StrokeStyle(lineWidth: 3, lineCap: .round, lineJoin: .round))
}
}
実際の画面への描画は、CanvasView
で StrokeView
を描画している
ドラッグジェスチャーもここでハンドルしている
struct CanvasView: View {
@ObservedObject var canvas: Canvas
var body: some View {
GeometryReader { _ in
ForEach(canvas.strokes) { stroke in
StrokeView(stroke: stroke)
}
...
if let activeStroke = canvas.activeStroke {
StrokeView(stroke: activeStroke)
}
}
...
}
var strokeGesture: some Gesture {
DragGesture()
.onChanged { value in
canvas.addPointToActiveStroke(value.location)
}
.onEnded { value in
canvas.addPointToActiveStroke(value.location)
canvas.finishStroke()
}
}
}
SharePlayを用いたお絵かき同期の実装
Canvas
クラスから、SharePlayを利用してお絵描きを同期している部分のコードを抜粋(簡潔にするため、画像を利用する機能まわりは割愛):
アクティブな参加者が増えたら現状の描画情報を送信
groupSession.$activeParticipants
.sink { activeParticipants in
let newParticipants = activeParticipants.subtracting(groupSession.activeParticipants)
Task {
try? await messenger.send(CanvasMessage(strokes: self.strokes, pointCount: self.pointCount), to: .only(newParticipants))
}
}
.store(in: &subscriptions)
UpsertStrokeMessage
を受信
他の参加者のストロークを自身のキャンバスに反映
for await (message, _) in messenger.messages(of: UpsertStrokeMessage.self) {
handle(message)
}
func handle(_ message: UpsertStrokeMessage) {
if let stroke = strokes.first(where: { $0.id == message.id }) {
stroke.points.append(message.point)
} else {
let stroke = Stroke(id: message.id, color: message.color)
stroke.points.append(message.point)
strokes.append(stroke)
}
}
CanvasMessage
を受信
他の参加者のキャンバス情報を自身のキャンバスに反映
for await (message, _) in messenger.messages(of: CanvasMessage.self) {
handle(message)
}
func handle(_ message: CanvasMessage) {
guard message.pointCount > self.pointCount else { return }
self.strokes = message.strokes
}
おまけ
SharePlayを初めて使ったときに「わかりにくい!」と思った部分をツリーに書き残しておきました:
コードも読んだ今となっては「グループセッションへの参加」と「アクティビティの開始」の2段階あるんだなとかわかるけど、一般ユーザーに浸透するのはまだ厳しいかもしれない...
Discussion