Add iOS 27 remote media session and system player extension

This commit is contained in:
2026-09-14 00:17:07 +03:00
parent 2fbf3bf859
commit e2902e6031
16 changed files with 667 additions and 30 deletions
@@ -0,0 +1,42 @@
import Foundation
import Security
enum SystemPlayerCredentials {
private static func query(_ id: String) throws -> [String: Any] {
guard let group = Bundle.main.object(forInfoDictionaryKey: "MusicBridgeSharedKeychainGroup") as? String,
!group.isEmpty, !group.contains("$(") else {
throw BridgeError.message("Подпись приложения не настроила общий Keychain для системного пульта.")
}
return [kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: "MusicBridge.system-player.v1",
kSecAttrAccount as String: id, kSecAttrAccessGroup as String: group]
}
static func save(_ connection: Connection, id: String) throws {
let query = try query(id)
let values: [String: Any] = [kSecValueData as String: try JSONEncoder().encode(connection),
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly]
var status = SecItemUpdate(query as CFDictionary, values as CFDictionary)
if status == errSecItemNotFound { status = SecItemAdd(query.merging(values) { _, v in v } as CFDictionary, nil) }
guard status == errSecSuccess else { throw failure(status) }
}
static func load(_ id: String) throws -> Connection {
var query = try query(id)
query[kSecReturnData as String] = true; query[kSecMatchLimit as String] = kSecMatchLimitOne
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status == errSecSuccess, let data = item as? Data else { throw failure(status) }
let saved = try JSONDecoder().decode(Connection.self, from: data)
guard let token = saved.token, Connection.isFingerprint(token) else { throw BridgeError.unauthorized }
return try Connection(endpoint: saved.endpoint.absoluteString, fingerprint: saved.fingerprint, token: token)
}
static func delete(_ id: String) throws {
let status = SecItemDelete(try query(id) as CFDictionary)
guard status == errSecSuccess || status == errSecItemNotFound else { throw failure(status) }
}
private static func failure(_ status: OSStatus) -> BridgeError {
.message("Системный пульт не получил доступ к Keychain (\(status)). Проверьте подпись приложения и расширения; обычный плеер остаётся доступен.")
}
}