Exploring MusicKit: Repeat Mode
Book

Exploring MusicKit and Apple Music API

Unlock the full power of MusicKit & Apple Music APIs in your apps with the best guide! Use code musickit-blog for a limited-time 35% discount!

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 RepeatMode, and how we can implement it in our app.

RepeatMode

RepeatMode is an enum that contains three cases -

  • all - Repeating the currently playing collection.
  • none - In disabled state, and playback stops after the current item.
  • one - Repeating the currently playing entry.

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, there’s a repeat mode button that you can iterate through to repeat all the songs or a particular one. To create something similar in my app, Musadora, I extended MusicPlayer.RepeatMode to conform to CaseIterable. As I’m extending the enum outside the framework, it prevents automatic synthesis of ‘allCases’, a requirement of protocol CaseIterable. To fix that, I manually add a static variable to the extension -

public static var allCases: [MusicPlayer.RepeatMode] = [.none, .all, .one]

Now, I add a mutating function next() for circulation iteration of the options. It sets to the first element if it is the last one. Otherwise, increments to the next one.

mutating func next() {
    let allCases = Self.allCases
    let currentIndex = allCases.index(after: allCases.firstIndex(of: self)!)

    self = allCases[currentIndex == allCases.endIndex ? allCases.startIndex : currentIndex]
}

The extension looks like -

extension MusicPlayer.RepeatMode: CaseIterable {
    public static var allCases: [MusicPlayer.RepeatMode] = [.none, .all, .one]

    mutating func next() {
        let allCases = Self.allCases
        let currentIndex = allCases.index(after: allCases.firstIndex(of: self)!)

        self = allCases[currentIndex == allCases.endIndex ? allCases.startIndex : currentIndex]
    }
}

In the NowPlayingPrimaryControlsView , I observe the value of state -

@ObservedObject private var state = ApplicationMusicPlayer.shared.state

When the user taps on the repeat mode button, the next() method of the enum is called and based on the response, the image is changed.

Button(action: { state.repeatMode?.next() }) {
    Image(systemName: state.repeatMode == .one ? "repeat.1" : "repeat")
        .font(.title3)
        .padding(12)
        .background(state.repeatMode == MusicPlayer.RepeatMode.none ? nil : Circle().opacity(0.1))
}

Here’s how it looks -

My Replay 2015 playlist on Apple Music

Based on the value of the repeatMode, the MusicPlayer decides if the song is repeated or not.

You can similarly create the shuffle button as well!

Button(action: { state.shuffleMode?.next() }) {
    Image(systemName: "shuffle")
        .font(.title3)
        .padding(12)
        .background(state.shuffleMode == .songs ? Circle().opacity(0.1) : nil)
}
Book

Exploring MusicKit and Apple Music API

Unlock the full power of MusicKit & Apple Music APIs in your apps with the best guide! Use code musickit-blog for a limited-time 35% discount!

Written by

Rudrank Riyam

Hi, my name is Rudrank. I create apps for Apple Platforms while listening to music all day and night. Author of "Exploring MusicKit". Apple WWDC 2019 scholarship winner.