WWDC Quick Look đź’“ By SwiftGGTeam
Integrate MusicKit into your app

Integrate MusicKit into your app

Watch original video

Highlight

MusicKit adds two native SwiftUI modifiers, .musicPicker() and .musicSubscriptionOffer(), so even non-music apps can handle authorization, song selection, playback controls, and subscription prompts in just a few lines of code.

Core Ideas

Why Apple Music integration used to be painful

Imagine adding background music to a fitness app. The old flow looked like this: write authorization logic, check whether the user subscribes to Apple Music, design your own subscription prompt if they do not, build a song picker if they do, manage the playback queue, and observe playback state. By the time you only wanted a BGM feature, you might have written a thousand lines of code.

What Apple changed

MusicKit in WWDC26 packages that flow into two SwiftUI modifiers and a native music picker. Use MusicAuthorization.request() for authorization, .musicSubscriptionOffer() for subscription guidance, .musicPicker() for picking music, and ApplicationMusicPlayer.shared for playback control. Integration moves from “rebuild half the app” to “add a few modifiers.”

How the new capabilities remove friction

Take the fitness app from the session. When the user starts a cycling workout, they tap a “Pick Music” button to present .musicPicker() and choose songs from the Apple Music catalog or their personal library. If the user is not subscribed, the interface can automatically show a subscription button; tapping it presents .musicSubscriptionOffer() so they can subscribe without leaving the app. The selected song plays through ApplicationMusicPlayer, while the app shows album artwork, song metadata, and playback controls directly in its UI. The whole experience feels native and seamless.

Details

Authorization and project setup

(01:03) The first step in adopting MusicKit is enabling the MusicKit service in Xcode. In the App Services tab of your App ID registration, check MusicKit. Xcode automatically generates the developer token, so you do not need to manage it manually.

(03:12) Request user authorization with MusicAuthorization.request(). This asynchronous method returns the authorization status. To make the permission prompt explain itself clearly, add the Media Library capability in Xcode’s Signing & Capabilities settings and fill in the description text for why your app uses the music library.

Subscription guidance

(04:46) For users who do not subscribe to Apple Music, use the .musicSubscriptionOffer() modifier to show the subscription offer directly inside your app. Users do not need to jump to the Music app or the App Store.

@State var showSubscriptionOffer = false

let options = MusicSubscriptionOffer.Options(
    messageIdentifier: .playMusic
)

@ViewBuilder
var musicSubscriptionButton: some View {
    Button("Subscribe to Apple Music", systemImage: "music.note") {
        showSubscriptionOffer = true
    }
    .musicSubscriptionOffer(isPresented: $showSubscriptionOffer, options: options)
}

Key points:

  • The messageIdentifier in MusicSubscriptionOffer.Options determines the copy and style shown in the offer UI; .playMusic fits playback scenarios.
  • Developers can earn subscription commission through the Apple Services Performance Partner Program.
  • The offer is shown and hidden through the isPresented binding.

(05:59) In the main view, observe subscription state and show the button only when the user is eligible to subscribe:

@State var subscription: MusicSubscription?

var body: some View {
    VStack {
        if let subscription, subscription.canBecomeSubscriber {
            musicSubscriptionButton
        }
    }
    .task(id: isAuthorized) {
        self.subscription = try? await MusicSubscription.current
        for await subscription in MusicSubscription.subscriptionUpdates {
            self.subscription = subscription
        }
    }
}

Key points:

  • MusicSubscription.current gets the current subscription state.
  • MusicSubscription.subscriptionUpdates is an AsyncSequence that automatically emits new values when subscription state changes.
  • canBecomeSubscriber tells you whether the user is eligible to subscribe, meaning they are not subscribed and their region supports it.

Music picker

(08:48) .musicPicker() is a SwiftUI view modifier that presents a native interface for browsing the Apple Music catalog and the user’s personal library. It supports single selection, multiple selection, albums, and playlists.

@State var showMusicPicker = false
@State var selectedSong: Song? = nil

@ViewBuilder
var musicPickerButton: some View {
    Button("Pick some Music", systemImage: "music.note.list") {
        showMusicPicker = true
    }
    .musicPicker(isPresented: $showMusicPicker, selection: $selectedSong)
}

Key points:

  • Bind selection to Song? for single selection, or to [Song] for multiple selection.
  • Users can use the picker without an Apple Music subscription, but unsubscribed users only see content in their personal library.
  • The picker supports search, album detail browsing, and selecting an entire playlist.

Choosing between two players

(10:54) MusicKit provides two players:

FeatureSystemMusicPlayerApplicationMusicPlayer
What it controlsThe system Music appYour app itself
Background playbackSupported automaticallyRequires Audio Background Mode
Queue accessRead-only current itemFull read-write access
Best fitStandalone music clientsApps that need BGM, such as fitness or meditation apps

ApplicationMusicPlayer supports lazy loading for its queue. If you initialize a queue with an album or playlist, the player loads songs as needed instead of loading everything up front, which can significantly reduce startup latency.

(13:02) The prepareToPlay() method can pre-buffer audio assets before the user taps play, removing the wait after the tap:

let player = ApplicationMusicPlayer.shared

// Buffer ahead of time
Task {
    try await player.prepareToPlay()
}

Playback controls and observing state

