musickit-audio

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

MusicKit + MediaPlayer

MusicKit + MediaPlayer

Search the Apple Music catalog, manage playback with
ApplicationMusicPlayer
, check subscriptions, and publish Now Playing metadata via
MPNowPlayingInfoCenter
and
MPRemoteCommandCenter
. Targets Swift 6.2 / iOS 26+.
搜索Apple Music曲库,通过
ApplicationMusicPlayer
管理播放,检查订阅状态,通过
MPNowPlayingInfoCenter
MPRemoteCommandCenter
发布当前播放元数据。适用于Swift 6.2 / iOS 26+版本。

Contents

目录

Setup

配置

Project Configuration

项目配置

  1. Enable the MusicKit capability in Xcode (adds the
    com.apple.developer.musickit
    entitlement)
  2. Add
    NSAppleMusicUsageDescription
    to Info.plist explaining why the app accesses Apple Music
  3. For background playback, add the
    audio
    background mode to
    UIBackgroundModes
  1. 在Xcode中开启MusicKit能力(会添加
    com.apple.developer.musickit
    权限声明)
  2. 在Info.plist中添加
    NSAppleMusicUsageDescription
    字段,说明应用访问Apple Music的原因
  3. 如果需要后台播放,在
    UIBackgroundModes
    中添加
    audio
    后台模式

Imports

导入依赖

swift
import MusicKit       // Catalog, auth, playback
import MediaPlayer    // MPRemoteCommandCenter, MPNowPlayingInfoCenter
swift
import MusicKit       // 曲库、授权、播放相关能力
import MediaPlayer    // MPRemoteCommandCenter、MPNowPlayingInfoCenter相关能力

Authorization

授权

Request permission before accessing the user's music data or playing Apple Music content. Authorization is a one-time prompt per app install.
swift
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
在访问用户音乐数据或播放Apple Music内容前需要先请求权限,每个应用安装后仅会弹出一次授权提示。
swift
func requestMusicAccess() async -> MusicAuthorization.Status {
    let status = await MusicAuthorization.request()
    switch status {
    case .authorized:
        // 已获得MusicKit API的完整访问权限
        break
    case .denied, .restricted:
        // 展示引导提示,指引用户到设置中开启权限
        break
    case .notDetermined:
        break
    @unknown default:
        break
    }
    return status
}

// 不弹出提示的前提下检查当前授权状态
let current = MusicAuthorization.currentStatus

Catalog Search

曲库搜索

Use
MusicCatalogSearchRequest
to search the Apple Music catalog. The user must have an Apple Music subscription for full catalog access.
swift
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
}
使用
MusicCatalogSearchRequest
搜索Apple Music曲库,用户需要有有效的Apple Music订阅才能访问完整曲库。
swift
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
}

Displaying Results

展示搜索结果

swift
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
    }
}
swift
for song in songs {
    print("\(song.title) by \(song.artistName)")
    if let artwork = song.artwork {
        let url = artwork.url(width: 300, height: 300)
        // 从url加载封面图
    }
}

Subscription Checks

订阅检查

Check whether the user has an active Apple Music subscription before offering playback features.
swift
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订阅。
swift
func checkSubscription() async throws -> Bool {
    let subscription = try await MusicSubscription.current
    return subscription.canPlayCatalogContent
}

// 监听订阅状态变化
func observeSubscription() async {
    for await subscription in MusicSubscription.subscriptionUpdates {
        if subscription.canPlayCatalogContent {
            // 启用完整播放UI
        } else {
            // 展示订阅引导
        }
    }
}

Offering Apple Music

提供Apple Music订阅入口

Present the Apple Music subscription offer sheet when the user is not subscribed.
swift
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)
    }
}
当用户未订阅时展示Apple Music订阅引导页。
swift
import MusicKit
import SwiftUI

struct MusicOfferView: View {
    @State private var showOffer = false

    var body: some View {
        Button("订阅Apple Music") {
            showOffer = true
        }
        .musicSubscriptionOffer(isPresented: $showOffer)
    }
}

