// // MAPlayerManager.swift // Mobile Music Assistant // // Created by Sven Hanold on 26.03.26. // import Foundation import OSLog private let logger = Logger(subsystem: "com.musicassistant.mobile", category: "PlayerManager") /// Manages player state and real-time updates @Observable final class MAPlayerManager { // MARK: - Properties private(set) var players: [String: MAPlayer] = [:] private(set) var queues: [String: [MAQueueItem]] = [:] private weak var service: MAService? private var eventTask: Task? // MARK: - Initialization init(service: MAService?) { self.service = service } func setService(_ service: MAService) { self.service = service } deinit { stopListening() } // MARK: - Event Listening /// Start listening to player events func startListening() { guard eventTask == nil, let service else { return } logger.info("Starting event listener") eventTask = Task { for await event in service.webSocketClient.eventStream { await handleEvent(event) } } } /// Stop listening to events func stopListening() { logger.info("Stopping event listener") eventTask?.cancel() eventTask = nil } private func handleEvent(_ event: MAEvent) async { logger.debug("Handling event: \(event.event)") switch event.event { case "player_updated": await handlePlayerUpdated(event) case "queue_updated": await handleQueueUpdated(event) case "queue_items_updated": await handleQueueItemsUpdated(event) default: logger.debug("Unhandled event: \(event.event)") } } private func handlePlayerUpdated(_ event: MAEvent) async { guard let data = event.data else { return } do { let player = try data.decode(as: MAPlayer.self) await MainActor.run { players[player.playerId] = player logger.debug("Updated player: \(player.name) - \(player.state.rawValue)") } } catch { logger.error("Failed to decode player update: \(error.localizedDescription)") } } private func handleQueueUpdated(_ event: MAEvent) async { guard let data = event.data, let dict = data.value as? [String: Any], let queueId = dict["queue_id"] as? String else { return } // Reload queue for this player guard let service else { return } do { let items = try await service.getQueue(playerId: queueId) await MainActor.run { queues[queueId] = items logger.debug("Updated queue for player \(queueId): \(items.count) items") } } catch { logger.error("Failed to reload queue: \(error.localizedDescription)") } } private func handleQueueItemsUpdated(_ event: MAEvent) async { // Similar to queue_updated await handleQueueUpdated(event) } // MARK: - Data Loading /// Load all players func loadPlayers() async throws { guard let service else { throw MAWebSocketClient.ClientError.notConnected } logger.info("Loading players") let playerList = try await service.getPlayers() await MainActor.run { players = Dictionary(uniqueKeysWithValues: playerList.map { ($0.playerId, $0) }) } } /// Load queue for specific player func loadQueue(playerId: String) async throws { guard let service else { throw MAWebSocketClient.ClientError.notConnected } logger.info("Loading queue for player \(playerId)") let items = try await service.getQueue(playerId: playerId) await MainActor.run { queues[playerId] = items } } // MARK: - Player Control func play(playerId: String) async throws { guard let service else { throw MAWebSocketClient.ClientError.notConnected } try await service.play(playerId: playerId) } func pause(playerId: String) async throws { guard let service else { throw MAWebSocketClient.ClientError.notConnected } try await service.pause(playerId: playerId) } func stop(playerId: String) async throws { guard let service else { throw MAWebSocketClient.ClientError.notConnected } try await service.stop(playerId: playerId) } func nextTrack(playerId: String) async throws { guard let service else { throw MAWebSocketClient.ClientError.notConnected } try await service.nextTrack(playerId: playerId) } func previousTrack(playerId: String) async throws { guard let service else { throw MAWebSocketClient.ClientError.notConnected } try await service.previousTrack(playerId: playerId) } func setVolume(playerId: String, level: Int) async throws { guard let service else { throw MAWebSocketClient.ClientError.notConnected } try await service.setVolume(playerId: playerId, level: level) } func playMedia(playerId: String, uri: String) async throws { guard let service else { throw MAWebSocketClient.ClientError.notConnected } try await service.playMedia(playerId: playerId, uri: uri) } func playIndex(playerId: String, index: Int) async throws { guard let service else { throw MAWebSocketClient.ClientError.notConnected } try await service.playIndex(playerId: playerId, index: index) } }