WWDC Quick Look 💓 By SwiftGGTeam
Explore media metadata publishing and playback interactions

Explore media metadata publishing and playback interactions

观看原视频

Highlight

iOS 16 将 MPNowPlayingSession 从 tvOS 扩展到 iPhone 和 iPad,配合自动元数据发布功能,开发者只需几行代码就能让应用的播放信息正确显示在控制中心、锁屏、Apple Watch 和 HomePod 上。

核心内容

无处不在的 Now Playing

用户在控制中心调节音量时,会看到当前播放内容的封面和标题。锁屏状态下可以快速暂停或 AirPlay 到其他设备。Apple Watch 上也有 Now Playing App,甚至内置了 Apple TV 遥控器。

这些界面都依赖应用正确发布元数据。如果元数据缺失或更新不及时,用户体验就会断裂——封面不显示、进度条不准、远程控制没反应。

MPNowPlayingSession 统一多平台

02:18

MPNowPlayingSession 之前在 tvOS 上独占,iOS 16 正式开放。它代表一个独立的播放会话,一个应用可以有多个会话——比如主播放器和画中画各一个。

系统判断应用是否有资格成为 Now Playing 应用有两个条件:

  1. 至少注册了一个远程命令处理器
  2. AVAudioSession 配置为非混音类别

自动发布减少维护负担

07:16

iOS 16 新增了自动元数据发布。开启后,MPNowPlayingSession 自动观察播放器状态,实时更新时长、已播放时间、播放状态等字段。开发者只需要设置标题和封面,其余交给系统。

详细内容

创建播放会话

04:34

单播放器场景:

self.session = MPNowPlayingSession(players: [player])

画中画场景,两个独立会话:

self.session = MPNowPlayingSession(players: [player])
self.pipSession = MPNowPlayingSession(players: [pipPlayer])

多视角同内容场景,一个会话多个播放器:

self.session = MPNowPlayingSession(players: [topLeft, topRight, bottomLeft, bottomRight])

关键点:

  • 同一个 MPNowPlayingSession 内的多个播放器应属于同一内容
  • 多会话场景需要手动提升活跃会话

切换活跃会话

04:58

画中画内容全屏时,切换活跃会话:

self.pipSession.becomeActiveIfPossible { becameActive in
    // 成功后,pipSession 的数据会填充到锁屏和控制中心
    // 远程控制命令也会路由到 pipSession
}

关键点:

  • becomeActiveIfPossible 是异步的,通过回调确认是否成功
  • 系统可能拒绝切换(如另一个应用正在播放)

响应远程命令

05:32

播放和暂停:

self.session = MPNowPlayingSession(players: [player])

self.session.remoteCommandCenter.playCommand.addTarget { event in
    player.play()
    return .success
}

self.session.remoteCommandCenter.pauseCommand.addTarget { event in
    player.pause()
    return .success
}

快进命令:

self.session.remoteCommandCenter.skipForwardCommand.preferredIntervals = [15.0]
self.session.remoteCommandCenter.skipForwardCommand.addTarget { event in
    let skipCommand = event as! MPSkipIntervalCommandEvent
    let newTime = CMTimeAdd(
        player.currentTime(),
        CMTimeMakeWithSeconds(skipCommand.interval, preferredTimescale: 1)
    )
    player.seek(to: newTime)
    return .success
}

// 广告期间禁用快进
self.session.remoteCommandCenter.skipForwardCommand.isEnabled = false

关键点:

  • 每个 MPNowPlayingSession 有自己的 MPRemoteCommandCenter 实例
  • 为所有适用的命令注册处理器,不支持的命令可以禁用
  • preferredIntervals 定义快进/快退的秒数
  • MPSkipIntervalCommandEvent.interval 获取用户请求的具体跳过时长
  • 直播流等场景应禁用跳进命令

自动发布元数据

07:48

设置标题和封面,开启自动发布:

let artwork = MPMediaItemArtwork(image: image)
let title = "Magnificent"

playerItem.nowPlayingInfo = [
    MPMediaItemPropertyTitle: title,
    MPMediaItemPropertyArtwork: artwork
]