Playback with ApplicationMusicPlayer

使用ApplicationMusicPlayer播放

ApplicationMusicPlayer
plays Apple Music content independently from the Music app. It does not affect the system player's state.
swift
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()
}
ApplicationMusicPlayer
独立于系统音乐应用播放Apple Music内容,不会影响系统播放器的状态。
swift
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()
}

Observing Playback State

监听播放状态

swift
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
    }
}
swift
func observePlayback() {
    // player.state是@Observable属性
    let state = player.state
    switch state.playbackStatus {
    case .playing:
        break
    case .paused:
        break
    case .stopped, .interrupted, .seekingForward, .seekingBackward:
        break
    @unknown default:
        break
    }
}

Queue Management

队列管理

Build and manipulate the playback queue using
ApplicationMusicPlayer.Queue
.
swift
// 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)
}
使用
ApplicationMusicPlayer.Queue
构建和管理播放队列。
swift
// 用多个内容项初始化队列
func playAlbum(_ album: Album) async throws {
    player.queue = [album]
    try await player.play()
}

// 向现有队列末尾追加歌曲
func appendToQueue(_ songs: [Song]) async throws {
    try await player.queue.insert(songs, position: .tail)
}

// 插入歌曲到下一个播放位置
func playNext(_ song: Song) async throws {
    try await player.queue.insert(song, position: .afterCurrentEntry)
}

Now Playing Info

当前播放信息

Update
MPNowPlayingInfoCenter
so the Lock Screen, Control Center, and CarPlay display current track metadata. This is essential when playing custom audio (non-MusicKit sources).
ApplicationMusicPlayer
handles this automatically for Apple Music content.
swift
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
}
更新
MPNowPlayingInfoCenter
可以让锁屏、控制中心和CarPlay显示当前播放的曲目元数据,播放自定义音频(非MusicKit来源)时必须手动更新,播放Apple Music内容时
ApplicationMusicPlayer
会自动处理。
swift
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
}

Adding Artwork

添加封面图

swift
func setArtwork(_ image: UIImage) {
    let artwork = MPMediaItemArtwork(boundsSize: image.size) { _ in image }
    var info = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [:]
    info[MPMediaItemPropertyArtwork] = artwork
    MPNowPlayingInfoCenter.default().nowPlayingInfo = info
}
swift
func setArtwork(_ image: UIImage) {
    let artwork = MPMediaItemArtwork(boundsSize: image.size) { _ in image }
    var info = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [:]
    info[MPMediaItemPropertyArtwork] = artwork
    MPNowPlayingInfoCenter.default().nowPlayingInfo = info
}

Remote Command Center

远程指令中心

Register handlers for
MPRemoteCommandCenter
to respond to Lock Screen controls, AirPods tap gestures, and CarPlay buttons.
swift
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
}
MPRemoteCommandCenter
注册处理方法可以响应锁屏控制、AirPods手势、CarPlay按钮的操作。
swift
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
    }

    // 禁用不需要支持的指令
    center.seekForwardCommand.isEnabled = false
    center.seekBackwardCommand.isEnabled = false
}

Scrubbing Support

支持进度拖拽

swift
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
    }
}
swift
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
    }
}

Common Mistakes

常见错误

DON'T: Skip the MusicKit entitlement or usage description

不要:遗漏MusicKit权限声明或使用说明

Without the MusicKit entitlement your app crashes at authorization. Without
NSAppleMusicUsageDescription
, App Review rejects the submission.
swift
// 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()
没有MusicKit权限的话应用在授权时会崩溃,没有
NSAppleMusicUsageDescription
的话应用会被应用审核拒绝。
swift
// 错误:没有配置权限
let status = await MusicAuthorization.request() // 会崩溃

// 正确:先在Xcode中开启MusicKit能力,再在Info.plist中添加NSAppleMusicUsageDescription
let status = await MusicAuthorization.request()

DON'T: Forget to check subscription before playback

不要:播放前忘记检查订阅状态

