大多数浏览器和
Developer App 均支持流媒体播放。
-
探索媒体元数据发布和播放交互
学习如何在每个平台上突显 App 的“正在播放”信息。我们将简要介绍媒体元数据,了解如何在锁屏和控制中心等区域展示它们,并说明如何为您的内容编写和发布有效的媒体元数据。我们还将探索您的 App 对于来自其他设备 (如 HomePod) 命令的响应。
资源
相关视频
WWDC22
-
下载
♪ ♪ Nik: 大家好 我是 Nik 是 Video 团队的工程师 今天 我很高兴能为大家介绍 媒体元数据发布 和播放交互 这是什么意思呢 Apple 设备中有很多地方 是可以显示播放信息 并控制媒体播放的 例如 控制中心的正在播放部分 可以显示当前设备播放的 专辑 标题和进度条 您也可以播放 暂停 甚至是快进或快退 放大“正在播放”窗口 可以显示更多信息 如专辑和进度条 您也可以设置静音 增大或减小音量 锁定屏幕也显示了 同样的信息和控制按钮 让用户可以更方便地查看 播放进度 暂停 甚至不用解锁 就能 AirPlay 到 另一个设备中
不管是在什么设备上 Apple Watch 的“正在播放”App 都能为用户提供相同的体验 它甚至有内置的 Apple TV 遥控
在 Apple tvOS 上使用 AVKit 时 展示控制按钮的 覆盖信息会显示 播放标题和章节信息 如果您下滑到信息面板 会显示更详细的信息 如专辑名称和描述
按住 Apple TV 遥控上的 TV 按钮会显示控制中心 和 iOS 一样 会出现一个 “正在播放”的窗口 也支持放大 当 Apple tvOS 后台开始 播放音频内容时 按下遥控上的播放按钮 或者从另一个设备的 音乐 App 上选择一个曲目 会显示有正在播放 信息的通知 另外 Apple tvOS 上播放音频时 如果有一段时间未进行操作 会有全屏覆盖 显示当前播放信息
最后 在 iOS 上 Control Other Speakers and TVs 按钮 可以让您查看您其它所有设备的 正在播放信息 您可以控制播放
随着可支持显示正在播放信息 且可以控制播放的 设备和 UI 越来越多 如何适当发布 正在播放信息 并相应遥控指令显得愈发重要 这次讲座接下来的时间 我们将阐述如何响应 遥控指令中的播放交互 自动元数据发布 AVKit 显示以及手动发布 当使用 AVFoundation 来播放媒体内容时 显示正在播放元数据 并响应播放交互的最好方法 就是使用 MPNowPlayingSession 类
过去该类只可用于 Apple tvOS 但现在也可用于 iOS 16 了
它可用来显示不同的播放会话 如果您的 App 包含 多种活动会话时 也可提供对 正在播放状态的控制 它支持手动元数据发布 以及 iOS 和 Apple tvOS 16 上 全新的自动发布
MPNowPlayingSession 使用 AVKit 时 不应用在 Apple tvOS 上 它有自己的自动发布机制 我们稍后会讲到 成为“正在播放”App 意味着 您的 App 会出现在 控制中心 锁定屏幕等等 并接收播放控制 如用户从另一个界面按下暂停 有了 MPNowPlayingSession 您可以体现在一个 App 中 展示同时播放的多个播放会话 然而 使用多重会话时 您的 App 必须将其中一个 设置为活动会话 在用遥控控制您的 App 时 该会话会显示在整个系统中 例如 使用画中画时 可能同时有两个播放会话 全屏播放部分应是 活动的正在播放会话 系统也有特定的标准来判定 App 是否可以加入正在播放列表 首先 您要注册一个最少有 一个遥控指令的处理程序 如您所见 不能响应 播放交互的 App 并不适合显示在 正在播放列表中 第二 您 App 的 AVAudioSession 必须在 类别选择中配置有 non-mixable 类 Mixable 播放类别和选项 通常是在播放 后台通知时使用的 因此这可以提示系统 正在播放的内容并不适合 加入正在播放列表 以下是几个帮助您理解 播放会话的例子 在这个例子中 只有一个播放内容 所以可以用单一的 MPNowPlayingSession 来体现 如果您的 App 支持 PiP 您也会有两个 MPNowPlayingSessions 一个为主播放器 另一个是 PiP 播放 在更复杂的情况下 单一的 MPNowPlayingSession 有多个播放器 在这个例子中 有 4 个播放器 每个占 1/4 的屏幕空间 显示同一场比赛的不同视角 添加到相同 MPNowPlayingSession 中的播放器 应该具有相同的内容 这里为每个例子做了说明 首先 我们只播放单一内容 因此 在一个播放器 开启了一个会话 第二个例子用的是画中画 所以我们有两个会话 每个会话一个播放器 第一个全屏显示 第二个是画中画 最后一个例子 在一个会话中 从多个视角 展示了四个参赛者的情况 App 有多重会话时 在必要时 App 需将指定的会话 定义为活动会话 例如 如果媒体以画中画的形式 来播放 如果用户将其放大到全屏 之前的全屏会话应该 不再为活跃会话 或者说当前为全屏的 正在播放和 PiP 会话 应为活跃会话 这种过渡可以通过 在 MPNowPlayingSession 上 调用 becomeActiveIfPossible 来实现 现在我们已经了解了设置 MPNowPlayingSession 实例 以及控制正在播放会话的 基础知识 我们来看下从锁定屏幕 或另一个房间的 HomePod 中 接收和响应遥控指令 首先是注册播放和暂停指令的 基本例子 这样 用户在另一个设备上 按下播放或暂停按钮 或用 Siri 来发布指令时 您的 App 可以 收到回调 我们先将 MPNowPlayingSession 实例化 由于我们只有一个会话 就无需唤醒 ‘becomeActiveIfPossible’方法 当您只有一个会话 且 App 为“正在播放”App 时 该会话则为默认会话 每个 MPNowPlayingSession 实例 有其自己的 MPRemoteCommandCenter 实例 可用于声明您的播放会话 响应的是哪个遥控指令 接下来 我们为 playCommand 添加处理程序 用于在播放器中唤醒 play 方法 返回 success 然后 pauseCommand 也是同样的操作 您要为 App 支持的每个指令 都添加处理程序 这适用于当前播放内容 另一个例子是快进和快退指令 这个指令应该用于大部分内容 但对不能向前跳转的情况不适用 如流媒体直播 首先我们要指明首选间隔 或者跳转的秒数 在这里 我们用 15 秒 然后与播放和暂停指令的 操作一样 添加一个处理程序 在用户按下快进按钮 或让 Siri 快进时唤醒该程序 在我们的处理程序中 将会收到 MPSkipIntervalCommandEvent 所以首先我们要将事件 转换为该类型 然后通过 MPSkipIntervalCommandEvent 提供的当前时间和间隔 计算新的运行时间 找到该位置 返回 success 表明我们跳转到新的位置 也有可能您的 App 正处于 指令暂时不可用的情形 例如在广告时快进的时候 这样的话 可禁用 skipForwardCommand 现在我们响应了遥控指令 可以再处理自动元数据发布 自动发布让您能更容易 保证元数据的精准性 它可自动维持其直接在播放器 观察到的元数据属性 如持续时间 当前运行时间 播放状态 播放进度 如果内容中有植入广告 则不应计入 总持续时间和运行时间 而应计算纯运行时间并报告 其它元数据如标题 描述 和专辑可以 用 nowPlayingInfo 属性 直接添加到 AVPlayerItems 在这个例子中 我们用自动发布 来做大部分工作 并自己设置标题和专辑 首先 创建一个新的 MPMediaItemArtwork 实例 传递专辑图片 大部分 App 会运行 网络请求来抓取这些内容 然后我们设置内容的视频标题 然后将专辑和标题 在当前使用 MPMediaItemPropertyTitle 和 MPMediaItemPropertyArtwork 的 播放器上 设置为 nowPlayingInfo 词典 正在播放元数据可以包含 和 MPNowPlayingInfoProperty 最后 我们创建 MPNowPlayingSession 实例 传递到播放器 将 automaticallyPublishNowPlayingInfo 设置为 true automaticallyPublishNowPlayingInfo 设置为 true 后 MPNowPlayingSession 实例将会开始 观察播放器的 状态变化 如静音 播放 / 暂停事件 或当前播放项目的变化 这是另一个例子 可以演示有植入广告时 如果您希望广告时间不计入 总持续时间或当前运行时间 我们可以如何在实例中 使用自动发布 我们要为每个植入的广告 创建 MPAdTimeRange 实例 在这个例子中 我们有一条 30 秒的广告 在视频最开始播放 所以我们创建开始点为 0 持续时间为 30 秒 与之前标题和专辑的 处理方式类似 我们只要在使用 MPNowPlayingInfoPropertyAdTimeRanges 的播放项目上 添加一组 MPAdTimeRange's 到其 nowPlayingInfo 词典 然后和之前的操作一样 创建 MPNowPlayingSession 启用自动发布 接下来是 AVKit 的 元数据发布 在 Apple tvOS 上发布 AVKit 的 正在播放元数据 与 MPNowPlayingSession 操作类似 元数据直接添加到 AVPlayerItem 发布如运行时间 持续时间 播放状态的数值 并保持更新 直接从播放器 和资产中获取的元数据 与您 App 在 AVPlayerItem 上 提供的元数据 一起输入到播放器 UI 的 信息面板 AVKit 也负责注册 和响应遥控指令 用 AVKit 是最好 也是最简单的方法来整合 我们迄今讨论过的平台 以及其它如 AirPlay 和画中画的特性 使用 AVKit 时设置元数据 可以通过在 AVPlayerItem 上 使用 externalMetadata 数组来完成 包括描述内容的 AVMetadataItem 实例 每个 AVMetadataItem 您最多可以设置三个数值 首先 标识符 这是表明 AVMetadataItem 代表的是 什么元数据的关键 例如 当前标题的 AVMetadataCommonIdentifierTitle 或专辑的 AVMetadataCommonIdentifierArtwork 第二是数值 在标题中 这是包含标题的字符串 在专辑中 这是包含图像数据的 NSData 实例 dataType 用于指明 所提供专辑的格式 如果包含 JPEG 数据 将会使用 kCMMetadatabaseDataType_JPEG 最后 extendedLanguageTag 用于指明字符串 所用的语言 如标题和描述 大部分时候 这里应该用 数值“und” 来确保所有观众都看到 同样的数值 如果数值是英语 您可能会想使用“en-us” 但这样的话会该语言的设备 设置成其它语言 如西班牙语 从而不显示元数据
这里我们有一个 设置专辑和标题的例子 首先 从软件包中抓取专辑图像数据 大部分 App 会通过网络资源 来抓取该信息 然后 我们将新的 可变 AVMetadataItem 实例化 设置标识符为 .commonIdentifierArtwork 然后设置数值为 原始专辑图像数据及 NSData 由于图像数据是 JPEG 我们将 dataType 设置为 kCMMetadataBaseDataType_JPEG 如果您的专辑是 PNG 那就用 kCMMetadataBaseDataType_PNG 因为我们希望 不管用户设备是什么语言 这个元数据都是可见的 所以将 extendedLanguageTag 设置为“und,”或“undefined.” 然后为标题重复同样的步骤 用 .commonIdentifierTitle 视频标题作为数值 再用“und” 作为 extendedLanguageTag 我们建立好所有元数据项目后 将其添加到数组 设置为 AVPlayerItem 的 externalMetadata 属性 现在播放项目上已经添加好了 专辑和标题 您可以看下这是如何映射到 iOS 的控制中心和锁定屏幕上的 如专辑一样 还有其它元数据类型 是可以设置的 如描述 字幕信息 内容评级 您的 App 可以设置 尽量多的信息 尽可能为用户提供丰富的体验 到目前为止 我们讨论了 MPNowPlayingSession 的自动发布 AVKit 的发布 但 MPNowPlayingSession 及其自动发布功能 需要传递一个 AVPlayer 实例 这并不一定适用于所有 App 仍然可以使用手动发布 手动发布需要您提供 所有元数据数值 与自动发布不同 如运行时间 播放状态的信息无法由系统提供 这意味着您需要对低等级 播放状态进行手动细粒度控制 您的 App 保证在播放状态改变下 信息随着时间的准确性 要注意 注册和响应遥控指令 同样也是需要的 因为我们没有 使用 MPNowPlayingSession 那就要使用 MPRemoteCommandCenter 的共享实例 以下是更新正在播放Info 词典的基础示例 首先 创建包含图像的 MPMediaItemArtwork 实例 和我们在自动发布时的操作类似 然后 创建包含 我们已有元数据的词典 在这里 我们设置标题 专辑 以及播放持续时间 运行时间 播放状态的数值 然后将其设置到 MPNowPlayingInfoCenter 默认实例上 在播放期间有任意变化 如播放或暂停 用户滑动快进或快退 或者开始播放新项目时 这一元数据都应相应更新 您不需要定期更新运行时间 系统会根据自上一次更新以来 过去了多少时间 从而推算 正确的运行时间 现在您已经熟悉了 发布正在播放元数据 及响应其它设备和界面遥控指令的 所有不同方法 那就可以集成这些方法 从而最大化用户体验了 这是前所未有的便捷 当前的集成同样也能用上 转换到自动发布 从而避免 未来的性能倒退 最小化您要维持的代码数量 您可访问 developer.apple.com 上的 MediaPlayer 以获取更多信息 感谢大家的观看
-
-
4:34 - Instantiation examples
// playing Magnificent self.session = MPNowPlayingSession(players: [player]) // Playing different WWDC sessions, one full screen and one in PiP self.session = MPNowPlayingSession(players: [player]) self.pipSession = MPNowPlayingSession(players: [pipPlayer]) // playing multi-view race self.session = MPNowPlayingSession(players: [topLeft, topRight, bottomLeft, bottomRight])
-
4:58 - Promoting and demoting sessions as Now Playing
// Promoting and demoting sessions as Now Playing self.session = MPNowPlayingSession(players: [player]) self.pipSession = MPNowPlayingSession(players: [pipPlayer]) // if the content in PiP is promoted to full screen, swap active self.pipSession.becomeActiveIfPossible { becameActive in // if success, pipSession data populates lock screen, etc, and // controls from lock screen, etc are routed to pipSession }
-
5:32 - Responding to play and pause commands
// Example of responding to play and pause commands self.session = MPNowPlayingSession(players: [player]) // respond to play commands self.session.remoteCommandCenter.playCommand.addTarget { event in player.play() return .success } // respond to pause commands self.session.remoteCommandCenter.pauseCommand.addTarget { event in player.pause() return .success }
-
6:22 - Responding to skip forward commands
// Example responding to skip forward commands self.session.remoteCommandCenter.skipForwardCommand.preferredIntervals = [15.0] self.session.remoteCommandCenter.skipForwardCommand.addTarget { event in let skipCommand = event as! MPSkipIntervalCommandEvent player.seek(to: CMTimeAdd(player.currentTime(), CMTimeMakeWithSeconds(skipCommand.interval, preferredTimescale: 1))) return .success } // commands can also be disabled. for example, during an ad: self.session.remoteCommandCenter.skipForwardCommand.isEnabled = false // add handlers for all commands that are applicable to the content // https://developer.apple.com/documentation/mediaplayer/mpremotecommandcenter
-
7:48 - Setting artwork metadata with automatic publishing
// Example of setting artwork metadata let artwork = MPMediaItemArtwork(image: image) let title = "Magnificent" playerItem.nowPlayingInfo = [ MPMediaItemPropertyTitle: title, MPMediaItemPropertyArtwork: artwork, // … ] self.session = MPNowPlayingSession(players: [player]) self.session.automaticallyPublishNowPlayingInfo = true
-
8:38 - Setting ad time ranges for automatic publishing
// Example with ads that should not contribute to elapsed time and duration let preroll = MPAdTimeRange(timeRange: CMTimeRange(start: CMTime.zero, duration: CMTimeMakeWithSeconds(30, preferredTimescale: 1))) playerItem.nowPlayingInfo = [ … MPNowPlayingInfoPropertyAdTimeRanges: [preroll] … ] self.session = MPNowPlayingSession(players: [player]) self.session.automaticallyPublishNowPlayingInfo = true
-
11:02 - Setting artwork and title metadata for AVKit
// Example of setting artwork metadata let path = Bundle.main.path(forResource: "poster", ofType: "jpg") let posterData = FileManager.default.contents(atPath: path!)! let artwork = AVMutableMetadataItem() artwork.identifier = .commonIdentifierArtwork artwork.value = posterData as NSData artwork.dataType = kCMMetadataBaseDataType_JPEG as String artwork.extendedLanguageTag = "und" let title = AVMutableMetadataItem() title.identifier = .commonIdentifierTitle title.value = "Magnificent" as NSString title.extendedLanguageTag = "und" playerItem.externalMetadata = [artwork, title]
-
12:59 - Manually publishing Now Playing
// Example of setting Now Playing information let artwork = MPMediaItemArtwork(image: image) let nowPlayingInfo = [ MPMediaItemPropertyTitle: title, MPMediaItemPropertyArtwork: artwork, MPMediaItemPropertyPlaybackDuration: playerItem.duration, MPNowPlayingInfoPropertyElapsedPlaybackTime: player.currentTime().seconds, MPNowPlayingInfoPropertyPlaybackRate: player.rate ] MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
-
13:25 - Updating Now Playing metadata
// On any non-linear time change, playback rate change, or play/pause var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player.currentTime().seconds nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player.rate
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。