Experimenting with MusicKit for Swift - Playback Status

In MusicKit for Swift, Apple provides us with a MusicPlayer object that our app uses to play music. It has three kinds of enumerations -

  • PlaybackStatus - The music player playback status modes.
  • RepeatMode - The repeat modes for the music player.
  • ShuffleMode - The shuffle modes for the music player.

In this post, we’ll explore PlaybackStatus and how we can observe it in our app.

PlaybackStatus is an enum that contains six cases -

  • stopped
  • playing
  • paused
  • interrupted - cases when there’s an incoming call. It automatically resumes playing after the call.
  • seekingForward
  • seekingBackward

The MusicPlayer also exposes a State class, which is an ObservableObject. We observe the properties of the music player such as playbackStatus, playbackRate, repeatMode and shuffleMode.

If you take the example of Apple Music, whenever you take your earphones off your ears, the music stops playing, and the app updates the interface accordingly. To achieve something similar in my app, Musadora, I observed MusicPlayer.PlaybackStatus in the now playing bar and view.

@ObservedObject private var state = ApplicationMusicPlayer.shared.state

In the NowPlayingPrimaryControlsView, I add a button for the play and pause button -

PlayButton(isPlaying: state.playbackStatus == .playing) {
    state.playbackStatus == .playing ? viewModel.pause() : viewModel.play()
}

where PlayButton is -

struct PlayButton: View {
    var isPlaying: Bool
    var font: Font = .title2
    var padding: CGFloat = 20

    var action: () -> ()

    var body: some View {
        Button(action: action) {
            Image(systemName: isPlaying ? "pause.fill" : "play.fill")
                .font(font)
                .padding(padding)
                .background(Circle().opacity(0.1))
        }
        .accentColor(.white)
    }
}

The same button is used in NowPlayingBar -

PlayButton(isPlaying: state.playbackStatus == .playing, font: .title3, padding: 15) {
    state.playbackStatus == .playing ? viewModel.pause() : viewModel.play()
}
.padding(.trailing)

Based on the playbackStatus, the play and pause image change. To have one single source of truth, instead of having my own isPlaying boolean value in the view model, I rely on the playbackStatus itself.

In every case except when the player is playing, I want to pause the player and display the pause image. Hence, I only check if the playbackStatus is equal to playing or not.

Now, if you open the control centre, and pause the music, it is automatically reflected in the music player, or if you take out your AirPods, the state changes automatically.

My Replay 2015 playlist on Apple Music

I find it amazing that I was able to implement this with only a few lines of code.