使用 MusicKit 與 MediaPlayer 整合 Apple Music 播放、音樂庫搜尋以及「正在播放」中繼資料。適用於在 iOS App 中加入音樂搜尋、Apple Music 訂閱流程、佇列管理、播放控制、遠端控制命令處理或「正在播放」資訊等情境。
MusicKit
使用 ApplicationMusicPlayer 搜尋 Apple Music 目錄與管理播放,檢查訂閱狀態,並透過 MPNowPlayingInfoCenter 及 MPRemoteCommandCenter 發布「正在播放」中繼資料。
目錄
開發流程
- 在除錯程式碼前,先確認已啟用 MusicKit App Service、Bundle Identifier、使用目的說明字串(purpose string)以及背景音訊模式(background-audio mode)。
- 請求授權,並針對所有未授權的狀態進行明確建模處理。
- 在將內容加入播放佇列前,先搜尋或載入目錄內容,並檢查
MusicSubscription.current。 - 在 App 內部播放時,選擇使用
ApplicationMusicPlayer;僅在 App 擁有這些控制介面時,才接管「正在播放」與遠端命令。 - 測試已授權、拒絕授權、未訂閱、離線、佇列失敗、播放中斷以及切換曲目等狀態。修正發生錯誤的最底層,還原測試條件並重新執行相同狀態矩陣。
設定
專案設定
- 在 Apple Developer Portal 中,為 App 的明確 Bundle ID 啟用 MusicKit App Service,以便 MusicKit 自動生成開發者權杖(developer token)。
- 在 Info.plist 中新增
NSAppleMusicUsageDescription,說明 App 存取使用者媒體庫的原因。 - 若需支援背景播放,請在
UIBackgroundModes中加入audio背景模式。
匯入模組
import MusicKit // 目錄、授權、播放
import MediaPlayer // MPRemoteCommandCenter, MPNowPlayingInfoCenter
權限要求
存取使用者的音樂資料或播放 Apple Music 內容前,必須先請求權限。request() 會在必要時顯示 Apple 的同意對話框;使用 currentStatus 可以在不跳出提示的情況下讀取目前設定。
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
目錄搜尋
使用 MusicCatalogSearchRequest 搜尋 Apple Music 目錄。目錄查詢可以取得 Apple Music 的資源,但播放訂閱目錄中的內容仍必須由 MusicSubscription.current.canPlayCatalogContent 來控管權限。
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)
// 從 url 載入專輯封面
}
}
訂閱檢查
在提供播放功能前,先確認使用者是否擁有有效的 Apple Music 訂閱。
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 {
// 開啟完整播放介面
} else {
// 顯示訂閱提示
}
}
}
推廣 Apple Music 訂閱
當使用者尚未訂閱時,跳出 Apple Music 訂閱推廣頁面(subscription offer sheet)。先檢查 canBecomeSubscriber,當頁面需要情境中繼資料或載入錯誤處理時,傳入 MusicSubscriptionOffer.Options 或 onLoadCompletion。
import MusicKit
import SwiftUI
struct MusicOfferView: View {
@State private var showOffer = false
var body: some View {
Button("Subscribe to Apple Music") {
Task {
let subscription = try? await MusicSubscription.current
showOffer = subscription?.canBecomeSubscriber == true
}
}
.musicSubscriptionOffer(
isPresented: $showOffer,
options: .default,
onLoadCompletion: { error in
if let error {
// 在 App UI 或診斷資訊中呈現載入錯誤
print(error)
}
}
)
}
}
使用 ApplicationMusicPlayer 進行播放
ApplicationMusicPlayer 可以獨立於「音樂」App 之外播放 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 為 @Observable 屬性
let state = player.state
switch state.playbackStatus {
case .playing:
break
case .paused:
break
case .stopped, .interrupted, .seekingForward, .seekingBackward:
break
@unknown default:
break
}
}
佇列管理
使用 ApplicationMusicPlayer.Queue 建立與操作播放佇列。
// 使用多個項目初始化
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)
}
正在播放資訊
更新 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
}
// 停用不支援的命令
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
}
}
常見錯誤
| 常見錯誤 | 修正方式 |
|---|---|
| 在設定 App Service 與使用目的說明字串前即進行權限除錯 | 先確認 App Service、Bundle ID 與 NSAppleMusicUsageDescription 設定。 |
| 未設定訂閱檢查門檻即將目錄內容加入佇列 | 檢查 canPlayCatalogContent;僅在 canBecomeSubscriber 為真時提供訂閱。 |
在 App 專用播放中使用 SystemMusicPlayer |
請使用 ApplicationMusicPlayer;系統播放器會變更「音樂」App 的全域佇列。 |
| 僅發布一次「正在播放」中繼資料 | 當曲目、時長、播放速率與已播放時間變更時重新整理。 |
| 註冊不支援的遠端命令 | 停用不支援的命令;受支援的處理常式必須執行動作並回傳 .success。 |
審查清單
- [ ] 已為 App 的明確 Bundle ID 啟用 MusicKit App Service
- [ ] 已在 Info.plist 中新增
NSAppleMusicUsageDescription - [ ] 在進行任何 MusicKit 存取前呼叫
MusicAuthorization.request() - [ ] 在嘗試播放目錄內容前已檢查訂閱狀態
- [ ] 在顯示訂閱推廣前已檢查
canBecomeSubscriber - [ ] 在寫入媒體庫前已檢查
hasCloudLibraryEnabled - [ ] 使用
ApplicationMusicPlayer(而非SystemMusicPlayer)進行 App 範圍內的播放 - [ ] 若需在背景播放音樂,已啟用背景音訊模式(Background audio mode)
- [ ] 每當曲目變更時均更新「正在播放」資訊(針對自訂音訊)
- [ ] 支援的命令其遠端命令處理常式均回傳
.success - [ ] 已透過
isEnabled = false停用不支援的遠端命令 - [ ] 在「正在播放」資訊中提供封面圖示以供鎖定畫面顯示
- [ ] 定期更新已播放時間以維持進度條精確度
- [ ] 當使用者未訂閱 Apple Music 時顯示訂閱推廣
參考資料
- 進階模式(SwiftUI 整合、流派瀏覽、播放清單管理):references/musickit-patterns.md
- MusicKit 框架
- 使用 Apple Music API 自動生成開發者權杖
- MusicAuthorization
- ApplicationMusicPlayer
- MusicCatalogSearchRequest
- MusicSubscription
- canPlayCatalogContent
- canBecomeSubscriber
- hasCloudLibraryEnabled
- MusicCatalogChartsRequest 建構子
- [musicSubscriptionOffer(isPresented:options:onLoadCompletion:)](htt




