diff --git a/Persephone.xcodeproj/project.pbxproj b/Persephone.xcodeproj/project.pbxproj index 122f776..039488d 100644 --- a/Persephone.xcodeproj/project.pbxproj +++ b/Persephone.xcodeproj/project.pbxproj @@ -18,7 +18,6 @@ E408D3C2220E134F0006D9BE /* AlbumViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3C1220E134F0006D9BE /* AlbumViewController.swift */; }; E408D3CB220E341D0006D9BE /* AlbumItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = E408D3C9220E341D0006D9BE /* AlbumItem.xib */; }; E40F41F3221EDE27004B6CB8 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40F41F2221EDE27004B6CB8 /* Preferences.swift */; }; - E40FE719221B48E300A4223F /* AlbumViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40FE718221B48E300A4223F /* AlbumViewLayout.swift */; }; E40FE71B221B904300A4223F /* NSEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40FE71A221B904300A4223F /* NSEvent.swift */; }; E41B22C021FB6BBA00D544F6 /* libmpdclient.2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; settings = {ATTRIBUTES = (Required, ); }; }; E41B22C121FB6C3300D544F6 /* libmpdclient.2.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; @@ -26,11 +25,15 @@ E41EA46C221636AF0068EF46 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EA46B221636AF0068EF46 /* PreferencesViewController.swift */; }; E42A8F3B22176D6400A13ED9 /* LICENSE.md in Resources */ = {isa = PBXBuildFile; fileRef = E42A8F3922176D6400A13ED9 /* LICENSE.md */; }; E42A8F3C22176D6400A13ED9 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = E42A8F3A22176D6400A13ED9 /* README.md */; }; + E435E3E2221CD4E200184CFC /* NSFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435E3E1221CD4E200184CFC /* NSFont.swift */; }; + E435E3E4221CD75D00184CFC /* NSImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435E3E3221CD75D00184CFC /* NSImage.swift */; }; E465049A21E94DF500A70F4C /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E465049921E94DF500A70F4C /* WindowController.swift */; }; E47E2FD122205C4600F747E6 /* MainSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FD022205C4600F747E6 /* MainSplitViewController.swift */; }; E47E2FD322205D2500F747E6 /* MainWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FD222205D2500F747E6 /* MainWindow.swift */; }; E47E2FD5222071FD00F747E6 /* AlbumItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FD4222071FD00F747E6 /* AlbumItem.swift */; }; E47E2FD72220720300F747E6 /* AlbumItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FD62220720300F747E6 /* AlbumItemView.swift */; }; + E47E2FDD2220A6D100F747E6 /* Time.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FDC2220A6D100F747E6 /* Time.swift */; }; + E47E2FE52220AA0700F747E6 /* AlbumViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FE42220AA0700F747E6 /* AlbumViewLayout.swift */; }; E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4928E0A2218D62A001D4BEA /* CGColor.swift */; }; E4A642DA22090CBE00067D21 /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A642D922090CBE00067D21 /* Status.swift */; }; E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC8F2204EC7F0024217A /* Delegate.swift */; }; @@ -94,7 +97,6 @@ E408D3C1220E134F0006D9BE /* AlbumViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumViewController.swift; sourceTree = ""; }; E408D3C9220E341D0006D9BE /* AlbumItem.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AlbumItem.xib; sourceTree = ""; }; E40F41F2221EDE27004B6CB8 /* Preferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; - E40FE718221B48E300A4223F /* AlbumViewLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumViewLayout.swift; sourceTree = ""; }; E40FE71A221B904300A4223F /* NSEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSEvent.swift; sourceTree = ""; }; E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libmpdclient.2.dylib; path = libmpdclient/output/libmpdclient.2.dylib; sourceTree = ""; }; E41B22C421FB715A00D544F6 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; @@ -137,11 +139,15 @@ E41EA46B221636AF0068EF46 /* PreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = ""; }; E42A8F3922176D6400A13ED9 /* LICENSE.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = ""; }; E42A8F3A22176D6400A13ED9 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + E435E3E1221CD4E200184CFC /* NSFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSFont.swift; sourceTree = ""; }; + E435E3E3221CD75D00184CFC /* NSImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSImage.swift; sourceTree = ""; }; E465049921E94DF500A70F4C /* WindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = ""; }; E47E2FD022205C4600F747E6 /* MainSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSplitViewController.swift; sourceTree = ""; }; E47E2FD222205D2500F747E6 /* MainWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainWindow.swift; sourceTree = ""; }; E47E2FD4222071FD00F747E6 /* AlbumItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumItem.swift; sourceTree = ""; }; E47E2FD62220720300F747E6 /* AlbumItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumItemView.swift; sourceTree = ""; }; + E47E2FDC2220A6D100F747E6 /* Time.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Time.swift; sourceTree = ""; }; + E47E2FE42220AA0700F747E6 /* AlbumViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumViewLayout.swift; sourceTree = ""; }; E4928E0A2218D62A001D4BEA /* CGColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGColor.swift; sourceTree = ""; }; E4A642D922090CBE00067D21 /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = ""; }; E4E8CC8F2204EC7F0024217A /* Delegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Delegate.swift; sourceTree = ""; }; @@ -207,7 +213,7 @@ E407861A2110CE6E006887B1 /* Persephone */ = { isa = PBXGroup; children = ( - E40FE717221B48CE00A4223F /* Layouts */, + E47E2FE32220AA0700F747E6 /* Layouts */, E4F6B461221E124700ACF42A /* Models */, E4F6B45E221E117600ACF42A /* DataSources */, E407861F2110CE70006887B1 /* Assets.xcassets */, @@ -249,6 +255,8 @@ E408D3B5220DD8970006D9BE /* Notification.swift */, E408D3B8220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift */, E40FE71A221B904300A4223F /* NSEvent.swift */, + E435E3E1221CD4E200184CFC /* NSFont.swift */, + E435E3E3221CD75D00184CFC /* NSImage.swift */, ); path = Extensions; sourceTree = ""; @@ -270,14 +278,6 @@ path = Views; sourceTree = ""; }; - E40FE717221B48CE00A4223F /* Layouts */ = { - isa = PBXGroup; - children = ( - E40FE718221B48E300A4223F /* AlbumViewLayout.swift */, - ); - path = Layouts; - sourceTree = ""; - }; E41B22BE21FB6B3300D544F6 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -337,6 +337,14 @@ path = mpd; sourceTree = ""; }; + E47E2FE32220AA0700F747E6 /* Layouts */ = { + isa = PBXGroup; + children = ( + E47E2FE42220AA0700F747E6 /* AlbumViewLayout.swift */, + ); + path = Layouts; + sourceTree = ""; + }; E4A642DB220912FA00067D21 /* MPDClient */ = { isa = PBXGroup; children = ( @@ -402,6 +410,7 @@ E4F6B461221E124700ACF42A /* Models */ = { isa = PBXGroup; children = ( + E47E2FDC2220A6D100F747E6 /* Time.swift */, E40F41F2221EDE27004B6CB8 /* Preferences.swift */, E4F6B462221E125900ACF42A /* SongItem.swift */, ); @@ -558,7 +567,6 @@ E4A642DA22090CBE00067D21 /* Status.swift in Sources */, E47E2FD72220720300F747E6 /* AlbumItemView.swift in Sources */, E4E8CC942206097F0024217A /* NotificationsController.swift in Sources */, - E40FE719221B48E300A4223F /* AlbumViewLayout.swift in Sources */, E408D3B6220DD8970006D9BE /* Notification.swift in Sources */, E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */, E4EB2379220F10B8008C70C0 /* Pair.swift in Sources */, @@ -566,7 +574,9 @@ E465049A21E94DF500A70F4C /* WindowController.swift in Sources */, E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */, E40F41F3221EDE27004B6CB8 /* Preferences.swift in Sources */, + E47E2FDD2220A6D100F747E6 /* Time.swift in Sources */, E407861C2110CE6E006887B1 /* AppDelegate.swift in Sources */, + E47E2FE52220AA0700F747E6 /* AlbumViewLayout.swift in Sources */, E47E2FD322205D2500F747E6 /* MainWindow.swift in Sources */, E47E2FD122205C4600F747E6 /* MainSplitViewController.swift in Sources */, E4E8CC9A22075D370024217A /* Song.swift in Sources */, @@ -576,6 +586,8 @@ E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */, E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */, E4EB237B220F7CF1008C70C0 /* Album.swift in Sources */, + E435E3E4221CD75D00184CFC /* NSImage.swift in Sources */, + E435E3E2221CD4E200184CFC /* NSFont.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Persephone/Controllers/NotificationsController.swift b/Persephone/Controllers/NotificationsController.swift index 1d004bf..2b1b4ab 100644 --- a/Persephone/Controllers/NotificationsController.swift +++ b/Persephone/Controllers/NotificationsController.swift @@ -26,6 +26,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/QueueViewController.swift b/Persephone/Controllers/QueueViewController.swift index 7023ff0..3a9e4cf 100644 --- a/Persephone/Controllers/QueueViewController.swift +++ b/Persephone/Controllers/QueueViewController.swift @@ -12,12 +12,6 @@ class QueueViewController: NSViewController, NSOutlineViewDelegate { var dataSource = QueueDataSource() - let systemFontRegular = NSFont.systemFont(ofSize: 13, weight: .regular) - let systemFontBold = NSFont.systemFont(ofSize: 13, weight: .bold) - - let playIcon = NSImage(named: "playButton") - let pauseIcon = NSImage(named: "pauseButton") - @IBOutlet var queueView: NSOutlineView! override func viewDidLoad() { @@ -100,14 +94,14 @@ class QueueViewController: NSViewController, let cellView = outlineView.makeView( withIdentifier: .queueSongTitle, owner: self - ) as! NSTableCellView + ) as! NSTableCellView cellView.textField?.stringValue = songItem.song.getTag(.title) if songItem.isPlaying { - cellView.textField?.font = systemFontBold + cellView.textField?.font = .systemFontBold cellView.imageView?.image = dataSource.queueIcon } else { - cellView.textField?.font = systemFontRegular + cellView.textField?.font = .systemFontRegular cellView.imageView?.image = nil } @@ -118,13 +112,13 @@ class QueueViewController: NSViewController, let cellView = outlineView.makeView( withIdentifier: .queueSongArtist, owner: self - ) as! NSTableCellView + ) as! NSTableCellView cellView.textField?.stringValue = songItem.song.getTag(.artist) if songItem.isPlaying { - cellView.textField?.font = systemFontBold + cellView.textField?.font = .systemFontBold } else { - cellView.textField?.font = systemFontRegular + cellView.textField?.font = .systemFontRegular } return cellView @@ -134,7 +128,7 @@ class QueueViewController: NSViewController, let cellView = outlineView.makeView( withIdentifier: .queueHeading, owner: self - ) as! NSTableCellView + ) as! NSTableCellView cellView.textField?.stringValue = "QUEUE" diff --git a/Persephone/Controllers/WindowController.swift b/Persephone/Controllers/WindowController.swift index d1ea8f8..1c6bab8 100644 --- a/Persephone/Controllers/WindowController.swift +++ b/Persephone/Controllers/WindowController.swift @@ -13,8 +13,10 @@ class WindowController: NSWindowController { case prevTrack, playPause, stop, nextTrack } - let playIcon = NSImage(named: "playButton") - let pauseIcon = NSImage(named: "pauseButton") + var state: MPDClient.Status.State? + var totalTime: UInt? + var elapsedTimeMs: UInt? + var trackTimer: Timer? override func windowDidLoad() { super.windowDidLoad() @@ -26,6 +28,16 @@ class WindowController: NSWindowController { name: Notification.stateChanged, object: AppDelegate.mpdClient ) + + NotificationCenter.default.addObserver( + self, + selector: #selector(timeChanged(_:)), + name: Notification.timeChanged, + object: AppDelegate.mpdClient + ) + + trackProgress.font = .timerFont + trackRemaining.font = .timerFont } override func keyDown(with event: NSEvent) { @@ -41,19 +53,123 @@ class WindowController: NSWindowController { 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) { + 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) transportControls.setEnabled(state.isOneOf([.playing, .paused]), forSegment: 3) if state.isOneOf([.paused, .stopped, .unknown]) { - transportControls.setImage(playIcon, forSegment: 1) + transportControls.setImage(.playIcon, forSegment: 1) } else { - transportControls.setImage(pauseIcon, forSegment: 1) + transportControls.setImage(.pauseIcon, forSegment: 1) + } + } + + @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 setTrackProgressControls() { + guard let totalTime = totalTime, + let elapsedTimeMs = elapsedTimeMs + else { return } + + trackProgressBar.isEnabled = [.playing, .paused].contains(state) + trackProgressBar.maxValue = Double(totalTime * 1000) + + if state == .playing { + trackTimer?.invalidate() + + trackTimer = Timer.scheduledTimer( + timeInterval: 0.25, + target: self, + selector: #selector(updateProgress(_:)), + userInfo: [ + "startTime": CACurrentMediaTime(), + "startElapsed": Double(elapsedTimeMs) / 1000 + ], + repeats: true + ) + } else { + trackTimer?.invalidate() + + trackProgressBar.integerValue = Int(elapsedTimeMs) + setTimeElapsed() + setTimeRemaining() + } + } + + @objc func updateProgress(_ timer: Timer) { + let currentTime = CACurrentMediaTime() + + guard let userInfo = timer.userInfo as? Dictionary, + let startTime = userInfo["startTime"] as? Double, + let startElapsed = userInfo["startElapsed"] as? Double + else { return } + + let timeDiff = currentTime - startTime + let newElapsedTimeMs = (startElapsed + timeDiff) * 1000 + + self.elapsedTimeMs = UInt(newElapsedTimeMs) + trackProgressBar.integerValue = Int(newElapsedTimeMs) + + setTimeElapsed() + setTimeRemaining() + } + + func setTimeElapsed() { + guard let elapsedTimeMs = elapsedTimeMs else { return } + + let time = Time(timeInSeconds: Int(elapsedTimeMs) / 1000) + + trackProgress.stringValue = time.formattedTime + } + + func setTimeRemaining() { + guard let elapsedTimeMs = elapsedTimeMs, + let totalTime = totalTime + else { return } + + let time = Time(timeInSeconds: -(Int(totalTime) - Int(elapsedTimeMs) / 1000)) + + trackRemaining.stringValue = time.formattedTime + } + + // TODO: Refactor this using a gesture recognizer + @IBAction func changeTrackProgress(_ sender: NSSlider) { + guard let event = NSApplication.shared.currentEvent else { + return + } + + switch event.type { + case .leftMouseDown: + trackTimer?.invalidate() + case .leftMouseDragged: + self.elapsedTimeMs = UInt(sender.integerValue) + + setTimeElapsed() + setTimeRemaining() + case .leftMouseUp: + let seekTime = Float(sender.integerValue) / 1000 + + AppDelegate.mpdClient.seekCurrentSong(timeInSeconds: seekTime) + default: + break } } @@ -74,4 +190,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/NSFont.swift b/Persephone/Extensions/NSFont.swift new file mode 100644 index 0000000..d3e1415 --- /dev/null +++ b/Persephone/Extensions/NSFont.swift @@ -0,0 +1,16 @@ +// +// NSFont.swift +// Persephone +// +// Created by Daniel Barber on 2019/2/19. +// Copyright © 2019 Dan Barber. All rights reserved. +// + +import Cocoa + +extension NSFont { + static let systemFontRegular = systemFont(ofSize: 13, weight: .regular) + static let systemFontBold = systemFont(ofSize: 13, weight: .bold) + + static let timerFont = monospacedDigitSystemFont(ofSize: 13, weight: .regular) +} diff --git a/Persephone/Extensions/NSImage.swift b/Persephone/Extensions/NSImage.swift new file mode 100644 index 0000000..d85c8b9 --- /dev/null +++ b/Persephone/Extensions/NSImage.swift @@ -0,0 +1,14 @@ +// +// NSImage.swift +// Persephone +// +// Created by Daniel Barber on 2019/2/19. +// Copyright © 2019 Dan Barber. All rights reserved. +// + +import Cocoa + +extension NSImage { + static let playIcon = NSImage(named: "playButton") + static let pauseIcon = NSImage(named: "pauseButton") +} diff --git a/Persephone/Extensions/Notification.swift b/Persephone/Extensions/Notification.swift index 462bec8..6a8f5b4 100644 --- a/Persephone/Extensions/Notification.swift +++ b/Persephone/Extensions/Notification.swift @@ -9,16 +9,19 @@ import Foundation extension Notification { - static let didConnect = Notification.Name("MPDClientDidConnect") - static let willDisconnect = Notification.Name("MPDClientWillDisconnect") + static let didConnect = Name("MPDClientDidConnect") + static let willDisconnect = Name("MPDClientWillDisconnect") - static let stateChanged = Notification.Name("MPDClientStateChanged") - static let queueChanged = Notification.Name("MPDClientQueueChanged") - static let queuePosChanged = Notification.Name("MPDClientQueuePosChanged") - static let loadedAlbums = Notification.Name("MPDClientLoadedAlbums") + static let stateChanged = Name("MPDClientStateChanged") + static let timeChanged = Name("MPDClientTimeChanged") + static let queueChanged = Name("MPDClientQueueChanged") + static let queuePosChanged = Name("MPDClientQueuePosChanged") + static let loadedAlbums = Name("MPDClientLoadedAlbums") static let stateKey = "state" 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 ad63a74..86dbf8a 100644 --- a/Persephone/MPDClient/MPDClient.swift +++ b/Persephone/MPDClient/MPDClient.swift @@ -64,6 +64,7 @@ class MPDClient { self.delegate?.didConnect(mpdClient: self) self.delegate?.didUpdateState(mpdClient: self, state: self.status!.state) + self.delegate?.didUpdateTime(mpdClient: self, total: self.status!.totalTime, elapsedMs: self.status!.elapsedTimeMs) self.delegate?.didUpdateQueue(mpdClient: self, queue: self.queue) self.delegate?.didUpdateQueuePos(mpdClient: self, song: self.status!.song) } @@ -71,11 +72,11 @@ class MPDClient { func disconnect() { guard isConnected else { return } - + noIdle() commandQueue.async { [unowned self] in self.delegate?.willDisconnect(mpdClient: self) - + mpd_connection_free(self.connection) self.isConnected = false } @@ -119,6 +120,14 @@ class MPDClient { idle() } + func seekCurrentSong(timeInSeconds: Float) { + noIdle() + commandQueue.async { [unowned self] in + mpd_run_seek_current(self.connection, timeInSeconds, false) + } + idle() + } + func playAlbum(_ album: Album) { noIdle() commandQueue.async { [unowned self] in @@ -142,7 +151,7 @@ class MPDClient { func queueCommand(command: Command) { guard isConnected else { return } - + noIdle() commandQueue.async { [unowned self] in self.sendCommand(command: command) @@ -246,8 +255,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) } } @@ -261,8 +270,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 c98edf0..25de673 100644 --- a/Persephone/MPDClient/Protocols/Delegate.swift +++ b/Persephone/MPDClient/Protocols/Delegate.swift @@ -13,7 +13,10 @@ protocol MPDClientDelegate { func willDisconnect(mpdClient: MPDClient) 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/Models/Time.swift b/Persephone/Models/Time.swift new file mode 100644 index 0000000..4c2596f --- /dev/null +++ b/Persephone/Models/Time.swift @@ -0,0 +1,28 @@ +// +// Time.swift +// Persephone +// +// Created by Daniel Barber on 2019/2/19. +// Copyright © 2019 Dan Barber. All rights reserved. +// + +import Foundation + +struct Time { + let timeInSeconds: Int + + var formattedTime: String { + let formatter = DateComponentsFormatter() + + if timeInSeconds >= 3600 { + formatter.allowedUnits = [.second, .minute, .hour] + } else { + formatter.allowedUnits = [.second, .minute] + } + + formatter.zeroFormattingBehavior = .pad + formatter.unitsStyle = .positional + + return formatter.string(from: TimeInterval(timeInSeconds))! + } +} diff --git a/Persephone/Resources/Base.lproj/Main.storyboard b/Persephone/Resources/Base.lproj/Main.storyboard index 7266679..b05c8ab 100644 --- a/Persephone/Resources/Base.lproj/Main.storyboard +++ b/Persephone/Resources/Base.lproj/Main.storyboard @@ -62,582 +62,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Default - - - - - - - Left to Right - - - - - - - Right to Left - - - - - - - - - - - Default - - - - - - - Left to Right - - - - - - - Right to Left - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -720,9 +144,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -730,6 +200,9 @@ + + +