Changes
4 changed files (+149/-3)
-
-
@@ -107,6 +107,24 @@ return nil} } func control(_ zone: CoreZone, _ action: PlaybackAction) { let value: UInt16 switch action { case .Next: value = UInt16(PlacKit.PLAC_TRANSPORT_ACTION_NEXT) case .Pause: value = UInt16(PlacKit.PLAC_TRANSPORT_ACTION_PAUSE) case .Play: value = UInt16(PlacKit.PLAC_TRANSPORT_ACTION_PLAY) case .Prev: value = UInt16(PlacKit.PLAC_TRANSPORT_ACTION_PREV) case .Seek: value = UInt16(PlacKit.PLAC_TRANSPORT_ACTION_SEEK) } PlacKit.plac_connection_control(ptr, zone.ptr, value) } func subscribeZoneChanges() { PlacKit.plac_connection_subscribe_zones(ptr) }
-
-
-
@@ -96,11 +96,21 @@ self.imageKey = imageKey} } enum PlaybackAction { case Next case Prev case Pause case Play case Seek } protocol Zone { var id: String { get } var name: String { get } var playback: PlacKit.plac_transport_playback_state { get } var nowPlaying: NowPlaying? { get } func isAllowedTo(_ action: PlaybackAction) -> Bool } class CoreZone: Identifiable, Zone {
-
@@ -127,6 +137,31 @@deinit { PlacKit.plac_transport_zone_release(ptr) } func isAllowedTo(_ action: PlaybackAction) -> Bool { switch action { case .Next: return (ptr.pointee.allowed_action & UInt16(PlacKit.PLAC_TRANSPORT_ACTION_NEXT)) != 0 case .Prev: return (ptr.pointee.allowed_action & UInt16(PlacKit.PLAC_TRANSPORT_ACTION_PREV)) != 0 case .Pause: return (ptr.pointee.allowed_action & UInt16(PlacKit.PLAC_TRANSPORT_ACTION_PAUSE)) != 0 case .Play: return (ptr.pointee.allowed_action & UInt16(PlacKit.PLAC_TRANSPORT_ACTION_PLAY)) != 0 case .Seek: return (ptr.pointee.allowed_action & UInt16(PlacKit.PLAC_TRANSPORT_ACTION_SEEK)) != 0 } } } class MockZone: Identifiable, Zone {
-
@@ -134,17 +169,24 @@ var id: Stringvar name: String var playback: plac_transport_playback_state var nowPlaying: (any NowPlaying)? var allowedActions: [PlaybackAction] init( id: String, name: String, playback: plac_transport_playback_state, nowPlaying: (any NowPlaying)? = nil nowPlaying: (any NowPlaying)? = nil, allowedActions: [PlaybackAction] = [], ) { self.id = id self.name = name self.playback = playback self.nowPlaying = nowPlaying self.allowedActions = allowedActions } func isAllowedTo(_ action: PlaybackAction) -> Bool { return self.allowedActions.firstIndex(of: action) != nil } }
-
-
-
@@ -146,6 +146,7 @@ @State private var zone_id: String? = nil@State private var zones: [String: CoreZone] = [:] private let queue = DispatchQueue(label: "plac.event-loop") private let actionQueue = DispatchQueue(label: "plac.ConnectedView.actions") var onAuthorizeAction: ((_ token: String) -> Void)?
-
@@ -171,6 +172,22 @@ }) PureConnectedView(state: state) .onPlaybackAction { action in guard let zone_id = zone_id else { return } guard let zone = zones[zone_id] else { return } await withUnsafeContinuation { cont in actionQueue.async { conn.control(zone, action) cont.resume() } } } .task { startLoop() }
-
@@ -238,6 +255,8 @@struct PureConnectedView<TZone: Zone & Identifiable>: View { @Binding var state: ConnectionState<TZone> var playbackActionHandler: ((PlaybackAction) async -> Void)? var body: some View { switch state { case .authorized(let zone_id, let zones):
-
@@ -267,6 +286,9 @@Divider() PlaybackBar<TZone>(zone_id: zoneIdProxy, zones: zones) .onPlaybackAction { action in await playbackActionHandler?(action) } .frame(maxWidth: .infinity) } case .authorizing:
-
@@ -282,6 +304,16 @@ "Connecting to your Roon Server. Enable access at \"Settings > Extensions\" in official Roon Client application.") } } } } extension PureConnectedView { func onPlaybackAction(_ handler: @escaping (PlaybackAction) async -> Void) -> PureConnectedView { var new = self new.playbackActionHandler = handler return new } }
-
-
-
@@ -28,34 +28,58 @@ zone.id == zone_id})?.value } var onPlaybackActionHandler: ((PlaybackAction) async -> Void)? @State private var isPerformingAction = false var body: some View { HStack { HStack { Button { Task { await performAction(.Prev) } } label: { Label("Previous", systemImage: "backward.end.fill") } .font(.title3) .disabled(isPerformingAction) .disabled(zone?.isAllowedTo(.Prev) != true) if zone?.playback == PlacKit.PLAC_TRANSPORT_PLAYBACK_PLAYING { Button { Task { await performAction(.Pause) } } label: { Label("Pause", systemImage: "pause.fill") } .font(.title) .disabled(isPerformingAction) .disabled(zone?.isAllowedTo(.Pause) != true) } else { Button { Task { await performAction(.Play) } } label: { Label("Play", systemImage: "play.fill") } .font(.title) .disabled(isPerformingAction) .disabled(zone?.isAllowedTo(.Play) != true) } Button { Task { await performAction(.Next) } } label: { Label("Next", systemImage: "forward.end.fill") } .font(.title3) .disabled(isPerformingAction) .disabled(zone?.isAllowedTo(.Next) != true) } .labelStyle(.iconOnly) .buttonStyle(.borderless)
-
@@ -90,6 +114,28 @@ }.padding([.horizontal], 10) .padding([.vertical], 6) } private func performAction(_ action: PlaybackAction) async { guard let handler = onPlaybackActionHandler else { return } isPerformingAction = true await handler(action) isPerformingAction = false } } extension PlaybackBar { func onPlaybackAction(_ handler: @escaping (PlaybackAction) async -> Void) -> PlaybackBar { var new = self new.onPlaybackActionHandler = handler return new } } #Preview("PlaybackBar") {
-
@@ -104,14 +150,22 @@ nowPlaying: MockNowPlaying(line1: "My great song", line2: "Some untrusted artist", seek: (5, 3 * 60 + 30) ) ), allowedActions: [.Play, .Pause, .Prev] ), "bar": MockZone( id: "bar", name: "Bar", playback: PlacKit.PLAC_TRANSPORT_PLAYBACK_STOPPED playback: PlacKit.PLAC_TRANSPORT_PLAYBACK_STOPPED, allowedActions: [.Play, .Pause, .Prev, .Next] ), ] PlaybackBar(zone_id: $zone_id, zones: zones) .onPlaybackAction { action in do { try await Task.sleep(for: .seconds(1)) print(action) } catch {} } }
-