初识 Group Activities
Meet Group Activities
2021年6月7日
一句话判断
Group Activities(即 SharePlay)是 Apple 对”一起看视频、一起听歌”场景的系统级解决方案——如果你做媒体类 App,不集成 SharePlay 就等于把社交场景拱手让给竞争对手。
这场 Session 讲了什么
Session 介绍了 Group Activities 框架(对应消费者端的 SharePlay 功能)的技术架构和 API 设计。Group Activities 允许多个用户在 FaceTime 通话中共享 App 内的体验——同步观看视频、同步听音乐、或者共享自定义的交互界面。
框架的核心是三个概念:GroupActivity(定义一个可共享的活动,比如”一起看电影”)、GroupSession(活动的实例,包含参与者和状态信息)、GroupSessionMessenger(参与者之间的消息通道)。Session 展示了完整的 SharePlay 流程:发起者在 FaceTime 通话中启动一个 GroupActivity -> 其他参与者收到邀请 -> 接受后进入 GroupSession -> 所有人的播放状态自动同步。底层通信由 Apple 的 FaceTime 基础设施处理,开发者不需要自己搭建信令服务器。
值得深挖的点
同步机制:谁控制播放进度
GroupSession 中的播放同步是自动的——发起者(发起活动的人)是”主机”,其他人是”从机”。主机播放/暂停/跳转进度时,所有从机自动同步。但如果任何从机做了一次播放操作(比如暂停),这个操作也会广播给所有参与者。Session 建议为你的 GroupActivity 明确一个”leader”角色——在视频场景中通常是发起者,在协作场景中可以动态切换。
GroupSessionMessenger 的消息模型
GroupSessionMessenger 支持两种消息模式:send() 广播给所有人,send(_:to:) 发送给指定参与者。消息类型必须是 Codable。重要细节:消息不保证送达——如果一个参与者网络中断,他恢复后不会收到离线期间的消息。你的 App 需要自己处理”重新同步”逻辑——参与者重连时请求当前的播放状态。
代码片段
// 定义一个 GroupActivity
struct WatchTogether: GroupActivity {
// 活动的元数据——显示在邀请卡片上
var metadata: GroupActivityMetadata {
GroupActivityMetadata()
.$title.set("一起看电影")
.$subtitle.set(movie.title)
.$previewImageUrl.set(movie.posterURL)
.$type.set(.watchTogether)
}
let movie: Movie
}
// 发起 SharePlay 会话
class MoviePlayerViewModel: ObservableObject {
@Published var isEligibleForGroupSession = false
private var session: GroupSession<WatchTogether>?
private var messenger: GroupSessionMessenger?
func startSharing(movie: Movie) async {
let activity = WatchTogether(movie: movie)
do {
// 发起活动,触发系统邀请流程
_ = try await activity.activate()
} catch {
print("启动 SharePlay 失败: \(error)")
}
}
// 监听会话创建
func configureGroupSession() async {
for await session in WatchTogether.sessions() {
self.session = session
self.messenger = GroupSessionMessenger(session: session)
// 监听参与者的播放状态消息
Task {
for await (message, _) in session.messages(of: PlaybackMessage.self) {
handlePlaybackMessage(message)
}
}
// 监听参与者变化
session.$activeParticipants.sink { participants in
print("当前参与者: \(participants.count) 人")
}
session.join()
}
}
}
// 同步播放状态的消息类型
enum PlaybackMessage: Codable {
case play
case pause
case seek(to: Double)
case changeMovie(movieID: String)
}
// 广播播放操作给所有参与者
func broadcastPlayback(_ action: PlaybackMessage) async {
guard let messenger = messenger else { return }
do {
try await messenger.send(action)
} catch {
print("发送消息失败: \(error)")
}
}
// 处理收到的播放同步消息
func handlePlaybackMessage(_ message: PlaybackMessage) {
switch message {
case .play:
player.play()
case .pause:
player.pause()
case .seek(let time):
player.seek(to: CMTime(seconds: time, preferredTimescale: 600))
case .changeMovie(let movieID):
// 所有人切换到新电影
loadMovie(id: movieID)
}
}
// 配置 AVPlayer 支持 Group Activities
func setupPlayerForSharePlay(player: AVPlayer) {
// AVPlayer 在 GroupSession 中自动处理播放同步
// 只需要设置 coordinatedPlaybackPolicy
player.coordinatedPlaybackPolicy = .groupSession
// 监听等待状态——所有人准备就绪后才开始播放
player.publisher(for: \.reasonForWaitingToPlay)
.sink { reason in
if reason == .coordinatedPlaybackNotEstablished {
print("等待其他参与者准备就绪...")
}
}
}
最佳实践
- 活动元数据(metadata)中的
previewImageUrl使用高质量的海报图——这是用户决定是否加入活动的唯一视觉线索。 - 消息频率控制:视频 seek 操作做防抖处理(用户拖动进度条时不要每帧都广播),使用 500ms 的节流(throttle)避免消息风暴。
- 为不支持 SharePlay 的设备(iOS 14 及以下)提供降级方案——比如”分享链接”让用户在浏览器中观看。
还有什么值得关注
- Group Activities 框架支持自定义活动(不限于媒体播放),可以用来做协作编辑、白板共享等场景。
- FaceTime 通话中的屏幕共享(Screen Sharing)和 Group Activities 可以同时使用——参与者可以边看视频边分享自己的屏幕。
- Group Activities 的 SDK 支持 iOS、iPadOS、macOS 和 tvOS,一个活动可以跨设备(比如 iPhone 和 Apple TV 同时播放)。