(14:49) The queue and state of ApplicationMusicPlayer are observable objects, so you can use them directly in SwiftUI views.

Showing album artwork for the current song:

@State var queue = ApplicationMusicPlayer.shared.queue

var body: some View {
    VStack {
        if let artwork = queue.currentEntry?.artwork {
            ArtworkImage(artwork, width: 200, height: 200)
        } else {
            RoundedRectangle(cornerRadius: 16)
                .fill(.quaternary)
                .frame(width: 200, height: 200)
        }
    }
}

Key points:

  • ArtworkImage is a SwiftUI view provided by MusicKit that handles artwork loading and display automatically.
  • queue.currentEntry?.artwork gets the artwork resource for the currently playing item.
  • A .quaternary fill makes a natural-looking placeholder while artwork is unavailable.

(15:06) Showing the song title and subtitle:

if let currentSong = queue.currentEntry {
    Text(currentSong.title)
        .font(.title3.bold())

    if let subtitle = currentSong.subtitle {
        Text(subtitle)
            .font(.subheadline)
            .foregroundStyle(.secondary)
    }
}

(15:14) Play and pause control:

let player = ApplicationMusicPlayer.shared
@State var state = ApplicationMusicPlayer.shared.state

var isPlaying: Bool {
    state.playbackStatus == .playing
}

var playPause: some View {
    Button(
        isPlaying ? "Pause" : "Play",
        systemImage: isPlaying ? "pause.fill" : "play.fill"
    ) {
        if isPlaying {
            player.pause()
        } else {
            Task {
                try await player.play()
            }
        }
    }
}

Key points:

  • state.playbackStatus returns enum values such as .playing and .paused.
  • player.play() and player.pause() are asynchronous methods and should be called from a Task.
  • Playback state changes automatically trigger SwiftUI view updates.

(15:38) Previous and next controls:

let player = ApplicationMusicPlayer.shared

var controls: some View {
    HStack {
        Button("Back", systemImage: "backward.fill") {
            Task {
                try await player.skipToPreviousEntry()
            }
        }
        // ...
        Button("Next", systemImage: "forward.fill") {
            Task {
                try await player.skipToNextEntry()
            }
        }
    }
}

Catalog requests and cross-territory equivalents

(16:26) MusicCatalogResourceRequest requests specific resources from the Apple Music catalog. You can specify related data, such as a song’s Artists relationship, and limit the number of returned items.

(18:58) Cross-territory equivalence is an important MusicKit feature. The same song can have different IDs in different Apple Music territories, and some territories may offer a “clean” version instead of an explicit version. The .findEquivalents option handles these mappings automatically:

func fetchSongs(songIDs: [MusicItemID]) async throws -> (featured: Song?, other: [Song]) {
    var request = MusicCatalogResourceRequest<Song>(
        matching: \.id, memberOf: songIDs
    )
    request.options = [.findEquivalents]

    let response = try await request.response()

    let featuredSongID = songIDs[0]
    let featuredSong = response.item(for: featuredSongID)

    let others: [Song] = songIDs[1...].compactMap { songID in
        return response.item(for: songID)
    }

    return (featuredSong, others)
}

Key points:

  • MusicCatalogResourceRequest<Song> is a generic request, and <Song> specifies the returned type.
  • matching: \.id, memberOf: songIDs matches songs in the request list by ID.
  • .findEquivalents enables cross-territory equivalent lookup and automatically substitutes unavailable resources.
  • response.item(for:) gets a result by the original ID, even if the actual returned item is an equivalent version.
  • MusicItemCollection supports pagination through hasNextBatch and nextBatch().

Practical Ideas

1. Add workout BGM to a fitness app

Add a “Pick Music” button to the workout start screen. Use .musicPicker() to let users choose a playlist, then use ApplicationMusicPlayer to loop it during the workout. Call prepareToPlay() during the countdown so the music starts immediately when the user taps “Start.”

Entry APIs: ApplicationMusicPlayer.shared + .musicPicker()

2. Build white noise or background music for a focus timer

A pomodoro or meditation app can use MusicCatalogResourceRequest to request focus playlists or white-noise albums from Apple Music, then present them as presets. Users can start playback with a tap without leaving your app to search in Music.

Entry APIs: MusicCatalogResourceRequest<Playlist> + ApplicationMusicPlayer

3. Add a “share what I’m listening to” feature to a social app

Use queue.currentEntry to get the currently playing song, then combine it with ArtworkImage to generate a share card. Use .findEquivalents on MusicCatalogResourceRequest so the shared song link can open correctly for users in different territories.

Entry APIs: queue.currentEntry + MusicCatalogResourceRequest + .findEquivalents

4. Switch dynamic background music in a game

Preload different MusicItemCollection values for game states such as combat, exploration, and story moments. Use ApplicationMusicPlayer queue operations to switch seamlessly. After enabling Audio Background Mode, music can continue playing even if the player switches to another app.

Entry APIs: ApplicationMusicPlayer.shared.queue + prepareToPlay()

5. Filter content safely in a children’s app

Use the .findEquivalents option on MusicCatalogResourceRequest so when explicit content is unavailable under the user’s account settings, MusicKit automatically returns a clean version. Your app does not need to maintain its own content rating logic.

Entry APIs: MusicCatalogResourceRequest + .findEquivalents

Comments

GitHub Issues · utterances