import ActivityKit import Foundation @MainActor enum LiveActivityCommands { private static var busy = false static let bindingKey = "MusicBridge.liveActivityPC" static func perform(_ action: MusicRemoteAction, activityID: String) async { guard !busy, let activity = Activity.activities.first(where: { $0.id == activityID }), activity.activityState == .active || activity.activityState == .stale else { return } busy = true defer { busy = false } do { guard let saved = try KeychainStore.load(), UserDefaults.standard.string(forKey: bindingKey + activityID) == saved.fingerprint else { throw BridgeError.message("Откройте приложение и включите карточку заново.") } let client = BridgeClient(saved, timeout: 5) defer { client.close() } let fresh = try await execute(action, client: client) { (activity.activityState == .active || activity.activityState == .stale) && UserDefaults.standard.string(forKey: bindingKey + activityID) == saved.fingerprint } guard fresh.hasSession else { await activity.end(nil, dismissalPolicy: .immediate); return } await activity.update(ActivityContent(state: LiveActivityManager.content(fresh), staleDate: Date().addingTimeInterval(15))) } catch { if case BridgeError.unauthorized = error { await activity.end(nil, dismissalPolicy: .immediate) return } var value = activity.content.state value.message = MusicActivityAttributes.ContentState.bounded(BridgeError.describe(error)) await activity.update(ActivityContent(state: value, staleDate: Date())) } } // A fresh capability check, one command at most, then a fresh snapshot. No command retries. static func execute(_ action: MusicRemoteAction, client: BridgeClient, stillValid: () -> Bool = { true }) async throws -> MediaState { let before = try await client.state() guard stillValid() else { throw BridgeError.message("Карточка закрыта. Откройте MusicBridge.") } if action == .refresh { return before } guard before.hasSession else { return before } let command = try command(action, state: before) do { try await client.command(Command(type: command, value: nil)) } 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: MusicRemoteAction, state: MediaState) throws -> String { switch action { case .previous where state.capabilities.previous: return "previous" case .next where state.capabilities.next: return "next" case .toggle where state.capabilities.toggle: return "toggle" case .toggle where state.playbackStatus == "Playing" && state.capabilities.pause: return "pause" case .toggle where state.playbackStatus != "Playing" && state.capabilities.play: return "play" default: throw BridgeError.message("Плеер на ПК не поддерживает эту команду.") } } }