Files
ios/MusicBridge/LiveActivityCommands.swift
T

69 lines
3.7 KiB
Swift
Raw Normal View History

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<MusicActivityAttributes>.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("Плеер на ПК не поддерживает эту команду.")
}
}
}