大多数浏览器和
Developer App 均支持流媒体播放。
-
使用群组活动协调媒体体验
探索如何帮助人们通过 SharePlay 和群组活动框架同步观看或收听内容。我们将展示如何将媒体 app 调整为多人同步的、启用 SharePlay 的体验。了解如何将群组活动添加到您的 app,探索画中画布局,并了解播放协调器对象如何帮助在多个设备上微调播放。
资源
相关视频
WWDC22
WWDC21
-
下载
大家好 欢迎观看本视频 “用 Group Activities 协调媒体体验” 我是海登 是Apple的Group Activities 团队的一名工程师 今天我们聊一聊 如何创建同步的媒体APP 让多个用户可以在多个设备上 看到和听到同步的内容 我们的目标是 让用户感觉上无论在哪里 媒体播放都是同步的 GroupActivities API是Swift下的框架 用于创建共享体验 它通过回放协调和分组会话管理API 来为您协调媒体类内容 在很多Apple平台都支持它 例如iOS、iPadOS、 macOS以及tvOS 我们从一个GroupActivities API的演示开始吧 我会与我的同事莫里茨通话 然后与他通过样品App共享一段视频 我们看看莫里茨现在有没有空吧
你好 莫里茨 你好 海登 点击Home键 然后打开样品App 可以看到这是一条来自 Apple Park的无人机视频列表 我要给莫里茨共享这个大剧院的视频 看着怎么样 莫里茨 你想看吗 看起来很不错啊 我很喜欢这个视频 但是你看过我上周在彩虹拱门那里 新上传的那一个视频吗 哦 看起来真棒 我要播放一下 然后我们两方的设备上 会同步播放这些视频 可以看到如果我暂停 那么莫里茨那边也暂停了 若他往前拉进度条 我这边也跟着动
看起来真棒 好的 谢谢你 莫里茨 对 看起来棒极了 再见 海登 当创建一个 协调媒体回放的App时 您需要考虑三个主要问题 第一 新的 GroupActivities API的使用 第二 如何利用画中画 来提供尽可能无缝丝滑的共享体验 第三 我的同事莫里茨会为您深度解析 回放协调是如何工作的 以及新的回放协调对象的功能 我们先通过GroupActivities 应用程序的生命周期 来了解一下熟悉一下 GroupActivities API 现在 两台iPhone正在 进行FaceTime通话 就像第一个示例中一样 左边是我的设备 我的同事莫里茨的设备是右边那台 在我的设备上 我打开了共享App 然后我在App里向分组共享了一个活动 GroupActivities框架为该活动 创建了一个会话对象 然后发送给我的app 同时这个会话对象也分享给了莫里茨 在他的设备那边 框架句柄打开了同样的app 最后 框架将莫里茨的App 传递给GroupSession 现在 两个App可以 通过同一个GroupSession通讯了 现在我们看一下 用GroupActivities框架 需要经过哪些步骤 第一步是随着其元数据 创建一个GroupActivities 现在我们谈谈 GroupActivities是什么 以及如何定义它 GroupActivity是一种Swift协议 代表您的用户正一起共享的内容 它是一条单独的内容 例如一部电影或一首歌 在设置共享时所需的任何属性 都要被包含进这个类中 例如您可能想要 将视频的URL包含进来 这样就能为会话加载好一个视频 为了让框架可以通过网络发送数据 它要遵循Codable要求 也就是说您的属性也都 必须符合Codable要求 您会注意到这里有两个必需的属性 ActivityIdentifier 是一个独一无二的类型识别符 系统通过它来引用这项活动 GroupActivityMetadata包含了 在远程设备里的系统UI中 显示本活动的信息 这是一个新型的 异步的只读属性 今年才在Swift Concurrency 被引入使用 关于这部分的更多信息 请观看今年WWDC的 “Swift中的异步/等待” 现在我们定义了一个GroupActivity 然后通过调用 GroupActivity上的活动方法 将其分享给该通话 当您激活一个GroupActivity 框架会创建一个GroupSession对象 同时发送给本地和远程设备 这个系统负责启动App 而系统UI会显示活动的元数据 但是首先我们略过了一些事情 你的App如何知道 你正在FaceTime通话 还有 如果一个用户选择了一条内容 但是他并不是想分享给分组 而只是想本地观看的话呢 PrepareForActivation函数 会为您解决这些问题 它会将用户的意图告诉给系统 这里是一个 prepareForActivation API的例子 在开关的声明中 我们要处理三种情况 第一种activationDisabled 对应当用户没有处于FaceTime通话时 或系统认为他们是想在本地使用内容时 第二种是activationPreferred 当用户处于FaceTime通话 且希望在分组中共享内容时 而当用户取消了分享行为时 则对应Canceled 知道了这些就足够创建一个 GroupActivities app 来共享一项活动 我们打开Xcode 我实际操作给您看 在添加GroupActivities支持之前 我带您快速浏览一下这个工程 这是一个简单的看视频用的App 您可以选用一个视频然后观看 其包含了一个可看影片的列表 还有一个影片详情页面 上面有视频播放器 那么现在App只允许您自己观看影片 我们对此进行修改
您会注意到我们已经将分组活动 的entitlement设置好了 然后添加一个GroupActivity 到Movie.swift 第一件事是导入GroupActivities
然后添加GroupActivity 即MovieWatchingActivity 您会注意到它有一个movie属性 它会用这个属性来填写元数据 我们现在将活动进行共享 来到CoordinationManager选项卡 找到prepareToPlay函数 当前情况下 此函数会通过影片排队 来立刻启动回放 但这里我们转而 用之前幻灯片里展示的 prepareForActivation替换它
可以看到该影片 立刻在activationDisabled的 case下排队了
当用户想要在FaceTime中共享它时 则activate()被调用 稍后 我们会排队影片来启动回放 但此刻我们来看看 直接这样运行App会怎么样 我已经拿到了莫里茨的设备 并在这两台设备之间 开启了FaceTime 我要在我的手机上点home键到桌面 然后启动样品App 我会分享刚才我们看的 那条彩虹拱门视频 并在莫里茨的设备上也启动App 可以看到它收到了GroupActivity 但是加载的视频却不对 而且播放也不同步 那么现在我们学一下如何处理这个问题 接收Group Activity 主要要注意的是 弄懂GroupSession对象 以及收到GroupSession的方式 即GroupSessions的同步序列 这里的这张高阶图示 展示了典型GroupSession生命周期 本地和远程设备都收到一个分组会话 然后App应该为此准备好回放 最后 当一切就绪 就加入到分组会话中 GroupSession代表了 一个分组活动的设备之间 的实时对话的对象 能告诉我们该会话的状态 例如最近一次的分组活动 连接状态 以及连接到会话的活动参与者们 稍等一下我们就会看到 它还在播放的同步工作上起作用 GroupSession的AsyncSequence 会将GroupSessions发送到您的App 您的App不会直接 将GroupSession对象实例化 所以这是接收GroupSessions 的唯一方法 有一点很重要 那就是本地和远程设备都应该 从此序列获取最近的GroupSession AsyncSequence也是 早前提到的Swift Concurrency 的新特色之一 这就是GroupSessions 的AsyncSequence 的等待状态的代码 当一个GroupActivity被激活 系统会从AsyncSequence 返回一条GroupSession 到您的等待循环中 现在我们已经收到了 一条GroupSession 接着就来学一下 如何用它设置回放的同步 这是最新版本的AVFoundation 它引入了一个新的类型 称为AVPlaybackCoordinator 莫里茨随后会在视频中为您详细讲解 但是现在我先为您展示 如何将一个GroupSession 连接到播放协调器上 这样就可以实现两设备上同步的回放 要访问回放协调器 就需要AVPlayer上的 playbackCoordinator属性 接着要将GroupSession 连接到协调器 我们要调用coordinateWithSession 然后传入GroupSession 就是这样 在后台 框架会为您处理 所有播放协调和 实时网络涉及的繁杂过程 会话管理还有最后一步 那就是最后加入会话 初始状态下 GroupSession是未连接状态 而是处于“waiting”状态 调用join()将 GroupSession连接到分组 启动实时连接 允许来自分组中其他设备的信息收发 一旦GroupSession成功加入 回放同步就也开始了 现在我们将会话管理代码 添加到我们的样品App 在CoordinationManager中 我们将会话同步序列添加到等待循环
请记住 这样无论从本地还是远程设备 发起GroupSessions 您的app都能获得GroupSessions 现在将这个GroupSession 存储到CoordinatorManager 它会将任何变动 传给MoviePlayerViewController
设置好之后 我们将会话对象 连接到AVPlayer 方法是playbackCoordinator. coordinateWithSession
然后回到等待循环中 我们会从会话对象获得该影片
由于在会话中活动是会变化的 所以我们使用Combine发布者 来获取此活动 然后我们送一条影片 进入排队来启动回放 最后 我们加入会话 现在我们已经设置好了代码 用以接收GroupSession和同步回放 现在我们在设备上运行App 看看效果如何 两台设备再次进行FaceTime 通话 我会在第一台设备上打开App 然后分享彩虹拱门那条视频
可以看到莫里茨的设备收到了分组活动 那么我们启动App 这一次 两条设备上 都播放了正确的视频
然后 若我点击“播放” 可以看到两边的回放同步效果很好 若我在一台设备上暂停 另一台上也会暂停
或者我往前拉进度条 另一台设备也一起快进
回放处于完美的同步状态
关于GroupSession的 最后一件重要事情就是 会话如何结束 结束GroupSession有两种方法 第一种是leave()函数 它将本地用户从会话中断开连接 但是会话对于通话中的其余参与者 还是激活状态的 第二个方法是end() 这会直接结束会话 不仅针对本地参与者 而是整个通话都结束了 更多关于创建高级 GroupActivities的app的详情 请观看以下WWDC课程 “用分组活动构建自定义体验” 该课程讲述了 如何更改GroupSession的活动 监视GroupSession的状态 以及高级功能的用法 例如GroupSessionMessenger 它使您可以在分组中发送任意消息 我对此强烈推荐 现在我们换一换工具 看看如何利用画中画 使视频GroupActivity的共享体验 尽可能的丝滑无缝 为何对于GroupActivities类App 画中画值得考虑呢 因为支持画中画的话 内容在共享之后可以马上开始播放 因为这里不会将用户从当前情景剥离 所以不需要外部用户交互来启动回放 这就为用户省去了额外的步骤 获得了流畅丝滑的内容共享体验 关于设置画中画的更多信息 请观看2019 WWDC课程: “用AVKit提供流畅的媒体回放” 但是在GroupActivitities中 使用画中画还是有点细微区别 我们现在一一道来 GroupActivities框架会在后台 向您的媒体App发送会话 让App有机会设置画中画 若GroupSession告诉你 App可以在背景里开始播放 那么你就应该设置画中画 然后按照常规 GroupActivities流程进行 系统就会直接以画中画形式播放该内容 而非以全屏模式启动App 这样用户就获得了无缝流畅的体验 不需要被扯离当前的情景 但有些情况下 您无法设置画中画 因为可能您需要用户登录App 或该内容必须由用户进行一些操作 否则就不可用 这些情况下 GroupSession 会为您的App提供API 发出请求 要求跳回到前景 画中画的另一项方便之处是 它还会通过一个系统对话为你处理 GroupSession的离开和结束 这样就不用担心画中画激活后 如何离开和结束会话的问题 现在将镜头交给我的同事莫里茨 他为您深度讲解 回放协调器对象的问题
大家好 我叫莫里茨维滕哈根 是AVFoundation团队 的一名工程师 海登已为您介绍了 AVPlaybackCoordinator 它是一个使多个设备上 回放自动同步的对象 我们也在实例演示中 看到了它是如何工作的 那么接下来我将会揭秘 协调器到底在后台都做了什么 以及它如何与您的代码进行交互 大部分时间我会聚焦于 协调器如何与AVPlayer交互 您应如何为协调回放选择素材 单个参与者如何暂停这项协调工作 我还会带您简单了解一下 不使用AVPlayer时 如何实现这种协调 AVPlaybackCoordinator是一个对象 它可在设备之间共享播放状态 可以在那些设备之间协调播放的启动 目标是所有参与者都不会错过任何内容 协调器有2个子类 AVPlayerPlaybackCoordinator 在实践中 经常连到某个特定的AVPlayer 负责您所有的远程状态管理 所以它是协调回放的最简单方法 强烈建议您从这里开始 关于 AVDelegatingPlaybackCoordinator 我们不会说很多 但是这个子类使您 能灵活控制非AVPlayer的回放对象 现在我们再次把目光回到设备设置上 接下里的时间里 我们在整个GroupSession里 都使用GroupActivities对象 我们还假设您的UI 使用AVPlayer播放AVPlayerItem 新的AVPlayerPlaybackCoordinator 这时候就派上用场了 当您像海登之前演示的一样 用coordinateWithSession调用协调器 我们就有效的连接了这2个AVPlayer 然后开始相互影响 这里的基础规则是 协调器会拦截任何传输控制的API 也就是任何改变速率或当前时间的API 它接收到这些命令后 判断是否要与别人进行协调 然后在合适的时间 通过AVPlayer发挥作用 我们看个例子吧 这里海登和我 处于同一个GroupSession 因为GroupActivity告诉我们 要加载哪个URL 我们排队了同一个AVPlayerItem 现在若我的设备 改变了AVPlayer的速度属性 那么回放协调器会拦截这个命令 不会立刻允许播放器开始回放 相反地 它会要求播放器 进入timeControlStatus 的WaitingToPlayAtSpecifiedRate 状态 UI一般会将此timeControlStatus 以一个等待spinner的形式代替 协调器随即会将命令 发送给海登的iPad 那边的AVPlayerPlaybackCoordinator 收到了命令 会要求海登的AVPlayer 一样地改变速率 然后进入等待环节 协调器给每个人时间来为回放做准备 目标是所有人能同时开始回放 没人会错过任何内容 当每个人都准备就绪 所有的设备一同开始回放 会话中的所有协调器都是平等的 这意味着海登也可以发起一条命令 这次我们让他共享一下定位操作 同样地 时间点定位API也被拦截 协调器强制AVPlayer原地等待 同时它将命令共享给连接的其他协调器 每个人都得到时间去完成时间点定位 当所有设备一切就绪 所有人的回放也都同时恢复 您或会问自己 当AVPlayers播放不同的项目时 发生了什么 答案就是 协调器仅在两个播放器回放相同内容时 才将状态应用给另一个播放器 稍后我会详细讲解“相同内容”的含义 现在我们就姑且认为 从同一的URL创建item时 内容是相同的 这意味着您发送给内容A的任何命令 只有当收信方也在 播放内容A时才会起作用 若将内容改成了B 那么会忽略 所有来自内容A的状态 这样做是因为在到达内容之前 就对命令做出区分 使参与者能安全地加入 并在内容之间进行切换 我为您演示一下这是什么意思 还是我们这个例子 但是这一次 我们让海登的设备先处于播放状态 当我加入并连接 我的回放协调器到同一个会话 我的AVPlayer不会有任何变动 因为我此时并未播放 海登设备上的相同内容 即使创建相同内容也没有任何影响 因为此内容当下并未在我的播放器里 也就意味着我可以随意拉进度条 而不影响任何其他人 只有当此内容现在 已经在AVPlayer中播放 协调器才会开始工作 将正确的状态应用进来 此处的规则是 当存在分组状态时 协调器总是按照分组状态开始的 由于海登当时已经 在GroupSession中播放内容 对协调器来说 他的状态要优先于 我自己排队之前的配置 这意味着我的协调器 会覆盖AVPlayer和AVPlayerItem的配置 然后匹配到海登的设备 这样子我们就处于同样的状态 可以同步播放了 我们用内容切换再来一次这个过程 当我们都接近内容A的结尾 我的AVQueuePlayer做好了准备 去播放下一条内容 通常情况下 我们会觉得 海登那边也一样做好了准备 但在本例中 我们假设海登的iPad网络状态不好 下一条内容还未加载好 现在我已经播到了内容A的结尾 且我这边内容B已经排入队中 准备好接着播放了 但因为B并不是当前播放的内容 协调器现在还并未做任何事情 只有当我的播放器切换到新的内容 协调器才会再次开始工作 而这次它没有找到属于B的已存在状态 所以它将沿着 我的播放器提供的状态持续下去 尽管协调器将新状态共享给了海登 他也不会受影响 因为他仍在显示的是A内容 但当他那边终于 切换到我当前这个内容B后 他的协调器会再次将现有状态 应用到他的播放器 这样一来 所有人再次实现了同步播放 这里是我的代码 请对您的内容改变 和控制命令传输的顺序多加留心 这里我们有一个beginPlayback函数 它会将内容送去排队 并自动开始播放 每当用户选中某条内容来播放时 或当GroupSession通知说有新活动时 就会调用这段代码 有一点很重要 那就是要先统一开始时间 同样地 我们还要在内容排队之前 将播放器速度改变 这样我们的初始配置就不会影响别人 如果我们按这样顺序进行 那么 回放协调器就能判断我们是不是第一个 我们的状态是否应该共享给所有人 或是否别人有已存在的状态 应该用来覆盖我们的状态 还有 审核你的传输控制命令 思考它们是否应该影响分组中的每个人 通常情况下 一条API的调用 如果起源于回放UI 则应该影响分组 所以若该用户暂停了 每个人也都应暂停 这种情况下 照常调用AVPlayer API即可 那么对于 非来自回放UI的其他API调用 我们应该怎么做呢 通常情况下这些是自动的暂停 因为您的App进入了某种系统事件 这种的自动暂停 不会影响其他的参与者 在这种情况下 当您的用户与其他人一起播放时 您首先应该考虑如何让其他人不要暂停 由于当其他所有人都在继续播放 用户一般会倾向于跟上分组进度 即使会造成一点不完美 例如暂时不能看到内容等 但当您别无选择必须暂停时 此处有2种选择 或先移除该内容 或启动协调回放的暂停 稍后我会详细讲解这一种 现在您已了解了回放协调器如何工作 我们来讲一讲协调后回放的内容 我之前说过 我们姑且先认为 从同一URL创建的素材时 不同设备上的两条播放内容是一样的 这种前提下一切正常工作 但是也有时候您希望更改这种行为 例如 您的App或许 想给用户提供一个选择 将该内容下载和缓存到设备上 所以若我已将该素材下载到本地缓存 而海登还在从云端获取该内容 那么我们使用的 就不是来自同一URL的内容 意味着回放协调器 无法保证我们的回放状态同步 同样的 观众们有人或已经体验过 AVMutableMovies或 AVCompositions的状态协调 这时候根本就没有URL可言 回放协调器再一次不知道怎么处理了 要解决这个问题 您可以为AVPlayerItem 提供一串自定义字符串作为识别符 若有这个字符串 那么协调器会通过它 判断是否两条内容是一样的 并忽略URL信息 这就需要AVPlayerPlayback CoordinatorDelegate协议 和playbackCoordinator identifierFor playerItem函数 只要你将一条内容送入播放器排队 协调器就会向其委托方索要识别符 只要回放协调器判断两条内容是一样的 那么确保一台设备上的时间 与另一台的相匹配是很重要的 这就是说 对于服务器自动注入回放流中的内容 要十分小心 所以若海登和我向同一URL发起请求 但服务器决定 向我的数据流插入一条广告 那么当我播放广告时 海登还在播放正片内容 我们设备之间的同步就破坏了 解决这种问题的正确方法是 将广告以及其他插播广告 移到另一个播放器 这样正片素材就完好无损了 现在当我播放到广告的位置 在广告期间我的手机 切换到另一个不同的播放器 当广告播完之后 协调器可以再次轻松 与其他人的时间线同步 当您播放HLS内容时 AVPlayer新出的AVPlayer InterstitialEvent API也很有用 请观看HLS相关课程 以获取更多动态前贴和中贴的内容 总结一下 当URL不能提供正确信息时 请使用自定义的识别符 来确保内容的匹配 确保每个人的时间都是一样的 若您想播放个性化的片中广告 那么就在不同的播放器中播放 这样正片内容就不受影响了 最后 当协调直播内容时 可以使用数据标签 这样协调器就可以 与每个人共享正确的时间 到目前我们只考虑了完美回放的案例 这些情况下整个过程中 所有人都处于同步状态 不幸的是 这情况并不总是行得通 我们假设海登和我正在同步播放 然后闹铃响了 提醒我去为小猫佐罗喂食 根据AVAudioSession的规则 我的app会被暂停 即使我是在分组中播放 这些规则也依旧有效 但是我们不希望 我的暂停影响其他人 把每个人遇到的短暂停顿 共享给整个分组 会造成糟糕的使用体验 所以这里我们希望 海登的iPad继续播放 当我关掉我的闹钟后 回放也会跟上来 所有人又回到了同步状态 那么要是如何实现这样的行为呢 这里我们使用一个新的对象 AVCoordinatedPlaybackSuspension 这种暂停代表着 一个参与者的协调器 与另一参与者协调器的分离 该参与者从分组中分离下来 播放器的速度改变 或拉进度条都不再影响别人 同样地 任何来自分组的速度改变 也不会影响AVPlayer的速度或者时间 在示例中这意味着 当我的闹钟还响着 海登就无法启动我的播放器 暂停分为2种不同的种类 自动暂停和您自己添加的暂停 当播放器自动暂停时 AVPlayerPlaybackCoordinator 会添加自动暂停 我们已经看过了音频打断的例子 但是对于网络停滞 或通过AVPlayer 插播广告API来播放广告 引起的暂停也适用 当播放器恢复播放时 回放协调器添加的暂停也会结束 且协调器会使播放器将时间 与当前分组状态进行匹配 在本例中这意味着当闹钟取消后 打扰结束的通知 一旦被App接收并处理 那么我会自动的重新加入分组 下面两个例子为您展示 系统如何使用协调回放的暂停
这里在用样品app 播放彩虹拱门的无人机视频 我们看一下刚才讨论的遭遇打断的例子 我设定了一个闹钟 若干秒钟后就启动 我们现在等闹钟响
响了 闹钟令左边的iPad暂停播放 但是如您所见 另一台设备仍然播放的很欢快 当我取消闹钟 左侧设备的播放 直接快进到与另一台设备一样 它们再次同步 这不是系统使用暂停的 唯一方式 AVKit也利用暂停 来代替拉进度条时 快速掠过的随机帧 您或许已经注意到了 拉进度条时 只有我操作的那台设备 会显示即时帧画面 当我我按住进度条滑块 右侧设备继续正常播放 而左侧的设备 则显示的是滑块处的画面 只有当我放开滑块 新的播放时间点才会共享给另一台设备 所有人的播放恢复正常 这些就是系统利用暂停的实例 现在我们看看实践中如何使用 我们回到Xcode实际操作一下 假设您希望实现一个功能 让一个参与者 可以重播之前错过的内容 比如我们看的这条无人机视频里 有一处特别精彩的瞬间 而回想我们截至目前讲过的协调器行为 定位回去再播放某一片段 就会影响所有人 而且我想要强调的是 尽可能保持所有用户同步 这其实是非常好的事情 但是拉上所有人一起倒带看 这可不行 所以我们解决的方法是定位回去若干秒 然后按2倍速播放 直到我们追上所有人为止 我已经在UI中添加了一个按钮 用户可以用来回看错过的一些内容 它连接的是 MoviePlayerViewController 的这个函数 我们编写这里的代码
这里是播放器的逻辑 我们弄清楚定位到什么时间 然后定位过去 接着将播放器设定成2倍速 等追上其他人播放进度 再将播放速度恢复到1倍速 目前为止这对每个人都适用 因为回放协调器会拦截所有的API调用 这里需要用到暂停 当我们定位过去之后 我想要将我们的播放器与其他人的分离 要实现这一点 需要用协调器的 beginSuspension函数 它要求有一个原因 要提供这个原因 我们将Reason struct 扩展出一个新的字符常量
本例中是“what-happened” 现在我们可以在 beginSuspension调用中使用它了
由于协调器现在已经暂停 我们可以安全地 仅针对自己的播放器 进行时间点定位 设定速率 当我们准备好重新汇合其他人 我们需要给协调器发通知 让它再次听从分组的指挥 这里我们调用suspension.end() 请注意 这里我们不需要 再更改播放器速率 因为暂停结束后会自动加入其他人 我们的播放器速率 也会自动匹配当前分组的速率
现在我们来试一试
现在所有人都同步播放 我错过了一处精彩瞬间 例如逼近彩虹门的镜头 我点击新添加的 “what-happened”按钮 可以看到 我操作的设备 倒回去了几秒并重播了内容 且加快了播放速度去追上其他参与者 而另一台设备并不受影响 现在 我们有再次完美同步了 所以当您在AVPlaybackCoordinator上 开始一个暂停 就等于将它的播放器从分组分离出来 然后它就可以自由改变速率或定位时间 而不影响其他人 暂停结束后会从新加入分组 并匹配当前分组的时间和速度 另外如幻灯片所示 您还能选择在暂停结束后 为分组设定一个新的时间 这也就是如何实现 拉进度条时只有松开滑块后 才改变所有人的播放时间点 以下总结一下 何时AVPlayer传输控制命令 会与其他参与者共享 首先你必须将 AVPlayerPlaybackCoordinator 通过分组会话连接到其他参与者 就像本视频开始时海登为您演示的那样 第二 AVPlayer当前的AVPlayerItem 必须与其他参与者 播放器内容来自同一URL 或者若你提供了自己的识别符 那么他们必须有相同的自定义识别符 一旦一条内容进入了排队 所有的定位操作和速率改变 都会影响分组内每个人 除非当你启动了协调播放中的暂停 在结束AVPlayerPlaybackCoordinator 的讨论之前 我们快速串讲一下 与协调回放相关的其他API 若您希望其他参与者 在一个用户暂停时也进入等待 您可通过配置协调器的 suspensionReasons ThatTriggerWaiting属性 这样就算参与者的广告时长不一样 也能保证没有人错过任何内容 关于其他参与者状态的更多内容 请观看协调器的 otherParticipants属性 和其对应通知的相关课程 AVCoordinated PlaybackParticipant函数 最大的亮点就是 为您提供了suspensionReasons列表 这对于为UI提供信息是很有用的 尤其上面提到的suspension ReasonsThatTriggerWaiting 每当协调器请求其AVPlayer进入等待 都会在播放器的 reasonForWaitingToPlay的 waitingForCoordinatedPlayback 原因反映出来 要覆盖等待状态 且不管其他参与者的状态立刻开始回放 那么请使用 playImmediately(atRate)函数 请注意 这可能导致 其他参与者错过一些内容 所以使用此API时要牢记这一点 AVPlayer还引入了新函数 rateDidChangeNotification 为播放速率提供了更多信息 包括了是何时由另一个参与者引起它 同样地 AVPlayerItem的 TimeJumpedNotification函数 还会告诉你某次时间点的跳跃 是否由另一参与者发起 有一些AVPlayer API 不能使用 AVPlayerPlaybackCoordinator iOS上默认的time pitch算法 曾经是LowQualityZeroLatency iOS 15已不支持这个值了 协调回放不支持 LowQualityZeroLatency 所以您需要特别留心 防止代码被重置成这个现已废弃的值 请用别的算法代替 还有切记不要 在AVPlayerPlaybackCoordinator中 使用AVPlayer的 setRate(time:atHostTime:)函数 因为确保回放协调器 全权管理播放器时间 是至关重要的 它与外部的启动同步是不兼容的 讲了这么多AVPlayer的内容 我们快速进入 AVPlaybackCoordinator的第二个子类 即AVDelegatingPlaybackCoordinator 刚才讲讲过的很多内容 对于授权性回放协调器也依旧适用 只是授权协调器不再监视该播放器 而是直接要求你提供回放状态的信息 然后对回放对象应用该状态 自定义的回放对象设置看起来是这样的 您的UI控制着播放器 其皮下使用的是其他的系统渲染类API 而授权回放协调器 位于UI和播放器之间 就像名字所示一样 AVDelegatingPlaybackCoordinator 要求您实施一套授权协议 该协议可以接收回放命令来开始播放 暂停 时间点定位以及缓冲 这里UI不会直接向播放器发送播放命令 而是先通知协调器 然后让协调器判断是否需要 先与另一个连接中的播放器进行协商 然后再改变 您的回放对象的时间点或速率 您同样要告诉协调器 何时让播放器切换到下一条新内容 这样协调器就知道 把哪一条命令发送给您 和之前一样 播放内容 通过任意的字符串来识别 当协调器发送一条命令 它会影响发出方的UI 也会影响接收方的UI 当准备好可以播放时 其应以相同的方式影响所有的播放器 您要确保您的回放对象能完美跟上 且要根据命令 对UI进行合适的更新 我要特别提醒的是 授权协调器会经常要你缓冲 即使你的设备其实已经准备就绪 这意味着协调器还在 等待连接中的另一个参与者 所以即使您这边设备上没有什么能做的 也请在UI上反映出这一点 最后提一些警示点 请一定留心诸如路由变更之类的 会引其播放器短暂停顿与恢复的事件 您一定要确严格按照请求时间来 并根据需要重应用分组时间 若您由于一些原因无法遵循请求时间 您应通过暂停 来将这个通知给协调器 授权回放协调器 也不会添加任何自动暂停 这意味着当出现相关的系统事件 您应该自己启动和结束暂停 在需要的时候可以使用 AVFoundation提供的reason函数 要将一台设备的授权协调器连接到 另一设备的回放协调器是可以实现的 但是你必须在播放器 的回放协调器那一端 使用自定义的识别符 我们现在总结一下 构建一个可协调的媒体回放App 需要用GroupActivity 定义您要播放的内容 然后将其提交给分组 当App启动后 马上开始监视GroupSessions 这样当用户进入了FaceTime通话 且想共享播放时 你就马上能知道 最后 将回放协调器 连接到GroupSession 来保证您的内容 与所有人都同步 感谢观看 祝您WWDC过得愉快 [音乐]
-
-
3:06 - Define a GroupActivity
protocol GroupActivity: Codable { /// An identifier so the system knows how to reference this activity static var activityIdentifier: String { get } /// Information that the system uses to show this activity, such as title and a preview image var metadata: GroupActivityMetadata { get async } }
-
4:42 - Making your play buttons automatically start a group session when appropriate
func playButtonTapped() { let activity = MovieWatchingActivity(movie: movie) Task { switch await activity.prepareForActivation() { case .activationDisabled: // Playback coordination isn't active. Queue movie // for local playback. self.enqueuedMovie = movie case .activationPreferred: // Activate the activity. The system enqueues the movie // when the activity starts. activity.activate() case .cancelled: // The user cancelled the operation. Nothing to perform. break default: break } } }
-
8:31 - Receiving a GroupSession from the GroupSession AsyncSequence
// Receiving a GroupSession from the GroupSession AsyncSequence func listenForGroupSession() { Task { for await session in MovieWatchingActivity.sessions() { ... } } }
-
9:03 - Attaching an AVPlayer to the GroupSession
let player = AVPlayer() ... func listenForGroupSession() { Task { for await groupSession in MovieWatchingActivity.sessions() { // Verify content is available, prepare for playback to begin player.playbackCoordinator.coordinateWithSession(groupSession) ... } } }
-
31:26 - Custom suspensions
class AVPlaybackCoordinator { func beginSuspension(for reason: AVCoordinatedPlaybackSuspension.Reason) -> AVCoordinatedPlaybackSuspension } class AVCoordinatedPlaybackSuspension { func end() func end(proposingNewTime newTime: CMTime) }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。