mirror of
https://github.com/danbee/persephone
synced 2025-03-04 08:39:11 +00:00
Show the queue and update it when it changes
This commit is contained in:
parent
22926d343f
commit
e0a9290684
@ -20,6 +20,7 @@
|
|||||||
E4E8CC902204EC7F0024217A /* MPDClientDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC8F2204EC7F0024217A /* MPDClientDelegate.swift */; };
|
E4E8CC902204EC7F0024217A /* MPDClientDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC8F2204EC7F0024217A /* MPDClientDelegate.swift */; };
|
||||||
E4E8CC922204F4B80024217A /* QueueController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC912204F4B80024217A /* QueueController.swift */; };
|
E4E8CC922204F4B80024217A /* QueueController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC912204F4B80024217A /* QueueController.swift */; };
|
||||||
E4E8CC942206097F0024217A /* MPDClientNotificationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC932206097F0024217A /* MPDClientNotificationHandler.swift */; };
|
E4E8CC942206097F0024217A /* MPDClientNotificationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC932206097F0024217A /* MPDClientNotificationHandler.swift */; };
|
||||||
|
E4E8CC9A22075D370024217A /* MPDClientSong.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC9922075D370024217A /* MPDClientSong.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -109,6 +110,7 @@
|
|||||||
E4E8CC8F2204EC7F0024217A /* MPDClientDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDClientDelegate.swift; sourceTree = "<group>"; };
|
E4E8CC8F2204EC7F0024217A /* MPDClientDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDClientDelegate.swift; sourceTree = "<group>"; };
|
||||||
E4E8CC912204F4B80024217A /* QueueController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueController.swift; sourceTree = "<group>"; };
|
E4E8CC912204F4B80024217A /* QueueController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueController.swift; sourceTree = "<group>"; };
|
||||||
E4E8CC932206097F0024217A /* MPDClientNotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDClientNotificationHandler.swift; sourceTree = "<group>"; };
|
E4E8CC932206097F0024217A /* MPDClientNotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDClientNotificationHandler.swift; sourceTree = "<group>"; };
|
||||||
|
E4E8CC9922075D370024217A /* MPDClientSong.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDClientSong.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -171,6 +173,7 @@
|
|||||||
E40786242110CE70006887B1 /* Info.plist */,
|
E40786242110CE70006887B1 /* Info.plist */,
|
||||||
E40786252110CE70006887B1 /* Persephone.entitlements */,
|
E40786252110CE70006887B1 /* Persephone.entitlements */,
|
||||||
E4E8CC932206097F0024217A /* MPDClientNotificationHandler.swift */,
|
E4E8CC932206097F0024217A /* MPDClientNotificationHandler.swift */,
|
||||||
|
E4E8CC9922075D370024217A /* MPDClientSong.swift */,
|
||||||
E4E8CC8F2204EC7F0024217A /* MPDClientDelegate.swift */,
|
E4E8CC8F2204EC7F0024217A /* MPDClientDelegate.swift */,
|
||||||
E41B22C521FB932700D544F6 /* MPDClient.swift */,
|
E41B22C521FB932700D544F6 /* MPDClient.swift */,
|
||||||
);
|
);
|
||||||
@ -395,6 +398,7 @@
|
|||||||
E465049A21E94DF500A70F4C /* WindowController.swift in Sources */,
|
E465049A21E94DF500A70F4C /* WindowController.swift in Sources */,
|
||||||
E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */,
|
E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */,
|
||||||
E407861C2110CE6E006887B1 /* AppDelegate.swift in Sources */,
|
E407861C2110CE6E006887B1 /* AppDelegate.swift in Sources */,
|
||||||
|
E4E8CC9A22075D370024217A /* MPDClientSong.swift in Sources */,
|
||||||
E4E8CC902204EC7F0024217A /* MPDClientDelegate.swift in Sources */,
|
E4E8CC902204EC7F0024217A /* MPDClientDelegate.swift in Sources */,
|
||||||
E4E8CC922204F4B80024217A /* QueueController.swift in Sources */,
|
E4E8CC922204F4B80024217A /* QueueController.swift in Sources */,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -713,25 +713,9 @@
|
|||||||
</segmentedControl>
|
</segmentedControl>
|
||||||
</toolbarItem>
|
</toolbarItem>
|
||||||
<toolbarItem implicitItemIdentifier="NSToolbarFlexibleSpaceItem" id="9ol-aR-mzv"/>
|
<toolbarItem implicitItemIdentifier="NSToolbarFlexibleSpaceItem" id="9ol-aR-mzv"/>
|
||||||
<toolbarItem implicitItemIdentifier="6D294101-04FF-4E2A-98A7-77A48E2296CD" label="" paletteLabel="" sizingBehavior="auto" id="FoD-o2-4f2">
|
|
||||||
<nil key="toolTip"/>
|
|
||||||
<textField key="view" horizontalHuggingPriority="251" verticalHuggingPriority="750" id="KVt-e7-7I5">
|
|
||||||
<rect key="frame" x="0.0" y="14" width="72" height="17"/>
|
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
|
||||||
<textFieldCell key="cell" lineBreakMode="clipping" title="stateLabel" id="bCv-8Z-qfx">
|
|
||||||
<font key="font" usesAppearanceFont="YES"/>
|
|
||||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
|
||||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
|
||||||
</textFieldCell>
|
|
||||||
<connections>
|
|
||||||
<action selector="stateLabel:" target="B8D-0N-5wS" id="YXt-Ni-yie"/>
|
|
||||||
</connections>
|
|
||||||
</textField>
|
|
||||||
</toolbarItem>
|
|
||||||
</allowedToolbarItems>
|
</allowedToolbarItems>
|
||||||
<defaultToolbarItems>
|
<defaultToolbarItems>
|
||||||
<toolbarItem reference="p3r-ty-Pxf"/>
|
<toolbarItem reference="p3r-ty-Pxf"/>
|
||||||
<toolbarItem reference="FoD-o2-4f2"/>
|
|
||||||
</defaultToolbarItems>
|
</defaultToolbarItems>
|
||||||
</toolbar>
|
</toolbar>
|
||||||
<connections>
|
<connections>
|
||||||
@ -739,7 +723,6 @@
|
|||||||
</connections>
|
</connections>
|
||||||
</window>
|
</window>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="stateLabel" destination="KVt-e7-7I5" id="xMD-Iq-CSh"/>
|
|
||||||
<outlet property="transportControls" destination="EBk-sD-nG7" id="yOo-58-Fby"/>
|
<outlet property="transportControls" destination="EBk-sD-nG7" id="yOo-58-Fby"/>
|
||||||
<segue destination="fnD-7K-pHK" kind="relationship" relationship="window.shadowedContentViewController" id="fQQ-kB-KVc"/>
|
<segue destination="fnD-7K-pHK" kind="relationship" relationship="window.shadowedContentViewController" id="fQQ-kB-KVc"/>
|
||||||
</connections>
|
</connections>
|
||||||
@ -807,7 +790,7 @@
|
|||||||
</textFieldCell>
|
</textFieldCell>
|
||||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||||
<prototypeCellViews>
|
<prototypeCellViews>
|
||||||
<tableCellView identifier="HeaderCell" id="GOd-cg-juD">
|
<tableCellView identifier="queueHeaderCell" id="GOd-cg-juD">
|
||||||
<rect key="frame" x="1" y="1" width="447" height="17"/>
|
<rect key="frame" x="1" y="1" width="447" height="17"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
@ -829,7 +812,7 @@
|
|||||||
<outlet property="textField" destination="xgd-Cz-np3" id="5jx-ag-Xoc"/>
|
<outlet property="textField" destination="xgd-Cz-np3" id="5jx-ag-Xoc"/>
|
||||||
</connections>
|
</connections>
|
||||||
</tableCellView>
|
</tableCellView>
|
||||||
<tableCellView identifier="DataCell" id="5rR-Gz-AcP">
|
<tableCellView identifier="songItemCell" id="5rR-Gz-AcP">
|
||||||
<rect key="frame" x="1" y="20" width="447" height="17"/>
|
<rect key="frame" x="1" y="20" width="447" height="17"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
@ -865,12 +848,16 @@
|
|||||||
</prototypeCellViews>
|
</prototypeCellViews>
|
||||||
</tableColumn>
|
</tableColumn>
|
||||||
</tableColumns>
|
</tableColumns>
|
||||||
|
<connections>
|
||||||
|
<outlet property="dataSource" destination="KIP-rq-4dM" id="pQO-Qh-B3D"/>
|
||||||
|
<outlet property="delegate" destination="KIP-rq-4dM" id="iI3-RL-4Hd"/>
|
||||||
|
</connections>
|
||||||
</outlineView>
|
</outlineView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<nil key="backgroundColor"/>
|
<nil key="backgroundColor"/>
|
||||||
</clipView>
|
</clipView>
|
||||||
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="7mx-v9-DSr">
|
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="7mx-v9-DSr">
|
||||||
<rect key="frame" x="1" y="119" width="238" height="15"/>
|
<rect key="frame" x="0.0" y="284" width="450" height="16"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
</scroller>
|
</scroller>
|
||||||
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="p5z-C0-FUJ">
|
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="p5z-C0-FUJ">
|
||||||
@ -886,6 +873,9 @@
|
|||||||
<constraint firstAttribute="bottom" secondItem="S3o-nF-NN7" secondAttribute="bottom" id="hJz-ZT-b1Q"/>
|
<constraint firstAttribute="bottom" secondItem="S3o-nF-NN7" secondAttribute="bottom" id="hJz-ZT-b1Q"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
|
<connections>
|
||||||
|
<outlet property="queueView" destination="jEJ-jg-fll" id="VSN-Xu-fvv"/>
|
||||||
|
</connections>
|
||||||
</viewController>
|
</viewController>
|
||||||
<customObject id="du4-e9-TfX" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
<customObject id="du4-e9-TfX" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
|
|||||||
@ -15,18 +15,19 @@ class MPDClient {
|
|||||||
static let queueChanged = Notification.Name("MPDClientQueueChanged")
|
static let queueChanged = Notification.Name("MPDClientQueueChanged")
|
||||||
|
|
||||||
static let stateKey = "state"
|
static let stateKey = "state"
|
||||||
|
static let queueKey = "queue"
|
||||||
|
|
||||||
let HOST = "localhost"
|
let HOST = "localhost"
|
||||||
let PORT: UInt32 = 6600
|
let PORT: UInt32 = 6600
|
||||||
|
|
||||||
private var connection: OpaquePointer?
|
private var connection: OpaquePointer?
|
||||||
private var status: OpaquePointer?
|
private var status: OpaquePointer?
|
||||||
|
private var queue: [Song] = []
|
||||||
|
|
||||||
private let commandQueue = DispatchQueue(label: "commandQueue")
|
private let commandQueue = DispatchQueue(label: "commandQueue")
|
||||||
private var commandQueued = false
|
|
||||||
|
|
||||||
enum Command {
|
enum Command {
|
||||||
case prevTrack, nextTrack, playPause, stop, fetchStatus
|
case prevTrack, nextTrack, playPause, stop, fetchStatus, fetchQueue
|
||||||
}
|
}
|
||||||
|
|
||||||
enum State: UInt32 {
|
enum State: UInt32 {
|
||||||
@ -36,6 +37,22 @@ class MPDClient {
|
|||||||
case paused = 3
|
case paused = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Idle: OptionSet {
|
||||||
|
let rawValue: UInt32
|
||||||
|
|
||||||
|
static let database = Idle(rawValue: 0x1)
|
||||||
|
static let storedPlaylist = Idle(rawValue: 0x2)
|
||||||
|
static let queue = Idle(rawValue: 0x4)
|
||||||
|
static let player = Idle(rawValue: 0x8)
|
||||||
|
static let mixer = Idle(rawValue: 0x10)
|
||||||
|
static let output = Idle(rawValue: 0x20)
|
||||||
|
static let options = Idle(rawValue: 0x40)
|
||||||
|
static let update = Idle(rawValue: 0x80)
|
||||||
|
static let sticker = Idle(rawValue: 0x100)
|
||||||
|
static let subscription = Idle(rawValue: 0x200)
|
||||||
|
static let message = Idle(rawValue: 0x400)
|
||||||
|
}
|
||||||
|
|
||||||
init(withDelegate delegate: MPDClientDelegate?) {
|
init(withDelegate delegate: MPDClientDelegate?) {
|
||||||
self.delegate = delegate
|
self.delegate = delegate
|
||||||
}
|
}
|
||||||
@ -49,13 +66,20 @@ class MPDClient {
|
|||||||
|
|
||||||
self.connection = connection
|
self.connection = connection
|
||||||
self.status = status
|
self.status = status
|
||||||
self.initializeStatus()
|
|
||||||
|
fetchQueue()
|
||||||
|
|
||||||
|
self.delegate?.didUpdateState(mpdClient: self, state: self.getState())
|
||||||
|
self.delegate?.didUpdateQueue(mpdClient: self, queue: self.queue)
|
||||||
idle()
|
idle()
|
||||||
}
|
}
|
||||||
|
|
||||||
func disconnect() {
|
func disconnect() {
|
||||||
noIdle()
|
noIdle()
|
||||||
commandQueue.async { [unowned self] in
|
commandQueue.async { [unowned self] in
|
||||||
|
for song in self.queue {
|
||||||
|
song.free()
|
||||||
|
}
|
||||||
mpd_status_free(self.status)
|
mpd_status_free(self.status)
|
||||||
mpd_connection_free(self.connection)
|
mpd_connection_free(self.connection)
|
||||||
}
|
}
|
||||||
@ -65,13 +89,12 @@ class MPDClient {
|
|||||||
sendCommand(command: .fetchStatus)
|
sendCommand(command: .fetchStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initializeStatus() {
|
func fetchQueue() {
|
||||||
self.delegate?.didUpdateState(mpdClient: self, state: self.getState())
|
sendCommand(command: .fetchQueue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getState() -> State {
|
func getState() -> mpd_state {
|
||||||
let state = mpd_status_get_state(status)
|
return mpd_status_get_state(status)
|
||||||
return State(rawValue: state.rawValue)!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func playPause() {
|
func playPause() {
|
||||||
@ -91,11 +114,9 @@ class MPDClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func queueCommand(command: Command) {
|
func queueCommand(command: Command) {
|
||||||
commandQueued = true
|
|
||||||
noIdle()
|
noIdle()
|
||||||
commandQueue.async { [unowned self] in
|
commandQueue.async { [unowned self] in
|
||||||
self.sendCommand(command: command)
|
self.sendCommand(command: command)
|
||||||
self.commandQueued = false
|
|
||||||
}
|
}
|
||||||
idle()
|
idle()
|
||||||
}
|
}
|
||||||
@ -116,17 +137,26 @@ class MPDClient {
|
|||||||
case .fetchStatus:
|
case .fetchStatus:
|
||||||
guard let status = mpd_run_status(connection) else { break }
|
guard let status = mpd_run_status(connection) else { break }
|
||||||
self.status = status
|
self.status = status
|
||||||
|
|
||||||
|
case .fetchQueue:
|
||||||
|
self.queue = []
|
||||||
|
mpd_send_list_queue_meta(connection)
|
||||||
|
|
||||||
|
while let mpdSong = mpd_recv_song(connection) {
|
||||||
|
let song = Song(mpdSong)
|
||||||
|
self.queue.append(song)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendNextTrack() {
|
func sendNextTrack() {
|
||||||
if [.playing, .paused].contains(getState()) {
|
if [MPD_STATE_PLAY, MPD_STATE_PAUSE].contains(getState()) {
|
||||||
mpd_run_next(connection)
|
mpd_run_next(connection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendPreviousTrack() {
|
func sendPreviousTrack() {
|
||||||
if [.playing, .paused].contains(getState()) {
|
if [MPD_STATE_PLAY, MPD_STATE_PAUSE].contains(getState()) {
|
||||||
mpd_run_previous(connection)
|
mpd_run_previous(connection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,7 +166,7 @@ class MPDClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sendPlay() {
|
func sendPlay() {
|
||||||
if getState() == .stopped {
|
if getState() == MPD_STATE_STOP {
|
||||||
mpd_run_play(connection)
|
mpd_run_play(connection)
|
||||||
} else {
|
} else {
|
||||||
mpd_run_toggle_pause(connection)
|
mpd_run_toggle_pause(connection)
|
||||||
@ -150,13 +180,25 @@ class MPDClient {
|
|||||||
func idle() {
|
func idle() {
|
||||||
commandQueue.async { [unowned self] in
|
commandQueue.async { [unowned self] in
|
||||||
mpd_send_idle(self.connection)
|
mpd_send_idle(self.connection)
|
||||||
mpd_recv_idle(self.connection, true)
|
let result = mpd_recv_idle(self.connection, true)
|
||||||
|
|
||||||
if !self.commandQueued {
|
self.handleIdleResult(result)
|
||||||
self.fetchStatus()
|
}
|
||||||
self.delegate?.didUpdateState(mpdClient: self, state: self.getState())
|
}
|
||||||
self.idle()
|
|
||||||
}
|
func handleIdleResult(_ result: mpd_idle) {
|
||||||
|
let mpdIdle = Idle(rawValue: result.rawValue)
|
||||||
|
|
||||||
|
if mpdIdle.contains(.queue) {
|
||||||
|
self.fetchQueue()
|
||||||
|
self.delegate?.didUpdateQueue(mpdClient: self, queue: self.queue)
|
||||||
|
}
|
||||||
|
if mpdIdle.contains(.player) {
|
||||||
|
self.fetchStatus()
|
||||||
|
self.delegate?.didUpdateState(mpdClient: self, state: self.getState())
|
||||||
|
}
|
||||||
|
if !mpdIdle.isEmpty {
|
||||||
|
self.idle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,9 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import mpdclient
|
||||||
|
|
||||||
protocol MPDClientDelegate {
|
protocol MPDClientDelegate {
|
||||||
func didUpdateState(mpdClient: MPDClient, state: MPDClient.State)
|
func didUpdateState(mpdClient: MPDClient, state: mpd_state)
|
||||||
|
func didUpdateQueue(mpdClient: MPDClient, queue: [MPDClient.Song])
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,11 +7,12 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import mpdclient
|
||||||
|
|
||||||
class MPDClientNotificationHandler: MPDClientDelegate {
|
class MPDClientNotificationHandler: MPDClientDelegate {
|
||||||
let notificationQueue = DispatchQueue.main
|
let notificationQueue = DispatchQueue.main
|
||||||
|
|
||||||
func didUpdateState(mpdClient: MPDClient, state: MPDClient.State) {
|
func didUpdateState(mpdClient: MPDClient, state: mpd_state) {
|
||||||
self.notificationQueue.async {
|
self.notificationQueue.async {
|
||||||
NotificationCenter.default.post(
|
NotificationCenter.default.post(
|
||||||
name: MPDClient.stateChanged,
|
name: MPDClient.stateChanged,
|
||||||
@ -20,4 +21,14 @@ class MPDClientNotificationHandler: MPDClientDelegate {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func didUpdateQueue(mpdClient: MPDClient, queue: [MPDClient.Song]) {
|
||||||
|
self.notificationQueue.async {
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: MPDClient.queueChanged,
|
||||||
|
object: AppDelegate.mpdClient,
|
||||||
|
userInfo: [MPDClient.queueKey: queue]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
31
Persephone/MPDClientSong.swift
Normal file
31
Persephone/MPDClientSong.swift
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
//
|
||||||
|
// MPDClientSong.swift
|
||||||
|
// Persephone
|
||||||
|
//
|
||||||
|
// Created by Daniel Barber on 2019/2/03.
|
||||||
|
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import mpdclient
|
||||||
|
|
||||||
|
extension MPDClient {
|
||||||
|
class Song {
|
||||||
|
let mpdSong: OpaquePointer
|
||||||
|
|
||||||
|
init(_ mpdSong: OpaquePointer) {
|
||||||
|
self.mpdSong = mpdSong
|
||||||
|
}
|
||||||
|
|
||||||
|
func free() {
|
||||||
|
mpd_song_free(mpdSong)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTag(_ tagType: mpd_tag_type) -> String {
|
||||||
|
guard let tag = mpd_song_get_tag(mpdSong, tagType, 0)
|
||||||
|
else { return "" }
|
||||||
|
|
||||||
|
return String(cString: tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,12 +7,55 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
import mpdclient
|
||||||
|
|
||||||
class QueueController: NSViewController {
|
class QueueController: NSViewController, NSOutlineViewDataSource, NSOutlineViewDelegate {
|
||||||
|
var queue: [MPDClient.Song] = []
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
// Do view setup here.
|
|
||||||
}
|
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(
|
||||||
|
self,
|
||||||
|
selector: #selector(queueChanged(_:)),
|
||||||
|
name: MPDClient.queueChanged,
|
||||||
|
object: AppDelegate.mpdClient
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func queueChanged(_ notification: Notification) {
|
||||||
|
guard let queue = notification.userInfo?[MPDClient.queueKey] as? [MPDClient.Song]
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
self.queue = queue
|
||||||
|
|
||||||
|
queueView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
|
||||||
|
return queue.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
|
||||||
|
return queue[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
|
||||||
|
guard let song = item as? MPDClient.Song else { return nil }
|
||||||
|
|
||||||
|
let tableView = outlineView.makeView(
|
||||||
|
withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "songItemCell"),
|
||||||
|
owner: self
|
||||||
|
) as! NSTableCellView
|
||||||
|
|
||||||
|
tableView.textField?.stringValue = "\(song.getTag(MPD_TAG_TITLE)) - \(song.getTag(MPD_TAG_ARTIST))"
|
||||||
|
|
||||||
|
return tableView
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBOutlet var queueView: NSOutlineView!
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
import mpdclient
|
||||||
|
|
||||||
class WindowController: NSWindowController {
|
class WindowController: NSWindowController {
|
||||||
enum TransportAction: Int {
|
enum TransportAction: Int {
|
||||||
@ -29,20 +30,19 @@ class WindowController: NSWindowController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func stateChanged(_ notification: Notification) {
|
@objc func stateChanged(_ notification: Notification) {
|
||||||
guard let state = notification.userInfo?[MPDClient.stateKey] as? MPDClient.State
|
guard let state = notification.userInfo?[MPDClient.stateKey] as? mpd_state
|
||||||
else { return }
|
else { return }
|
||||||
|
|
||||||
stateLabel.stringValue = "\(state)".localizedCapitalized
|
|
||||||
setTransportControlState(state)
|
setTransportControlState(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setTransportControlState(_ state: MPDClient.State) {
|
func setTransportControlState(_ state: mpd_state) {
|
||||||
transportControls.setEnabled([.playing, .paused].contains(state), forSegment: 0)
|
transportControls.setEnabled([MPD_STATE_PLAY, MPD_STATE_PAUSE].contains(state), forSegment: 0)
|
||||||
transportControls.setEnabled([.playing, .paused, .stopped].contains(state), forSegment: 1)
|
transportControls.setEnabled([MPD_STATE_PLAY, MPD_STATE_PAUSE, MPD_STATE_STOP].contains(state), forSegment: 1)
|
||||||
transportControls.setEnabled([.playing, .paused].contains(state), forSegment: 2)
|
transportControls.setEnabled([MPD_STATE_PLAY, MPD_STATE_PAUSE].contains(state), forSegment: 2)
|
||||||
transportControls.setEnabled([.playing, .paused].contains(state), forSegment: 3)
|
transportControls.setEnabled([MPD_STATE_PLAY, MPD_STATE_PAUSE].contains(state), forSegment: 3)
|
||||||
|
|
||||||
if [.paused, .stopped, .unknown].contains(state) {
|
if [MPD_STATE_PAUSE, MPD_STATE_STOP, MPD_STATE_UNKNOWN].contains(state) {
|
||||||
transportControls.setImage(NSImage(named: NSImage.Name(rawValue: "playButton")), forSegment: 1)
|
transportControls.setImage(NSImage(named: NSImage.Name(rawValue: "playButton")), forSegment: 1)
|
||||||
} else {
|
} else {
|
||||||
transportControls.setImage(NSImage(named: NSImage.Name(rawValue: "pauseButton")), forSegment: 1)
|
transportControls.setImage(NSImage(named: NSImage.Name(rawValue: "pauseButton")), forSegment: 1)
|
||||||
@ -65,6 +65,5 @@ class WindowController: NSWindowController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBOutlet var stateLabel: NSTextField!
|
|
||||||
@IBOutlet var transportControls: NSSegmentedCell!
|
@IBOutlet var transportControls: NSSegmentedCell!
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user