Attempting to play catalog content without a subscription silently fails or throws.
swift
// 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()
}
没有订阅的情况下尝试播放曲库内容会静默失败或者抛出异常。
swift
// 错误
func play(_ song: Song) async throws {
    player.queue = [song]
    try await player.play() // 无订阅时会失败
}

// 正确
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()
}

DON'T: Use SystemMusicPlayer when you mean ApplicationMusicPlayer

不要:混淆SystemMusicPlayer和ApplicationMusicPlayer

SystemMusicPlayer
controls the global Music app queue. Changes affect the user's Music app state. Use
ApplicationMusicPlayer
for app-scoped playback.
swift
// WRONG: Modifies the user's Music app queue
let player = SystemMusicPlayer.shared

// CORRECT: App-scoped playback
let player = ApplicationMusicPlayer.shared
SystemMusicPlayer
会控制全局的系统音乐应用队列,修改会影响用户音乐应用的状态,应用内播放请使用
ApplicationMusicPlayer
swift
// 错误:会修改用户的系统音乐应用队列
let player = SystemMusicPlayer.shared

// 正确:应用内独立播放
let player = ApplicationMusicPlayer.shared

DON'T: Forget to update Now Playing info when track changes

不要:曲目切换时忘记更新当前播放信息

Stale metadata on the Lock Screen confuses users. Update Now Playing info every time the current track changes.
swift
// 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
    )
}
锁屏上的过时元数据会让用户困惑,每次切换曲目时都要更新当前播放信息。
swift
// 错误:只设置一次就不管了
updateNowPlaying(title: firstSong.title, ...)

// 正确:每次曲目切换都更新
func onTrackChanged(_ song: Song) {
    updateNowPlaying(
        title: song.title,
        artist: song.artistName,
        duration: song.duration ?? 0,
        elapsed: 0
    )
}

DON'T: Register remote commands without handling them

不要:注册了远程指令却不处理

Registering a command but returning
.commandFailed
breaks Lock Screen controls. Disable commands you do not support instead.
swift
// WRONG
center.skipForwardCommand.addTarget { _ in .commandFailed }

// CORRECT
center.skipForwardCommand.isEnabled = false
注册了指令但返回
.commandFailed
会导致锁屏控制失效,不需要支持的指令直接禁用即可。
swift
// 错误
center.skipForwardCommand.addTarget { _ in .commandFailed }

// 正确
center.skipForwardCommand.isEnabled = false

Review Checklist

审核检查清单

  • MusicKit capability enabled in Xcode project
  • NSAppleMusicUsageDescription
    added to Info.plist
  • MusicAuthorization.request()
    called before any MusicKit access
  • Subscription checked before attempting catalog playback
  • ApplicationMusicPlayer
    used (not
    SystemMusicPlayer
    ) for app-scoped playback
  • Background audio mode enabled if music plays in background
  • Now Playing info updated on every track change (for custom audio)
  • Remote command handlers return
    .success
    for supported commands
  • Unsupported remote commands disabled with
    isEnabled = false
  • Artwork provided in Now Playing info for Lock Screen display
  • Elapsed playback time updated periodically for scrubber accuracy
  • Subscription offer presented when user lacks Apple Music subscription
  • Xcode项目中已开启MusicKit能力
  • Info.plist中已添加
    NSAppleMusicUsageDescription
  • 访问任何MusicKit能力前已调用
    MusicAuthorization.request()
  • 尝试播放曲库内容前已检查订阅状态
  • 应用内播放使用的是
    ApplicationMusicPlayer
    而非
    SystemMusicPlayer
  • 如果需要后台播放已开启后台音频模式
  • 自定义音频播放时每次切换曲目都更新了当前播放信息
  • 支持的远程指令处理方法返回
    .success
  • 不支持的远程指令已通过
    isEnabled = false
    禁用
  • 当前播放信息中包含封面图用于锁屏展示
  • 定期更新已播放时长保证进度条准确性
  • 用户没有Apple Music订阅时会展示订阅引导

References

参考资料