Highlight
Apple 在 iOS 15 中用 Group Activities 把 FaceTime 的低延迟通信能力开放给 App,开发者可以通过屏幕共享、同步媒体播放和实时消息,把原本面向单人的功能改造成 SharePlay 共享体验。
核心内容
很多 App 默认只服务一个人。
用户看房源、挑电影、翻照片、学 Swift 时,真实场景经常有另一个人在旁边。远程通话让这件事变麻烦。一个人只能口头描述自己看到什么,另一个人需要猜。媒体 App 还会遇到更具体的问题:一个人暂停了,另一个人的视频继续播放;一个人拖动进度条,其他人还停在原来的时间点。
Apple 在这场 session 里提出了一个设计起点:先找出 App 里用户本来就想一起做的事情。很多页面只适合个人浏览。适合共享的是那些能引发讨论、协作或共同娱乐的活动,比如一起看节目、一起听歌、一起看照片、一起学习。
找到活动以后,下一步是增强共享体验。Group Activities(群组活动)框架把 App 接入 SharePlay。它提供消息能力,也提供媒体播放同步能力。底层使用 FaceTime 的低延迟通信基础设施,开发者不用自己为通话参与者搭一套实时连接系统。
这场演讲把设计方案拆成三个入口:屏幕共享、同步媒体播放、自定义 UI。屏幕共享适合让对方直接看到你的 App。同步媒体播放适合受保护的音乐和电影内容。自定义 UI 适合协作画板、游戏、教学这类非媒体体验。
最后一个重点是上下文。用户在 FaceTime 通话中打开 App,本质上是在一边聊天一边操作。活动预览要写清楚大家即将加入什么内容。进入活动前的注册、订阅、购买和表单都要尽量推迟或自动化。朋友正在等你加入,共享体验不能被长流程卡住。
详细内容
屏幕共享:先处理哪些内容可以被看见
(02:41)FaceTime 通话中,用户可以共享屏幕,也可以自动共享你的 App。系统默认共享大部分界面,安全输入字段除外。演讲提醒开发者检查哪些 UI 适合出现在通话里,并在 iOS 上使用 UIKit API 进一步限制可见性。
import UIKit
final class AccountViewController: UIViewController {
@IBOutlet private weak var passwordField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
passwordField.isSecureTextEntry = true
}
}
关键点:
import UIKit引入 UIKit(用户界面框架),这是 session 中提到用于限制屏幕共享可见性的框架入口。AccountViewController是一个普通的 iOS 页面,代表可能被屏幕共享展示的 App 界面。passwordField对应用户输入敏感内容的文本框。viewDidLoad()在页面加载后配置输入框。isSecureTextEntry = true把文本框标记为安全输入字段;session 明确说明安全输入字段默认不会被屏幕共享。
这个设计点很实际。房产 App 可以共享房源照片和地图,但不应该共享账号资料。购物 App 可以共享商品页,但不应该共享支付信息。进入 SharePlay 之前,先把这些边界想清楚。
同步媒体播放:不要把视频流从一台设备传到另一台设备
(03:09)屏幕共享会自动共享音频,但受保护的音乐和电影不会通过屏幕共享传输。媒体 App 需要实现 Group Activities,使用协调媒体播放(coordinated media playback)让每个人的设备同时播放同一内容,并同步播放速率变化和 seek 操作。
import GroupActivities
import AVFoundation
struct WatchTogetherActivity: GroupActivity {
var metadata: GroupActivityMetadata {
var metadata = GroupActivityMetadata()
metadata.title = "Watch Together"
metadata.type = .watchTogether
return metadata
}
}
关键点:
import GroupActivities引入 Group Activities 框架,session 开头说明它负责把 App 接入 SharePlay。import AVFoundation引入媒体播放框架;session 讨论的同步媒体播放与媒体播放能力相关。WatchTogetherActivity定义一个可共享活动,代表“一起观看”这个场景。metadata提供活动预览需要的基础信息,session 后半段强调预览要给参与者上下文。metadata.title是参与者看到的活动标题,应该写成能说明当前动作的内容。metadata.type = .watchTogether标记这是一起观看类活动,和 session 中“watch a movie with a distant relative”的场景一致。
这里的关键事实是:SharePlay 不把媒体从发起人的设备串流给其他人。每台设备本地播放自己的媒体,Group Activities 负责同步开始时间、播放速率和进度跳转。智能音量控制和同步由系统处理,seek、倍速和播放控件的具体体验由 App 决定。
Messenger:给非媒体体验发送实时数据
(04:02)如果 App 不属于媒体播放类型,Group Activities 仍然可用。session 提到 Messenger protocol 可以把数据广播给通话中的其他 App,接近实时,传输过程和 FaceTime 通话一样是私密且端到端加密的。
import GroupActivities
struct DrawingActivity: GroupActivity {
var metadata: GroupActivityMetadata {
var metadata = GroupActivityMetadata()
metadata.title = "Shared Drawing"
metadata.type = .generic
return metadata
}
}
struct DrawingMessage: Codable {
let x: Double
let y: Double
let colorName: String
}
关键点:
DrawingActivity定义一个共享绘图活动,对应 session 中提到的自定义视图和 “Hello World” 绘图示例。metadata.title给活动预览提供标题,让参与者知道这是共享绘图。metadata.type = .generic表示这是通用共享活动,适合媒体播放以外的活动。DrawingMessage是准备在参与者之间传递的数据模型。Codable让消息可以被编码和解码,适合通过消息通道传递结构化数据。x、y、colorName描述一次绘制动作所需的最小数据。
自定义 UI 的设计目标应避免把整个界面截图发出去。更好的方式是发送动作数据。画板发送笔画坐标,小游戏发送玩家动作,教学 App 发送当前题目和选择结果。每个人的 App 在本地渲染同一份状态。
活动预览:用标题、字幕和缩略图解释大家要加入什么
(05:57)当 App 或内容被共享后,activity preview(活动预览)会告诉其他人发起者建议他们观看什么。session 建议把它当成 rich link(富链接)设计,使用标题、字幕和缩略图描述当前体验。
import GroupActivities
struct EpisodeActivity: GroupActivity {
let episodeTitle: String
let showTitle: String
var metadata: GroupActivityMetadata {
var metadata = GroupActivityMetadata()
metadata.title = episodeTitle
metadata.subtitle = showTitle
metadata.type = .watchTogether
return metadata
}
}
关键点:
EpisodeActivity把一次共享活动绑定到具体剧集。episodeTitle对应预览里的标题,告诉参与者马上要看的内容。showTitle对应预览里的字幕,补充节目来源。GroupActivityMetadata()创建活动元数据。metadata.title和metadata.subtitle对应 session 中提到的 title 和 subtitle。metadata.type = .watchTogether说明这是一起观看内容。
一个坏的预览只写“加入活动”。用户不知道朋友要他看电影、听歌,还是打开一个房源页面。一个好的预览写清楚内容和动作,比如“观看 Foundation 第 1 集”或“查看 Nashville 房源”。通话里的人正在聊天,预览必须减少解释成本。
自动进入:把加入活动前的步骤降到最低
(06:42)session 提醒开发者:用户在 SharePlay 中操作手机时是在多任务。App 应该自动化尽可能多的步骤,减少进入活动前的交互。支持 Picture in Picture(画中画)后,App 可以从后台自动启动到每个人的设备上。
import AVKit
let playerViewController = AVPlayerViewController()
playerViewController.allowsPictureInPicturePlayback = true
playerViewController.canStartPictureInPictureAutomaticallyFromInline = true
关键点:
import AVKit引入 AVKit(媒体播放界面框架),画中画支持由播放界面承载。AVPlayerViewController()创建系统播放器界面。allowsPictureInPicturePlayback = true允许播放器进入画中画。canStartPictureInPictureAutomaticallyFromInline = true允许内嵌播放自动进入画中画。- session 明确把 Picture in Picture 支持作为自动从后台启动体验的路径。
如果加入前必须注册、订阅或购买,session 建议通过 Group Activities API 告诉系统 App 需要前台展示。用户点击横幅后,App 被带到前台完成必要操作。这个能力要谨慎使用。朋友在等,长表单会破坏共同体验。
核心启发
-
做什么:给视频 App 增加“一起看本集”按钮。
- 为什么值得做:Group Activities 可以同步每个参与者设备上的播放开始、暂停、倍速变化和 seek。
- 怎么开始:为剧集定义
GroupActivity,用GroupActivityMetadata填写剧集标题、节目名和活动类型,再接入协调媒体播放。
-
做什么:给相册 App 增加 FaceTime 共享旅行相册模式。
- 为什么值得做:屏幕共享能让其他人看到同一组照片,适合边聊天边讲照片背后的故事。
- 怎么开始:先梳理相册页中可共享和不可共享的区域,把账号、位置隐私和安全输入字段排除,再为共享入口提供清晰提示。
-
做什么:做一个共享白板或涂鸦小游戏。
- 为什么值得做:Messenger protocol 可以在通话参与者之间广播自定义数据,适合传递笔画、坐标和玩家动作。
- 怎么开始:定义
Codable消息模型,例如坐标、颜色和动作类型;每个客户端收到消息后在本地更新画布。
-
做什么:给房产 App 增加“一起看房源”模式。
- 为什么值得做:session 用 Nashville 房源举例,说明远程讨论信息密集页面时,屏幕共享比口头描述更直接。
- 怎么开始:把房源图片、地图和价格作为共享核心;把联系信息、账号页和支付流程留在非共享路径。
-
做什么:把学习 App 的课程页面改造成通话里的共同练习。
- 为什么值得做:Group Activities 适合把学习内容和当前讨论关联起来,让参与者看到同一道题或同一段代码。
- 怎么开始:用活动预览写清课程名和练习标题;用消息同步当前题号、选择结果和讲解进度。
关联 Session
- Meet Group Activities — 从架构层介绍 Group Activities、GroupSession 和 SharePlay 的基础接入方式。
- Build custom experiences with Group Activities — 演示用 GroupSessionMessenger 构建实时共享画布。
- Coordinate media playback in Safari with Group Activities — 讲解网页和配套 App 如何接入 SharePlay 媒体播放。
- Deliver a great playback experience on tvOS — 补充媒体播放界面和控制体验的设计细节。
评论
GitHub Issues · utterances