esc

In the player

An add-on can put a button in Ledge's players, an entry in the player's ••• menu and Playing Next, for the music apps it covers. Ledge keeps the layout; the add-on answers for each song.

2The notch, menu bar, floating and lock screen players"player-action" · LedgeKit 0.7.0

A button

A player action is a button with two symbols, for off and on, shown while a song from one of its sources plays. Spotify Likes' heart is one:

let heart = PlayerAction("like", label: "Like", symbol: "heart", selectedSymbol: "heart.fill",
                         sources: ["com.spotify.client"])
handlers.activate = { AddOnSession.show(playerActions: [heart]) }

For each song, Ledge asks whether the button is on. When the user presses it, Ledge asks again with the state they want. Answer the state after it, or nil when you can't tell, which Ledge draws dimmed:

handlers.onPlayerAction = { request in
    guard request.action == "like", let id = request.track.id else { return nil }   // local files have no id
    if let liked = request.set { return try await setLiked(liked, id) }               // pressed
    return try await isLiked(id)                                                       // asked
}
PlayerTrackWhat it is
sourceThe player's bundle id, like com.apple.Music
idThe player's own id for the song when it gives one: Spotify's track id, or Music's store id
titleThe song's title
artistThe artist, when there is one
  • The button takes the player's side slot, or sits beside ••• when Playing Next holds that slot.
  • Pressed while its state is unknown, for example before the user signs in, it opens the add-on's settings page.
  • Up to 4 actions per add-on. If two add-ons cover the same player, the first by id wins.

A player extension adds an entry to the player's ••• menu whose items the add-on lists when it opens, such as Add to Playlist. Declare "player" in the manifest:

AddOnSession.show(player: PlayerExtension(
    sources: ["com.apple.Music"],
    menu: PlayerMenu("playlists", title: "Add to Playlist", symbol: "text.badge.plus"),
    queue: true))

handlers.onPlayerMenu = { request in
    if let id = request.choose {           // the user picked an item
        try await addToPlaylist(id)
        return []
    }
    return try await playlists().map { PlayerMenu.Item($0.id, title: $0.name) }
}

Playing Next

With queue: true, Playing Next in the notch, menu bar and lock screen players comes from the add-on. Answer the songs after the playing one. When the user picks one, playNext is its id: move it, then answer the new order.

handlers.onPlayerQueue = { request in
    if let id = request.playNext { try await moveToNext(id) }
    return try await upcoming().map {
        PlayerQueueEntry(id: $0.id, title: $0.title, artist: $0.artist, storeID: $0.storeID)
    }
}

Ledge loads each entry's cover from its https artworkURL, or looks its storeID up in Apple's catalogue, matching the exact id only. Throw AddOnError with a reason, like “Music isn't open”, and Ledge shows it in place of the list. show(player: nil) takes the menu entry and Playing Next away.

Limits

  • A menu lists up to 500 items, each title up to 512 characters.
  • Playing Next shows up to 50 songs; artwork addresses are https only.
  • Shuffle and repeat are Ledge's own, for every player.