<!--
{
  "documentType" : "article",
  "framework" : "NowPlaying",
  "identifier" : "/documentation/NowPlaying/publishing-media-sessions",
  "metadataVersion" : "0.1.0",
  "role" : "article",
  "title" : "Publishing media sessions"
}
-->

# Publishing media sessions

Show your app’s media on the Lock Screen and Control Center.

## Overview

A local session publishes media that’s playing on the current device. When you create one, the system shows your content’s metadata and playback controls across the Lock Screen, Control Center, and connected accessories, including CarPlay and AirPlay-capable devices.

To publish a local session, create a type that conforms to [`MediaSessionRepresentable`](/documentation/NowPlaying/MediaSessionRepresentable), then register it with the system using [`MediaSession`](/documentation/NowPlaying/MediaSession). The framework observes your model with <doc://com.apple.documentation/documentation/Observation/Observable> and syncs updates to the system automatically.

## Create a session representable

To publish your media to the system, conform to [`MediaSessionRepresentable`](/documentation/NowPlaying/MediaSessionRepresentable) and mark your type with the <doc://com.apple.documentation/documentation/Observation/Observable> macro. The framework reads four properties from your type: [`id`](/documentation/NowPlaying/MediaSessionRepresentable/id) to uniquely identify the session, [`content`](/documentation/NowPlaying/MediaSessionRepresentable/content) to describe what’s playing, [`playbackSnapshot`](/documentation/NowPlaying/MediaSessionRepresentable/playbackSnapshot) to capture playback state, and [`commands`](/documentation/NowPlaying/MediaSessionRepresentable/commands) to declare the controls your app supports.

```swift
import NowPlaying

@Observable
@MainActor
final class AudioPlayer: MediaSessionRepresentable {
    let id: String = UUID().uuidString

    var currentTrack: Track?
    var isPlaying: Bool = false
    var elapsedTime: TimeInterval = 0
    var timestamp: Date = .now

    var content: (any MediaContentRepresentable)? {
        guard let track = currentTrack else { return nil }
        return MusicContent(
            id: track.id,
            songTitle: track.title,
            artistName: track.artist,
            albumName: track.album,
            type: .audio,
            duration: .finite(track.duration),
            artwork: nil
        )
    }

    var playbackSnapshot: MediaPlaybackSnapshot? {
        MediaPlaybackSnapshot(
            state: isPlaying ? .playing() : .paused,
            elapsedTime: elapsedTime,
            timestamp: timestamp
        )
    }

    var commands: [MediaCommand] {
        [
            .play { self.play() },
            .pause { self.pause() },
        ]
    }

    func play() { /* ... */ }
    func pause() { /* ... */ }
}
```

The [`content`](/documentation/NowPlaying/MediaSessionRepresentable/content) property returns a content type that matches your media. The framework provides [`MusicContent`](/documentation/NowPlaying/MusicContent), [`PodcastContent`](/documentation/NowPlaying/PodcastContent), [`MovieContent`](/documentation/NowPlaying/MovieContent), [`TVShowContent`](/documentation/NowPlaying/TVShowContent), [`BookContent`](/documentation/NowPlaying/BookContent), [`RadioContent`](/documentation/NowPlaying/RadioContent), [`HomeMediaContent`](/documentation/NowPlaying/HomeMediaContent), and [`GenericContent`](/documentation/NowPlaying/GenericContent).

The [`playbackSnapshot`](/documentation/NowPlaying/MediaSessionRepresentable/playbackSnapshot) property returns a [`MediaPlaybackSnapshot`](/documentation/NowPlaying/MediaPlaybackSnapshot) that captures the current playback state and progress. Provide the elapsed time and a timestamp so the system extrapolates the playback position between updates.

## Add artwork

Provide artwork by adding an [`Artwork`](/documentation/NowPlaying/Artwork) structure for your content. The framework requests artwork from your process asynchronously at the size the system requires.

