import Foundation enum SystemPlayerAction { case play, pause, next, previous, seek, volume } @MainActor final class SystemPlayerTransport { private var busy = false func send(_ action: SystemPlayerAction, value: Double? = nil, sessionID: String) async throws -> MediaState { guard !busy else { throw BridgeError.message("Предыдущая команда ещё выполняется.") } busy = true defer { busy = false } let saved = try SystemPlayerCredentials.load(sessionID) let client = BridgeClient(saved, timeout: 5) defer { client.close() } return try await Self.execute(action, value: value, client: client) { let current = try SystemPlayerCredentials.load(sessionID) guard current.endpoint == saved.endpoint, current.fingerprint == saved.fingerprint, current.token == saved.token else { throw BridgeError.unauthorized } } } static func execute(_ action: SystemPlayerAction, value: Double? = nil, client: BridgeClient, validate: () throws -> Void = {}) async throws -> MediaState { let state = try await client.state() try validate() let command = try command(action, value: value, state: state) do { try await client.command(command) } catch { if error is URLError { throw BridgeError.message("Результат команды неизвестен. Откройте плеер перед повтором.") } throw error } do { return try await client.state() } catch { if case BridgeError.unauthorized = error { throw error } throw BridgeError.message("Команда выполнена, но новый трек не получен. Откройте приложение для обновления.") } } static func command(_ action: SystemPlayerAction, value: Double?, state: MediaState) throws -> Command { switch action { case .play where state.hasSession && state.capabilities.play: return Command(type: "play", value: nil) case .pause where state.hasSession && state.capabilities.pause: return Command(type: "pause", value: nil) case .next where state.hasSession && state.capabilities.next: return Command(type: "next", value: nil) case .previous where state.hasSession && state.capabilities.previous: return Command(type: "previous", value: nil) case .seek where state.hasSession && state.capabilities.seek && state.maxSeekSeconds > state.minSeekSeconds: guard let value, value.isFinite else { throw BridgeError.message("Некорректная позиция трека.") } return Command(type: "seek", value: min(state.maxSeekSeconds, max(state.minSeekSeconds, state.startSeconds + value))) case .volume where state.volume != nil: guard let value, value.isFinite else { throw BridgeError.message("Некорректная громкость.") } return Command(type: "volume", value: min(1, max(0, value))) default: throw BridgeError.message("Плеер на ПК не поддерживает эту команду.") } } }