关于 musickit-audio
面向 Apple 平台应用开发者的 MusicKit 与 MediaPlayer 技能,涵盖 Apple Music 搜索、订阅检查和应用内播放,也介绍 Now Playing 信息及远程控制。
主要功能
- 01搜索 Apple Music 曲库
- 02检查订阅状态
- 03使用 ApplicationMusicPlayer 播放
- 04管理播放队列
- 05更新 Now Playing 元数据
- 06处理远程播放命令
适用场景
- 01在应用中搜索 Apple Music 歌曲
- 02按订阅状态控制曲库播放
- 03为自定义音频显示锁屏信息
- 04响应耳机或 CarPlay 播放控制
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill musickit-audio安装后在 Claude Code 中输入/musickit-audio确认已加载。
附带文件references/musickit-patterns.md
详细介绍
MusicKit + MediaPlayer
搜索 Apple Music 目录,使用 ApplicationMusicPlayer 管理播放,检查订阅状态,并通过 MPNowPlayingInfoCenter 和 MPRemoteCommandCenter 发布“正在播放”元数据。面向 Swift 6.2 / iOS 26+。
目录
设置
项目配置
- 在 Xcode 中启用 MusicKit 功能(会添加
com.apple.developer.musickit权限) - 将
NSAppleMusicUsageDescription添加到 Info.plist,说明应用访问 Apple Music 的原因 - 如需后台播放,请将
audio后台模式添加到UIBackgroundModes
导入
import MusicKit // Catalog, auth, playback
import MediaPlayer // MPRemoteCommandCenter, MPNowPlayingInfoCenter
授权
在访问用户的音乐数据或播放 Apple Music 内容之前,请先请求权限。每次安装应用只会显示一次授权提示。
func requestMusicAccess() async -> MusicAuthorization.Status {
let status = await MusicAuthorization.request()
switch status {
case .authorized:
// Full access to MusicKit APIs
break
case .denied, .restricted:
// Show guidance to enable in Settings
break
case .notDetermined:
break
@unknown default:
break
}
return status
}
// Check current status without prompting
let current = MusicAuthorization.currentStatus
目录搜索
使用 MusicCatalogSearchRequest 搜索 Apple Music 目录。用户必须订阅 Apple Music,才能完整访问目录。
func searchCatalog(term: String) async throws -> MusicItemCollection<Song> {
var request = MusicCatalogSearchRequest(term: term, types: [Song.self])
request.limit = 25
let response = try await request.response()
return response.songs
}
显示结果
for song in songs {
print("\(song.title) by \(song.artistName)")
if let artwork = song.artwork {
let url = artwork.url(width: 300, height: 300)
// Load artwork from url
}
}
订阅检查
在提供播放功能之前,检查用户是否拥有有效的 Apple Music 订阅。
func checkSubscription() async throws -> Bool {
let subscription = try await MusicSubscription.current
return subscription.canPlayCatalogContent
}
// Observe subscription changes
func observeSubscription() async {
for await subscription in MusicSubscription.subscriptionUpdates {
if subscription.canPlayCatalogContent {
// Enable full playback UI
} else {
// Show subscription offer
}
}
}
提供 Apple Music
如果用户尚未订阅,请显示 Apple Music 订阅优惠页。
import MusicKit
import SwiftUI
struct MusicOfferView: View {
@State private var showOffer = false
var body: some View {
Button("Subscribe to Apple Music") {
showOffer = true
}
.musicSubscriptionOffer(isPresented: $showOffer)
}
}
使用 ApplicationMusicPlayer 播放
ApplicationMusicPlayer 可以独立于“音乐”应用播放 Apple Music 内容,不会影响系统播放器的状态。
let player = ApplicationMusicPlayer.shared
func playSong(_ song: Song) async throws {
player.queue = [song]
try await player.play()
}
func pause() {
player.pause()
}
func skipToNext() async throws {
try await player.skipToNextEntry()
}
观察播放状态
func observePlayback() {
// player.state is an @Observable property
let state = player.state
switch state.playbackStatus {
case .playing:
break
case .paused:
break
case .stopped, .interrupted, .seekingForward, .seekingBackward:
break
@unknown default:
break
}
}
队列管理
使用 ApplicationMusicPlayer.Queue 构建和操作播放队列。
// Initialize with multiple items
func playAlbum(_ album: Album) async throws {
player.queue = [album]
try await player.play()
}
// Append songs to the existing queue
func appendToQueue(_ songs: [Song]) async throws {
try await player.queue.insert(songs, position: .tail)
}
// Insert song to play next
func playNext(_ song: Song) async throws {
try await player.queue.insert(song, position: .afterCurrentEntry)
}
正在播放信息
更新 MPNowPlayingInfoCenter,以便锁定屏幕、控制中心和 CarPlay 显示当前曲目的元数据。播放自定义音频(非 MusicKit 来源)时,这一点至关重要。对于 Apple Music 内容,ApplicationMusicPlayer 会自动处理。
import MediaPlayer
func updateNowPlaying(title: String, artist: String, duration: TimeInterval, elapsed: TimeInterval) {
var info = [String: Any]()
info[MPMediaItemPropertyTitle] = title
info[MPMediaItemPropertyArtist] = artist
info[MPMediaItemPropertyPlaybackDuration] = duration
info[MPNowPlayingInfoPropertyElapsedPlaybackTime] = elapsed
info[MPNowPlayingInfoPropertyPlaybackRate] = 1.0
info[MPNowPlayingInfoPropertyMediaType] = MPNowPlayingInfoMediaType.audio.rawValue
MPNowPlayingInfoCenter.default().nowPlayingInfo = info
}
func clearNowPlaying() {
MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
}
添加封面图片
func setArtwork(_ image: UIImage) {
let artwork = MPMediaItemArtwork(boundsSize: image.size) { _ in image }
var info = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [:]
info[MPMediaItemPropertyArtwork] = artwork
MPNowPlayingInfoCenter.default().nowPlayingInfo = info
}
远程命令中心
为 MPRemoteCommandCenter 注册处理程序,以响应锁定屏幕控件、AirPods 轻点手势和 CarPlay 按钮。
func setupRemoteCommands() {
let center = MPRemoteCommandCenter.shared()
center.playCommand.addTarget { _ in
resumePlayback()
return .success
}
center.pauseCommand.addTarget { _ in
pausePlayback()
return .success
}
center.nextTrackCommand.addTarget { _ in
skipToNext()
return .success
}
center.previousTrackCommand.addTarget { _ in
skipToPrevious()
return .success
}
// Disable commands you do not support
center.seekForwardCommand.isEnabled = false
center.seekBackwardCommand.isEnabled = false
}
支持拖动进度条
func enableScrubbing() {
let center = MPRemoteCommandCenter.shared()
center.changePlaybackPositionCommand.addTarget { event in
guard let positionEvent = event as? MPChangePlaybackPositionCommandEvent else {
return .commandFailed
}
seek(to: positionEvent.positionTime)
return .success
}
}
常见错误
不要:跳过 MusicKit 权限或使用说明
没有 MusicKit 权限,应用会在授权时崩溃。没有 NSAppleMusicUsageDescription,App Review 会拒绝提交。
// WRONG: No entitlement configured
let status = await MusicAuthorization.request() // Crashes
// CORRECT: Enable MusicKit capability in Xcode first,
// then add NSAppleMusicUsageDescription to Info.plist
let status = await MusicAuthorization.request()
不要:忘记在播放前检查订阅
没有订阅却尝试播放目录内容,会静默失败或抛出错误。
// WRONG
func play(_ song: Song) async throws {
player.queue = [song]
try await player.play() // Fails if no subscription
}
// CORRECT
func play(_ song: Song) async throws {
let sub = try await MusicSubscription.current
guard sub.canPlayCatalogContent else {
showSubscriptionOffer()
return
}
player.queue = [song]
try await player.play()
}
不要:需要 ApplicationMusicPlayer 时却使用 SystemMusicPlayer
SystemMusicPlayer 控制全局“音乐”应用队列。更改会影响用户“音乐”应用的状态。请使用 ApplicationMusicPlayer 实现应用范围内的播放。
// WRONG: Modifies the user's Music app queue
let player = SystemMusicPlayer.shared
// CORRECT: App-scoped playback
let player = ApplicationMusicPlayer.shared
不要:曲目更改时忘记更新“正在播放”信息
锁定屏幕上的过时元数据会让用户困惑。当前曲目每次更改时都要更新“正在播放”信息。
// WRONG: Set once and forget
updateNowPlaying(title: firstSong.title, ...)
// CORRECT: Update on every track change
func onTrackChanged(_ song: Song) {
updateNowPlaying(
title: song.title,
artist: song.artistName,
duration: song.duration ?? 0,
elapsed: 0
)
}
不要:注册远程命令却不处理
注册命令却返回 .commandFailed 会导致锁定屏幕控件无法正常工作。应禁用不支持的命令。
// WRONG
center.skipForwardCommand.addTarget { _ in .commandFailed }
// CORRECT
center.skipForwardCommand.isEnabled = false
审查清单
- 已在 Xcode 项目中启用 MusicKit 功能
- 已将
NSAppleMusicUsageDescription添加到 Info.plist - 在访问 MusicKit 前调用了
MusicAuthorization.request() - 尝试播放目录内容前检查了订阅状态
- 使用
ApplicationMusicPlayer(而非SystemMusicPlayer)进行应用范围内的播放 - 如果音乐会在后台播放,已启用后台音频模式
- 每次曲目切换时都更新了「正在播放」信息(用于自定义音频)
- 远程命令处理程序对支持的命令返回
.success - 使用
isEnabled = false禁用不支持的远程命令 - 在「正在播放」信息中提供了封面图,以便在锁定屏幕上显示
- 定期更新已播放时长,确保拖动条准确
- 当用户没有 Apple Music 订阅时,展示订阅优惠
参考资料
- 扩展模式(SwiftUI 集成、流派浏览、播放列表管理):
references/musickit-patterns.md - MusicKit 框架
- MusicAuthorization
- ApplicationMusicPlayer
- MusicCatalogSearchRequest
- MusicSubscription
- MPRemoteCommandCenter
- MPNowPlayingInfoCenter
- NSAppleMusicUsageDescription