diff --git a/Persephone/Controllers/NotificationsController.swift b/Persephone/Controllers/NotificationsController.swift
index 74a9d20..78b11ed 100644
--- a/Persephone/Controllers/NotificationsController.swift
+++ b/Persephone/Controllers/NotificationsController.swift
@@ -18,6 +18,16 @@ class NotificationsController: MPDClientDelegate {
)
}
+ func didUpdateTime(mpdClient: MPDClient, total: UInt, elapsedMs: UInt) {
+ sendNotification(
+ name: Notification.timeChanged,
+ userInfo: [
+ Notification.totalTimeKey: total,
+ Notification.elapsedTimeMsKey: elapsedMs
+ ]
+ )
+ }
+
func didUpdateQueue(mpdClient: MPDClient, queue: [MPDClient.Song]) {
sendNotification(
name: Notification.queueChanged,
diff --git a/Persephone/Controllers/WindowController.swift b/Persephone/Controllers/WindowController.swift
index e95e853..86ba0ed 100644
--- a/Persephone/Controllers/WindowController.swift
+++ b/Persephone/Controllers/WindowController.swift
@@ -13,6 +13,11 @@ class WindowController: NSWindowController {
case prevTrack, playPause, stop, nextTrack
}
+ var state: MPDClient.Status.State?
+ var totalTime: UInt?
+ var elapsedTimeMs: UInt?
+ var trackTimer: Timer?
+
let playIcon = NSImage(named: "playButton")
let pauseIcon = NSImage(named: "pauseButton")
@@ -26,16 +31,38 @@ class WindowController: NSWindowController {
name: Notification.stateChanged,
object: AppDelegate.mpdClient
)
+
+ NotificationCenter.default.addObserver(
+ self,
+ selector: #selector(timeChanged(_:)),
+ name: Notification.timeChanged,
+ object: AppDelegate.mpdClient
+ )
}
@objc func stateChanged(_ notification: Notification) {
guard let state = notification.userInfo?[Notification.stateKey] as? MPDClient.Status.State
else { return }
- setTransportControlState(state)
+ self.state = state
+
+ setTransportControlState()
}
- func setTransportControlState(_ state: MPDClient.Status.State) {
+ @objc func timeChanged(_ notification: Notification) {
+ guard let totalTime = notification.userInfo?[Notification.totalTimeKey] as? UInt,
+ let elapsedTimeMs = notification.userInfo?[Notification.elapsedTimeMsKey] as? UInt
+ else { return }
+
+ self.totalTime = totalTime
+ self.elapsedTimeMs = elapsedTimeMs
+
+ setTrackProgressControls()
+ }
+
+ func setTransportControlState() {
+ guard let state = state else { return }
+
transportControls.setEnabled(state.isOneOf([.playing, .paused]), forSegment: 0)
transportControls.setEnabled(state.isOneOf([.playing, .paused, .stopped]), forSegment: 1)
transportControls.setEnabled(state.isOneOf([.playing, .paused]), forSegment: 2)
@@ -48,6 +75,43 @@ class WindowController: NSWindowController {
}
}
+ func setTrackProgressControls() {
+ guard let totalTime = totalTime,
+ let elapsedTimeMs = elapsedTimeMs
+ else { return }
+
+ trackProgressBar.maxValue = Double(totalTime * 1000)
+ trackProgressBar.integerValue = Int(elapsedTimeMs)
+
+ if [.playing, .paused].contains(state) {
+ trackProgressBar.isEnabled = true
+ } else {
+ trackProgressBar.isEnabled = false
+ }
+
+ if state == .playing {
+ trackTimer?.invalidate()
+
+ trackTimer = Timer.scheduledTimer(
+ timeInterval: 0.25,
+ target: self,
+ selector: #selector(updateProgress(_:)),
+ userInfo: nil,
+ repeats: true
+ )
+ } else {
+ trackTimer?.invalidate()
+ }
+ }
+
+ @objc func updateProgress(_ timer: Timer) {
+ guard let currentProgress = elapsedTimeMs else { return }
+
+ elapsedTimeMs = currentProgress + 250
+
+ trackProgressBar.integerValue = Int(elapsedTimeMs!)
+ }
+
@IBAction func handleTransportControl(_ sender: NSSegmentedControl) {
guard let transportAction = TransportAction(rawValue: sender.selectedSegment)
else { return }
@@ -65,4 +129,8 @@ class WindowController: NSWindowController {
}
@IBOutlet var transportControls: NSSegmentedCell!
+
+ @IBOutlet var trackProgress: NSTextField!
+ @IBOutlet var trackProgressBar: NSSlider!
+ @IBOutlet var trackRemaining: NSTextField!
}
diff --git a/Persephone/Extensions/Notification.swift b/Persephone/Extensions/Notification.swift
index 903f39a..72d901b 100644
--- a/Persephone/Extensions/Notification.swift
+++ b/Persephone/Extensions/Notification.swift
@@ -10,6 +10,7 @@ import Foundation
extension Notification {
static let stateChanged = Notification.Name("MPDClientStateChanged")
+ static let timeChanged = Notification.Name("MPDClientTimeChanged")
static let queueChanged = Notification.Name("MPDClientQueueChanged")
static let queuePosChanged = Notification.Name("MPDClientQueuePosChanged")
static let loadedAlbums = Notification.Name("MPDClientLoadedAlbums")
@@ -18,4 +19,6 @@ extension Notification {
static let queueKey = "queue"
static let queuePosKey = "song"
static let albumsKey = "albums"
+ static let totalTimeKey = "totalTime"
+ static let elapsedTimeMsKey = "elapsedTimeMs"
}
diff --git a/Persephone/MPDClient/MPDClient.swift b/Persephone/MPDClient/MPDClient.swift
index 35cc66c..fb8ea4f 100644
--- a/Persephone/MPDClient/MPDClient.swift
+++ b/Persephone/MPDClient/MPDClient.swift
@@ -213,8 +213,8 @@ class MPDClient {
func idle() {
commandQueue.async { [unowned self] in
mpd_send_idle(self.connection)
- let result = mpd_recv_idle(self.connection, true)
+ let result = mpd_recv_idle(self.connection, true)
self.handleIdleResult(result)
}
}
@@ -228,8 +228,12 @@ class MPDClient {
}
if mpdIdle.contains(.player) {
self.fetchStatus()
- self.delegate?.didUpdateState(mpdClient: self, state: self.status!.state)
- self.delegate?.didUpdateQueuePos(mpdClient: self, song: self.status!.song)
+
+ if let status = self.status {
+ self.delegate?.didUpdateState(mpdClient: self, state: status.state)
+ self.delegate?.didUpdateTime(mpdClient: self, total: status.totalTime, elapsedMs: status.elapsedTimeMs)
+ self.delegate?.didUpdateQueuePos(mpdClient: self, song: status.song)
+ }
}
if !mpdIdle.isEmpty {
self.idle()
diff --git a/Persephone/MPDClient/Models/Status.swift b/Persephone/MPDClient/Models/Status.swift
index bbd30ce..e43d140 100644
--- a/Persephone/MPDClient/Models/Status.swift
+++ b/Persephone/MPDClient/Models/Status.swift
@@ -34,9 +34,20 @@ extension MPDClient {
return State(rawValue: UInt(mpdState.rawValue))!
}
+ var totalTime: UInt {
+ let mpdTotalTime = mpd_status_get_total_time(mpdStatus)
+
+ return UInt(mpdTotalTime)
+ }
+
+ var elapsedTimeMs: UInt {
+ let mpdElapsedTimeMs = mpd_status_get_elapsed_ms(mpdStatus)
+
+ return UInt(mpdElapsedTimeMs)
+ }
+
var song: Int {
return Int(mpd_status_get_song_pos(mpdStatus))
}
-
}
}
diff --git a/Persephone/MPDClient/Protocols/Delegate.swift b/Persephone/MPDClient/Protocols/Delegate.swift
index 5db155e..f8199d3 100644
--- a/Persephone/MPDClient/Protocols/Delegate.swift
+++ b/Persephone/MPDClient/Protocols/Delegate.swift
@@ -10,7 +10,10 @@ import Foundation
protocol MPDClientDelegate {
func didUpdateState(mpdClient: MPDClient, state: MPDClient.Status.State)
+ func didUpdateTime(mpdClient: MPDClient, total: UInt, elapsedMs: UInt)
+
func didUpdateQueue(mpdClient: MPDClient, queue: [MPDClient.Song])
func didUpdateQueuePos(mpdClient: MPDClient, song: Int)
+
func didLoadAlbums(mpdClient: MPDClient, albums: [MPDClient.Album])
}
diff --git a/Persephone/Resources/Base.lproj/Main.storyboard b/Persephone/Resources/Base.lproj/Main.storyboard
index 1b54af7..cae16af 100644
--- a/Persephone/Resources/Base.lproj/Main.storyboard
+++ b/Persephone/Resources/Base.lproj/Main.storyboard
@@ -57,582 +57,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Default
-
-
-
-
-
-
- Left to Right
-
-
-
-
-
-
- Right to Left
-
-
-
-
-
-
-
-
-
-
- Default
-
-
-
-
-
-
- Left to Right
-
-
-
-
-
-
- Right to Left
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -680,7 +104,7 @@
-
+
@@ -715,9 +139,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -725,6 +188,9 @@
+
+
+