78 lines
3.5 KiB
Swift
78 lines
3.5 KiB
Swift
import ExtensionFoundation
|
|
import NowPlaying
|
|
import Observation
|
|
import Foundation
|
|
|
|
@main
|
|
struct MusicBridgeRemote: RemoteMediaSessionExtension {
|
|
var configuration: RemoteMediaSessionExtensionConfiguration<Self> {
|
|
RemoteMediaSessionExtensionConfiguration(extension: self)
|
|
}
|
|
func session(_ attributes: SystemPlayerAttributes) async throws -> PCMediaSession {
|
|
_ = try SystemPlayerCredentials.load(attributes.id)
|
|
return PCMediaSession(attributes)
|
|
}
|
|
}
|
|
|
|
@MainActor @Observable
|
|
final class PCMediaSession: RemoteMediaSessionRepresentable {
|
|
let id: String
|
|
var attributes: SystemPlayerAttributes
|
|
private let transport = SystemPlayerTransport()
|
|
init(_ attributes: SystemPlayerAttributes) { id = attributes.id; self.attributes = attributes }
|
|
|
|
func update(_ attributes: SystemPlayerAttributes) { self.attributes = attributes }
|
|
|
|
var content: (any MediaContentRepresentable)? {
|
|
guard attributes.hasSession else { return nil }
|
|
let artwork = attributes.artworkID.map { artworkID in
|
|
Artwork(id: artworkID) { _ in
|
|
let saved = try SystemPlayerCredentials.load(self.id)
|
|
let client = BridgeClient(saved, timeout: 5)
|
|
defer { client.close() }
|
|
return try ArtworkRepresentation(data: await client.artwork(artworkID))
|
|
}
|
|
}
|
|
return MusicContent(id: attributes.source + "/" + attributes.title + "/" + attributes.artist,
|
|
songTitle: attributes.title.isEmpty ? "Без названия" : attributes.title,
|
|
artistName: attributes.artist, albumName: "", type: .audio,
|
|
duration: attributes.duration > 0 ? .finite(attributes.duration) : nil, artwork: artwork)
|
|
}
|
|
|
|
var playbackSnapshot: MediaPlaybackSnapshot? {
|
|
MediaPlaybackSnapshot(state: attributes.playing ? .playing(rate: Float(attributes.rate)) : .paused,
|
|
elapsedTime: attributes.position, timestamp: attributes.timestamp)
|
|
}
|
|
|
|
var commands: [MediaCommand] {
|
|
guard attributes.hasSession else { return [] }
|
|
return [
|
|
.play { try await self.send(.play) }.enabled(attributes.capabilities.play),
|
|
.pause { try await self.send(.pause) }.enabled(attributes.capabilities.pause),
|
|
.next { try await self.send(.next) }.enabled(attributes.capabilities.next),
|
|
.previous { try await self.send(.previous) }.enabled(attributes.capabilities.previous),
|
|
.seekToPosition { try await self.send(.seek, value: $0) }.enabled(attributes.capabilities.seek)
|
|
]
|
|
}
|
|
|
|
var devices: [MediaDevice] {
|
|
let capabilities: [MediaDevice.Capability] = attributes.volume.map { volume in
|
|
[.absoluteVolume(Float(volume)) { try await self.send(.volume, value: Double($0)) }]
|
|
} ?? []
|
|
return [MediaDevice(id: id, name: "MusicBridge · ПК", type: .speaker, capabilities: capabilities)]
|
|
}
|
|
|
|
private func send(_ action: SystemPlayerAction, value: Double? = nil) async throws {
|
|
do {
|
|
let state = try await transport.send(action, value: value, sessionID: id)
|
|
attributes = SystemPlayerAttributes(id: id, state: state)
|
|
} catch BridgeError.unauthorized {
|
|
try SystemPlayerCredentials.delete(id)
|
|
attributes.hasSession = false
|
|
attributes.playing = false
|
|
attributes.volume = nil
|
|
throw BridgeError.unauthorized
|
|
}
|
|
}
|
|
}
|