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.
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
}| PlayerTrack | What it is |
|---|---|
source | The player's bundle id, like com.apple.Music |
id | The player's own id for the song when it gives one: Spotify's track id, or Music's store id |
title | The song's title |
artist | The 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 menu entry
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.