muzak 0.0.11 → 0.0.12

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- attr_reader :index, :player, :plugins, :playlists
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::PLAYER_MAP[Config.player].new(self)
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
 
@@ -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
@@ -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
@@ -1,19 +1,33 @@
1
1
  module Muzak
2
2
  class Playlist
3
- attr_accessor :filename, :songs
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
@@ -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(self) }
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