self.session = MPNowPlayingSession(players: [player])
self.session.automaticallyPublishNowPlayingInfo = true

关键点:

  • nowPlayingInfo 字典设置在 AVPlayerItem
  • MPMediaItemPropertyTitle 设置标题
  • MPMediaItemPropertyArtwork 设置封面,类型为 MPMediaItemArtwork
  • automaticallyPublishNowPlayingInfo = true 开启自动发布
  • 系统自动观察播放器,更新时长、已播放时间、播放状态

处理内嵌广告:

let preroll = MPAdTimeRange(
    timeRange: CMTimeRange(
        start: CMTime.zero,
        duration: CMTimeMakeWithSeconds(30, preferredTimescale: 1)
    )
)

playerItem.nowPlayingInfo = [
    MPMediaItemPropertyTitle: title,
    MPMediaItemPropertyArtwork: artwork,
    MPNowPlayingInfoPropertyAdTimeRanges: [preroll]
]

self.session = MPNowPlayingSession(players: [player])
self.session.automaticallyPublishNowPlayingInfo = true

关键点:

  • MPAdTimeRange 标记广告时间段
  • MPNowPlayingInfoPropertyAdTimeRanges 传入广告数组
  • 自动发布会自动扣除广告时间,计算净播放时长

AVKit 元数据设置

11:02

tvOS 上使用 AVKit 时,通过 externalMetadata 设置:

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]

关键点:

  • AVMutableMetadataItem 用于构建元数据项
  • identifier 指定元数据类型,.commonIdentifierArtwork 表示封面
  • value 是实际数据,封面用 NSData,标题用 NSString
  • dataType 标明图片格式,kCMMetadataBaseDataType_JPEG_PNG
  • extendedLanguageTag"und"(undefined)确保所有语言环境都可见
  • AVKit 自动处理远程命令和元数据发布

手动发布元数据

12:59

不使用 MPNowPlayingSession 时的 fallback:

let artwork = MPMediaItemArtwork(image: image)

let nowPlayingInfo: [String: Any] = [
    MPMediaItemPropertyTitle: title,
    MPMediaItemPropertyArtwork: artwork,
    MPMediaItemPropertyPlaybackDuration: playerItem.duration,
    MPNowPlayingInfoPropertyElapsedPlaybackTime: player.currentTime().seconds,
    MPNowPlayingInfoPropertyPlaybackRate: player.rate
]

MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo

更新已播放时间和播放速率:

var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo
nowPlayingInfo?[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player.currentTime().seconds
nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = player.rate
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo

关键点:

  • 手动发布需要自行维护所有字段
  • 在播放/暂停、跳转、切换内容时更新元数据
  • 不需要周期性更新已播放时间,系统会根据上次更新和播放速率自动推算
  • 使用 MPNowPlayingInfoCenter.default() 的共享实例

核心启发

  • 做什么:为播客应用添加完整的锁屏控制支持

  • 为什么值得做:用户经常在开车或运动时听播客,锁屏和耳机控制是主要交互方式

  • 怎么开始:创建 MPNowPlayingSession,注册 play/pause/skipForward/skipBackward 命令,设置 nowPlayingInfo 包含标题、封面和章节信息

  • 做什么:构建多房间音频控制应用

  • 为什么值得做:用户可以通过 HomePod 控制 iPhone 上的播放,元数据发布让这种跨设备控制无缝工作

  • 怎么开始:确保 AVAudioSession 配置为非混音类别,注册远程命令处理器,使用 MPNowPlayingSession 管理播放状态

  • 做什么:开发支持画中画的视频应用

  • 为什么值得做:多会话管理让用户在画中画和全屏之间切换时,Now Playing 信息始终正确

  • 怎么开始:为主播放器和画中画各创建 MPNowPlayingSession,切换时调用 becomeActiveIfPossible

  • 做什么:创建直播流媒体应用

  • 为什么值得做:直播场景需要禁用某些命令(如快进),同时保持其他控制可用

  • 怎么开始:注册命令后根据直播状态动态设置 isEnabled,广告期间禁用跳过功能

关联 Session

评论

GitHub Issues · utterances