| name | axiom-now-playing-musickit |
| description | MusicKit Now Playing integration patterns. Use when playing Apple Music content with ApplicationMusicPlayer and understanding automatic vs manual Now Playing info updates. |
| license | MIT |
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
import MusicKit
func requestMusicAccess() async -> Bool {
let status = await MusicAuthorization.request()
return status == .authorized
}
let currentStatus = MusicAuthorization.currentStatus
Check Apple Music Subscription
func checkSubscription() async -> Bool {
do {
let subscription = try await MusicSubscription.current
return subscription.canPlayCatalogContent
} catch {
return false
}
}
func observeSubscription() {
Task {
for await subscription in MusicSubscription.subscriptionUpdates {
if subscription.canPlayCatalogContent {
} else if subscription.canBecomeSubscriber {
showSubscriptionOffer()
}
}
}
}
Subscription Offer Sheet
import MusicKit
import StoreKit
MusicSubscriptionOffer.Options(
messageIdentifier: .playMusic,
itemID: song.id
)
.musicSubscriptionOffer(isPresented: $showOffer, options: offerOptions)
Graceful Fallback Without Subscription
@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 {
try await play(song: song)
} else {
if let previewURL = song.previewAssets?.first?.url {
playPreview(url: previewURL)
}
}
} catch {
handleError(error)
}
}
}
Playback
Basic Playback
import MusicKit
@MainActor
class MusicKitPlayer {
private let player = ApplicationMusicPlayer.shared
func play(song: Song) async throws {
player.queue = [song]
try await player.play()
}
func pause() {
player.pause()
}
func stop() {
player.stop()
}
}
Observing Playback State
@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() {
Task {
for await state in player.state.objectWillChange.values {
isPlaying = player.state.playbackStatus == .playing
}
}
Task {
for await queue in player.queue.objectWillChange.values {
currentEntry = player.queue.currentEntry
}
}
}
}
Queue Management
Setting the Queue
let player = ApplicationMusicPlayer.shared
player.queue = [song]
player.queue = ApplicationMusicPlayer.Queue(album: album)
player.queue = ApplicationMusicPlayer.Queue(playlist: playlist)
player.queue = ApplicationMusicPlayer.Queue(for: [song1, song2, song3])
player.queue = ApplicationMusicPlayer.Queue(for: songs, startingAt: songs[2])
Queue Operations
try await player.skipToNextEntry()
try await player.skipToPreviousEntry()
player.restartCurrentEntry()
try await player.queue.insert(song, position: .afterCurrentEntry)
try await player.queue.insert(song, position: .tail)
player.state.shuffleMode = .songs
player.state.repeatMode = .all
Observing Queue Changes
if let entry = player.queue.currentEntry {
let title = entry.title
let subtitle = entry.subtitle
let artwork = entry.artwork
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:
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
case ownContent
}
func playAppleMusicSong(_ song: Song) async throws {
avPlayer?.pause()
currentSource = .appleMusic
musicKitPlayer.queue = [song]
try await musicKitPlayer.play()
}
func playOwnContent(_ url: URL) {
musicKitPlayer.pause()
currentSource = .ownContent
avPlayer = AVPlayer(url: url)
avPlayer?.play()
updateNowPlayingForOwnContent()
}
() {
nowPlayingInfo [: ]()
nowPlayingInfo[]
.default().nowPlayingInfo nowPlayingInfo
}
}
Common Mistake
func playAppleMusicSong(_ song: Song) async throws {
try await ApplicationMusicPlayer.shared.play()
var nowPlayingInfo = [String: Any]()
nowPlayingInfo[MPMediaItemPropertyTitle] = song.title
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
func playAppleMusicSong(_ song: Song) async throws {
try await ApplicationMusicPlayer.shared.play()
}
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