Queue mgmt, Podcast support, Favorites section.

This commit is contained in:
2026-04-08 10:26:50 +02:00
parent f55b7e478b
commit d7e7bef83f
14 changed files with 1177 additions and 60 deletions
+61 -4
View File
@@ -231,17 +231,47 @@ final class MAService {
}
/// Move queue item
func moveQueueItem(playerId: String, fromIndex: Int, toIndex: Int) async throws {
logger.debug("Moving queue item from \(fromIndex) to \(toIndex)")
func moveQueueItem(playerId: String, queueItemId: String, posShift: Int) async throws {
logger.debug("Moving queue item \(queueItemId) by \(posShift) positions")
_ = try await webSocketClient.sendCommand(
"player_queues/move_item",
args: [
"queue_id": playerId,
"queue_item_id": fromIndex,
"pos_shift": toIndex - fromIndex
"queue_item_id": queueItemId,
"pos_shift": posShift
]
)
}
func setQueueShuffle(playerId: String, enabled: Bool) async throws {
logger.debug("Setting shuffle \(enabled) on queue \(playerId)")
_ = try await webSocketClient.sendCommand(
"player_queues/shuffle",
args: [
"queue_id": playerId,
"shuffle_enabled": enabled
]
)
}
func setQueueRepeatMode(playerId: String, mode: RepeatMode) async throws {
logger.debug("Setting repeat mode \(mode.rawValue) on queue \(playerId)")
_ = try await webSocketClient.sendCommand(
"player_queues/repeat",
args: [
"queue_id": playerId,
"repeat_mode": mode.rawValue
]
)
}
func clearQueue(playerId: String) async throws {
logger.debug("Clearing queue \(playerId)")
_ = try await webSocketClient.sendCommand(
"player_queues/clear",
args: ["queue_id": playerId]
)
}
// MARK: - Library
@@ -302,6 +332,31 @@ final class MAService {
resultType: [MAPlaylist].self
)
}
/// Get all podcasts in the library
func getPodcasts() async throws -> [MAPodcast] {
logger.debug("Fetching podcasts")
return try await webSocketClient.sendCommand(
"music/podcasts/library_items",
resultType: [MAPodcast].self
)
}
/// Get all episodes for a podcast
func getPodcastEpisodes(podcastUri: String) async throws -> [MAMediaItem] {
logger.debug("Fetching episodes for podcast \(podcastUri)")
guard let (provider, itemId) = parseMAUri(podcastUri) else {
throw MAWebSocketClient.ClientError.serverError("Invalid podcast URI: \(podcastUri)")
}
return try await webSocketClient.sendCommand(
"music/podcasts/podcast_episodes",
args: [
"item_id": itemId,
"provider_instance_id_or_domain": provider
],
resultType: [MAMediaItem].self
)
}
/// Get full artist details (includes biography in metadata.description).
/// Results are cached in memory once biography data is available, so repeated
@@ -460,6 +515,7 @@ final class MAService {
let artists: [MAMediaItem]?
let playlists: [MAMediaItem]?
let radios: [MAMediaItem]?
let podcasts: [MAMediaItem]?
}
let searchResults = try result.decode(as: SearchResults.self)
@@ -471,6 +527,7 @@ final class MAService {
if let artists = searchResults.artists { allItems.append(contentsOf: artists) }
if let playlists = searchResults.playlists { allItems.append(contentsOf: playlists) }
if let radios = searchResults.radios { allItems.append(contentsOf: radios) }
if let podcasts = searchResults.podcasts { allItems.append(contentsOf: podcasts) }
logger.info("✅ Decoded \(allItems.count) search results (albums: \(searchResults.albums?.count ?? 0), tracks: \(searchResults.tracks?.count ?? 0), artists: \(searchResults.artists?.count ?? 0), radios: \(searchResults.radios?.count ?? 0))")
return allItems