# Axiom Now Playing Musickit

> MusicKit Now Playing integration patterns. Use when playing Apple Music content with ApplicationMusicPlayer and understanding automatic vs manual Now Playing info updates.

- Skill: `megastep/axiom-now-playing-musickit` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add megastep/axiom-now-playing-musickit`
- Raw SKILL.md: https://api.skillmd.com/api/skills/megastep/axiom-now-playing-musickit/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- License: MIT
- Author: megastep (https://skillmd.com/u/megastep)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/megastep/axiom-now-playing-musickit

---


# MusicKit Integration (Apple Music)

**Time cost**: 5-10 minutes

## Key Insight

**MusicKit's ApplicationMusicPlayer automatically publishes to MPNowPlayingInfoCenter.** You don't need to manually update Now Playing info when playing Apple Music content.

## What's Automatic

When using `ApplicationMusicPlayer`:
- Track title, artist, album
- Artwork (Apple's album art)
- Duration and elapsed time
- Playback rate (playing/paused state)

The system handles all MPNowPlayingInfoCenter updates for you.

## What's NOT Automatic

- Custom metadata (chapter markers, custom artist notes)
- Remote command customization beyond standard controls
- Mixing MusicKit content with your own content

---

## Subscription and Authorization

### Check Music Authorization

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

### Check Apple Music Subscription

```swift
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()
            }
        }
    }
}
```

### Subscription Offer Sheet

```swift
import MusicKit
import StoreKit

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

// In SwiftUI
.musicSubscriptionOffer(isPresented: $showOffer, options: offerOptions)
```

### Graceful Fallback Without Subscription

```swift
@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)
        }
    }
}
```

---

## Playback

### Basic Playback

```swift
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()
    }
}
```

### Observing Playback State

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

---

## Queue Management

### Setting the Queue

```swift
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])
```

### Queue Operations

```swift
// 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
```

### Observing Queue Changes

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

---

## Hybrid Apps (Own Content + Apple Music)

If your app plays both Apple Music and your own content:

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

---

## Common Mistake

```swift
// ❌ 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.
}
```

## When to Use Manual Updates with MusicKit

Only override MPNowPlayingInfoCenter if:
- You're mixing in additional metadata (e.g., podcast chapter markers)
- You're displaying custom content alongside Apple Music
- You have a specific reason to replace MusicKit's automatic behavior

**Default**: Let MusicKit manage Now Playing automatically.

## Resources

**Docs**: /musickit, /musickit/applicationmusicplayer, /musickit/musicsubscription

**Skills**: axiom-now-playing, axiom-now-playing-carplay

