musickit

musickit

熱門

使用 MusicKit 與 MediaPlayer 整合 Apple Music 播放、音樂庫搜尋以及「正在播放」中繼資料。適用於在 iOS App 中加入音樂搜尋、Apple Music 訂閱流程、佇列管理、播放控制、遠端控制命令處理或「正在播放」資訊等情境。

967星標
49分支
更新於 2026/7/31
SKILL.md
唯讀
名稱
musickit
描述

使用 MusicKit 與 MediaPlayer 整合 Apple Music 播放、音樂庫搜尋以及「正在播放」中繼資料。適用於在 iOS App 中加入音樂搜尋、Apple Music 訂閱流程、佇列管理、播放控制、遠端控制命令處理或「正在播放」資訊等情境。

MusicKit

使用 ApplicationMusicPlayer 搜尋 Apple Music 目錄與管理播放,檢查訂閱狀態,並透過 MPNowPlayingInfoCenterMPRemoteCommandCenter 發布「正在播放」中繼資料。

目錄

開發流程

  1. 在除錯程式碼前,先確認已啟用 MusicKit App Service、Bundle Identifier、使用目的說明字串(purpose string)以及背景音訊模式(background-audio mode)。
  2. 請求授權,並針對所有未授權的狀態進行明確建模處理。
  3. 在將內容加入播放佇列前,先搜尋或載入目錄內容,並檢查 MusicSubscription.current
  4. 在 App 內部播放時,選擇使用 ApplicationMusicPlayer;僅在 App 擁有這些控制介面時,才接管「正在播放」與遠端命令。
  5. 測試已授權、拒絕授權、未訂閱、離線、佇列失敗、播放中斷以及切換曲目等狀態。修正發生錯誤的最底層,還原測試條件並重新執行相同狀態矩陣。

設定

專案設定

  1. 在 Apple Developer Portal 中,為 App 的明確 Bundle ID 啟用 MusicKit App Service,以便 MusicKit 自動生成開發者權杖(developer token)。
  2. 在 Info.plist 中新增 NSAppleMusicUsageDescription,說明 App 存取使用者媒體庫的原因。
  3. 若需支援背景播放,請在 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.OptionsonLoadCompletion

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 時顯示訂閱推廣

參考資料