```swift
var content: (any MediaContentRepresentable)? {
    guard let track = currentTrack else { return nil }
    return MusicContent(
        id: track.id,
        songTitle: track.title,
        artistName: track.artist,
        albumName: track.album,
        type: .audio,
        duration: .finite(track.duration),
        artwork: Artwork(id: track.artworkID) { size in
            let (data, _) = try await URLSession.shared.data(from: track.artworkURL)
            return try ArtworkRepresentation(data: data)
        }
    )
}
```

## Manage the session life cycle

Create a [`MediaSession`](/documentation/NowPlaying/MediaSession) from your representable and call [`requestToBecomeApplicationPrimary()`](/documentation/NowPlaying/MediaSession/requestToBecomeApplicationPrimary()) to begin publishing. When playback ends, set your session reference to `nil` to remove it from the system.

```swift
@Observable
@MainActor
final class AudioPlayer: MediaSessionRepresentable {
    var session: MediaSession<AudioPlayer>?

    func activate() async throws {
        let session = MediaSession(self)
        self.session = session
        try await session.requestToBecomeApplicationPrimary()
    }

    func deactivate() {
        session = nil
    }

    // ...
}
```

If your app plays audio (such as music, a podcast, or an audiobook), configure the audio session before calling [`requestToBecomeApplicationPrimary()`](/documentation/NowPlaying/MediaSession/requestToBecomeApplicationPrimary()):

```swift
let audioSession = AVAudioSession.sharedInstance()
try audioSession.setCategory(.playback, mode: .default)
try audioSession.setActive(true)
```

When you’re done with playback, call `setActive(false)` and pass `notifyOthersOnDeactivation` in the options so other apps can resume:

```swift
try audioSession.setActive(false, options: .notifyOthersOnDeactivation)
```

## Coordinate multiple sessions

Apps can have more than one local session. For example, your app can be both a music player and a podcast player. Each session observes its own representable independently. Use [`requestToBecomeApplicationPrimary()`](/documentation/NowPlaying/MediaSession/requestToBecomeApplicationPrimary()) to switch which session represents your app to the system.

To display a session on the Lock Screen and Control Center, call [`requestToBecomeSystemPrimary()`](/documentation/NowPlaying/MediaSession/requestToBecomeSystemPrimary()). This makes the session both your app’s primary session and the system’s active session.

```swift
try await session.requestToBecomeSystemPrimary()
```

> Note: Your app must be in the foreground when you call ``doc://com.apple.nowplaying/documentation/NowPlaying/MediaSession/requestToBecomeSystemPrimary()``. If your app is in the background, the request has no effect.

Observe [`isApplicationPrimary`](/documentation/NowPlaying/MediaSession/isApplicationPrimary) and [`isSystemPrimary`](/documentation/NowPlaying/MediaSession/isSystemPrimary) to reflect the current state in your UI.

## Respond to commands

Declare the commands your app supports in the [`commands`](/documentation/NowPlaying/MediaSessionRepresentable/commands) property. Each entry is a [`MediaCommand`](/documentation/NowPlaying/MediaCommand) created with a static factory method that takes an action closure. The system calls the closure when someone interacts with the corresponding control.

```swift
var commands: [MediaCommand] {
    [
        .play { self.play() },
        .pause { self.pause() },
        .next { self.next() }.enabled(hasNextTrack),
        .previous { self.previous() }.enabled(hasPreviousTrack),
        .seekToPosition { time in
            self.seek(to: time)
        },
        .feedback(status: currentFeedback) { newStatus in
            self.setFeedback(newStatus)
        },
    ]
}
```

Use [`enabled(_:)`](/documentation/NowPlaying/MediaCommand/enabled(_:)) to conditionally make a command available or unavailable. For example, make [`next(_:)`](/documentation/NowPlaying/MediaCommand/next(_:)) unavailable when there are no more tracks in the queue.

For the full set of commands you can offer, including seek, skip, playback rate, repeat, shuffle, and feedback, see [Playback commands](/documentation/NowPlaying/playback-commands).

---

Copyright &copy; 2026 Apple Inc. All rights reserved. | [Terms of Use](https://www.apple.com/legal/internet-services/terms/site.html) | [Privacy Policy](https://www.apple.com/privacy/privacy-policy)