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 应用有两个条件:
- 至少注册了一个远程命令处理器
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设置封面,类型为MPMediaItemArtworkautomaticallyPublishNowPlayingInfo = 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,标题用NSStringdataType标明图片格式,kCMMetadataBaseDataType_JPEG或_PNGextendedLanguageTag用"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
- Create a more responsive media app — 使用 AVFoundation 构建响应式媒体应用
- Deliver reliable streams with HLS Content Steering — HLS 内容 steering 技术
- What’s new in AVKit — AVKit 新特性
评论
GitHub Issues · utterances