muzak 0.0.11 → 0.0.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/lib/muzak/album.rb +11 -4
- data/lib/muzak/cmd.rb +1 -0
- data/lib/muzak/cmd/config.rb +3 -0
- data/lib/muzak/cmd/index.rb +15 -0
- data/lib/muzak/cmd/meta.rb +11 -0
- data/lib/muzak/cmd/player.rb +45 -0
- data/lib/muzak/cmd/playlist.rb +29 -13
- data/lib/muzak/config.rb +11 -1
- data/lib/muzak/const.rb +16 -1
- data/lib/muzak/index.rb +37 -1
- data/lib/muzak/instance.rb +23 -2
- data/lib/muzak/player.rb +9 -1
- data/lib/muzak/player/mpv.rb +42 -0
- data/lib/muzak/player/stub_player.rb +51 -0
- data/lib/muzak/playlist.rb +34 -1
- data/lib/muzak/plugin.rb +8 -1
- data/lib/muzak/plugin/stub_plugin.rb +4 -4
- data/lib/muzak/song.rb +36 -1
- data/lib/muzak/utils.rb +52 -0
- metadata +4 -6
- data/lib/muzak/plugin/cava.rb +0 -44
- data/lib/muzak/plugin/notify.rb +0 -16
- data/lib/muzak/plugin/scrobble.rb +0 -81
data/lib/muzak/instance.rb
CHANGED
@@ -1,8 +1,15 @@
|
|
1
1
|
module Muzak
|
2
|
+
# Encapsulates the entirety of muzak's running state.
|
2
3
|
class Instance
|
3
4
|
include Cmd
|
4
5
|
include Utils
|
5
6
|
|
7
|
+
# Sends a command to the instance.
|
8
|
+
# @param cmd [String] the name of the command
|
9
|
+
# @param args [Array<String>] the command's arguments
|
10
|
+
# @example
|
11
|
+
# instance.command "enqueue-playlist", "favorites"
|
12
|
+
# instance.command "pause"
|
6
13
|
def command(cmd, *args)
|
7
14
|
send Utils.resolve_command(cmd), *args
|
8
15
|
end
|
@@ -12,14 +19,24 @@ module Muzak
|
|
12
19
|
help
|
13
20
|
end
|
14
21
|
|
15
|
-
|
22
|
+
# @return [Index] the instance's music index
|
23
|
+
attr_reader :index
|
24
|
+
|
25
|
+
# @return [StubPlayer] the instance's player
|
26
|
+
attr_reader :player
|
27
|
+
|
28
|
+
# @return [Array<StubPlugin>] the instance's plugins
|
29
|
+
attr_reader :plugins
|
30
|
+
|
31
|
+
# @return [Hash{String => Playlist}] the instance's playlists
|
32
|
+
attr_reader :playlists
|
16
33
|
|
17
34
|
def initialize(opts = {})
|
18
35
|
verbose "muzak is starting..."
|
19
36
|
|
20
37
|
@index = Index.new(Config.music, deep: Config.deep_index)
|
21
38
|
|
22
|
-
@player = Player
|
39
|
+
@player = Player.load_player!(self)
|
23
40
|
|
24
41
|
@plugins = Plugin.load_plugins!
|
25
42
|
|
@@ -28,6 +45,10 @@ module Muzak
|
|
28
45
|
enqueue_playlist Config.autoplay if Config.autoplay
|
29
46
|
end
|
30
47
|
|
48
|
+
# Dispatch an event to all plugins.
|
49
|
+
# @param type [Symbol] the type of event to dispatch
|
50
|
+
# @param args [Array] the event's arguments
|
51
|
+
# @note {Muzak::PLUGIN_EVENTS} contains all valid events.
|
31
52
|
def event(type, *args)
|
32
53
|
return unless PLUGIN_EVENTS.include?(type)
|
33
54
|
|
data/lib/muzak/player.rb
CHANGED
@@ -4,10 +4,18 @@ require_relative "player/stub_player"
|
|
4
4
|
Dir.glob(File.join(__dir__, "player/*")) { |file| require_relative file }
|
5
5
|
|
6
6
|
module Muzak
|
7
|
+
# The namespace for muzak players.
|
7
8
|
module Player
|
9
|
+
# An association of shorthand player "names" to Class objects.
|
8
10
|
PLAYER_MAP = {
|
9
11
|
"stub" => Player::StubPlayer,
|
10
12
|
"mpv" => Player::MPV
|
11
|
-
}
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
# Returns an instantiated player as specified in `Config.player`.
|
16
|
+
# @return [StubPlayer] the player instance
|
17
|
+
def self.load_player!(instance)
|
18
|
+
PLAYER_MAP[Config.player].new(instance)
|
19
|
+
end
|
12
20
|
end
|
13
21
|
end
|
data/lib/muzak/player/mpv.rb
CHANGED
@@ -5,7 +5,9 @@ require "thread"
|
|
5
5
|
|
6
6
|
module Muzak
|
7
7
|
module Player
|
8
|
+
# Exposes MPV's IPC to muzak for playback control.
|
8
9
|
class MPV < StubPlayer
|
10
|
+
# @return [Boolean] Whether or not the current instance is running.
|
9
11
|
def running?
|
10
12
|
begin
|
11
13
|
!!@pid && Process.waitpid(@pid, Process::WNOHANG).nil?
|
@@ -14,6 +16,8 @@ module Muzak
|
|
14
16
|
end
|
15
17
|
end
|
16
18
|
|
19
|
+
# Activate mpv by executing it and preparing for event processing.
|
20
|
+
# @return [void]
|
17
21
|
def activate!
|
18
22
|
return if running?
|
19
23
|
|
@@ -56,6 +60,8 @@ module Muzak
|
|
56
60
|
instance.event :player_activated
|
57
61
|
end
|
58
62
|
|
63
|
+
# Deactivate mpv by killing it and cleaning up.
|
64
|
+
# @return [void]
|
59
65
|
def deactivate!
|
60
66
|
return unless running?
|
61
67
|
|
@@ -73,38 +79,59 @@ module Muzak
|
|
73
79
|
File.delete(@sock_path) if @sock_path && File.exists?(@sock_path)
|
74
80
|
end
|
75
81
|
|
82
|
+
# Tell mpv to begin playback.
|
83
|
+
# @return [void]
|
84
|
+
# @note Does nothing is playback is already in progress.
|
76
85
|
def play
|
77
86
|
return unless running?
|
78
87
|
|
79
88
|
set_property "pause", false
|
80
89
|
end
|
81
90
|
|
91
|
+
# Tell mpv to pause playback.
|
92
|
+
# @return [void]
|
93
|
+
# @note Does nothing is playback is already paused.
|
82
94
|
def pause
|
83
95
|
return unless running?
|
84
96
|
|
85
97
|
set_property "pause", true
|
86
98
|
end
|
87
99
|
|
100
|
+
# @return [Boolean] Whether or not mpv is currently playing.
|
88
101
|
def playing?
|
89
102
|
return false unless running?
|
90
103
|
|
91
104
|
!get_property "pause"
|
92
105
|
end
|
93
106
|
|
107
|
+
# Tell mpv to play the next song in its queue.
|
108
|
+
# @return [void]
|
109
|
+
# @note Does nothing if the current song is the last.
|
94
110
|
def next_song
|
95
111
|
command "playlist-next"
|
96
112
|
end
|
97
113
|
|
114
|
+
# Tell mpv to play the previous song in its queue.
|
115
|
+
# @return [void]
|
116
|
+
# @note Does nothing if the current song is the first.
|
98
117
|
def previous_song
|
99
118
|
command "playlist-prev"
|
100
119
|
end
|
101
120
|
|
121
|
+
# Tell mpv to add the given song to its queue.
|
122
|
+
# @param song [Song] the song to add
|
123
|
+
# @return [void]
|
124
|
+
# @note Activates mpv if not already activated.
|
102
125
|
def enqueue_song(song)
|
103
126
|
activate! unless running?
|
104
127
|
|
105
128
|
load_song song, song.best_guess_album_art
|
106
129
|
end
|
107
130
|
|
131
|
+
# Tell mpv to add the given album to its queue.
|
132
|
+
# @param album [Album] the album to add
|
133
|
+
# @return [void]
|
134
|
+
# @note Activates mpv if not already activated.
|
108
135
|
def enqueue_album(album)
|
109
136
|
activate! unless running?
|
110
137
|
|
@@ -113,6 +140,10 @@ module Muzak
|
|
113
140
|
end
|
114
141
|
end
|
115
142
|
|
143
|
+
# Tell mpv to add the given playlist to its queue.
|
144
|
+
# @param playlist [Playlist] the playlist to add
|
145
|
+
# @return [void]
|
146
|
+
# @note Activates mpv if not already activated.
|
116
147
|
def enqueue_playlist(playlist)
|
117
148
|
activate! unless running?
|
118
149
|
|
@@ -121,6 +152,9 @@ module Muzak
|
|
121
152
|
end
|
122
153
|
end
|
123
154
|
|
155
|
+
# Get mpv's internal queue.
|
156
|
+
# @return [Array<Song>] all songs in mpv's queue
|
157
|
+
# @note This includes songs already played.
|
124
158
|
def list_queue
|
125
159
|
entries = get_property "playlist/count"
|
126
160
|
|
@@ -133,18 +167,24 @@ module Muzak
|
|
133
167
|
playlist
|
134
168
|
end
|
135
169
|
|
170
|
+
# Shuffle mpv's internal queue.
|
171
|
+
# @return [void]
|
136
172
|
def shuffle_queue
|
137
173
|
return unless running?
|
138
174
|
|
139
175
|
command "playlist-shuffle"
|
140
176
|
end
|
141
177
|
|
178
|
+
# Clears mpv's internal queue.
|
179
|
+
# @return [void]
|
142
180
|
def clear_queue
|
143
181
|
return unless running?
|
144
182
|
|
145
183
|
command "playlist-clear"
|
146
184
|
end
|
147
185
|
|
186
|
+
# Get mpv's currently playing song.
|
187
|
+
# @return [Song] the currently playing song
|
148
188
|
def now_playing
|
149
189
|
return unless running? && playing?
|
150
190
|
|
@@ -201,6 +241,8 @@ module Muzak
|
|
201
241
|
# somehow.
|
202
242
|
song = Song.new(get_property "path")
|
203
243
|
instance.event :song_loaded, song
|
244
|
+
when "end-file"
|
245
|
+
instance.event :song_unloaded
|
204
246
|
end
|
205
247
|
end
|
206
248
|
end
|
@@ -1,70 +1,121 @@
|
|
1
1
|
module Muzak
|
2
2
|
module Player
|
3
|
+
# A no-op player that all players inherit from.
|
3
4
|
class StubPlayer
|
4
5
|
include Utils
|
5
6
|
|
7
|
+
# @return [Instance] the instance associated with this player
|
6
8
|
attr_reader :instance
|
7
9
|
|
10
|
+
# @param instance [Instance] the instance associated with the player
|
8
11
|
def initialize(instance)
|
9
12
|
@instance = instance
|
10
13
|
end
|
11
14
|
|
15
|
+
# @return [false] whether or not the player is running
|
16
|
+
# @note NO-OP
|
12
17
|
def running?
|
13
18
|
debug "#running?"
|
19
|
+
false
|
14
20
|
end
|
15
21
|
|
22
|
+
# Activates the player.
|
23
|
+
# @return [void]
|
24
|
+
# @note NO-OP
|
16
25
|
def activate!
|
17
26
|
debug "#activate!"
|
18
27
|
end
|
19
28
|
|
29
|
+
# Deactivates the player.
|
30
|
+
# @return [void]
|
31
|
+
# @note NO-OP
|
20
32
|
def deactivate!
|
21
33
|
debug "#deactivate!"
|
22
34
|
end
|
23
35
|
|
36
|
+
# Starts playback.
|
37
|
+
# @return [void]
|
38
|
+
# @note NO-OP
|
24
39
|
def play
|
25
40
|
debug "#play"
|
26
41
|
end
|
27
42
|
|
43
|
+
# Ends playback.
|
44
|
+
# @return [void]
|
45
|
+
# @note NO-OP
|
28
46
|
def pause
|
29
47
|
debug "#pause"
|
30
48
|
end
|
31
49
|
|
50
|
+
# @return [false] whether or not the player is currently playing
|
51
|
+
# @note NO-OP
|
32
52
|
def playing?
|
33
53
|
debug "#playing?"
|
54
|
+
false
|
34
55
|
end
|
35
56
|
|
57
|
+
# Moves to the next song.
|
58
|
+
# @return [void]
|
59
|
+
# @note NO-OP
|
36
60
|
def next_song
|
37
61
|
debug "#next_song"
|
38
62
|
end
|
39
63
|
|
64
|
+
# Moves to the previous song.
|
65
|
+
# @return [void]
|
66
|
+
# @note NO-OP
|
40
67
|
def previous_song
|
41
68
|
debug "#previous_song"
|
42
69
|
end
|
43
70
|
|
71
|
+
# Enqueues the given song.
|
72
|
+
# @param song [Song] the song to enqueue
|
73
|
+
# @return [void]
|
74
|
+
# @note NO-OP
|
44
75
|
def enqueue_song(song)
|
45
76
|
debug "#enqueue_song"
|
46
77
|
end
|
47
78
|
|
79
|
+
# Enqueues the given album.
|
80
|
+
# @param album [Album] the album to enqueue
|
81
|
+
# @return [void]
|
82
|
+
# @note NO-OP
|
48
83
|
def enqueue_album(album)
|
49
84
|
debug "#enqueue_album"
|
50
85
|
end
|
51
86
|
|
87
|
+
# Enqueues the given playlist.
|
88
|
+
# @param playlist [Playlist] the playlist to enqueue
|
89
|
+
# @return [void]
|
90
|
+
# @note NO-OP
|
52
91
|
def enqueue_playlist(playlist)
|
53
92
|
debug "#enqueue_playlist"
|
54
93
|
end
|
55
94
|
|
95
|
+
# List the player's queue.
|
96
|
+
# @return [void]
|
97
|
+
# @note NO-OP
|
56
98
|
def list_queue
|
57
99
|
debug "#list_queue"
|
58
100
|
end
|
59
101
|
|
102
|
+
# Shuffle the player's queue.
|
103
|
+
# @return [void]
|
104
|
+
# @note NO-OP
|
60
105
|
def shuffle_queue
|
61
106
|
debug "#shuffle_queue"
|
62
107
|
end
|
63
108
|
|
109
|
+
# Clear the player's queue.
|
110
|
+
# @return [void]
|
111
|
+
# @note NO-OP
|
64
112
|
def clear_queue
|
65
113
|
debug "#clear_queue"
|
66
114
|
end
|
67
115
|
|
116
|
+
# Get the currently playing song.
|
117
|
+
# @return [void]
|
118
|
+
# @note NO-OP
|
68
119
|
def now_playing
|
69
120
|
debug "#now_playing"
|
70
121
|
end
|
data/lib/muzak/playlist.rb
CHANGED
@@ -1,19 +1,33 @@
|
|
1
1
|
module Muzak
|
2
2
|
class Playlist
|
3
|
-
|
3
|
+
# @return [String] the absolute path to the playlist on disk
|
4
|
+
attr_accessor :filename
|
4
5
|
|
6
|
+
# @return [Array<Song>] the playlist's songs
|
7
|
+
attr_accessor :songs
|
8
|
+
|
9
|
+
# @param pname [String] the playlist's name
|
10
|
+
# @return [String] the absolute path to the given playlist name
|
5
11
|
def self.path_for(pname)
|
6
12
|
File.join(PLAYLIST_DIR, pname) + ".yml"
|
7
13
|
end
|
8
14
|
|
15
|
+
# @param pname [String] the playlist's name
|
16
|
+
# @return [Boolean] whether or not the given playlist name already exists
|
9
17
|
def self.exist?(pname)
|
10
18
|
File.exist?(path_for(pname))
|
11
19
|
end
|
12
20
|
|
21
|
+
# Deletes the given playlist from disk.
|
22
|
+
# @param pname [String] the playlist's name
|
23
|
+
# @return [void]
|
24
|
+
# @note If already instantiated, the playlist may still be present in
|
25
|
+
# memory (and may reappear on disk if modified in memory)
|
13
26
|
def self.delete!(pname)
|
14
27
|
File.delete(path_for(pname)) if exist? pname
|
15
28
|
end
|
16
29
|
|
30
|
+
# @return [Array<String>] the names of all currently available playlists
|
17
31
|
def self.playlist_names
|
18
32
|
Dir.entries(PLAYLIST_DIR).reject do |ent|
|
19
33
|
ent.start_with?(".")
|
@@ -22,6 +36,9 @@ module Muzak
|
|
22
36
|
end
|
23
37
|
end
|
24
38
|
|
39
|
+
# Instantiates all playlists by loading them from disk.
|
40
|
+
# @return [Hash{String => Playlist}] an association of playlist names to
|
41
|
+
# {Playlist} instances
|
25
42
|
def self.load_playlists!
|
26
43
|
playlists = {}
|
27
44
|
playlists.default_proc = proc { |h, k| h[k] = Playlist.new(k) }
|
@@ -33,6 +50,9 @@ module Muzak
|
|
33
50
|
playlists
|
34
51
|
end
|
35
52
|
|
53
|
+
# Create a new {Playlist} with the given name, or load one by that
|
54
|
+
# name if it already exists.
|
55
|
+
# @param pname [String] the playlist's name
|
36
56
|
def initialize(pname)
|
37
57
|
@filename = self.class.path_for pname
|
38
58
|
|
@@ -46,10 +66,13 @@ module Muzak
|
|
46
66
|
sync!
|
47
67
|
end
|
48
68
|
|
69
|
+
# @return [String] the playlist's name
|
49
70
|
def name
|
50
71
|
File.basename(@filename, File.extname(@filename))
|
51
72
|
end
|
52
73
|
|
74
|
+
# @param songs [Song, Array<Song>] one or more songs to add to the playlist
|
75
|
+
# @return [void]
|
53
76
|
def add(songs)
|
54
77
|
# coerce a single song into an array
|
55
78
|
[*songs].each do |song|
|
@@ -60,20 +83,30 @@ module Muzak
|
|
60
83
|
sync!
|
61
84
|
end
|
62
85
|
|
86
|
+
# @param songs [Song, Array<Song>] one or more songs to delete from the
|
87
|
+
# playlist
|
88
|
+
# @return [void]
|
63
89
|
def delete(songs)
|
64
90
|
[*songs].each { |song| @songs.delete(song) }
|
65
91
|
|
66
92
|
sync!
|
67
93
|
end
|
68
94
|
|
95
|
+
# Shuffles the internal order of the playlist's songs.
|
96
|
+
# @return [void]
|
69
97
|
def shuffle!
|
70
98
|
@songs.shuffle!
|
71
99
|
end
|
72
100
|
|
101
|
+
# Synchronizes the current instance with its disk representation.
|
102
|
+
# @return [void]
|
103
|
+
# @note You shouldn't need to call this.
|
73
104
|
def sync!
|
74
105
|
File.open(@filename, "w") { |io| io.write to_hash.to_yaml }
|
75
106
|
end
|
76
107
|
|
108
|
+
# Provides a hash representation of the current instance.
|
109
|
+
# @return [Hash{String => Array<Song>}] the instance's state
|
77
110
|
def to_hash
|
78
111
|
{ "songs" => @songs }
|
79
112
|
end
|
data/lib/muzak/plugin.rb
CHANGED
@@ -5,25 +5,32 @@ require_relative "plugin/stub_plugin"
|
|
5
5
|
Dir.glob(File.join(__dir__, "plugin/*")) { |file| require_relative file }
|
6
6
|
|
7
7
|
module Muzak
|
8
|
+
# The namespace for muzak plugins.
|
8
9
|
module Plugin
|
9
10
|
# load plugins included by the user
|
10
11
|
Dir.glob(File.join(USER_PLUGIN_DIR, "*")) { |file| require file }
|
11
12
|
|
13
|
+
# @return [Array<Class>] all plugin classes visible under Plugin
|
12
14
|
def self.plugin_classes
|
13
15
|
constants.map(&Plugin.method(:const_get)).grep(Class)
|
14
16
|
end
|
15
17
|
|
18
|
+
# @return [Array<String>] the names of all plugin classes under Plugin
|
19
|
+
# @see StubPlugin.plugin_name
|
16
20
|
def self.plugin_names
|
17
21
|
plugin_classes.map do |pk|
|
18
22
|
pk.plugin_name
|
19
23
|
end
|
20
24
|
end
|
21
25
|
|
26
|
+
# Instantiates all configured plugins and returns them.
|
27
|
+
# @return [Array<StubPlugin>] the instantiated plugins
|
22
28
|
def self.load_plugins!
|
23
29
|
pks = Plugin.plugin_classes.select { |pk| Config.plugin? pk.plugin_name }
|
24
|
-
pks.map { |pk| pk.new
|
30
|
+
pks.map { |pk| pk.new }
|
25
31
|
end
|
26
32
|
|
33
|
+
# An association of plugin names to their Class objects.
|
27
34
|
PLUGIN_MAP = plugin_names.zip(plugin_classes).to_h.freeze
|
28
35
|
end
|
29
36
|
end
|