1
1
mirror of https://github.com/danbee/persephone synced 2025-03-04 08:39:11 +00:00

Compare commits

..

23 Commits

Author SHA1 Message Date
770a01c604
Update to 0.16.0-alpha 2020-02-19 18:15:18 -05:00
91c82bbc6f
Add volume control 2020-02-19 18:08:22 -05:00
123f9c1e4a
Tweak priorities of commands 2020-02-16 14:39:12 -05:00
cd2e5efc95
Cache the small image when we get the browser art
We use the RawImageDataProvider here to pass the already downloaded
image onto this transform, to avoid fetching it from MPD again. The
180x180 size artwork is plenty big enough to use as source for the 32x32
queue/dragged song artwork.
2020-02-16 12:51:11 -05:00
a663315013
💄 2020-02-16 12:50:56 -05:00
6236884195
Fix a nasty deadlock 2020-02-16 12:50:32 -05:00
cff3e7bfc6
Bump version to 0.16.0-prealpha 2020-02-15 21:47:30 -05:00
f22b5b022c
We were chasing a red herring
It should actually be perfectly ok to send `noidle` before the call to
`recv_idle`. It just means the `recv_idle` will return immediately.
2020-02-15 21:36:41 -05:00
cacb0b0124
Trying to lock when we already have the lock is bad 2020-02-15 21:36:41 -05:00
f39c2a4f99
Remove redundant selfs 2020-02-15 21:36:41 -05:00
abe2c293eb
This does not need to be async on the main thread
In fact doing so causes us to idle excessively.
2020-02-15 21:36:40 -05:00
abf1579789
Hold onto idleLock while we handle idle 2020-02-15 21:36:40 -05:00
2f59eaeecf
Woops, we were reading this outside of the lock! 2020-02-15 21:36:40 -05:00
fa338ee790
Address some PR feedback 2020-02-15 21:36:40 -05:00
a7e7620f68
Attempt to fix album detail view crash
Very occasionally the album detail view fails to get the album tracks
and this causes a crash. This extra guard avoids the crash, but doesn't
fix the underlying problem.

The problem itself is very hard to track down because it's incredibly
hard to reproduce.
2020-02-15 21:36:39 -05:00
eb5cee2a75
Fix potential race 2020-02-15 21:36:39 -05:00
f39f69b4dc
We can just reuse the same provider 2020-02-15 21:36:39 -05:00
8d24d9df19
Update all the packages 2020-02-15 21:36:39 -05:00
7c99a9a712
We never mutate this 2020-02-15 21:36:39 -05:00
7f8e209970
Self *could* get deallocated here 2020-02-15 21:36:38 -05:00
c0f8badbb5
Fix deprecation warning 2020-02-15 21:36:38 -05:00
3b4b5f5b5c
Refactor cover art refresh using Promises 2020-02-15 21:36:38 -05:00
ff3c7c4856
Fetching album art via MPD connection now
- Remove music dir prefs
+ Add refresh album art option to album list context menu
+ Wire up album view context menu
+ Force an idle after transport commands
+ Add "clear cache" button
2020-02-15 21:36:38 -05:00
77 changed files with 921 additions and 367 deletions

View File

