mcpField
M
musickit

musickit

github.com/dpearson2699开发1.2k3.2k 次安装13 周前更新
代码仓库

关于 musickit

MusicKit 是面向 Apple 平台开发者的 Swift 技能指南,用于搜索 Apple Music 曲库、管理应用内播放、检查订阅状态并设置播放信息。它涵盖授权、播放队列以及锁定屏幕和远程控制。

主要功能

  1. 01搜索 Apple Music 曲库
  2. 02检查音乐授权与订阅
  3. 03管理应用内播放和队列
  4. 04发布正在播放的曲目信息
  5. 05处理远程播放控制

适用场景

  1. 01在应用中搜索歌曲和专辑
  2. 02按订阅状态决定是否播放曲库内容
  3. 03构建并更新播放队列
  4. 04让锁定屏幕显示曲目信息并响应控制

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill musickit

安装后在 Claude Code 中输入/musickit确认已加载。

附带文件evals/evals.jsonreferences/musickit-patterns.md

详细介绍

MusicKit

搜索 Apple Music 目录,使用 ApplicationMusicPlayer 管理播放,检查订阅状态,并通过 MPNowPlayingInfoCenter 和 MPRemoteCommandCenter 发布“正在播放”元数据。

目录

工作流

  1. 调试代码前,先验证 MusicKit App Service、Bundle ID、用途说明字符串和后台音频模式。
  2. 请求授权,然后明确处理所有非授权状态。
  3. 搜索或加载目录内容,并在将内容加入播放队列前检查 MusicSubscription.current。
  4. 对于应用范围内的播放,选择 ApplicationMusicPlayer;仅当应用负责相应界面时,才接入“正在播放”信息和远程命令。
  5. 测试已授权、已拒绝、未订阅、离线、队列失败、中断和曲目变更等状态。修复最小范围内的故障层,恢复测试样例,并重新运行相同的状态矩阵。

设置

项目配置
  1. 在 Apple Developer 门户中为应用的显式 Bundle ID 启用 MusicKit App Service,以便 MusicKit 自动生成开发者令牌。
  2. 在 Info.plist 中添加 NSAppleMusicUsageDescription,说明应用访问用户媒体资料库的原因。
  3. 如需后台播放,请将 audio 后台模式添加到 UIBackgroundModes。
导入
import MusicKit       // Catalog, auth, playback
import MediaPlayer    // MPRemoteCommandCenter, MPNowPlayingInfoCenter

授权

访问用户的音乐数据或播放 Apple Music 内容前,请先请求权限。必要时,request() 会显示 Apple 的授权对话框;使用 currentStatus 可在不触发提示的情况下读取当前设置。

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 资源,但播放订阅目录内容仍必须先通过 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)
        // 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 订阅优惠页面。先检查 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 {
                    // Surface loading errors in app UI or diagnostics.
                    print(error)
                }
            }
        )
    }
}

使用 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
    }
}

常见错误

错误修复方法
尚未配置 App Service 和用途说明字符串就开始调试授权首先验证服务、Bundle ID 和 NSAppleMusicUsageDescription。
未检查订阅状态就将目录内容加入队列检查 canPlayCatalogContent;仅在 canBecomeSubscriber 为 true 时提供订阅。
使用 SystemMusicPlayer 播放应用自有内容使用 ApplicationMusicPlayer;系统播放器会更改 Music App 的全局队列。
仅发布一次“正在播放”元数据在曲目、时长、速率和已播放时间发生变化时刷新。
注册不受支持的远程命令将其禁用;受支持的处理程序必须执行相应操作并返回 .success。

检查清单

  • 已为应用的显式 Bundle ID 启用 MusicKit App Service
  • 已将 NSAppleMusicUsageDescription 添加到 Info.plist
  • 在访问任何 MusicKit 内容之前已调用 MusicAuthorization.request()
  • 尝试播放目录内容之前已检查订阅状态
  • 显示订阅优惠之前已检查 canBecomeSubscriber
  • 写入资料库之前已检查 hasCloudLibraryEnabled
  • 应用范围的播放使用了 ApplicationMusicPlayer(而非 SystemMusicPlayer)
  • 如果音乐需要在后台播放,已启用后台音频模式
  • 自定义音频的每次曲目变更都会更新“正在播放”信息
  • 受支持的命令对应的远程命令处理程序会返回 .success
  • 不受支持的远程命令已通过 isEnabled = false 禁用
  • “正在播放”信息中已提供封面图,以便在锁定屏幕上显示
  • 已定期更新已播放时间,确保进度条准确
  • 用户没有 Apple Music 订阅时会显示订阅优惠

参考资料

评论

支持 Markdown