mcpField
A
axiom-now-playing-musickit

axiom-now-playing-musickit

github.com/charleswiltgen开发1.2k166 次安装今天更新
代码仓库

关于 axiom-now-playing-musickit

面向使用 MusicKit 集成 Apple Music 的 Swift 技能,介绍授权、订阅检查、播放和队列管理,并说明 ApplicationMusicPlayer 会自动更新 Now Playing 信息。

主要功能

  1. 01请求 MusicKit 授权
  2. 02检查 Apple Music 订阅
  3. 03控制播放和队列
  4. 04观察播放及队列状态
  5. 05处理混合内容播放

适用场景

  1. 01构建 Apple Music 播放流程
  2. 02根据订阅状态控制目录播放
  3. 03显示当前曲目和封面
  4. 04将 Apple Music 与应用内容结合

安装

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

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-now-playing-musickit

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

详细介绍

MusicKit 集成(Apple Music)

所需时间:5-10 分钟

关键要点

MusicKit 的 ApplicationMusicPlayer 会自动发布信息到 MPNowPlayingInfoCenter。 播放 Apple Music 内容时,无需手动更新“正在播放”信息。

自动处理的内容

使用 ApplicationMusicPlayer 时:

  • 曲目标题、艺人、专辑
  • 封面(Apple 的专辑封面)
  • 时长和已播放时间
  • 播放速率(播放/暂停状态)

系统会为你处理所有 MPNowPlayingInfoCenter 更新。

不会自动处理的内容

  • 自定义元数据(章节标记、自定义艺人备注)
  • 对标准控制项之外的远程命令进行自定义
  • 将 MusicKit 内容与自有内容混合

订阅与授权

检查 Music 授权
import MusicKit

func requestMusicAccess() async -> Bool {
    let status = await MusicAuthorization.request()
    return status == .authorized
}

// Check current status without prompting
let currentStatus = MusicAuthorization.currentStatus
// .authorized, .denied, .notDetermined, .restricted
检查 Apple Music 订阅
func checkSubscription() async -> Bool {
    do {
        let subscription = try await MusicSubscription.current
        return subscription.canPlayCatalogContent
    } catch {
        return false
    }
}

// Observe subscription changes
func observeSubscription() {
    Task {
        for await subscription in MusicSubscription.subscriptionUpdates {
            if subscription.canPlayCatalogContent {
                // Full Apple Music access
            } else if subscription.canBecomeSubscriber {
                // Show subscription offer
                showSubscriptionOffer()
            }
        }
    }
}
订阅优惠页
import MusicKit
import StoreKit

// Present Apple Music subscription offer
MusicSubscriptionOffer.Options(
    messageIdentifier: .playMusic,
    itemID: song.id
)

// In SwiftUI
.musicSubscriptionOffer(isPresented: $showOffer, options: offerOptions)
无订阅时的平稳回退
@MainActor
class MusicPlayer: ObservableObject {
    @Published var canPlay = false

    func handlePlayRequest(song: Song) async {
        let authorized = await requestMusicAccess()
        guard authorized else {
            showAuthorizationDeniedAlert()
            return
        }

        do {
            let subscription = try await MusicSubscription.current
            if subscription.canPlayCatalogContent {
                // Full playback
                try await play(song: song)
            } else {
                // Preview only (30-second clips)
                if let previewURL = song.previewAssets?.first?.url {
                    playPreview(url: previewURL)
                }
            }
        } catch {
            handleError(error)
        }
    }
}

播放

基本播放
import MusicKit

@MainActor
class MusicKitPlayer {
    private let player = ApplicationMusicPlayer.shared

    func play(song: Song) async throws {
        // ✅ Just play - MPNowPlayingInfoCenter updates automatically
        player.queue = [song]
        try await player.play()

        // ❌ DO NOT manually set nowPlayingInfo here
        // MPNowPlayingInfoCenter.default().nowPlayingInfo = [...] // WRONG!
    }

    func pause() {
        player.pause()
    }

    func stop() {
        player.stop()
    }
}
观察播放状态
@MainActor
class PlayerViewModel: ObservableObject {
    private let player = ApplicationMusicPlayer.shared
    @Published var isPlaying = false
    @Published var currentEntry: ApplicationMusicPlayer.Queue.Entry?
    @Published var playbackTime: TimeInterval = 0