@ -53,9 +53,11 @@
E44051A0227BB0AB0090CD6F /* UIState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E440519F227BB0AB0090CD6F /* UIState.swift */; }; E44051A0227BB0AB0090CD6F /* UIState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E440519F227BB0AB0090CD6F /* UIState.swift */; };
E442CCCD2347E73C00004E0C /* Artist.swift in Sources */ = {isa = PBXBuildFile; fileRef = E442CCCC2347E73C00004E0C /* Artist.swift */; }; E442CCCD2347E73C00004E0C /* Artist.swift in Sources */ = {isa = PBXBuildFile; fileRef = E442CCCC2347E73C00004E0C /* Artist.swift */; };
E450AD7E222620A10091BED3 /* Album.swift in Sources */ = {isa = PBXBuildFile; fileRef = E450AD7D222620A10091BED3 /* Album.swift */; }; E450AD7E222620A10091BED3 /* Album.swift in Sources */ = {isa = PBXBuildFile; fileRef = E450AD7D222620A10091BED3 /* Album.swift */; };
E450AD9522262DF10091BED3 /* CoverArtQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = E450AD9422262DF10091BED3 /* CoverArtQueue.swift */; };
E451E36B22BD214D008BE9B2 /* DraggedSongType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E451E36A22BD214D008BE9B2 /* DraggedSongType.swift */; }; E451E36B22BD214D008BE9B2 /* DraggedSongType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E451E36A22BD214D008BE9B2 /* DraggedSongType.swift */; };
E451E36E22BD2501008BE9B2 /* DraggedSong.swift in Sources */ = {isa = PBXBuildFile; fileRef = E451E36C22BD23DB008BE9B2 /* DraggedSong.swift */; }; E451E36E22BD2501008BE9B2 /* DraggedSong.swift in Sources */ = {isa = PBXBuildFile; fileRef = E451E36C22BD23DB008BE9B2 /* DraggedSong.swift */; };
E453825223FA0186007F6BFC /* VolumeControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E453825023FA0186007F6BFC /* VolumeControlView.swift */; };
E453825323FA0186007F6BFC /* VolumeControlView.xib in Resources */ = {isa = PBXBuildFile; fileRef = E453825123FA0186007F6BFC /* VolumeControlView.xib */; };
E453825523FA347C007F6BFC /* MPDClient+Mixer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E453825423FA347C007F6BFC /* MPDClient+Mixer.swift */; };
E45878382296173C00586A1C /* AlbumDetailSongRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E45878372296173C00586A1C /* AlbumDetailSongRowView.swift */; }; E45878382296173C00586A1C /* AlbumDetailSongRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E45878372296173C00586A1C /* AlbumDetailSongRowView.swift */; };
E45962C62241A78500FC1A1E /* MPDCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = E45962C52241A78500FC1A1E /* MPDCommand.swift */; }; E45962C62241A78500FC1A1E /* MPDCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = E45962C52241A78500FC1A1E /* MPDCommand.swift */; };
E45E4FDA22515D87004B537F /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = E45E4FD722515D87004B537F /* CHANGELOG.md */; }; E45E4FDA22515D87004B537F /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = E45E4FD722515D87004B537F /* CHANGELOG.md */; };
@ -81,6 +83,8 @@
E4A642DA22090CBE00067D21 /* MPDStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A642D922090CBE00067D21 /* MPDStatus.swift */; }; E4A642DA22090CBE00067D21 /* MPDStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A642D922090CBE00067D21 /* MPDStatus.swift */; };
E4A83BEF2221F8CF0098FED6 /* CoverArtPrefsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BEE2221F8CF0098FED6 /* CoverArtPrefsController.swift */; }; E4A83BEF2221F8CF0098FED6 /* CoverArtPrefsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BEE2221F8CF0098FED6 /* CoverArtPrefsController.swift */; };
E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */; }; E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */; };
E4B079C723E5E0AD0044B6D3 /* libmpdclient.2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; };
E4B079C823E5E0AD0044B6D3 /* libmpdclient.2.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
E4B11B53226928F20075461B /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B52226928F20075461B /* AppState.swift */; }; E4B11B53226928F20075461B /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B52226928F20075461B /* AppState.swift */; };
E4B11B61226A4C000075461B /* PlayerReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B60226A4BFF0075461B /* PlayerReducer.swift */; }; E4B11B61226A4C000075461B /* PlayerReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B60226A4BFF0075461B /* PlayerReducer.swift */; };
E4B11B63226A4C510075461B /* AppReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B62226A4C510075461B /* AppReducer.swift */; }; E4B11B63226A4C510075461B /* AppReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B62226A4C510075461B /* AppReducer.swift */; };
@ -90,8 +94,6 @@
E4B11B73226A6C770075461B /* TrackTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B72226A6C770075461B /* TrackTimer.swift */; }; E4B11B73226A6C770075461B /* TrackTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B72226A6C770075461B /* TrackTimer.swift */; };
E4B11B75226CC4D30075461B /* QueueReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B74226CC4D30075461B /* QueueReducer.swift */; }; E4B11B75226CC4D30075461B /* QueueReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B74226CC4D30075461B /* QueueReducer.swift */; };
E4B11B79226D346B0075461B /* AlbumListReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B78226D346B0075461B /* AlbumListReducer.swift */; }; E4B11B79226D346B0075461B /* AlbumListReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B78226D346B0075461B /* AlbumListReducer.swift */; };
E4B11BA62274E44A0075461B /* libmpdclient.2.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
E4B11BA72274E4500075461B /* libmpdclient.2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; };
E4B11BA92274EDE30075461B /* Loading.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11BA82274EDE30075461B /* Loading.swift */; }; E4B11BA92274EDE30075461B /* Loading.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11BA82274EDE30075461B /* Loading.swift */; };
E4B11BB62275374B0075461B /* UserNotificationsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11BB52275374B0075461B /* UserNotificationsController.swift */; }; E4B11BB62275374B0075461B /* UserNotificationsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11BB52275374B0075461B /* UserNotificationsController.swift */; };
E4B11BB8227538FA0075461B /* CurrentCoverArtView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11BB7227538FA0075461B /* CurrentCoverArtView.swift */; }; E4B11BB8227538FA0075461B /* CurrentCoverArtView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11BB7227538FA0075461B /* CurrentCoverArtView.swift */; };
@ -100,11 +102,14 @@
E4B11BC22275EE410075461B /* AlbumListActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11BC12275EE410075461B /* AlbumListActions.swift */; }; E4B11BC22275EE410075461B /* AlbumListActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11BC12275EE410075461B /* AlbumListActions.swift */; };
E4B3DF6523D66A4400728F6B /* QueueSongCoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B3DF6423D66A4400728F6B /* QueueSongCoverView.swift */; }; E4B3DF6523D66A4400728F6B /* QueueSongCoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B3DF6423D66A4400728F6B /* QueueSongCoverView.swift */; };
E4B5AE7E22F4C49600CCEC65 /* MPDServerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B5AE7D22F4C49600CCEC65 /* MPDServerDelegate.swift */; }; E4B5AE7E22F4C49600CCEC65 /* MPDServerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B5AE7D22F4C49600CCEC65 /* MPDServerDelegate.swift */; };
E4BB7F8F23E5E7BC00906E2F /* MPDAlbumArtImageDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BB7F8E23E5E7BC00906E2F /* MPDAlbumArtImageDataProvider.swift */; };
E4BB7F9323E9150A00906E2F /* CoverArtService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BB7F9223E9150A00906E2F /* CoverArtService.swift */; };
E4BBD2F323357C0700702C16 /* ArtistListState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BBD2F223357C0700702C16 /* ArtistListState.swift */; }; E4BBD2F323357C0700702C16 /* ArtistListState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BBD2F223357C0700702C16 /* ArtistListState.swift */; };
E4C8B53C22342005009A20F3 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */; }; E4C8B53C22342005009A20F3 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */; };
E4C8B53E22349002009A20F3 /* MPDIdle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53D22349002009A20F3 /* MPDIdle.swift */; }; E4C8B53E22349002009A20F3 /* MPDIdle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53D22349002009A20F3 /* MPDIdle.swift */; };
E4D3BFA622B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3BFA522B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift */; }; E4D3BFA622B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3BFA522B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift */; };
E4DA820623D6236200C1EE58 /* NSSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4DA820523D6236200C1EE58 /* NSSize.swift */; }; E4DA820623D6236200C1EE58 /* NSSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4DA820523D6236200C1EE58 /* NSSize.swift */; };
E4DCCFAE23E4DB5D009A8113 /* MPDClientWrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = E4DCCFAD23E4DB5D009A8113 /* MPDClientWrapper.c */; };
E4E7A6AD22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E7A6AC22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift */; }; E4E7A6AD22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E7A6AC22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift */; };
E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC8F2204EC7F0024217A /* Delegate.swift */; }; E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC8F2204EC7F0024217A /* Delegate.swift */; };
E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC912204F4B80024217A /* QueueViewController.swift */; }; E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC912204F4B80024217A /* QueueViewController.swift */; };
@ -151,15 +156,15 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
E4B11BA52274E43B0075461B /* Embed Frameworks */ = { E4B079C923E5E0AD0044B6D3 /* Embed Libraries */ = {
isa = PBXCopyFilesBuildPhase; isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
dstPath = ""; dstPath = "";
dstSubfolderSpec = 10; dstSubfolderSpec = 10;
files = ( files = (
E4B11BA62274E44A0075461B /* libmpdclient.2.dylib in Embed Frameworks */, E4B079C823E5E0AD0044B6D3 /* libmpdclient.2.dylib in Embed Libraries */,
); );
name = "Embed Frameworks"; name = "Embed Libraries";
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
@ -254,10 +259,12 @@
E440519F227BB0AB0090CD6F /* UIState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIState.swift; sourceTree = "<group>"; }; E440519F227BB0AB0090CD6F /* UIState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIState.swift; sourceTree = "<group>"; };
E442CCCC2347E73C00004E0C /* Artist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Artist.swift; sourceTree = "<group>"; }; E442CCCC2347E73C00004E0C /* Artist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Artist.swift; sourceTree = "<group>"; };
E450AD7D222620A10091BED3 /* Album.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Album.swift; sourceTree = "<group>"; }; E450AD7D222620A10091BED3 /* Album.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Album.swift; sourceTree = "<group>"; };
E450AD9422262DF10091BED3 /* CoverArtQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverArtQueue.swift; sourceTree = "<group>"; };
E450AD9E2229B9BC0091BED3 /* PersephoneBridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PersephoneBridgingHeader.h; sourceTree = "<group>"; }; E450AD9E2229B9BC0091BED3 /* PersephoneBridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PersephoneBridgingHeader.h; sourceTree = "<group>"; };
E451E36A22BD214D008BE9B2 /* DraggedSongType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggedSongType.swift; sourceTree = "<group>"; }; E451E36A22BD214D008BE9B2 /* DraggedSongType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggedSongType.swift; sourceTree = "<group>"; };
E451E36C22BD23DB008BE9B2 /* DraggedSong.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggedSong.swift; sourceTree = "<group>"; }; E451E36C22BD23DB008BE9B2 /* DraggedSong.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggedSong.swift; sourceTree = "<group>"; };
E453825023FA0186007F6BFC /* VolumeControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolumeControlView.swift; sourceTree = "<group>"; };
E453825123FA0186007F6BFC /* VolumeControlView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = VolumeControlView.xib; sourceTree = "<group>"; };
E453825423FA347C007F6BFC /* MPDClient+Mixer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Mixer.swift"; sourceTree = "<group>"; };
E45878372296173C00586A1C /* AlbumDetailSongRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumDetailSongRowView.swift; sourceTree = "<group>"; }; E45878372296173C00586A1C /* AlbumDetailSongRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumDetailSongRowView.swift; sourceTree = "<group>"; };
E45962C52241A78500FC1A1E /* MPDCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDCommand.swift; sourceTree = "<group>"; }; E45962C52241A78500FC1A1E /* MPDCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDCommand.swift; sourceTree = "<group>"; };
E45E4FD722515D87004B537F /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = SOURCE_ROOT; }; E45E4FD722515D87004B537F /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = SOURCE_ROOT; };
@ -294,11 +301,16 @@
E4B11BC12275EE410075461B /* AlbumListActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumListActions.swift; sourceTree = "<group>"; }; E4B11BC12275EE410075461B /* AlbumListActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumListActions.swift; sourceTree = "<group>"; };
E4B3DF6423D66A4400728F6B /* QueueSongCoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueSongCoverView.swift; sourceTree = "<group>"; }; E4B3DF6423D66A4400728F6B /* QueueSongCoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueSongCoverView.swift; sourceTree = "<group>"; };
E4B5AE7D22F4C49600CCEC65 /* MPDServerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDServerDelegate.swift; sourceTree = "<group>"; }; E4B5AE7D22F4C49600CCEC65 /* MPDServerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDServerDelegate.swift; sourceTree = "<group>"; };
E4BB7F8E23E5E7BC00906E2F /* MPDAlbumArtImageDataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDAlbumArtImageDataProvider.swift; sourceTree = "<group>"; };
E4BB7F9223E9150A00906E2F /* CoverArtService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverArtService.swift; sourceTree = "<group>"; };
E4BBD2F223357C0700702C16 /* ArtistListState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArtistListState.swift; sourceTree = "<group>"; }; E4BBD2F223357C0700702C16 /* ArtistListState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArtistListState.swift; sourceTree = "<group>"; };
E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; }; E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; };
E4C8B53D22349002009A20F3 /* MPDIdle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDIdle.swift; sourceTree = "<group>"; }; E4C8B53D22349002009A20F3 /* MPDIdle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDIdle.swift; sourceTree = "<group>"; };
E4D3BFA522B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QueueViewController+NSOutlineViewDelegate.swift"; sourceTree = "<group>"; }; E4D3BFA522B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QueueViewController+NSOutlineViewDelegate.swift"; sourceTree = "<group>"; };
E4DA820523D6236200C1EE58 /* NSSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSSize.swift; sourceTree = "<group>"; }; E4DA820523D6236200C1EE58 /* NSSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSSize.swift; sourceTree = "<group>"; };
E4DCCFAB23E4DB5D009A8113 /* Persephone-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Persephone-Bridging-Header.h"; sourceTree = "<group>"; };
E4DCCFAC23E4DB5D009A8113 /* MPDClientWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPDClientWrapper.h; sourceTree = "<group>"; };
E4DCCFAD23E4DB5D009A8113 /* MPDClientWrapper.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = MPDClientWrapper.c; sourceTree = "<group>"; };
E4E7A6AC22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AlbumDetailView+NSTableViewDelegate.swift"; sourceTree = "<group>"; }; E4E7A6AC22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AlbumDetailView+NSTableViewDelegate.swift"; sourceTree = "<group>"; };
E4E8CC8F2204EC7F0024217A /* Delegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Delegate.swift; sourceTree = "<group>"; }; E4E8CC8F2204EC7F0024217A /* Delegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Delegate.swift; sourceTree = "<group>"; };
E4E8CC912204F4B80024217A /* QueueViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueViewController.swift; sourceTree = "<group>"; }; E4E8CC912204F4B80024217A /* QueueViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueViewController.swift; sourceTree = "<group>"; };
@ -322,12 +334,12 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
E4B11BA72274E4500075461B /* libmpdclient.2.dylib in Frameworks */,
E49A5485233E5ADC00EED353 /* Differ in Frameworks */, E49A5485233E5ADC00EED353 /* Differ in Frameworks */,
E4677C48233E60E70041474F /* MediaKeyTap in Frameworks */, E4677C48233E60E70041474F /* MediaKeyTap in Frameworks */,
E43BECA0238835DC00CAF1EB /* Kingfisher in Frameworks */, E43BECA0238835DC00CAF1EB /* Kingfisher in Frameworks */,
E49A548E233E5B6000EED353 /* SwiftyJSON in Frameworks */, E49A548E233E5B6000EED353 /* SwiftyJSON in Frameworks */,
E4E96D13233E630800AFD36F /* PMKFoundation in Frameworks */, E4E96D13233E630800AFD36F /* PMKFoundation in Frameworks */,
E4B079C723E5E0AD0044B6D3 /* libmpdclient.2.dylib in Frameworks */,
E49A5482233E580800EED353 /* PromiseKit in Frameworks */, E49A5482233E580800EED353 /* PromiseKit in Frameworks */,
E49A548B233E5B2D00EED353 /* CryptoSwift in Frameworks */, E49A548B233E5B2D00EED353 /* CryptoSwift in Frameworks */,
E49A5488233E5B0000EED353 /* ReSwift in Frameworks */, E49A5488233E5B0000EED353 /* ReSwift in Frameworks */,
@ -387,7 +399,6 @@
E40786242110CE70006887B1 /* Info.plist */, E40786242110CE70006887B1 /* Info.plist */,
E4F6B461221E124700ACF42A /* Models */, E4F6B461221E124700ACF42A /* Models */,
E4A642DB220912FA00067D21 /* MPDClient */, E4A642DB220912FA00067D21 /* MPDClient */,
E450AD8922262B420091BED3 /* Operations */,
E40786252110CE70006887B1 /* Persephone.entitlements */, E40786252110CE70006887B1 /* Persephone.entitlements */,
E450AD9E2229B9BC0091BED3 /* PersephoneBridgingHeader.h */, E450AD9E2229B9BC0091BED3 /* PersephoneBridgingHeader.h */,
E4A83BF2222207BE0098FED6 /* Services */, E4A83BF2222207BE0098FED6 /* Services */,
@ -441,11 +452,15 @@
E42410B52241B956005ED6DF /* MPDClient+Database.swift */, E42410B52241B956005ED6DF /* MPDClient+Database.swift */,
E41E5304223BFB0700173814 /* MPDClient+Error.swift */, E41E5304223BFB0700173814 /* MPDClient+Error.swift */,
E41E5302223BF9C300173814 /* MPDClient+Idle.swift */, E41E5302223BF9C300173814 /* MPDClient+Idle.swift */,
E453825423FA347C007F6BFC /* MPDClient+Mixer.swift */,
E41E5300223BF99300173814 /* MPDClient+Queue.swift */, E41E5300223BF99300173814 /* MPDClient+Queue.swift */,
E42A4D5022E2167E001C6CAD /* MPDClient+Songs.swift */, E42A4D5022E2167E001C6CAD /* MPDClient+Songs.swift */,
E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */,
E41E5306223C019100173814 /* MPDClient+Status.swift */, E41E5306223C019100173814 /* MPDClient+Status.swift */,
E41E52FE223BF95E00173814 /* MPDClient+Transport.swift */, E41E52FE223BF95E00173814 /* MPDClient+Transport.swift */,
E4DCCFAD23E4DB5D009A8113 /* MPDClientWrapper.c */,
E4DCCFAC23E4DB5D009A8113 /* MPDClientWrapper.h */,
E4DCCFAB23E4DB5D009A8113 /* Persephone-Bridging-Header.h */,
E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */,
); );
path = Extensions; path = Extensions;
sourceTree = "<group>"; sourceTree = "<group>";
@ -512,6 +527,7 @@
E442CCC42347D5B900004E0C /* Components */ = { E442CCC42347D5B900004E0C /* Components */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E453824D23F9F700007F6BFC /* VolumeControl */,
E442CCCB2347D77A00004E0C /* Browser */, E442CCCB2347D77A00004E0C /* Browser */,
E4A83BEC2221F5DD0098FED6 /* Preferences */, E4A83BEC2221F5DD0098FED6 /* Preferences */,
E442CCC62347D5E700004E0C /* Queue */, E442CCC62347D5E700004E0C /* Queue */,
@ -574,6 +590,7 @@
E442CCC92347D6FD00004E0C /* Shared */ = { E442CCC92347D6FD00004E0C /* Shared */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E4BB7F8D23E5E7A300906E2F /* ImageDataProviders */,
E4E13C2C2350D8CB00092A6E /* Layouts */, E4E13C2C2350D8CB00092A6E /* Layouts */,
E408D3B7220DE8CC0006D9BE /* Extensions */, E408D3B7220DE8CC0006D9BE /* Extensions */,
E489E3A222B9D31800CA8CBD /* DraggedSongView.swift */, E489E3A222B9D31800CA8CBD /* DraggedSongView.swift */,
@ -594,12 +611,13 @@
path = Browser; path = Browser;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
E450AD8922262B420091BED3 /* Operations */ = { E453824D23F9F700007F6BFC /* VolumeControl */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E450AD9422262DF10091BED3 /* CoverArtQueue.swift */, E453825023FA0186007F6BFC /* VolumeControlView.swift */,
E453825123FA0186007F6BFC /* VolumeControlView.xib */,
); );
path = Operations; path = VolumeControl;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
E4A642DB220912FA00067D21 /* MPDClient */ = { E4A642DB220912FA00067D21 /* MPDClient */ = {
@ -628,6 +646,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E439109722640213002982E9 /* SongNotifierService.swift */, E439109722640213002982E9 /* SongNotifierService.swift */,
E4BB7F9223E9150A00906E2F /* CoverArtService.swift */,
); );
path = Services; path = Services;
sourceTree = "<group>"; sourceTree = "<group>";
@ -675,6 +694,14 @@
path = Actions; path = Actions;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
E4BB7F8D23E5E7A300906E2F /* ImageDataProviders */ = {
isa = PBXGroup;
children = (
E4BB7F8E23E5E7BC00906E2F /* MPDAlbumArtImageDataProvider.swift */,
);
path = ImageDataProviders;
sourceTree = "<group>";
};
E4D1B594220BA2490026F233 /* Models */ = { E4D1B594220BA2490026F233 /* Models */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -735,7 +762,7 @@
E40786162110CE6E006887B1 /* Resources */, E40786162110CE6E006887B1 /* Resources */,
E42A98F122430936004D8180 /* ShellScript */, E42A98F122430936004D8180 /* ShellScript */,
E421AC9B221F7319008B2449 /* CopyFiles */, E421AC9B221F7319008B2449 /* CopyFiles */,
E4B11BA52274E43B0075461B /* Embed Frameworks */, E4B079C923E5E0AD0044B6D3 /* Embed Libraries */,
); );
buildRules = ( buildRules = (
); );
@ -804,7 +831,7 @@
TargetAttributes = { TargetAttributes = {
E40786172110CE6E006887B1 = { E40786172110CE6E006887B1 = {
CreatedOnToolsVersion = 9.4.1; CreatedOnToolsVersion = 9.4.1;
LastSwiftMigration = 1020; LastSwiftMigration = 1130;
SystemCapabilities = { SystemCapabilities = {
com.apple.HardenedRuntime = { com.apple.HardenedRuntime = {
enabled = 1; enabled = 1;
@ -863,6 +890,7 @@
files = ( files = (
E45E4FDB22515D87004B537F /* Brewfile in Resources */, E45E4FDB22515D87004B537F /* Brewfile in Resources */,
E42A8F3B22176D6400A13ED9 /* LICENSE.md in Resources */, E42A8F3B22176D6400A13ED9 /* LICENSE.md in Resources */,
E453825323FA0186007F6BFC /* VolumeControlView.xib in Resources */,
E45E4FDA22515D87004B537F /* CHANGELOG.md in Resources */, E45E4FDA22515D87004B537F /* CHANGELOG.md in Resources */,
E43B67AB22909793007DCF55 /* AlbumDetailView.xib in Resources */, E43B67AB22909793007DCF55 /* AlbumDetailView.xib in Resources */,
E489E3A522B9D31800CA8CBD /* DraggedSongView.xib in Resources */, E489E3A522B9D31800CA8CBD /* DraggedSongView.xib in Resources */,
@ -933,13 +961,14 @@
E4B11B63226A4C510075461B /* AppReducer.swift in Sources */, E4B11B63226A4C510075461B /* AppReducer.swift in Sources */,
E41E5307223C019100173814 /* MPDClient+Status.swift in Sources */, E41E5307223C019100173814 /* MPDClient+Status.swift in Sources */,
E442CCCD2347E73C00004E0C /* Artist.swift in Sources */, E442CCCD2347E73C00004E0C /* Artist.swift in Sources */,
E4DCCFAE23E4DB5D009A8113 /* MPDClientWrapper.c in Sources */,
E41E530B223C033700173814 /* MPDClient+Album.swift in Sources */, E41E530B223C033700173814 /* MPDClient+Album.swift in Sources */,
E42410B62241B956005ED6DF /* MPDClient+Database.swift in Sources */, E42410B62241B956005ED6DF /* MPDClient+Database.swift in Sources */,
E4BB7F8F23E5E7BC00906E2F /* MPDAlbumArtImageDataProvider.swift in Sources */,
E4235640228623D2001216D6 /* QueueSongInfoView.swift in Sources */, E4235640228623D2001216D6 /* QueueSongInfoView.swift in Sources */,
E451E36E22BD2501008BE9B2 /* DraggedSong.swift in Sources */, E451E36E22BD2501008BE9B2 /* DraggedSong.swift in Sources */,
E4A642DA22090CBE00067D21 /* MPDStatus.swift in Sources */, E4A642DA22090CBE00067D21 /* MPDStatus.swift in Sources */,
E4B11BC02275EE150075461B /* QueueActions.swift in Sources */, E4B11BC02275EE150075461B /* QueueActions.swift in Sources */,
E450AD9522262DF10091BED3 /* CoverArtQueue.swift in Sources */,
E43B67AD229194CD007DCF55 /* AlbumTracksDataSource.swift in Sources */, E43B67AD229194CD007DCF55 /* AlbumTracksDataSource.swift in Sources */,
E41E52FD223BF87300173814 /* MPDClient+Connection.swift in Sources */, E41E52FD223BF87300173814 /* MPDClient+Connection.swift in Sources */,
E450AD7E222620A10091BED3 /* Album.swift in Sources */, E450AD7E222620A10091BED3 /* Album.swift in Sources */,
@ -968,6 +997,7 @@
E419E2872249B96600216A8C /* Song.swift in Sources */, E419E2872249B96600216A8C /* Song.swift in Sources */,
E451E36B22BD214D008BE9B2 /* DraggedSongType.swift in Sources */, E451E36B22BD214D008BE9B2 /* DraggedSongType.swift in Sources */,
E4BBD2F323357C0700702C16 /* ArtistListState.swift in Sources */, E4BBD2F323357C0700702C16 /* ArtistListState.swift in Sources */,
E453825223FA0186007F6BFC /* VolumeControlView.swift in Sources */,
E44051A0227BB0AB0090CD6F /* UIState.swift in Sources */, E44051A0227BB0AB0090CD6F /* UIState.swift in Sources */,
E4FF718E2276010E00D4C412 /* PreferencesState.swift in Sources */, E4FF718E2276010E00D4C412 /* PreferencesState.swift in Sources */,
E439109822640213002982E9 /* SongNotifierService.swift in Sources */, E439109822640213002982E9 /* SongNotifierService.swift in Sources */,
@ -978,6 +1008,7 @@
E4F26F7923411B1500D45FF9 /* ArtistReducer.swift in Sources */, E4F26F7923411B1500D45FF9 /* ArtistReducer.swift in Sources */,
E4B11B73226A6C770075461B /* TrackTimer.swift in Sources */, E4B11B73226A6C770075461B /* TrackTimer.swift in Sources */,
E44051942278765A0090CD6F /* App.swift in Sources */, E44051942278765A0090CD6F /* App.swift in Sources */,
E4BB7F9323E9150A00906E2F /* CoverArtService.swift in Sources */,
E4B11B79226D346B0075461B /* AlbumListReducer.swift in Sources */, E4B11B79226D346B0075461B /* AlbumListReducer.swift in Sources */,
E47E2FE52220AA0700F747E6 /* FlexibleGridViewLayout.swift in Sources */, E47E2FE52220AA0700F747E6 /* FlexibleGridViewLayout.swift in Sources */,
E41E52FF223BF95E00173814 /* MPDClient+Transport.swift in Sources */, E41E52FF223BF95E00173814 /* MPDClient+Transport.swift in Sources */,
@ -985,6 +1016,7 @@
E47E2FD122205C4600F747E6 /* MainSplitViewController.swift in Sources */, E47E2FD122205C4600F747E6 /* MainSplitViewController.swift in Sources */,
E4B5AE7E22F4C49600CCEC65 /* MPDServerDelegate.swift in Sources */, E4B5AE7E22F4C49600CCEC65 /* MPDServerDelegate.swift in Sources */,
E4B11BB8227538FA0075461B /* CurrentCoverArtView.swift in Sources */, E4B11BB8227538FA0075461B /* CurrentCoverArtView.swift in Sources */,
E453825523FA347C007F6BFC /* MPDClient+Mixer.swift in Sources */,
E4E8CC9A22075D370024217A /* MPDSong.swift in Sources */, E4E8CC9A22075D370024217A /* MPDSong.swift in Sources */,
E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */, E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */,
E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */, E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */,
@ -1173,6 +1205,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
@ -1188,10 +1221,12 @@
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/libmpdclient/output", "$(PROJECT_DIR)/libmpdclient/output",
); );
MARKETING_VERSION = "0.15.2-alpha"; MARKETING_VERSION = "0.16.0-alpha";
PRODUCT_BUNDLE_IDENTIFIER = me.danbarber.Persephone; PRODUCT_BUNDLE_IDENTIFIER = me.danbarber.Persephone;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Persephone/MPDClient/Extensions/Persephone-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
SYSTEM_HEADER_SEARCH_PATHS = Persephone/include; SYSTEM_HEADER_SEARCH_PATHS = Persephone/include;
USER_HEADER_SEARCH_PATHS = libmpdclient/output; USER_HEADER_SEARCH_PATHS = libmpdclient/output;
@ -1202,6 +1237,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
@ -1217,10 +1253,11 @@
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/libmpdclient/output", "$(PROJECT_DIR)/libmpdclient/output",
); );
MARKETING_VERSION = "0.15.2-alpha"; MARKETING_VERSION = "0.16.0-alpha";
PRODUCT_BUNDLE_IDENTIFIER = me.danbarber.Persephone; PRODUCT_BUNDLE_IDENTIFIER = me.danbarber.Persephone;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Persephone/MPDClient/Extensions/Persephone-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
SYSTEM_HEADER_SEARCH_PATHS = Persephone/include; SYSTEM_HEADER_SEARCH_PATHS = Persephone/include;
USER_HEADER_SEARCH_PATHS = libmpdclient/output; USER_HEADER_SEARCH_PATHS = libmpdclient/output;

View File

@ -6,8 +6,8 @@
"repositoryURL": "https://github.com/krzyzanowskim/CryptoSwift", "repositoryURL": "https://github.com/krzyzanowskim/CryptoSwift",
"state": { "state": {
"branch": null, "branch": null,
"revision": "3a2acbb32ab68215ee1596ee6004da8e90c3721b", "revision": "a44caef0550c346e0ab9172f7c9a3852c1833599",
"version": "1.0.0" "version": "1.3.0"
} }
}, },
{ {
@ -15,8 +15,8 @@
"repositoryURL": "https://github.com/tonyarnold/Differ", "repositoryURL": "https://github.com/tonyarnold/Differ",
"state": { "state": {
"branch": null, "branch": null,
"revision": "e2cca36e7258dd8add88ae46b5ea56509b066e21", "revision": "dd5d4bfb1c27012d4790e877b29847d2ab9d989b",
"version": "1.4.3" "version": "1.4.4"
} }
}, },
{ {
@ -24,8 +24,8 @@
"repositoryURL": "https://github.com/PromiseKit/Foundation", "repositoryURL": "https://github.com/PromiseKit/Foundation",
"state": { "state": {
"branch": null, "branch": null,
"revision": "ee06d95342a5007de2fffd898f4f35de026842ac", "revision": "1a276e598dac59489ed904887e0740fa75e571e0",
"version": "3.3.3" "version": "3.3.4"
} }
}, },
{ {
@ -33,8 +33,8 @@
"repositoryURL": "https://github.com/onevcat/Kingfisher.git", "repositoryURL": "https://github.com/onevcat/Kingfisher.git",
"state": { "state": {
"branch": null, "branch": null,
"revision": "8ef6ca8b1b767ac2400762ed2f2bf75ddea3de5b", "revision": "44bfa76787a0f07e4d5ae3304e9d4f631fd519bd",
"version": "5.10.1" "version": "5.13.0"
} }
}, },
{ {
@ -51,8 +51,8 @@
"repositoryURL": "https://github.com/mxcl/PromiseKit", "repositoryURL": "https://github.com/mxcl/PromiseKit",
"state": { "state": {
"branch": null, "branch": null,
"revision": "4d8d1287d2e50c53a9f8430ffe88925292838c57", "revision": "f14f16cc2602afec1030e4f492100d6d43dca544",
"version": "6.11.0" "version": "6.13.1"
} }
}, },
{ {

View File

@ -27,6 +27,15 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E40786172110CE6E006887B1"
BuildableName = "Persephone.app"
BlueprintName = "Persephone"
ReferencedContainer = "container:Persephone.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables> <Testables>
<TestableReference <TestableReference
skipped = "NO"> skipped = "NO">
@ -49,17 +58,6 @@
</BuildableReference> </BuildableReference>
</TestableReference> </TestableReference>
</Testables> </Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E40786172110CE6E006887B1"
BuildableName = "Persephone.app"
BlueprintName = "Persephone"
ReferencedContainer = "container:Persephone.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Debug"
@ -81,8 +79,6 @@
ReferencedContainer = "container:Persephone.xcodeproj"> ReferencedContainer = "container:Persephone.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "Release" buildConfiguration = "Release"

View File

@ -0,0 +1,25 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "speakerDisabled.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "speakerDisabled@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

View File

@ -0,0 +1,25 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "speakerHigh.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "speakerHigh@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

View File

@ -0,0 +1,25 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "speakerLow.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "speakerLow@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 B

View File

@ -0,0 +1,25 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "speakerMid.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "speakerMid@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

View File

@ -0,0 +1,25 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "speakerOff.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "speakerOff@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

View File

@ -56,10 +56,14 @@ class AlbumViewItem: NSCollectionViewItem {
} }
func setAlbumCover(_ album: Album) { func setAlbumCover(_ album: Album) {
guard let imagePath = album.coverArtFilePath else { return } guard let song = album.mpdAlbum.firstSong
else { return }
let provider = MPDAlbumArtImageDataProvider(
songUri: song.uriString,
cacheKey: album.hash
)
let imageURL = URL(fileURLWithPath: imagePath)
let provider = LocalFileImageDataProvider(fileURL: imageURL)
albumCoverView.kf.setImage( albumCoverView.kf.setImage(
with: .provider(provider), with: .provider(provider),
placeholder: NSImage.defaultCoverArt, placeholder: NSImage.defaultCoverArt,
@ -67,7 +71,34 @@ class AlbumViewItem: NSCollectionViewItem {
.processor(DownsamplingImageProcessor(size: .albumListCoverSize)), .processor(DownsamplingImageProcessor(size: .albumListCoverSize)),
.scaleFactor(2), .scaleFactor(2),
] ]
) ) { result in
switch result {
case .success(let imageResult):
guard let imageData = imageResult.image.tiffRepresentation
else { return }
let rawProvider = RawImageDataProvider(
data: imageData,
cacheKey: album.hash
)
self.cacheSmallCover(provider: rawProvider)
case .failure(_):
break
}
}
}
func cacheSmallCover(provider: ImageDataProvider) {
_ = KingfisherManager.shared.retrieveImage(
with: .provider(provider),
options: [
.memoryCacheExpiration(.never),
.processor(DownsamplingImageProcessor(size: .queueSongCoverSize)),
.scaleFactor(2),
]
) { result in }
} }
func setAppearance(selected isSelected: Bool) { func setAppearance(selected isSelected: Bool) {
@ -88,6 +119,18 @@ class AlbumViewItem: NSCollectionViewItem {
boxLayer.backgroundColor = albumCoverBox.layer?.borderColor boxLayer.backgroundColor = albumCoverBox.layer?.borderColor
} }
} }
func refreshAlbumArt() {
guard let album = album,
let mpdSong = album.mpdAlbum.firstSong
else { return }
let song = Song(mpdSong: mpdSong)
CoverArtService(song: song).refresh {
self.setAlbumCover(album)
}
}
@IBAction func showAlbumDetail(_ sender: NSButton) { @IBAction func showAlbumDetail(_ sender: NSButton) {
guard let album = album else { return } guard let album = album else { return }
@ -121,4 +164,8 @@ class AlbumViewItem: NSCollectionViewItem {
App.mpdClient.addAlbumToQueue(album: album.mpdAlbum, at: queueLength) App.mpdClient.addAlbumToQueue(album: album.mpdAlbum, at: queueLength)
} }
@IBAction func refreshAlbumArtMenuAction(_ sender: NSMenuItem) {
refreshAlbumArt()
}
} }

View File

@ -35,7 +35,7 @@
</shadow> </shadow>
<buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="defaultCoverArt" imagePosition="only" alignment="center" lineBreakMode="truncatingTail" refusesFirstResponder="YES" state="on" transparent="YES" imageScaling="proportionallyUpOrDown" inset="2" id="t8A-Hz-L38"> <buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="defaultCoverArt" imagePosition="only" alignment="center" lineBreakMode="truncatingTail" refusesFirstResponder="YES" state="on" transparent="YES" imageScaling="proportionallyUpOrDown" inset="2" id="t8A-Hz-L38">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="label" size="13"/> <font key="font" metaFont="system"/>
</buttonCell> </buttonCell>
<connections> <connections>
<action selector="showAlbumDetail:" target="-2" id="A4Q-gb-B45"/> <action selector="showAlbumDetail:" target="-2" id="A4Q-gb-B45"/>
@ -54,7 +54,7 @@
<textField identifier="albumTitle" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="KEh-NL-c2W"> <textField identifier="albumTitle" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="KEh-NL-c2W">
<rect key="frame" x="8" y="28" width="142" height="17"/> <rect key="frame" x="8" y="28" width="142" height="17"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" title="Label" id="pDs-0t-e1j"> <textFieldCell key="cell" lineBreakMode="truncatingTail" title="Label" id="pDs-0t-e1j">
<font key="font" metaFont="label" size="13"/> <font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
@ -62,7 +62,7 @@
<textField identifier="albumArtist" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5Uu-j1-qyT"> <textField identifier="albumArtist" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5Uu-j1-qyT">
<rect key="frame" x="8" y="10" width="142" height="16"/> <rect key="frame" x="8" y="10" width="142" height="16"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" title="Label" id="yZn-e9-zyP"> <textFieldCell key="cell" lineBreakMode="truncatingTail" title="Label" id="yZn-e9-zyP">
<font key="font" metaFont="label" size="13"/> <font key="font" metaFont="system"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
@ -98,6 +98,13 @@
<action selector="addAlbumToQueueMenuAction:" target="-2" id="6wW-oR-ykh"/> <action selector="addAlbumToQueueMenuAction:" target="-2" id="6wW-oR-ykh"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem isSeparatorItem="YES" id="g9v-9R-xDB"/>
<menuItem title="Refresh album art" id="4Ld-db-wka">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="refreshAlbumArtMenuAction:" target="-2" id="wyO-Dn-k9r"/>
</connections>
</menuItem>
</items> </items>
<point key="canvasLocation" x="191" y="38"/> <point key="canvasLocation" x="191" y="38"/>
</menu> </menu>

View File

@ -116,27 +116,27 @@ class AlbumDetailView: NSViewController {
App.mpdClient.getAlbumSongs(for: album.mpdAlbum) { [weak self] (mpdSongs: [MPDClient.MPDSong]) in App.mpdClient.getAlbumSongs(for: album.mpdAlbum) { [weak self] (mpdSongs: [MPDClient.MPDSong]) in
guard let self = self else { return } guard let self = self else { return }
self.dataSource.setAlbumSongs(
mpdSongs.map { Song(mpdSong: $0) }
)
DispatchQueue.main.async { DispatchQueue.main.async {
self.dataSource.setAlbumSongs(
mpdSongs.map { Song(mpdSong: $0) }
)
self.albumTracksView.reloadData() self.albumTracksView.reloadData()
guard let mpdSong = album.mpdAlbum.firstSong
else { return }
self.getBigCoverArt(song: Song(mpdSong: mpdSong), album: album)
} }
guard let song = self.dataSource.albumSongs.first?.song ??
self.dataSource.albumSongs[1].song
else { return }
self.getBigCoverArt(song: song, album: album)
} }
} }
func getBigCoverArt(song: Song, album: Album) { func getBigCoverArt(song: Song, album: Album) {
guard let imagePath = album.coverArtFilePath else { return } let provider = MPDAlbumArtImageDataProvider(
songUri: song.mpdSong.uriString,
cacheKey: album.hash
)
let imageURL = URL(fileURLWithPath: imagePath)
let provider = LocalFileImageDataProvider(fileURL: imageURL)
albumCoverView.kf.setImage( albumCoverView.kf.setImage(
with: .provider(provider), with: .provider(provider),
placeholder: NSImage.defaultCoverArt, placeholder: NSImage.defaultCoverArt,
@ -145,18 +145,6 @@ class AlbumDetailView: NSViewController {
.scaleFactor(2), .scaleFactor(2),
] ]
) )
cacheSmallCover(provider: provider)
}
func cacheSmallCover(provider: ImageDataProvider) {
_ = KingfisherManager.shared.retrieveImage(
with: .provider(provider),
options: [
.processor(DownsamplingImageProcessor(size: .queueSongCoverSize)),
.scaleFactor(2),
]
) { result in }
} }
func setAppearance() { func setAppearance() {

View File

@ -50,7 +50,8 @@ class AlbumTracksDataSource: NSObject, NSTableViewDataSource {
type: .albumSongItem(song.mpdSong.uriString), type: .albumSongItem(song.mpdSong.uriString),
title: song.title, title: song.title,
artist: song.artist, artist: song.artist,
cover: song.album.coverArtFilePath album: song.album.title,
uri: song.mpdSong.uriString
), ),
ofType: .songPasteboardType ofType: .songPasteboardType
) )
@ -69,10 +70,11 @@ class AlbumTracksDataSource: NSObject, NSTableViewDataSource {
) { draggingItem, index, stop in ) { draggingItem, index, stop in
guard let item = draggingItem.item as? NSPasteboardItem, guard let item = draggingItem.item as? NSPasteboardItem,
let draggedSong = item.draggedSong(forType: .songPasteboardType), let draggedSong = item.draggedSong(forType: .songPasteboardType),
case let (title?, artist?, cover?) = ( case let (title, artist, album, uri) = (
draggedSong.title, draggedSong.title,
draggedSong.artist, draggedSong.artist,
draggedSong.cover draggedSong.album,
draggedSong.uri
) )
else { return } else { return }
@ -81,7 +83,8 @@ class AlbumTracksDataSource: NSObject, NSTableViewDataSource {
let draggedSongView = DraggedSongView( let draggedSongView = DraggedSongView(
title: title, title: title,
artist: artist, artist: artist,
cover: cover album: album,
uri: uri
) )
component.contents = draggedSongView.view.image() component.contents = draggedSongView.view.image()

View File

@ -7,15 +7,12 @@
// //
import AppKit import AppKit
import Kingfisher
class CoverArtPrefsController: NSViewController { class CoverArtPrefsController: NSViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
if let mpdLibraryDir = App.store.state.preferencesState.mpdLibraryDir {
mpdLibraryDirField.stringValue = mpdLibraryDir
}
if App.store.state.preferencesState.fetchMissingArtworkFromInternet { if App.store.state.preferencesState.fetchMissingArtworkFromInternet {
fetchMissingArtworkFromInternet.state = .on fetchMissingArtworkFromInternet.state = .on
} else { } else {
@ -33,12 +30,6 @@ class CoverArtPrefsController: NSViewController {
self.parent?.view.window?.title = title self.parent?.view.window?.title = title
} }
@IBAction func updateMpdLibraryDir(_ sender: NSTextField) {
App.store.dispatch(UpdateMPDLibraryDir(mpdLibraryDir: sender.stringValue))
}
@IBOutlet var mpdLibraryDirField: NSTextField!
@IBAction func updateFetchMissingArtworkFromInternet(_ sender: NSButton) { @IBAction func updateFetchMissingArtworkFromInternet(_ sender: NSButton) {
App.store.dispatch( App.store.dispatch(
UpdateFetchMissingArtworkFromInternet( UpdateFetchMissingArtworkFromInternet(
@ -47,5 +38,10 @@ class CoverArtPrefsController: NSViewController {
) )
} }
@IBAction func clearAlbumArtCache(_ sender: NSButton) {
KingfisherManager.shared.cache.clearDiskCache()
KingfisherManager.shared.cache.clearMemoryCache()
}
@IBOutlet var fetchMissingArtworkFromInternet: NSButton! @IBOutlet var fetchMissingArtworkFromInternet: NSButton!
} }

View File

@ -17,13 +17,23 @@ class CurrentCoverArtView: NSImageView {
App.store.subscribe(self) { App.store.subscribe(self) {
$0.select { $0.playerState.currentSong } $0.select { $0.playerState.currentSong }
} }
NotificationCenter.default.addObserver(self, selector: #selector(didReloadAlbumArt), name: .didReloadAlbumArt, object: nil)
}
@objc func didReloadAlbumArt() {
guard let song = App.store.state.playerState.currentSong
else { return }
setSongImage(song)
} }
func setAlbumImage(_ album: Album) { func setSongImage(_ song: Song) {
guard let imagePath = album.coverArtFilePath else { return } let provider = MPDAlbumArtImageDataProvider(
songUri: song.mpdSong.uriString,
cacheKey: song.album.hash
)
let imageURL = URL(fileURLWithPath: imagePath)
let provider = LocalFileImageDataProvider(fileURL: imageURL)
kf.setImage( kf.setImage(
with: .provider(provider), with: .provider(provider),
placeholder: NSImage.defaultCoverArt, placeholder: NSImage.defaultCoverArt,
@ -44,6 +54,6 @@ extension CurrentCoverArtView: StoreSubscriber {
return return
} }
setAlbumImage(song.album) setSongImage(song)
} }
} }

View File

@ -44,7 +44,8 @@ class QueueDataSource: NSObject, NSOutlineViewDataSource {
type: .queueItem(queueItem.queuePos), type: .queueItem(queueItem.queuePos),
title: queueItem.song.title, title: queueItem.song.title,
artist: queueItem.song.artist, artist: queueItem.song.artist,
cover: queueItem.song.album.coverArtFilePath album: queueItem.song.album.title,
uri: queueItem.song.mpdSong.uriString
), ),
ofType: .songPasteboardType ofType: .songPasteboardType
) )
@ -128,10 +129,11 @@ class QueueDataSource: NSObject, NSOutlineViewDataSource {
guard let item = draggingItem.item as? NSPasteboardItem, guard let item = draggingItem.item as? NSPasteboardItem,
let data = item.data(forType: .songPasteboardType), let data = item.data(forType: .songPasteboardType),
let draggedSong = try? PropertyListDecoder().decode(DraggedSong.self, from: data), let draggedSong = try? PropertyListDecoder().decode(DraggedSong.self, from: data),
case let (title?, artist?, cover?) = ( case let (title, artist, album, uri) = (
draggedSong.title, draggedSong.title,
draggedSong.artist, draggedSong.artist,
draggedSong.cover draggedSong.album,
draggedSong.uri
) )
else { return } else { return }
@ -140,7 +142,8 @@ class QueueDataSource: NSObject, NSOutlineViewDataSource {
let draggedSongView = DraggedSongView( let draggedSongView = DraggedSongView(
title: title, title: title,
artist: artist, artist: artist,
cover: cover album: album,
uri: uri
) )
let view = draggedSongView.view let view = draggedSongView.view

View File

@ -60,14 +60,15 @@ class QueueSongCoverView: NSTableCellView {
} }
func setSong(_ queueItem: QueueItem, queueIcon: NSImage?) { func setSong(_ queueItem: QueueItem, queueIcon: NSImage?) {
guard let imagePath = queueItem.song.album.coverArtFilePath let song = queueItem.song
else { return }
isPlaying = queueItem.isPlaying isPlaying = queueItem.isPlaying
let imageURL = URL(fileURLWithPath: imagePath)
let provider = LocalFileImageDataProvider(fileURL: imageURL)
let provider = MPDAlbumArtImageDataProvider(
songUri: song.mpdSong.uriString,
cacheKey: song.album.hash
)
queueSongCover.kf.setImage( queueSongCover.kf.setImage(
with: .provider(provider), with: .provider(provider),
placeholder: NSImage.defaultCoverArt, placeholder: NSImage.defaultCoverArt,

View File

@ -24,6 +24,7 @@ class QueueViewController: NSViewController {
NotificationCenter.default.addObserver(self, selector: #selector(didConnect), name: .didConnect, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(didConnect), name: .didConnect, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(willDisconnect), name: .willDisconnect, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(willDisconnect), name: .willDisconnect, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didReloadAlbumArt), name: .didReloadAlbumArt, object: nil)
queueView.dataSource = dataSource queueView.dataSource = dataSource
queueView.columnAutoresizingStyle = .sequentialColumnAutoresizingStyle queueView.columnAutoresizingStyle = .sequentialColumnAutoresizingStyle
@ -61,6 +62,10 @@ class QueueViewController: NSViewController {
App.store.dispatch(UpdateQueueAction(queue: [])) App.store.dispatch(UpdateQueueAction(queue: []))
} }
} }
@objc func didReloadAlbumArt() {
queueView.reloadData()
}
@IBAction func playTrack(_ sender: Any) { @IBAction func playTrack(_ sender: Any) {
let queuePos = queueView.selectedRow let queuePos = queueView.selectedRow

View File

@ -16,12 +16,14 @@ class DraggedSongView: NSViewController {
private let songTitle: String private let songTitle: String
private let songArtist: String private let songArtist: String
private let songCover: String? private let songAlbum: String
private let songUri: String
init(title: String, artist: String, cover: String? = nil) { init(title: String, artist: String, album: String, uri: String) {
songTitle = title songTitle = title
songArtist = artist songArtist = artist
songCover = cover songAlbum = album
songUri = uri
super.init(nibName: nil, bundle: nil) super.init(nibName: nil, bundle: nil)
} }
@ -58,11 +60,13 @@ class DraggedSongView: NSViewController {
} }
func setCoverArt() { func setCoverArt() {
guard let imagePath = songCover else { return } let mpdAlbum = MPDClient.MPDAlbum(title: songAlbum, artist: songArtist)
let provider = MPDAlbumArtImageDataProvider(
songUri: songUri,
cacheKey: Album(mpdAlbum: mpdAlbum).hash
)
let imageURL = URL(fileURLWithPath: imagePath)
let provider = LocalFileImageDataProvider(fileURL: imageURL)
coverImage.kf.setImage( coverImage.kf.setImage(
with: .provider(provider), with: .provider(provider),
placeholder: NSImage.defaultCoverArt, placeholder: NSImage.defaultCoverArt,

View File

@ -15,34 +15,10 @@ extension NSImage {
static let queuePauseIcon = NSImage(named: "queuePauseButton") static let queuePauseIcon = NSImage(named: "queuePauseButton")
static let defaultCoverArt = NSImage(named: "defaultCoverArt") static let defaultCoverArt = NSImage(named: "defaultCoverArt")
func toFitBox(size: NSSize) -> NSImage { static let speakerDisabled = NSImage(named: "speakerDisabled")
var newSize: NSSize = NSSize.zero static let speakerOff = NSImage(named: "speakerOff")
let aspectRatio = self.size.width / self.size.height static let speakerLow = NSImage(named: "speakerLow")
let boxAspectRatio = size.width / size.height static let speakerMid = NSImage(named: "speakerMid")
static let speakerHigh = NSImage(named: "speakerHigh")
if aspectRatio > boxAspectRatio {
newSize = NSSize(width: size.width, height: size.width / aspectRatio)
} else {
newSize = NSSize(width: size.height * aspectRatio, height: size.height)
}
let newImage = NSImage(size: newSize)
newImage.lockFocus()
self.draw(in: newImage.alignmentRect)
newImage.unlockFocus()
return newImage
}
func jpegData(compressionQuality: CGFloat) -> Data? {
guard let image = cgImage(forProposedRect: nil, context: nil, hints: nil)
else { return nil }
let bitmapImageRep = NSBitmapImageRep(cgImage: image)
return bitmapImageRep.representation(
using: .jpeg,
properties: [.compressionFactor: compressionQuality]
)
}
} }

View File

@ -11,4 +11,5 @@ import Foundation
extension Notification.Name { extension Notification.Name {
static let didConnect = Notification.Name("MPDClientDidConnect") static let didConnect = Notification.Name("MPDClientDidConnect")
static let willDisconnect = Notification.Name("MPDClientWillDisconnect") static let willDisconnect = Notification.Name("MPDClientWillDisconnect")
static let didReloadAlbumArt = Notification.Name("MPDDidReloadAlbumArt")
} }

View File

@ -0,0 +1,34 @@
//
// MPDAlbumArtImageDataProvider.swift
// Persephone
//
// Created by Daniel Barber on 2/1/20.
// Copyright © 2020 Dan Barber. All rights reserved.
//
import Foundation
import Kingfisher
public struct MPDAlbumArtImageDataProvider: ImageDataProvider {
let songUri: String
init(songUri: String, cacheKey: String) {
self.songUri = songUri
self.cacheKey = cacheKey
}
public var cacheKey: String
public func data(handler: @escaping (Result<Data, Error>) -> Void) {
App.mpdClient.fetchAlbumArt(songUri: songUri, imageData: nil) { imageData in
guard let imageData = imageData
else { return }
handler(.success(imageData))
}
}
public var contentURL: String? {
return songUri
}
}

View File

@ -18,14 +18,16 @@ class UserNotificationsController {
} }
func notifyTrack(_ state: Song?) { func notifyTrack(_ state: Song?) {
guard let currentSong = state, guard let song = state,
let coverArtFilePath = currentSong.album.coverArtFilePath,
let status = App.mpdClient.status, let status = App.mpdClient.status,
status.state == .playing status.state == .playing
else { return } else { return }
let provider = MPDAlbumArtImageDataProvider(
songUri: song.mpdSong.uriString,
cacheKey: song.album.hash
)
let imageURL = URL(fileURLWithPath: coverArtFilePath)
let provider = LocalFileImageDataProvider(fileURL: imageURL)
_ = KingfisherManager.shared.retrieveImage( _ = KingfisherManager.shared.retrieveImage(
with: .provider(provider), with: .provider(provider),
options: [ options: [
@ -35,7 +37,7 @@ class UserNotificationsController {
) { result in ) { result in
switch result { switch result {
case .success(let value): case .success(let value):
SongNotifierService(song: currentSong, image: value.image) SongNotifierService(song: song, image: value.image)
.deliver() .deliver()
case .failure: case .failure:
break break

View File

@ -0,0 +1,45 @@
//
// VolumeControlView.swift
// Persephone
//
// Created by Daniel Barber on 2/16/20.
// Copyright © 2020 Dan Barber. All rights reserved.
//
import AppKit
import ReSwift
class VolumeControlView: NSViewController {
static let shared = VolumeControlView()
static let popover = NSPopover()
var currentVolume: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
App.store.subscribe(self) {
$0.select { $0.playerState }
}
}
@IBAction func volumeSliderAction(_ sender: NSSlider) {
let newVolume = sender.integerValue
if newVolume != currentVolume {
App.mpdClient.setVolume(to: newVolume)
currentVolume = newVolume
}
}
@IBOutlet var volumeSlider: NSSlider!
}
extension VolumeControlView: StoreSubscriber {
typealias StoreSubscriberStateType = PlayerState
func newState(state: StoreSubscriberStateType) {
volumeSlider.integerValue = state.volume
currentVolume = state.volume
}
}

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="15705" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15705"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="VolumeControlView" customModule="Persephone" customModuleProvider="target">
<connections>
<outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
<outlet property="volumeSlider" destination="E78-vZ-qV0" id="7qP-Fm-MDY"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="Hz6-mo-xeY">
<rect key="frame" x="0.0" y="0.0" width="36" height="145"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<slider horizontalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="E78-vZ-qV0" userLabel="Volume Slider">
<rect key="frame" x="9" y="12" width="19" height="121"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<sliderCell key="cell" continuous="YES" alignment="left" maxValue="100" doubleValue="50" tickMarkPosition="right" sliderType="linear" id="22u-9w-IXT"/>
<connections>
<action selector="volumeSliderAction:" target="-2" id="1yh-vH-sgN"/>
</connections>
</slider>
</subviews>
<point key="canvasLocation" x="-83" y="90.5"/>
</customView>
</objects>
</document>

View File

@ -208,7 +208,7 @@
<rect key="frame" x="0.0" y="14" width="153" height="24"/> <rect key="frame" x="0.0" y="14" width="153" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<segmentedCell key="cell" borderStyle="border" alignment="left" style="texturedRounded" trackingMode="momentary" id="EBk-sD-nG7"> <segmentedCell key="cell" borderStyle="border" alignment="left" style="texturedRounded" trackingMode="momentary" id="EBk-sD-nG7">
<font key="font" metaFont="system"/> <font key="font" metaFont="label" size="13"/>
<segments> <segments>
<segment image="prevTrackButton" width="32" enabled="NO"/> <segment image="prevTrackButton" width="32" enabled="NO"/>
<segment image="playButton" width="48" enabled="NO" tag="1"/> <segment image="playButton" width="48" enabled="NO" tag="1"/>
@ -230,7 +230,7 @@
<rect key="frame" x="16" y="14" width="55" height="17"/> <rect key="frame" x="16" y="14" width="55" height="17"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" placeholderString="8:88:88" id="g0c-k5-wCA"> <textFieldCell key="cell" lineBreakMode="clipping" alignment="right" placeholderString="8:88:88" id="g0c-k5-wCA">
<font key="font" metaFont="system"/> <font key="font" metaFont="label" size="13"/>
<color key="textColor" name="tertiaryLabelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="tertiaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
@ -257,7 +257,7 @@
<rect key="frame" x="16" y="14" width="60" height="17"/> <rect key="frame" x="16" y="14" width="60" height="17"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="left" placeholderString="-8:88:88" id="XUa-pD-s5c"> <textFieldCell key="cell" lineBreakMode="clipping" alignment="left" placeholderString="-8:88:88" id="XUa-pD-s5c">
<font key="font" metaFont="system"/> <font key="font" metaFont="label" size="13"/>
<color key="textColor" name="tertiaryLabelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="tertiaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
@ -277,9 +277,9 @@
<button key="view" verticalHuggingPriority="750" id="E8L-uK-XT0"> <button key="view" verticalHuggingPriority="750" id="E8L-uK-XT0">
<rect key="frame" x="2" y="14" width="42" height="24"/> <rect key="frame" x="2" y="14" width="42" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="shuffleButton" imagePosition="only" alignment="center" lineBreakMode="truncatingTail" borderStyle="border" inset="2" id="YNb-hd-ax8"> <buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="shuffleButton" imagePosition="overlaps" alignment="center" lineBreakMode="truncatingTail" borderStyle="border" inset="2" id="YNb-hd-ax8">
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/> <behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/> <font key="font" metaFont="label" size="13"/>
</buttonCell> </buttonCell>
<connections> <connections>
<action selector="handleShuffleButton:" target="B8D-0N-5wS" id="THd-0g-fmb"/> <action selector="handleShuffleButton:" target="B8D-0N-5wS" id="THd-0g-fmb"/>
@ -295,7 +295,7 @@
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="repeatButton" imagePosition="only" alignment="center" lineBreakMode="truncatingTail" borderStyle="border" inset="2" id="1bu-vK-3Hb"> <buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="repeatButton" imagePosition="only" alignment="center" lineBreakMode="truncatingTail" borderStyle="border" inset="2" id="1bu-vK-3Hb">
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/> <behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/> <font key="font" metaFont="label" size="13"/>
</buttonCell> </buttonCell>
<connections> <connections>
<action selector="handleRepeatButton:" target="B8D-0N-5wS" id="EN2-u4-DNl"/> <action selector="handleRepeatButton:" target="B8D-0N-5wS" id="EN2-u4-DNl"/>
@ -308,7 +308,7 @@
<rect key="frame" x="0.0" y="14" width="96" height="22"/> <rect key="frame" x="0.0" y="14" width="96" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" usesSingleLineMode="YES" bezelStyle="round" id="F3N-3P-tS3"> <searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" usesSingleLineMode="YES" bezelStyle="round" id="F3N-3P-tS3">
<font key="font" metaFont="system"/> <font key="font" metaFont="label" size="13"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</searchFieldCell> </searchFieldCell>
@ -317,6 +317,20 @@
</connections> </connections>
</searchField> </searchField>
</toolbarItem> </toolbarItem>
<toolbarItem implicitItemIdentifier="01E8DA80-2BDE-49F9-B311-69876CF0AE8E" label="Volume" paletteLabel="Volume" image="speakerHigh" sizingBehavior="auto" id="cMg-Mj-j7q">
<nil key="toolTip"/>
<button key="view" verticalHuggingPriority="750" id="cfN-LI-Cab">
<rect key="frame" x="2" y="14" width="42" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="speakerHigh" imagePosition="overlaps" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="bJh-X9-7q0">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="label" size="13"/>
</buttonCell>
<connections>
<action selector="showVolumeControl:" target="B8D-0N-5wS" id="UoW-fa-jBM"/>
</connections>
</button>
</toolbarItem>
</allowedToolbarItems> </allowedToolbarItems>
<defaultToolbarItems> <defaultToolbarItems>
<toolbarItem reference="p3r-ty-Pxf"/> <toolbarItem reference="p3r-ty-Pxf"/>
@ -329,6 +343,8 @@
<toolbarItem reference="s1h-EC-nvL"/> <toolbarItem reference="s1h-EC-nvL"/>
<toolbarItem reference="5U7-UV-xn2"/> <toolbarItem reference="5U7-UV-xn2"/>
<toolbarItem reference="9ol-aR-mzv"/> <toolbarItem reference="9ol-aR-mzv"/>
<toolbarItem reference="cMg-Mj-j7q"/>
<toolbarItem reference="mhg-16-CNM"/>
<toolbarItem reference="FRe-rR-Ulo"/> <toolbarItem reference="FRe-rR-Ulo"/>
</defaultToolbarItems> </defaultToolbarItems>
</toolbar> </toolbar>
@ -345,6 +361,7 @@
<outlet property="trackProgressBar" destination="KMy-xf-rmN" id="a67-JU-cyQ"/> <outlet property="trackProgressBar" destination="KMy-xf-rmN" id="a67-JU-cyQ"/>
<outlet property="trackRemaining" destination="9WZ-ij-lrb" id="0pH-d7-wvD"/> <outlet property="trackRemaining" destination="9WZ-ij-lrb" id="0pH-d7-wvD"/>
<outlet property="transportControls" destination="EBk-sD-nG7" id="yOo-58-Fby"/> <outlet property="transportControls" destination="EBk-sD-nG7" id="yOo-58-Fby"/>
<outlet property="volumeState" destination="cfN-LI-Cab" id="hrE-SY-J9E"/>
<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>
</windowController> </windowController>
@ -409,7 +426,7 @@
<tabView key="tabView" type="noTabsNoBorder" id="6dC-M0-oC5"> <tabView key="tabView" type="noTabsNoBorder" id="6dC-M0-oC5">
<rect key="frame" x="0.0" y="0.0" width="418" height="300"/> <rect key="frame" x="0.0" y="0.0" width="418" height="300"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<font key="font" metaFont="system"/> <font key="font" metaFont="label" size="13"/>
<connections> <connections>
<outlet property="delegate" destination="zhe-qh-Mal" id="LUL-qN-JlP"/> <outlet property="delegate" destination="zhe-qh-Mal" id="LUL-qN-JlP"/>
</connections> </connections>
@ -429,93 +446,47 @@
<objects> <objects>
<viewController title="Cover Art" id="3C9-vU-zjZ" userLabel="Cover Art" customClass="CoverArtPrefsController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController"> <viewController title="Cover Art" id="3C9-vU-zjZ" userLabel="Cover Art" customClass="CoverArtPrefsController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="PyK-v2-kus"> <view key="view" id="PyK-v2-kus">
<rect key="frame" x="0.0" y="0.0" width="524" height="168"/> <rect key="frame" x="0.0" y="0.0" width="420" height="103"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="zZn-Rm-e1f">
<rect key="frame" x="53" y="129" width="104" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Music Directory:" id="sPn-V6-CfK">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gDk-ca-eOa">
<rect key="frame" x="162" y="125" width="288" height="21"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="~/Music" drawsBackground="YES" id="7WZ-b7-GUs">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<action selector="updateMpdLibraryDir:" target="3C9-vU-zjZ" id="3Ta-fH-5Zh"/>
</connections>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pRL-MG-1Be"> <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pRL-MG-1Be">
<rect key="frame" x="160" y="94" width="264" height="18"/> <rect key="frame" x="82" y="67" width="264" height="18"/>
<constraints>
<constraint firstAttribute="width" constant="260" id="PJN-iZ-3RV"/>
</constraints>
<buttonCell key="cell" type="check" title="Fetch missing artwork from MusicBrainz" bezelStyle="regularSquare" imagePosition="left" enabled="NO" inset="2" id="LpD-Ew-HMd"> <buttonCell key="cell" type="check" title="Fetch missing artwork from MusicBrainz" bezelStyle="regularSquare" imagePosition="left" enabled="NO" inset="2" id="LpD-Ew-HMd">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/> <font key="font" metaFont="label" size="13"/>
</buttonCell> </buttonCell>
<connections> <connections>
<action selector="updateFetchMissingArtworkFromInternet:" target="3C9-vU-zjZ" id="I7x-9V-xJr"/> <action selector="updateFetchMissingArtworkFromInternet:" target="3C9-vU-zjZ" id="I7x-9V-xJr"/>
</connections> </connections>
</button> </button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="z1g-nP-ksw"> <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="mXh-kY-tMC">
<rect key="frame" x="160" y="63" width="264" height="18"/> <rect key="frame" x="78" y="21" width="185" height="32"/>
<constraints> <buttonCell key="cell" type="push" title="Clear album art cache..." bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="l81-SG-7mf">
<constraint firstAttribute="width" constant="260" id="gK0-aW-CJy"/> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
</constraints> <font key="font" metaFont="label" size="13"/>
<buttonCell key="cell" type="check" title="Save fetched artwork to music directory" bezelStyle="regularSquare" imagePosition="left" enabled="NO" inset="2" id="ZeZ-O4-vjS">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell> </buttonCell>
<connections>
<action selector="clearAlbumArtCache:" target="3C9-vU-zjZ" id="tXg-rz-lvh"/>
</connections>
</button> </button>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="xgS-Kg-8KR">
<rect key="frame" x="162" y="26" width="144" height="21"/>
<constraints>
<constraint firstAttribute="width" constant="144" id="DSX-th-Wn1"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" enabled="NO" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="folder.jpg" drawsBackground="YES" id="nKF-YI-xBL">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="SmH-w6-5QI">
<rect key="frame" x="37" y="30" width="119" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" enabled="NO" alignment="right" title="Cover art filename:" id="b4u-u7-iWD">
<font key="font" metaFont="system"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews> </subviews>
<constraints> <constraints>
<constraint firstItem="pRL-MG-1Be" firstAttribute="centerX" secondItem="z1g-nP-ksw" secondAttribute="centerX" id="0Ev-Ia-XBO"/> <constraint firstItem="pRL-MG-1Be" firstAttribute="leading" secondItem="PyK-v2-kus" secondAttribute="leading" constant="84" id="81s-YL-o8A"/>
<constraint firstItem="gDk-ca-eOa" firstAttribute="leading" secondItem="pRL-MG-1Be" secondAttribute="leading" id="1jd-wZ-a4Q"/> <constraint firstAttribute="bottom" secondItem="pRL-MG-1Be" secondAttribute="bottom" constant="69" id="L7b-jE-keG"/>
<constraint firstItem="pRL-MG-1Be" firstAttribute="leading" secondItem="z1g-nP-ksw" secondAttribute="leading" id="3tZ-Ub-RaT"/> <constraint firstItem="mXh-kY-tMC" firstAttribute="leading" secondItem="pRL-MG-1Be" secondAttribute="leading" id="b9X-hO-aYJ"/>
<constraint firstItem="gDk-ca-eOa" firstAttribute="leading" secondItem="zZn-Rm-e1f" secondAttribute="trailing" constant="7" id="C0m-yx-gXh"/> <constraint firstAttribute="bottom" secondItem="mXh-kY-tMC" secondAttribute="bottom" constant="28" id="ras-nE-Oq8"/>
<constraint firstItem="z1g-nP-ksw" firstAttribute="leading" secondItem="xgS-Kg-8KR" secondAttribute="leading" id="Dkv-ai-X2G"/>
<constraint firstItem="xgS-Kg-8KR" firstAttribute="leading" secondItem="SmH-w6-5QI" secondAttribute="trailing" constant="8" symbolic="YES" id="KQk-nr-H8y"/>
<constraint firstItem="zZn-Rm-e1f" firstAttribute="leading" secondItem="PyK-v2-kus" secondAttribute="leading" constant="55" id="OzK-MR-zuB"/>
<constraint firstAttribute="bottom" secondItem="SmH-w6-5QI" secondAttribute="bottom" constant="30" id="aBY-Ny-jPe"/>
<constraint firstItem="pRL-MG-1Be" firstAttribute="top" secondItem="gDk-ca-eOa" secondAttribute="bottom" constant="15" id="dKy-uC-r43"/>
<constraint firstItem="xgS-Kg-8KR" firstAttribute="top" secondItem="z1g-nP-ksw" secondAttribute="bottom" constant="18" id="lfR-Im-bd4"/>
<constraint firstAttribute="trailing" secondItem="gDk-ca-eOa" secondAttribute="trailing" constant="74" id="n8X-T2-tXA"/>
<constraint firstAttribute="bottom" secondItem="xgS-Kg-8KR" secondAttribute="bottom" constant="26" id="oXZ-qo-HwX"/>
<constraint firstItem="SmH-w6-5QI" firstAttribute="top" secondItem="zZn-Rm-e1f" secondAttribute="bottom" constant="83" id="qhC-mD-Bvw"/>
<constraint firstItem="z1g-nP-ksw" firstAttribute="top" secondItem="pRL-MG-1Be" secondAttribute="bottom" constant="17" id="sTP-hk-zfU"/>
</constraints> </constraints>
</view> </view>
<connections> <connections>
<outlet property="fetchMissingArtworkFromInternet" destination="pRL-MG-1Be" id="Xcp-sb-iZm"/> <outlet property="fetchMissingArtworkFromInternet" destination="pRL-MG-1Be" id="Xcp-sb-iZm"/>
<outlet property="mpdLibraryDirField" destination="gDk-ca-eOa" id="myi-BQ-0NS"/>
</connections> </connections>
</viewController> </viewController>
<customObject id="KzD-E3-lpA" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/> <customObject id="KzD-E3-lpA" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="1459" y="-242"/> <point key="canvasLocation" x="1407" y="-274.5"/>
</scene> </scene>
<!--View Controller--> <!--View Controller-->
<scene sceneID="VvW-vT-alQ"> <scene sceneID="VvW-vT-alQ">
@ -528,7 +499,7 @@
<subviews> <subviews>
<tabView type="noTabsNoBorder" initialItem="XgS-cX-SDH" translatesAutoresizingMaskIntoConstraints="NO" id="ARv-cj-xlz"> <tabView type="noTabsNoBorder" initialItem="XgS-cX-SDH" translatesAutoresizingMaskIntoConstraints="NO" id="ARv-cj-xlz">
<rect key="frame" x="0.0" y="0.0" width="478" height="558"/> <rect key="frame" x="0.0" y="0.0" width="478" height="558"/>
<font key="font" metaFont="system"/> <font key="font" metaFont="label" size="13"/>
<tabViewItems> <tabViewItems>
<tabViewItem label="Albums" identifier="" id="XgS-cX-SDH"> <tabViewItem label="Albums" identifier="" id="XgS-cX-SDH">
<view key="view" id="hB7-hN-SbB"> <view key="view" id="hB7-hN-SbB">
@ -593,7 +564,7 @@
<real key="minimum" value="0.0"/> <real key="minimum" value="0.0"/>
<real key="maximum" value="65535"/> <real key="maximum" value="65535"/>
</numberFormatter> </numberFormatter>
<font key="font" metaFont="system"/> <font key="font" metaFont="label" size="13"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
@ -604,7 +575,7 @@
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="kvB-99-zwY"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="kvB-99-zwY">
<rect key="frame" x="76" y="61" width="80" height="16"/> <rect key="frame" x="76" y="61" width="80" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Server Host:" id="AVi-g9-Irz"> <textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Server Host:" id="AVi-g9-Irz">
<font key="font" metaFont="system"/> <font key="font" metaFont="label" size="13"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
@ -615,7 +586,7 @@
<constraint firstAttribute="width" constant="72" id="Of6-Ls-knP"/> <constraint firstAttribute="width" constant="72" id="Of6-Ls-knP"/>
</constraints> </constraints>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Server Port:" id="DgA-xT-2ir"> <textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Server Port:" id="DgA-xT-2ir">
<font key="font" metaFont="system"/> <font key="font" metaFont="label" size="13"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
@ -647,18 +618,18 @@
<objects> <objects>
<viewController id="KIP-rq-4dM" customClass="QueueViewController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController"> <viewController id="KIP-rq-4dM" customClass="QueueViewController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController">
<splitView key="view" misplaced="YES" dividerStyle="thin" id="84I-w3-Mxl"> <splitView key="view" misplaced="YES" dividerStyle="thin" id="84I-w3-Mxl">
<rect key="frame" x="0.0" y="0.0" width="329" height="543"/> <rect key="frame" x="0.0" y="0.0" width="329" height="498"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews> <subviews>
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="42" horizontalPageScroll="10" verticalLineScroll="42" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="S3o-nF-NN7"> <scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="42" horizontalPageScroll="10" verticalLineScroll="42" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="S3o-nF-NN7">
<rect key="frame" x="0.0" y="0.0" width="329" height="217"/> <rect key="frame" x="0.0" y="0.0" width="329" height="198"/>
<autoresizingMask key="autoresizingMask" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxY="YES"/>
<clipView key="contentView" drawsBackground="NO" id="WI8-Pw-03L"> <clipView key="contentView" drawsBackground="NO" id="WI8-Pw-03L">
<rect key="frame" x="0.0" y="0.0" width="329" height="217"/> <rect key="frame" x="0.0" y="0.0" width="329" height="198"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="sourceList" multipleSelection="NO" autosaveColumns="NO" rowHeight="42" rowSizeStyle="automatic" viewBased="YES" indentationMarkerFollowsCell="NO" outlineTableColumn="0Co-uF-CCB" id="jEJ-jg-fll"> <outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="sourceList" multipleSelection="NO" autosaveColumns="NO" rowHeight="42" rowSizeStyle="automatic" viewBased="YES" indentationMarkerFollowsCell="NO" outlineTableColumn="0Co-uF-CCB" id="jEJ-jg-fll">
<rect key="frame" x="0.0" y="0.0" width="329" height="217"/> <rect key="frame" x="0.0" y="0.0" width="329" height="198"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="0.0"/> <size key="intercellSpacing" width="3" height="0.0"/>
<color key="backgroundColor" name="_sourceListBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="_sourceListBackgroundColor" catalog="System" colorSpace="catalog"/>
@ -666,12 +637,11 @@
<tableColumns> <tableColumns>
<tableColumn identifier="songCoverColumn" width="43" minWidth="42" maxWidth="1000" id="0Co-uF-CCB" userLabel="Position"> <tableColumn identifier="songCoverColumn" width="43" minWidth="42" maxWidth="1000" id="0Co-uF-CCB" userLabel="Position">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border"> <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<font key="font" metaFont="message" size="11"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell> </tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="99v-Rb-3kv"> <textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="99v-Rb-3kv">
<font key="font" metaFont="system"/> <font key="font" metaFont="label" size="13"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
@ -712,12 +682,11 @@
</tableColumn> </tableColumn>
<tableColumn identifier="songInfoColumn" width="213" minWidth="10" maxWidth="3.4028234663852886e+38" id="HP0-ty-PFY" userLabel="Song Info"> <tableColumn identifier="songInfoColumn" width="213" minWidth="10" maxWidth="3.4028234663852886e+38" id="HP0-ty-PFY" userLabel="Song Info">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left"> <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left">
<font key="font" metaFont="message" size="11"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</tableHeaderCell> </tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="zb2-QK-DhK"> <textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="zb2-QK-DhK">
<font key="font" metaFont="system"/> <font key="font" metaFont="label" size="13"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
@ -738,7 +707,7 @@
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="i0h-bn-auJ" userLabel="Song Title View"> <textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="i0h-bn-auJ" userLabel="Song Title View">
<rect key="frame" x="1" y="23" width="211" height="16"/> <rect key="frame" x="1" y="23" width="211" height="16"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Song Title" id="ei8-1e-ErK"> <textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Song Title" id="ei8-1e-ErK">
<font key="font" metaFont="system"/> <font key="font" metaFont="label" size="13"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
@ -761,12 +730,11 @@
</tableColumn> </tableColumn>
<tableColumn identifier="songDurationColumn" width="64" minWidth="64" maxWidth="64" id="8O6-ox-kx2" userLabel="Duration"> <tableColumn identifier="songDurationColumn" width="64" minWidth="64" maxWidth="64" id="8O6-ox-kx2" userLabel="Duration">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left"> <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left">
<font key="font" metaFont="message" size="11"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</tableHeaderCell> </tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="JOa-Mc-ceQ"> <textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="JOa-Mc-ceQ">
<font key="font" metaFont="system"/> <font key="font" metaFont="label" size="13"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
@ -783,7 +751,7 @@
<constraint firstAttribute="height" constant="17" id="grB-CG-1vJ"/> <constraint firstAttribute="height" constant="17" id="grB-CG-1vJ"/>
</constraints> </constraints>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" alignment="right" title="88:88" id="JnJ-sF-vCP"> <textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" alignment="right" title="88:88" id="JnJ-sF-vCP">
<font key="font" metaFont="system"/> <font key="font" metaFont="label" size="13"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
@ -824,7 +792,7 @@
</scroller> </scroller>
</scrollView> </scrollView>
<customView misplaced="YES" id="iUb-eV-Qws"> <customView misplaced="YES" id="iUb-eV-Qws">
<rect key="frame" x="0.0" y="218" width="329" height="325"/> <rect key="frame" x="0.0" y="199" width="329" height="299"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="Dw3-M5-tWY" customClass="CurrentCoverArtView" customModule="Persephone" customModuleProvider="target"> <imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="Dw3-M5-tWY" customClass="CurrentCoverArtView" customModule="Persephone" customModuleProvider="target">
@ -932,6 +900,7 @@
<image name="prevTrackButton" width="17" height="17"/> <image name="prevTrackButton" width="17" height="17"/>
<image name="repeatButton" width="17" height="17"/> <image name="repeatButton" width="17" height="17"/>
<image name="shuffleButton" width="17" height="17"/> <image name="shuffleButton" width="17" height="17"/>
<image name="speakerHigh" width="21" height="17"/>
<image name="stopButton" width="17" height="17"/> <image name="stopButton" width="17" height="17"/>
</resources> </resources>
</document> </document>

View File

@ -27,6 +27,8 @@ class WindowController: NSWindowController {
@IBOutlet var shuffleState: NSButton! @IBOutlet var shuffleState: NSButton!
@IBOutlet var repeatState: NSButton! @IBOutlet var repeatState: NSButton!
@IBOutlet var volumeState: NSButton!
@IBOutlet weak var searchQuery: NSSearchField! @IBOutlet weak var searchQuery: NSSearchField!
override func windowDidLoad() { override func windowDidLoad() {
@ -120,6 +122,25 @@ class WindowController: NSWindowController {
trackRemaining.stringValue = time.formattedTime trackRemaining.stringValue = time.formattedTime
} }
func setVolumeControlIcon(_ state: PlayerState) {
volumeState.isEnabled = state.volume != -1
switch state.volume {
case -1:
volumeState.image = .speakerDisabled
case 0..<5:
volumeState.image = .speakerOff
case 5..<40:
volumeState.image = .speakerLow
case 40..<70:
volumeState.image = .speakerMid
case 70...100:
volumeState.image = .speakerHigh
default:
break
}
}
@objc func willDisconnect() { @objc func willDisconnect() {
DispatchQueue.main.async { DispatchQueue.main.async {
@ -176,6 +197,16 @@ class WindowController: NSWindowController {
@IBAction func handleSearchQuery(_ sender: NSSearchField) { @IBAction func handleSearchQuery(_ sender: NSSearchField) {
App.store.dispatch(SetSearchQuery(searchQuery: sender.stringValue)) App.store.dispatch(SetSearchQuery(searchQuery: sender.stringValue))
} }
@IBAction func showVolumeControl(_ sender: NSButton) {
VolumeControlView.popover.contentViewController = VolumeControlView.shared
VolumeControlView.popover.behavior = .transient
VolumeControlView.popover.show(
relativeTo: sender.bounds,
of: sender,
preferredEdge: .maxY
)
}
} }
extension WindowController: NSWindowDelegate { extension WindowController: NSWindowDelegate {
@ -201,6 +232,7 @@ extension WindowController: StoreSubscriber {
self.setShuffleRepeatState(state.playerState) self.setShuffleRepeatState(state.playerState)
self.setTrackProgressControls(state.playerState) self.setTrackProgressControls(state.playerState)
self.setDatabaseUpdatingIndicator(state.uiState) self.setDatabaseUpdatingIndicator(state.uiState)
self.setVolumeControlIcon(state.playerState)
} }
} }
} }

View File

@ -74,8 +74,8 @@ extension MPDClient {
let mpdAlbum = MPDAlbum( let mpdAlbum = MPDAlbum(
title: mpdSong.album.title, title: mpdSong.album.title,
artist: mpdSong.artist, artist: mpdSong.artist,
date: mpdSong.date, firstSong: mpdSong,
path: mpdSong.path date: mpdSong.date
) )
if (mpdAlbum != albums.last) { if (mpdAlbum != albums.last) {
albums.append(mpdAlbum) albums.append(mpdAlbum)

View File

@ -46,6 +46,11 @@ extension MPDClient {
guard let repeatState = userData["repeatState"] as? Bool guard let repeatState = userData["repeatState"] as? Bool
else { return } else { return }
sendRepeatState(repeatState: repeatState) sendRepeatState(repeatState: repeatState)
case .setVolume:
guard let volume = userData["volume"] as? Int
else { return }
sendSetVolume(to: volume)
// Database commands // Database commands
case .updateDatabase: case .updateDatabase:
@ -119,6 +124,22 @@ extension MPDClient {
else { return } else { return }
albumSongs(for: album, callback: callback) albumSongs(for: album, callback: callback)
// Song commands
case .fetchAlbumArt:
guard let songUri = userData["songUri"] as? String,
let offset = userData["offset"] as? Int32,
let callback = userData["callback"] as? (Data?) -> Void
else { return }
let imageData = userData["imageData"] as? Data? ?? nil
sendFetchAlbumArt(
forUri: songUri,
imageData: imageData,
offset: offset,
callback: callback
)
} }
} }
@ -126,6 +147,7 @@ extension MPDClient {
func enqueueCommand( func enqueueCommand(
command: MPDCommand, command: MPDCommand,
priority: BlockOperation.QueuePriority = .normal, priority: BlockOperation.QueuePriority = .normal,
forceIdle: Bool = false,
userData: Dictionary<String, Any> = [:] userData: Dictionary<String, Any> = [:]
) { ) {
guard isConnected else { return } guard isConnected else { return }
@ -135,8 +157,9 @@ extension MPDClient {
let commandOperation = BlockOperation() { [unowned self] in let commandOperation = BlockOperation() { [unowned self] in
self.sendCommand(command: command, userData: userData) self.sendCommand(command: command, userData: userData)
self.idle() self.idle(forceIdle)
} }
commandOperation.queuePriority = priority commandOperation.queuePriority = priority
commandQueue.addOperation(commandOperation) commandQueue.addOperation(commandOperation)
} }

View File

@ -11,58 +11,81 @@ import mpdclient
extension MPDClient { extension MPDClient {
func noIdle() { func noIdle() {
if isIdle { do {
mpd_send_noidle(connection) idleLock.lock()
isIdle = false defer { idleLock.unlock() }
if isIdle {
mpd_send_noidle(connection)
isIdle = false
}
} }
} }
func idle() { func idle(_ force: Bool = false) {
if !self.isIdle && self.commandQueue.operationCount == 1 { let shouldIdle: Bool
mpd_send_idle(self.connection)
self.isIdle = true do {
idleLock.lock()
defer { idleLock.unlock() }
shouldIdle = (!isIdle && commandQueue.operationCount == 1) || force
if shouldIdle {
mpd_send_idle(connection)
self.isIdle = true
}
}
let result = mpd_recv_idle(self.connection, true) if shouldIdle {
self.handleIdleResult(result) let result = mpd_recv_idle(connection, true)
handleIdleResult(result)
} }
} }
func handleIdleResult(_ result: mpd_idle) { func handleIdleResult(_ result: mpd_idle) {
isIdle = false
let mpdIdle = MPDIdle(rawValue: result.rawValue) let mpdIdle = MPDIdle(rawValue: result.rawValue)
let wasIdle: Bool
if mpdIdle.contains(.database) { do {
self.fetchAllAlbums() idleLock.lock()
defer { idleLock.unlock() }
wasIdle = isIdle
isIdle = false
} }
if mpdIdle.contains(.queue) {
self.fetchQueue()
self.fetchStatus()
self.delegate?.didUpdateQueue(mpdClient: self, queue: self.queue) if wasIdle {
if let status = self.status { if mpdIdle.contains(.database) {
self.delegate?.didUpdateQueuePos(mpdClient: self, song: status.song) self.fetchAllAlbums()
} }
} if mpdIdle.contains(.queue) {
if mpdIdle.contains(.player) || mpdIdle.contains(.options) { self.fetchQueue()
self.fetchStatus() self.fetchStatus()
if let status = self.status { self.delegate?.didUpdateQueue(mpdClient: self, queue: self.queue)
self.delegate?.didUpdateStatus(mpdClient: self, status: status) if let status = self.status {
self.delegate?.didUpdateQueuePos(mpdClient: self, song: status.song) self.delegate?.didUpdateQueuePos(mpdClient: self, song: status.song)
}
} }
} if mpdIdle.contains(.player) ||
if mpdIdle.contains(.update) { mpdIdle.contains(.options) ||
self.fetchStatus() mpdIdle.contains(.mixer) {
self.fetchStatus()
if self.status?.updating ?? false { if let status = self.status {
self.delegate?.willStartDatabaseUpdate(mpdClient: self) self.delegate?.didUpdateStatus(mpdClient: self, status: status)
} else { self.delegate?.didUpdateQueuePos(mpdClient: self, song: status.song)
self.delegate?.didFinishDatabaseUpdate(mpdClient: self) }
}
if mpdIdle.contains(.update) {
self.fetchStatus()
if self.status?.updating ?? false {
self.delegate?.willStartDatabaseUpdate(mpdClient: self)
} else {
self.delegate?.didFinishDatabaseUpdate(mpdClient: self)
}
}
if !mpdIdle.isEmpty {
self.idle()
} }
}
if !mpdIdle.isEmpty {
self.idle()
} }
} }
} }

View File

@ -0,0 +1,24 @@
//
// MPDClient+Mixer.swift
// Persephone
//
// Created by Daniel Barber on 2/16/20.
// Copyright © 2020 Dan Barber. All rights reserved.
//
import Foundation
import mpdclient
extension MPDClient {
func setVolume(to volume: Int) {
enqueueCommand(
command: .setVolume,
priority: .high,
userData: ["volume": volume]
)
}
func sendSetVolume(to volume: Int) {
mpd_run_set_volume(connection, UInt32(volume))
}
}

View File

@ -19,27 +19,51 @@ extension MPDClient {
} }
func playTrack(at queuePos: Int) { func playTrack(at queuePos: Int) {
enqueueCommand(command: .playTrack, userData: ["queuePos": queuePos]) enqueueCommand(
command: .playTrack,
forceIdle: true,
userData: ["queuePos": queuePos]
)
} }
func appendSong(_ song: MPDSong) { func appendSong(_ song: MPDSong) {
enqueueCommand(command: .appendSong, userData: ["song": song]) enqueueCommand(
command: .appendSong,
forceIdle: true,
userData: ["song": song]
)
} }
func removeSong(at queuePos: Int) { func removeSong(at queuePos: Int) {
enqueueCommand(command: .removeSong, userData: ["queuePos": queuePos]) enqueueCommand(
command: .removeSong,
forceIdle: true,
userData: ["queuePos": queuePos]
)
} }
func moveSongInQueue(at queuePos: Int, to newQueuePos: Int) { func moveSongInQueue(at queuePos: Int, to newQueuePos: Int) {
enqueueCommand(command: .moveSongInQueue, userData: ["oldQueuePos": queuePos, "newQueuePos": newQueuePos]) enqueueCommand(
command: .moveSongInQueue,
forceIdle: true,
userData: ["oldQueuePos": queuePos, "newQueuePos": newQueuePos]
)
} }
func addSongToQueue(songUri: String, at queuePos: Int) { func addSongToQueue(songUri: String, at queuePos: Int) {
enqueueCommand(command: .addSongToQueue, userData: ["uri": songUri, "queuePos": queuePos]) enqueueCommand(
command: .addSongToQueue,
forceIdle: true,
userData: ["uri": songUri, "queuePos": queuePos]
)
} }
func addAlbumToQueue(album: MPDAlbum, at queuePos: Int) { func addAlbumToQueue(album: MPDAlbum, at queuePos: Int) {
enqueueCommand(command: .addAlbumToQueue, userData: ["album": album, "queuePos": queuePos]) enqueueCommand(
command: .addAlbumToQueue,
forceIdle: true,
userData: ["album": album, "queuePos": queuePos]
)
} }
func sendPlayTrack(at queuePos: Int) { func sendPlayTrack(at queuePos: Int) {

View File

@ -10,19 +10,80 @@ import Foundation
import mpdclient import mpdclient
extension MPDClient { extension MPDClient {
func fetchAlbumArt(
songUri: String,
imageData: Data?,
offset: Int32 = 0,
callback: @escaping (Data?) -> Void
) {
enqueueCommand(
command: .fetchAlbumArt,
priority: .low,
userData: [
"songUri": songUri,
"callback": callback,
"imageData": imageData as Any,
"offset": offset,
]
)
}
func searchSongs(_ terms: [MPDClient.MPDTag: String]) -> [MPDSong] { func searchSongs(_ terms: [MPDClient.MPDTag: String]) -> [MPDSong] {
var songs: [MPDSong] = [] var songs: [MPDSong] = []
mpd_search_db_songs(self.connection, true) mpd_search_db_songs(connection, true)
for (tag, term) in terms { for (tag, term) in terms {
mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, tag.mpdTag(), term) mpd_search_add_tag_constraint(connection, MPD_OPERATOR_DEFAULT, tag.mpdTag(), term)
} }
mpd_search_commit(self.connection) mpd_search_commit(connection)
while let song = mpd_recv_song(self.connection) { while let song = mpd_recv_song(connection) {
songs.append(MPDSong(song)) songs.append(MPDSong(song))
} }
return songs return songs
} }
func sendFetchAlbumArt(
forUri songUri: String,
imageData: Data?,
offset: Int32,
callback: @escaping (Data?) -> Void
) -> Void {
var size: Int?
mpd_send_albumart(connection, songUri, String(offset))
guard let sizePair = mpd_recv_pair(connection) else {
mpd_connection_clear_error(connection)
return
}
size = Int(MPDPair(sizePair).value)
mpd_return_pair(connection, sizePair)
var data = imageData ?? Data(count: size!)
let binaryPair = MPDPair(mpd_recv_pair(connection))
let chunkSize = Int(binaryPair.value)!
mpd_return_pair(connection, binaryPair.pair)
_ = data[offset...].withUnsafeMutableBytes { (pointer) in
mpd_recv_binary(connection, pointer.baseAddress, chunkSize)
}
guard mpd_response_finish(connection) else { return }
let newOffset = offset + Int32(chunkSize)
if newOffset < size! {
fetchAlbumArt(
songUri: songUri,
imageData: data,
offset: newOffset,
callback: callback
)
} else {
callback(data)
}
}
} }

View File

@ -11,24 +11,42 @@ import mpdclient
extension MPDClient { extension MPDClient {
func playPause() { func playPause() {
enqueueCommand(command: .playPause) enqueueCommand(
command: .playPause,
priority: .high,
forceIdle: true
)
} }
func stop() { func stop() {
enqueueCommand(command: .stop) enqueueCommand(
command: .stop,
priority: .high,
forceIdle: true
)
} }
func prevTrack() { func prevTrack() {
enqueueCommand(command: .prevTrack) enqueueCommand(
command: .prevTrack,
priority: .high,
forceIdle: true
)
} }
func nextTrack() { func nextTrack() {
enqueueCommand(command: .nextTrack) enqueueCommand(
command: .nextTrack,
priority: .high,
forceIdle: true
)
} }
func seekCurrentSong(timeInSeconds: Float) { func seekCurrentSong(timeInSeconds: Float) {
enqueueCommand( enqueueCommand(
command: .seekCurrentSong, command: .seekCurrentSong,
priority: .high,
forceIdle: true,
userData: ["timeInSeconds": timeInSeconds] userData: ["timeInSeconds": timeInSeconds]
) )
} }
@ -36,6 +54,8 @@ extension MPDClient {
func setShuffleState(shuffleState: Bool) { func setShuffleState(shuffleState: Bool) {
enqueueCommand( enqueueCommand(
command: .setShuffleState, command: .setShuffleState,
priority: .high,
forceIdle: true,
userData: ["shuffleState": shuffleState] userData: ["shuffleState": shuffleState]
) )
} }
@ -43,6 +63,8 @@ extension MPDClient {
func setRepeatState(repeatState: Bool) { func setRepeatState(repeatState: Bool) {
enqueueCommand( enqueueCommand(
command: .setRepeatState, command: .setRepeatState,
priority: .high,
forceIdle: true,
userData: ["repeatState": repeatState] userData: ["repeatState": repeatState]
) )
} }

View File

@ -0,0 +1,15 @@
//
// MPDClientWrapper.c
// Persephone
//
// Created by Daniel Barber on 1/31/20.
// Copyright © 2020 Dan Barber. All rights reserved.
//
#include "MPDClientWrapper.h"
int
mpd_send_albumart(struct mpd_connection *connection, const char * uri, const char * offset)
{
return mpd_send_command(connection, "albumart", uri, offset, NULL);
}

View File

@ -0,0 +1,12 @@
//
// MPDClientWrapper.h
// Persephone
//
// Created by Daniel Barber on 1/31/20.
// Copyright © 2020 Dan Barber. All rights reserved.
//
#include <mpd/client.h>
int
mpd_send_albumart(struct mpd_connection *connection, const char * uri, const char * offset);

View File

@ -0,0 +1,5 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "MPDClientWrapper.h"

View File

@ -21,6 +21,8 @@ class MPDClient {
var queue: [MPDSong] = [] var queue: [MPDSong] = []
let commandQueue = OperationQueue() let commandQueue = OperationQueue()
let idleLock = NSLock()
init(withDelegate delegate: MPDClientDelegate?) { init(withDelegate delegate: MPDClientDelegate?) {
commandQueue.maxConcurrentOperationCount = 1 commandQueue.maxConcurrentOperationCount = 1

View File

@ -12,8 +12,8 @@ extension MPDClient {
struct MPDAlbum: Equatable { struct MPDAlbum: Equatable {
let title: String let title: String
let artist: String let artist: String
var firstSong: MPDSong?
var date: String? var date: String?
var path: String?
static func == (lhs: MPDAlbum, rhs: MPDAlbum) -> Bool { static func == (lhs: MPDAlbum, rhs: MPDAlbum) -> Bool {
return lhs.title == rhs.title && return lhs.title == rhs.title &&

View File

@ -22,6 +22,8 @@ extension MPDClient {
case setShuffleState case setShuffleState
case setRepeatState case setRepeatState
case setVolume
// Database commands // Database commands
case updateDatabase case updateDatabase
@ -48,5 +50,8 @@ extension MPDClient {
case playAlbum case playAlbum
case getAlbumFirstSong case getAlbumFirstSong
case getAlbumSongs case getAlbumSongs
// Song commands
case fetchAlbumArt
} }
} }

View File

@ -37,8 +37,7 @@ extension MPDClient {
return MPDAlbum( return MPDAlbum(
title: getTag(.album), title: getTag(.album),
artist: artist, artist: artist,
date: date, date: date
path: path
) )
} }
@ -54,11 +53,6 @@ extension MPDClient {
return getTag(.date) return getTag(.date)
} }
var path: String {
return NSString(string: uriString)
.deletingLastPathComponent
}
func getTag(_ tagType: MPDTag) -> String { func getTag(_ tagType: MPDTag) -> String {
guard let tag = mpd_song_get_tag(song, tagType.mpdTag(), 0) guard let tag = mpd_song_get_tag(song, tagType.mpdTag(), 0)
else { return "" } else { return "" }

View File

@ -57,6 +57,10 @@ extension MPDClient {
var repeatState: Bool { var repeatState: Bool {
return mpd_status_get_repeat(status) return mpd_status_get_repeat(status)
} }
var volume: Int {
return Int(mpd_status_get_volume(status))
}
var updating: Bool { var updating: Bool {
let updating = mpd_status_get_update_id(status) let updating = mpd_status_get_update_id(status)

View File

@ -32,29 +32,6 @@ struct Album {
var hash: String { var hash: String {
return "\(title) - \(artist)".sha1() return "\(title) - \(artist)".sha1()
} }
var coverArtFilenames: [String] {
return [
"cover.jpg",
"folder.jpg",
"\(artist) - \(title).jpg",
"cover.png",
"folder.png",
"\(artist) - \(title ).png",
]
}
var coverArtFilePath: String? {
let musicDir = App.store.state.preferencesState.expandedMpdLibraryDir
guard let albumPath = mpdAlbum.path else { return nil }
return coverArtFilenames
.lazy
.map { "\(musicDir)/\(albumPath)/\($0)" }
.first {
FileManager.default.fileExists(atPath: $0)
}
}
} }
extension Album: Equatable { extension Album: Equatable {

View File

@ -8,7 +8,8 @@
struct DraggedSong: Codable { struct DraggedSong: Codable {
var type: DraggedSongType var type: DraggedSongType
var title: String? var title: String
var artist: String? var artist: String
var cover: String? var album: String
var uri: String
} }

View File

@ -1,23 +0,0 @@
//
// CoverArtQueue.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/26.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import AppKit
class CoverArtQueue {
static let shared = CoverArtQueue()
let queue = DispatchQueue(label: "CoverArtQueue")
var lastDispatchedTime = DispatchTime(uptimeNanoseconds: 0) - 1
func addToQueue(workItem: DispatchWorkItem) {
let dispatchTime = max(lastDispatchedTime + 1, DispatchTime(uptimeNanoseconds: 0))
lastDispatchedTime = dispatchTime
queue.asyncAfter(deadline: dispatchTime, execute: workItem)
}
}

View File

@ -0,0 +1,83 @@
//
// CoverArtService.swift
// Persephone
//
// Created by Daniel Barber on 2/3/20.
// Copyright © 2020 Dan Barber. All rights reserved.
//
import Foundation
import PromiseKit
import Kingfisher
struct CoverArtService {
let song: Song
let provider: MPDAlbumArtImageDataProvider
init(song: Song) {
self.song = song
provider = MPDAlbumArtImageDataProvider(
songUri: song.mpdSong.uriString,
cacheKey: song.album.hash
)
}
func refreshAlbumListArt() -> Promise<Any> {
return Promise<Any> { seal in
_ = KingfisherManager.shared.retrieveImage(
with: .provider(provider),
options: [
.forceRefresh,
.memoryCacheExpiration(.never),
.processor(DownsamplingImageProcessor(size: .albumListCoverSize)),
.scaleFactor(2),
]
) { result in
seal.fulfill(result)
}
}
}
func refreshCurrentlyPlayingArt() -> Promise<Any> {
return Promise<Any> { seal in
_ = KingfisherManager.shared.retrieveImage(
with: .provider(provider),
options: [
.forceRefresh,
.memoryCacheExpiration(.never),
.processor(DownsamplingImageProcessor(size: .currentlyPlayingCoverSize)),
.scaleFactor(2),
.callbackQueue(.mainAsync)
]
) { result in
seal.fulfill(result)
}
}
}
func refreshQueueSongArt() -> Promise<Any> {
return Promise<Any> { seal in
_ = KingfisherManager.shared.retrieveImage(
with: .provider(provider),
options: [
.forceRefresh,
.memoryCacheExpiration(.never),
.processor(DownsamplingImageProcessor(size: .queueSongCoverSize)),
.scaleFactor(2),
]
) { result in
seal.fulfill(result)
}
}
}
func refresh(callback: @escaping () -> Void?) {
_ = firstly {
when(fulfilled: refreshAlbumListArt(), refreshQueueSongArt(), refreshCurrentlyPlayingArt())
}.done { _, _, _ in
NotificationCenter.default.post(name: .didReloadAlbumArt, object: nil)
callback()
}
}
}

View File

@ -20,3 +20,7 @@ struct UpdateElapsedTimeAction: Action {
struct UpdateStatusAction: Action { struct UpdateStatusAction: Action {
var status: MPDClient.MPDStatus var status: MPDClient.MPDStatus
} }
struct UpdateVolumeAction: Action {
var volume: Int
}

View File

@ -16,10 +16,6 @@ struct UpdateServerPort: Action {
var port: Int? var port: Int?
} }
struct UpdateMPDLibraryDir: Action {
var mpdLibraryDir: String?
}
struct UpdateFetchMissingArtworkFromInternet: Action { struct UpdateFetchMissingArtworkFromInternet: Action {
var fetchMissingArtworkFromInternet: Bool var fetchMissingArtworkFromInternet: Bool
} }

View File

@ -16,6 +16,8 @@ struct PlayerState: StateType {
var state: MPDClient.MPDStatus.State? var state: MPDClient.MPDStatus.State?
var shuffleState: Bool = false var shuffleState: Bool = false
var repeatState: Bool = false var repeatState: Bool = false
var volume: Int = 0
var totalTime: UInt? var totalTime: UInt?
var elapsedTimeMs: UInt? var elapsedTimeMs: UInt?
@ -23,10 +25,11 @@ struct PlayerState: StateType {
extension PlayerState: Equatable { extension PlayerState: Equatable {
static func == (lhs: PlayerState, rhs: PlayerState) -> Bool { static func == (lhs: PlayerState, rhs: PlayerState) -> Bool {
return (lhs.state == rhs.state) && return lhs.state == rhs.state &&
(lhs.totalTime == rhs.totalTime) && lhs.totalTime == rhs.totalTime &&
(lhs.elapsedTimeMs == rhs.elapsedTimeMs) && lhs.elapsedTimeMs == rhs.elapsedTimeMs &&
(lhs.shuffleState == rhs.shuffleState) && lhs.shuffleState == rhs.shuffleState &&
(lhs.repeatState == rhs.repeatState) lhs.repeatState == rhs.repeatState &&
lhs.volume == rhs.volume
} }
} }

View File

@ -13,17 +13,6 @@ struct PreferencesState: StateType, Equatable {
let preferences = UserDefaults.standard let preferences = UserDefaults.standard
var mpdServer: MPDServer var mpdServer: MPDServer
let mpdLibraryDirDefault = "~/Music"
var mpdLibraryDir: String?
var mpdLibraryDirOrDefault: String {
return mpdLibraryDir ?? mpdLibraryDirDefault
}
var expandedMpdLibraryDir: String {
return NSString(string: mpdLibraryDirOrDefault).expandingTildeInPath
}
var fetchMissingArtworkFromInternet: Bool var fetchMissingArtworkFromInternet: Bool
@ -32,7 +21,6 @@ struct PreferencesState: StateType, Equatable {
host: preferences.string(forKey: "mpdHost"), host: preferences.string(forKey: "mpdHost"),
port: preferences.value(forKey: "mpdPort") as? Int port: preferences.value(forKey: "mpdPort") as? Int
) )
self.mpdLibraryDir = preferences.string(forKey: "mpdLibraryDir")
self.fetchMissingArtworkFromInternet = preferences.bool( self.fetchMissingArtworkFromInternet = preferences.bool(
forKey: "fetchMissingArtworkFromInternet" forKey: "fetchMissingArtworkFromInternet"
) )
@ -45,7 +33,6 @@ struct PreferencesState: StateType, Equatable {
} else { } else {
preferences.removeObject(forKey: "mpdPort") preferences.removeObject(forKey: "mpdPort")
} }
preferences.set(mpdLibraryDir, forKey: "mpdLibraryDir")
preferences.set(fetchMissingArtworkFromInternet, forKey: "fetchMissingArtworkFromInternet") preferences.set(fetchMissingArtworkFromInternet, forKey: "fetchMissingArtworkFromInternet")
} }
} }

View File

@ -20,6 +20,7 @@ func playerReducer(action: Action, state: PlayerState?) -> PlayerState {
state.elapsedTimeMs = action.status.elapsedTimeMs state.elapsedTimeMs = action.status.elapsedTimeMs
state.shuffleState = action.status.shuffleState state.shuffleState = action.status.shuffleState
state.repeatState = action.status.repeatState state.repeatState = action.status.repeatState
state.volume = action.status.volume
if state.state == .playing { if state.state == .playing {
App.trackTimer.start(elapsedTimeMs: state.elapsedTimeMs) App.trackTimer.start(elapsedTimeMs: state.elapsedTimeMs)

View File

@ -20,9 +20,6 @@ func preferencesReducer(action: Action, state: PreferencesState?) -> Preferences
case let action as UpdateServerPort: case let action as UpdateServerPort:
state.mpdServer.port = action.port state.mpdServer.port = action.port
case let action as UpdateMPDLibraryDir:
state.mpdLibraryDir = action.mpdLibraryDir
case is SavePreferences: case is SavePreferences:
state.save() state.save()

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

Binary file not shown.