Highlight
Safari 15 通过 Media Session API 和 Coordinator 扩展,把 SharePlay 的同步播放能力带到网页,让 Mac 用户无需安装 App Store 应用,也能在 FaceTime 中加入同一段媒体播放。
核心内容
你已经有一个 iPhone 视频应用。用户可以在 FaceTime 里一起看电影预告片,因为应用已经接入 Group Activities。
问题出在 Mac 用户身上。
你的服务也有网站。Mac 用户打开 Safari 就能看同一段内容,但以前网页只负责本地播放。它不知道 FaceTime 里还有其他人,也不能把播放、暂停、拖动进度这些动作同步给其他设备。
这会让体验断开。iPhone 上的人发起 Watch Together,Mac 上的人还要去找网页、找片源、手动对齐播放时间。
Safari 15 给网页补上了这段链路。
第一步,原生 App 在 Group Activity 的 metadata 里提供 fallbackURL。这个 URL 指向网站上的具体内容。Mac 用户接受邀请后,Safari 会打开这个 URL。
第二步,网页接入 Media Session API(媒体会话 API)。它把当前媒体的标题、封面、播放状态、进度告诉 Safari,也把播放、暂停、跳转这些系统控制交回网页处理。
第三步,Safari 在 navigator.mediaSession 上增加 Coordinator(协调器)。网页通过它加入 SharePlay 会话,把本地用户的播放意图发给其他设备,并接收其他设备的播放意图。
这样,一个从 iPhone 发起的共享播放,可以落到 Safari 网页。网页仍然播放自己的 <video>,但播放命令由 Coordinator 协调。
详细内容
为 Safari 准备可打开的内容 URL
(02:50)
Safari 不能凭空加入一次 Group Activities 会话。原生 App 发起共享时,需要在 activity metadata 里提供 fallbackURL。
这个 URL 不能只指向网站首页。它必须能定位到要播放的具体内容。演讲里的例子是电影预告片网站,URL 中带着 contentIdentifier。
import Foundation
import GroupActivities
struct WatchTogether: GroupActivity {
var contentIdentifier: String
func metadata() async -> GroupActivityMetadata {
var metadata = GroupActivityMetadata()
metadata.fallbackURL = URL(string: "https://example.com/title/\(contentIdentifier)")
return metadata
}
}
关键点:
import GroupActivities引入 SharePlay 背后的框架。WatchTogether遵守GroupActivity,表示一次可共享的观看活动。contentIdentifier保存当前媒体内容的标识。metadata()返回这次共享活动的元数据。fallbackURL指向网站上的具体内容页。- Mac 用户接受邀请后,Safari 会加载这个 URL。
这一步解决的是入口问题。Safari 能打开正确网页后,网页还要让浏览器理解当前正在播放什么。
用 Media Session 暴露播放状态和系统控制
(05:29)
Media Session 是标准 Web API。网页用它告诉浏览器当前媒体的标题、封面、播放状态、时长和进度。
Safari 拿到这些信息后,可以把它们交给系统的 Now Playing。用户在 macOS 菜单栏里点播放或暂停时,Safari 会调用网页注册的 handler。
const video = document.querySelector('video');
const myPlayer = {
titleString: 'The Mosquito Coast — Trailer',
artworkURL: '/artwork/trailer.jpg'
};
if (navigator.mediaSession) {
navigator.mediaSession.setActionHandler('play', () => video.play());
navigator.mediaSession.setActionHandler('pause', () => video.pause());
navigator.mediaSession.setActionHandler('seekto', details => {
video.currentTime = details.seekTime;
});
}
let updateMediaSessionState = function() {
if (!navigator.mediaSession) {
return;
}
let playbackState = video.paused ? 'paused' : 'playing';
navigator.mediaSession.playbackState = playbackState;
let positionState = {
duration: video.duration,
playbackRate: video.playbackRate,
position: video.currentTime
};
navigator.mediaSession.setPositionState(positionState);
};
for (let event of ['playing', 'pause', 'durationchange', 'ratechange', 'timeupdate']) {
video.addEventListener(event, updateMediaSessionState);
}
if (navigator.mediaSession) {
navigator.mediaSession.metadata = new MediaMetadata({
title: myPlayer.titleString,
artwork: [{ src: myPlayer.artworkURL }]
});
}
关键点:
document.querySelector('video')找到页面里的视频元素。navigator.mediaSession用来判断浏览器是否支持 Media Session。setActionHandler('play', ...)把系统播放按钮连接到video.play()。setActionHandler('pause', ...)把系统暂停按钮连接到video.pause()。setActionHandler('seekto', ...)接收系统传入的跳转时间,并更新video.currentTime。playbackState把网页的播放或暂停状态同步给浏览器。setPositionState()把时长、播放速度、当前位置同步给浏览器。MediaMetadata提供标题和封面,供 Now Playing 展示。
这一步让网页能被系统控制。接下来还要把这些控制扩展到同一个 SharePlay 会话里的其他设备。
用 Coordinator 加入 SharePlay 同步播放
(09:32)
Safari 15 在 Media Session 上增加了 coordinator。网页通过它加入现有会话,发送播放意图,并接收其他设备的播放状态变化。
演讲强调了三个限制。
第一,Coordinator 还是 experimental web API,接口可能变化。第二,当时只有 Safari 实现。第三,网页可以加入现有会话,但 GroupSession 需要从 iPhone、iPad 或 macOS 应用发起。
const video = document.querySelector('video');
const controls = {
inSessionIcon: document.querySelector('#in-session-icon'),
playButton: document.querySelector('#play-button'),
pauseButton: document.querySelector('#pause-button'),
timeline: document.querySelector('#timeline')
};
navigator.mediaSession.addEventListener('coordinatorchange', () => {
let coordinator = navigator.mediaSession.coordinator;
if (coordinator) {
coordinator.join();
}
controls.inSessionIcon.hidden = !coordinator;
});
controls.inSessionIcon.addEventListener('click', () => {
let coordinator = navigator.mediaSession.coordinator;
if (coordinator && coordinator.state === 'joined') {
coordinator.leave();
}
});
controls.playButton.addEventListener('click', () => {
let coordinator = navigator.mediaSession.coordinator;
if (coordinator) {
coordinator.play();
} else {
video.play();
}
});
controls.pauseButton.addEventListener('click', () => {
let coordinator = navigator.mediaSession.coordinator;
if (coordinator) {
coordinator.pause();
} else {
video.pause();
}
});
controls.timeline.addEventListener('change', event => {
let coordinator = navigator.mediaSession.coordinator;
if (coordinator) {
coordinator.seekTo(event.target.value);
} else {
video.currentTime = event.target.value;
}
});
关键点:
coordinatorchange在 Coordinator 可用状态变化时触发。navigator.mediaSession.coordinator取到当前协调器。coordinator.join()让网页加入这次共享播放会话。controls.inSessionIcon.hidden根据是否处在会话中显示状态图标。- 点击会话图标时,如果状态是
joined,调用leave()退出。 - 播放按钮优先调用
coordinator.play(),由 Coordinator 通知所有设备开始播放。 - 暂停按钮优先调用
coordinator.pause(),由 Coordinator 通知所有设备暂停。 - 进度条变化时优先调用
coordinator.seekTo(),把跳转意图同步给会话。 - 没有 Coordinator 时,代码回退到本地
video控制。
Coordinator 会和会话里的其他设备通信,处理播放状态变化,解决冲突,并等待所有人准备好。准备完成后,它会调用前面注册的 Media Session action handler 来启动或暂停本地播放。
最小接入顺序
(07:32)
这场 session 的接入路径很短,但顺序不能乱。
先让原生 App 发出带 fallbackURL 的共享活动。再让网页用 Media Session 向 Safari 暴露状态和控制。最后用 Coordinator 把网页按钮改造成同步播放按钮。
function playFromControl() {
let coordinator = navigator.mediaSession && navigator.mediaSession.coordinator;
if (coordinator) {
coordinator.play();
return;
}
video.play();
}
关键点:
navigator.mediaSession && navigator.mediaSession.coordinator同时检查 Media Session 和 Coordinator。- 有 Coordinator 时,用户的播放动作进入共享会话。
- 没有 Coordinator 时,播放动作只影响本地网页。
- 这个分支让同一套网页控制同时服务普通播放和 SharePlay 播放。
核心启发
1. 给视频 App 增加网页端 SharePlay 入口
- 做什么:用户在 iPhone App 里发起一起看,Mac 用户接受邀请后直接进入 Safari 播放同一集内容。
- 为什么值得做:session 里的
fallbackURL和 Coordinator 让网页可以加入原生 App 发起的会话。 - 怎么开始:在
GroupActivityMetadata设置指向内容详情页的fallbackURL,网页端接入navigator.mediaSession和navigator.mediaSession.coordinator。
2. 做一个预告片共看片单
- 做什么:朋友在 FaceTime 中一起看电影预告片,一方拖动进度或暂停,其他人的画面同步变化。
- 为什么值得做:Coordinator 支持
play()、pause()、seekTo()这类播放意图同步。 - 怎么开始:先把每个预告片做成稳定 URL,再在网页播放器的按钮事件里优先调用 coordinator 方法。
3. 给音频网站补上 Now Playing 控制
- 做什么:播客或音乐网页在 macOS 菜单栏的 Now Playing 中显示标题、封面,并响应播放暂停。
- 为什么值得做:Media Session 可以暴露 playback state、position state 和 metadata。
- 怎么开始:为
play、pause、seekto注册setActionHandler(),监听音频元素事件后调用setPositionState()。
4. 给网页播放器加 SharePlay 状态提示
- 做什么:播放器进入共享会话后显示一个会话图标,用户点击后退出会话。
- 为什么值得做:session 里的例子用
coordinatorchange控制图标显示,并用leave()退出。 - 怎么开始:监听
navigator.mediaSession的coordinatorchange事件,根据coordinator是否存在切换 UI。
5. 同一套播放器同时支持本地播放和共享播放
- 做什么:普通用户点击播放时本地播放,SharePlay 用户点击播放时同步给所有人。
- 为什么值得做:网页可以在事件 handler 里检查
navigator.mediaSession.coordinator,按会话状态选择控制路径。 - 怎么开始:把播放、暂停、进度条事件统一封装,分支里优先调用
coordinator.play()、coordinator.pause()、coordinator.seekTo(),否则操作<video>。
关联 Session
- Meet Group Activities — 从框架层面理解 SharePlay、GroupActivity 和共享体验的基本模型。
- Design for Group Activities — 学习如何把应用流程设计成 FaceTime 中的多人共享体验。
- Build custom experiences with Group Activities — 用 GroupSessionMessenger 构建媒体同步之外的自定义实时协作。
- Coordinate media experiences with Group Activities — 在原生媒体 App 中用 Group Activities 和 AVFoundation 协调播放。
- Design for Safari 15 — 了解 Safari 15 的设计和 Web 能力变化,为网页端体验做适配。
评论
GitHub Issues · utterances