    func observeState() {
        // Observe playback status
        Task {
            for await state in player.state.objectWillChange.values {
                isPlaying = player.state.playbackStatus == .playing
            }
        }

        // Observe current entry (track changes)
        Task {
            for await queue in player.queue.objectWillChange.values {
                currentEntry = player.queue.currentEntry
            }
        }
    }
}

队列管理

设置队列
let player = ApplicationMusicPlayer.shared

// Single song
player.queue = [song]

// Album
player.queue = ApplicationMusicPlayer.Queue(album: album)

// Playlist
player.queue = ApplicationMusicPlayer.Queue(playlist: playlist)

// Multiple items
player.queue = ApplicationMusicPlayer.Queue(for: [song1, song2, song3])

// Start at specific item
player.queue = ApplicationMusicPlayer.Queue(for: songs, startingAt: songs[2])
队列操作
// Skip to next
try await player.skipToNextEntry()

// Skip to previous
try await player.skipToPreviousEntry()

// Restart current track
player.restartCurrentEntry()

// Append to queue
try await player.queue.insert(song, position: .afterCurrentEntry)
try await player.queue.insert(song, position: .tail)  // End of queue

// Shuffle and repeat
player.state.shuffleMode = .songs    // .off, .songs
player.state.repeatMode = .all       // .none, .one, .all
观察队列变化
// Current track info
if let entry = player.queue.currentEntry {
    let title = entry.title
    let subtitle = entry.subtitle      // Artist name
    let artwork = entry.artwork         // Artwork for display

    // Get full Song object if needed
    if case .song(let song) = entry.item {
        let albumTitle = song.albumTitle
    }
}

混合应用(自有内容 + Apple Music)

如果你的应用同时播放 Apple Music 和自有内容:

import MusicKit

@MainActor
class HybridPlayer {
    private let musicKitPlayer = ApplicationMusicPlayer.shared
    private var avPlayer: AVPlayer?
    private var currentSource: ContentSource = .none

    enum ContentSource {
        case none
        case appleMusic      // MusicKit handles Now Playing
        case ownContent  // We handle Now Playing
    }

    func playAppleMusicSong(_ song: Song) async throws {
        // Switch to MusicKit
        avPlayer?.pause()
        currentSource = .appleMusic

        musicKitPlayer.queue = [song]
        try await musicKitPlayer.play()
        // ✅ MusicKit handles Now Playing automatically
    }

    func playOwnContent(_ url: URL) {
        // Switch to AVPlayer
        musicKitPlayer.pause()
        currentSource = .ownContent

        avPlayer = AVPlayer(url: url)
        avPlayer?.play()

        // ✅ Manually update Now Playing (see axiom-now-playing)
        updateNowPlayingForOwnContent()
    }

    private func updateNowPlayingForOwnContent() {
        var nowPlayingInfo = [String: Any]()
        nowPlayingInfo[MPMediaItemPropertyTitle] = "My Track"
        // ... rest of manual setup
        MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
    }
}

常见错误

// ❌ WRONG - Overwrites MusicKit's automatic Now Playing data
func playAppleMusicSong(_ song: Song) async throws {
    try await ApplicationMusicPlayer.shared.play()

    // ❌ This clears MusicKit's Now Playing info!
    var nowPlayingInfo = [String: Any]()
    nowPlayingInfo[MPMediaItemPropertyTitle] = song.title
    MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}

// ✅ CORRECT - Let MusicKit handle it
func playAppleMusicSong(_ song: Song) async throws {
    try await ApplicationMusicPlayer.shared.play()
    // That's it! MusicKit publishes Now Playing automatically.
}

何时在 MusicKit 中使用手动更新

仅在以下情况下覆盖 MPNowPlayingInfoCenter:

  • 混入额外元数据(例如播客章节标记)
  • 在 Apple Music 内容旁显示自定义内容
  • 有特定理由需要替换 MusicKit 的自动行为

默认做法:让 MusicKit 自动管理“正在播放”信息。

资源

文档:/musickit、/musickit/applicationmusicplayer、/musickit/musicsubscription

技能:axiom-now-playing、axiom-now-playing-carplay

评论

支持 Markdown