Files
ios/MusicBridge/SystemPlayerManager.swift
T

114 lines
5.1 KiB
Swift

import SwiftUI
import NowPlaying
@MainActor
final class SystemPlayerManager: ObservableObject {
@Published private(set) var running = false
@Published private(set) var busy = false
@Published private(set) var error: String?
private var storage: Any?
private var id: String?
private var generation = UUID()
private var updates: Task<Void, Never>?
private var lastUpdate = Date.distantPast
private var lastTrack = ""
private var lastPlaying = false
private let savedIDKey = "MusicBridge.systemPlayerSession"
func start(state: MediaState, connection: Connection) async {
guard !busy, !running, state.hasSession, connection.token != nil,
UIApplication.shared.applicationState == .active else { return }
guard #available(iOS 27.0, *) else { error = "Системный пульт требует iOS 27."; return }
busy = true; error = nil
let operation = generation
let newID = UUID().uuidString
id = newID
defer { if generation == operation { busy = false } }
do {
try SystemPlayerCredentials.save(connection, id: newID)
_ = try SystemPlayerCredentials.load(newID)
let session = try await RemoteMediaSession.start(attributes: SystemPlayerAttributes(id: newID, state: state))
guard generation == operation else { try? await session.end(); return }
storage = session
try await session.requestToBecomeSystemPrimary()
guard generation == operation else { try? await session.end(); return }
running = true
UserDefaults.standard.set(newID, forKey: savedIDKey)
} catch {
guard generation == operation else { return }
self.error = "Не удалось включить системный пульт: " + BridgeError.describe(error)
end()
}
}
func restore(connection: Connection?) async {
guard #available(iOS 27.0, *), id == nil, !busy,
let savedID = UserDefaults.standard.string(forKey: savedIDKey) else { return }
busy = true
id = savedID
let operation = generation
defer { if generation == operation { busy = false } }
do {
let saved = try SystemPlayerCredentials.load(savedID)
guard let connection, saved.fingerprint == connection.fingerprint, saved.token == connection.token else {
try SystemPlayerCredentials.delete(savedID)
UserDefaults.standard.removeObject(forKey: savedIDKey)
for session in try await RemoteMediaSession<SystemPlayerAttributes>.sessions() where session.id == savedID { try await session.end() }
if generation == operation { id = nil }
return
}
if let session = try await RemoteMediaSession<SystemPlayerAttributes>.sessions().first(where: { $0.id == savedID }) {
guard generation == operation else { return }
storage = session; id = savedID; running = true
} else {
guard generation == operation else { return }
try SystemPlayerCredentials.delete(savedID)
UserDefaults.standard.removeObject(forKey: savedIDKey)
id = nil
}
} catch { if generation == operation { self.error = BridgeError.describe(error); id = nil } }
}
func receive(_ state: MediaState, connection: Connection) {
guard #available(iOS 27.0, *), running, let id,
let session = storage as? RemoteMediaSession<SystemPlayerAttributes> else { return }
guard state.hasSession else { end(); return }
let track = state.title + "\n" + state.artist
let playing = state.playbackStatus == "Playing"
guard track != lastTrack || playing != lastPlaying || Date().timeIntervalSince(lastUpdate) >= 5 else { return }
lastUpdate = Date(); lastTrack = track; lastPlaying = playing
let value = SystemPlayerAttributes(id: id, state: state)
let operation = generation
enqueue { [weak self] in
guard let self, self.generation == operation else { return }
do {
try SystemPlayerCredentials.save(connection, id: id)
try await session.update(value)
} catch { self.error = BridgeError.describe(error) }
}
}
func end() {
generation = UUID()
busy = false; running = false
if let id {
do { try SystemPlayerCredentials.delete(id) }
catch { self.error = BridgeError.describe(error) }
}
UserDefaults.standard.removeObject(forKey: savedIDKey)
id = nil
if #available(iOS 27.0, *), let session = storage as? RemoteMediaSession<SystemPlayerAttributes> {
enqueue { [weak self] in
do { try await session.end() }
catch { self?.error = BridgeError.describe(error) }
}
}
storage = nil
}
private func enqueue(_ action: @escaping @MainActor () async -> Void) {
let previous = updates
updates = Task { await previous?.value; await action() }
}
}