muzak 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 917b86d06294801e33561fe181e64fd755f09828
4
- data.tar.gz: 3aebb78550918a276f0cefc7fc11e57d277759a3
3
+ metadata.gz: a2c396ccd25b5ca8dad7e33aad87e5e6f277f82f
4
+ data.tar.gz: 28f0518d153ca7dffa8713884a481b583ccdbdbb
5
5
  SHA512:
6
- metadata.gz: 2f9bcb70833ce12b36e14e2f56e6042cdbfef39dc88362b8e9908a255f1eaad19d185956ebde3dbf8608f562e41a3e52151390a9c49b6afb2b38435646c53e77
7
- data.tar.gz: 817a120b22297d8f3ba64afa8fe11a42452914c628de0f4b1a5f64d4d701b44c371024b2cdba0e9191cb9555ed4a0bd7d84fd8cadc2f8921c2e0e3e7f231e1fd
6
+ metadata.gz: 7619e9fec7c0413fadbd240c3abc828cef121aa8e9f34ba0a1c504750c31171a29aa0c43f18fdbac8d99de2ed68815db88266852ddaf0ed353dd525ee53f598a
7
+ data.tar.gz: a3b2b9fa7d6606a4d1f081878034890358fe97b763bcc8816365bf7cd24690905414631754c39450b1aca3d01aabe45792daa40eb0ef2a7978022effe5a045e3
@@ -111,7 +111,9 @@ Muzak uses this key to point the indexer at the correct directory.
111
111
 
112
112
  *Default:* `mpv`
113
113
 
114
- `player: <player>` should be set to the short name of the user's music player.
114
+ `player: <player>` should be set to the human-friendly name of the user's music
115
+ player. This human-friendly name is generated from the player's ruby class name
116
+ just like a plugin (e.g., `Muzak::Player::MPV` becomes `mpv`).
115
117
  Muzak uses this key to find the correct {Muzak::Player} underclass to
116
118
  initialize and control.
117
119
 
@@ -143,11 +145,24 @@ It's entirely up to the user's player to obey this value.
143
145
 
144
146
  *Optional.*
145
147
 
148
+ *Default:* `false`
149
+
150
+ If `autoplay: true` is set in the configuration file, then muzak will begin
151
+ tell the player to begin playing as soon as media is loaded.
152
+
153
+ ### `default_playlist`
154
+
155
+ *Optional.*
156
+
146
157
  *No default.*
147
158
 
148
- `autoplay: <playlist>` should be set to a playlist that the user wishes to
149
- automatically start when muzak starts. If not set, no playlist is automatically
150
- played.
159
+ `default_playlist: <playlist>` should be set to a playlist that the user wishes
160
+ to automatically load when muzak starts. If not set, no playlist is
161
+ automatically loaded.
162
+
163
+ *Note:* `default_playlist` does not automatically play the specified playlist
164
+ (it only loads it). `autoplay: true` must be set to automatically play the
165
+ default playlist.
151
166
 
152
167
  ## Daemon/client configuration
153
168
 
data/README.md CHANGED
@@ -35,6 +35,5 @@ as well as on RubyDoc.
35
35
  ### TODO
36
36
 
37
37
  * GUI "frontend"?
38
- * isolation of art and music output (`Muzak::ArtProvider`?)
39
38
  * current indexing/sorting logic is terrible
40
- * replace MPV IPC implementation with [ruby-mpv](https://github.com/woodruffw/ruby-mpv)
39
+ * figure out how to do events/album art in VLC
@@ -12,5 +12,5 @@ require_relative "muzak/instance"
12
12
  # The primary namespace for muzak.
13
13
  module Muzak
14
14
  # Muzak's current version
15
- VERSION = "0.3.0".freeze
15
+ VERSION = "0.3.1".freeze
16
16
  end
@@ -39,6 +39,7 @@ module Muzak
39
39
  verbose "muzak is quitting..."
40
40
  player.deactivate!
41
41
 
42
+ event :instance_quitting
42
43
  build_response data: "quitting"
43
44
  end
44
45
  end
@@ -6,7 +6,7 @@ module Muzak
6
6
  # @note Many playback commands will automatically activate the player.
7
7
  def player_activate
8
8
  if player.running?
9
- warn "player is already running"
9
+ danger "player is already running"
10
10
  else
11
11
  player.activate!
12
12
  end
@@ -21,7 +21,7 @@ module Muzak
21
21
  # @cmdexample `muzak> player-deactivate`
22
22
  # @note Deactivating the player (usually) ends playback immediately.
23
23
  def player_deactivate
24
- warn "player is not running" unless player.running?
24
+ danger "player is not running" unless player.running?
25
25
 
26
26
  # do cleanup even if the player isn't running, just in case
27
27
  player.deactivate!
@@ -160,7 +160,7 @@ module Muzak
160
160
  def now_playing
161
161
  if player.playing?
162
162
  build_response data: {
163
- playing: player.now_playing.full_title
163
+ playing: player.now_playing&.full_title
164
164
  }
165
165
  else
166
166
  build_response error: "no currently playing song"
@@ -46,6 +46,7 @@ module Muzak
46
46
  # All events currently propagated by {Muzak::Instance#event}
47
47
  PLUGIN_EVENTS = [
48
48
  :instance_started,
49
+ :instance_quitting,
49
50
  :player_activated,
50
51
  :player_deactivated,
51
52
  :song_loaded,
@@ -61,6 +62,7 @@ module Muzak
61
62
  "music" => File.expand_path("~/music"),
62
63
  "player" => "mpv",
63
64
  "jukebox-size" => 100,
65
+ "autoplay" => false,
64
66
 
65
67
  # client/daemon defaults
66
68
  "daemon-port" => 2669,
@@ -7,6 +7,7 @@ module Muzak
7
7
  # Sends a command to the instance.
8
8
  # @param cmd [String] the name of the command
9
9
  # @param args [Array<String>] the command's arguments
10
+ # @return [Hash] the command's response hash
10
11
  # @example
11
12
  # instance.command "enqueue-playlist", "favorites"
12
13
  # instance.command "pause"
@@ -19,7 +20,7 @@ module Muzak
19
20
  build_response error: "got #{args.size} args, needed #{meth.arity}"
20
21
  end
21
22
  else
22
- warn "unknown command: '#{cmd}'"
23
+ danger "unknown command: '#{cmd}'"
23
24
  build_response error: "unknown command '#{cmd}'"
24
25
  end
25
26
  end
@@ -36,7 +37,7 @@ module Muzak
36
37
  # @return [Hash{String => Playlist}] the instance's playlists
37
38
  attr_reader :playlists
38
39
 
39
- def initialize(opts = {})
40
+ def initialize
40
41
  verbose "muzak is starting..."
41
42
 
42
43
  error! "#{Config.music} doesn't exist" unless File.exist?(Config.music)
@@ -49,7 +50,7 @@ module Muzak
49
50
 
50
51
  @playlists = Playlist.load_playlists!
51
52
 
52
- enqueue_playlist Config.autoplay if Config.autoplay
53
+ enqueue_playlist Config.default_playlist if Config.default_playlist
53
54
 
54
55
  event :instance_started, self
55
56
  end
@@ -57,6 +58,7 @@ module Muzak
57
58
  # Dispatch an event to all plugins.
58
59
  # @param type [Symbol] the type of event to dispatch
59
60
  # @param args [Array] the event's arguments
61
+ # @return [void]
60
62
  # @note {Config::PLUGIN_EVENTS} contains all valid events.
61
63
  def event(type, *args)
62
64
  return unless Config::PLUGIN_EVENTS.include?(type)
@@ -8,11 +8,18 @@ module Muzak
8
8
  module Player
9
9
  extend Utils
10
10
 
11
- # An association of shorthand player "names" to Class objects.
12
- PLAYER_MAP = {
13
- "stub" => Player::StubPlayer,
14
- "mpv" => Player::MPV
15
- }.freeze
11
+ # All classes (player implementations) under the {Player} namespace.
12
+ # @api private
13
+ PLAYER_CLASSES = constants.map(&Player.method(:const_get)).grep(Class).freeze
14
+
15
+ # All human-friendly player names.
16
+ # @see Player::StubPlayer.player_name
17
+ # @api private
18
+ PLAYER_NAMES = PLAYER_CLASSES.map(&:player_name).freeze
19
+
20
+ # An association of human-friendly player names to implementation classes.
21
+ # @api private
22
+ PLAYER_MAP = PLAYER_NAMES.zip(PLAYER_CLASSES).to_h.freeze
16
23
 
17
24
  # Returns an instantiated player as specified in `Config.player`.
18
25
  # @return [StubPlayer] the player instance
@@ -8,14 +8,14 @@ module Muzak
8
8
  module Player
9
9
  # Exposes MPV's IPC to muzak for playback control.
10
10
  class MPV < StubPlayer
11
- # @return [Boolean] Whether or not MPV is available for execution
11
+ # @return [Boolean] whether or not MPV is available for execution
12
12
  def self.available?
13
13
  Utils.which?("mpv")
14
14
  end
15
15
 
16
- # @return [Boolean] Whether or not the current instance is running.
16
+ # @return [Boolean] whether or not the current instance is running.
17
17
  def running?
18
- @mpv&.running?
18
+ !!@mpv&.running?
19
19
  end
20
20
 
21
21
  # Activate mpv by executing it and preparing for event processing.
@@ -179,7 +179,8 @@ module Muzak
179
179
  # @return [void]
180
180
  # @api private
181
181
  def load_song(song, art)
182
- cmds = ["loadfile", song.path, "append-play"]
182
+ append_type = Config.autoplay ? "append-play" : "append"
183
+ cmds = ["loadfile", song.path, append_type]
183
184
  cmds << "external-file=\"#{art}\"" if art
184
185
  @mpv.command *cmds
185
186
  end
@@ -1,12 +1,20 @@
1
1
  module Muzak
2
2
  module Player
3
3
  # A no-op player that all players inherit from.
4
+ # @abstract Subclass and implement all public methods to implement
5
+ # a player.
4
6
  class StubPlayer
5
7
  include Utils
6
8
 
7
9
  # @return [Instance] the instance associated with this player
8
10
  attr_reader :instance
9
11
 
12
+ # The player's human friendly name.
13
+ # @return [String] the name
14
+ def self.player_name
15
+ name.split("::").last.downcase
16
+ end
17
+
10
18
  # @return [true] whether or not this type of player is available
11
19
  def self.available?
12
20
  true
@@ -0,0 +1,173 @@
1
+ require "vlc-client"
2
+
3
+ module Muzak
4
+ module Player
5
+ # Exposes a VLC process to muzak for playback control.
6
+ class VLC < StubPlayer
7
+ # The expression used to extract the filename out of {#status}
8
+ # @api private
9
+ INPUT_REGEX = /\( new input: file:\/\/(.*) \)/.freeze
10
+
11
+ # The expression used to extract the volume out of {#status}
12
+ # @api private
13
+ VOLUME_REGEX = /\( audio volume: (\d+) \)/.freeze
14
+
15
+ # The expression used to extract the playing status out of {#status}
16
+ # @api private
17
+ PLAYING_REGEX = /\( state (.*) \)/.freeze
18
+
19
+ # @return [Boolean] whether or not VLC is available
20
+ def self.available?
21
+ Utils.which?("vlc") && Utils.which?("cvlc")
22
+ end
23
+
24
+ # @return [Boolean] whether or not the current instance is running.
25
+ def running?
26
+ !!@vlc&.connected?
27
+ end
28
+
29
+ # Activates a VLC process.
30
+ # @return [void]
31
+ def activate!
32
+ return if running?
33
+
34
+ debug "activating #{self.class}"
35
+
36
+ @vlc = ::VLC::System.new
37
+
38
+ instance.event :player_activated
39
+ end
40
+
41
+ # Deactivates the VLC process, if one is running.
42
+ # @return [void]
43
+ def deactivate!
44
+ return unless running?
45
+
46
+ debug "deactivating #{self.class}"
47
+
48
+ @vlc.client.disconnect
49
+ @vlc.server.stop
50
+
51
+ instance.event :player_deactivated
52
+ end
53
+
54
+ # Tell VLC to begin playback.
55
+ # @return [void]
56
+ # @note Does nothing is playback is already in progress.
57
+ def play
58
+ @vlc.play
59
+ end
60
+
61
+
62
+ # Tell VLC to pause playback.
63
+ # @return [void]
64
+ # @note Does nothing is playback is already paused.
65
+ def pause
66
+ @vlc.pause
67
+ end
68
+
69
+ # @return [Boolean] whether or not VLC is currently playing.
70
+ def playing?
71
+ @vlc.playing?
72
+ end
73
+
74
+ # Tell VLC to play the next song in its queue.
75
+ # @return [void]
76
+ # @note Does nothing if the current song is the last.
77
+ def next_song
78
+ @vlc.next
79
+ end
80
+
81
+ # Tell VLC to play the previous song in its queue.
82
+ # @return [void]
83
+ # @note Restarts the song if the current song is the first.
84
+ def previous_song
85
+ @vlc.previous
86
+ end
87
+
88
+ # Tell VLC to add the given song to its queue.
89
+ # @param song [Song] the song to add
90
+ # @return [void]
91
+ # @note Activates VLC if not already activated.
92
+ def enqueue_song(song)
93
+ activate! unless running?
94
+
95
+ load_song song
96
+ end
97
+
98
+ # Tell VLC to add the given album to its queue.
99
+ # @param album [Album] the album to add
100
+ # @return [void]
101
+ # @note Activates VLC if not already activated.
102
+ def enqueue_album(album)
103
+ activate! unless running?
104
+
105
+ album.songs.each do |song|
106
+ load_song song
107
+ end
108
+ end
109
+
110
+ # Tell VLC to add the given playlist to its queue.
111
+ # @param playlist [Playlist] the playlist to add
112
+ # @return [void]
113
+ # @note Activates VLC if not already activated.
114
+ def enqueue_playlist(playlist)
115
+ activate! unless running?
116
+
117
+ playlist.songs.each do |song|
118
+ load_song song
119
+ end
120
+ end
121
+
122
+ # Get VLC's internal queue.
123
+ # @return [Array<Song>] all songs in VLC's queue
124
+ # @note This includes songs already played.
125
+ # @todo Implement this.
126
+ def list_queue
127
+ debug @vlc.playlist.to_s
128
+ danger "this player doesn't support list_queue"
129
+ # TODO: figure out how to get VLC::Client#playlist to return filenames
130
+ []
131
+ end
132
+
133
+ # Shuffle VLC's internal queue.
134
+ # @return [void]
135
+ # @todo Implement this.
136
+ def shuffle
137
+ danger "this player doesn't support shuffling (?)"
138
+ end
139
+
140
+ # Clear VLC's internal queue.
141
+ # @return [void]
142
+ def clear_queue
143
+ @vlc.clear
144
+ end
145
+
146
+ # Get VLC's currently loaded song.
147
+ # @return [Song, nil] the currently loaded song
148
+ def now_playing
149
+ filename = INPUT_REGEX.match(status.first)[1]
150
+ Song.new(filename)
151
+ end
152
+
153
+ # Load a song into VLC.
154
+ # @param song [Song] the song to load
155
+ # @return [void]
156
+ # @api private
157
+ def load_song(song)
158
+ @vlc.add_to_playlist song.path
159
+ @vlc.play if Config.autoplay
160
+ end
161
+
162
+ # Get VLC's status.
163
+ # @return [Array<String>] the loaded file, volume, and playing statuses
164
+ # @api private
165
+ # @note The strings returned by this method can be handled via
166
+ # {INPUT_REGEX}, {VOLUME_REGEX}, and {PLAYING_REGEX}.
167
+ def status
168
+ @vlc.connection.write "status"
169
+ 3.times.collect { @vlc.connection.read }
170
+ end
171
+ end
172
+ end
173
+ end
@@ -10,27 +10,24 @@ module Muzak
10
10
  # load plugins included by the user
11
11
  Dir.glob(File.join(Config::USER_PLUGIN_DIR, "*")) { |file| require file }
12
12
 
13
- # @return [Array<Class>] all plugin classes visible under Plugin
14
- def self.plugin_classes
15
- constants.map(&Plugin.method(:const_get)).grep(Class)
16
- end
13
+ # All classes (plugins) under the {Player} namespace.
14
+ # @see Plugin::StubPlugin.plugin_name
15
+ # @api private
16
+ PLUGIN_CLASSES = constants.map(&Plugin.method(:const_get)).grep(Class).freeze
17
17
 
18
- # @return [Array<String>] the names of all plugin classes under Plugin
19
- # @see StubPlugin.plugin_name
20
- def self.plugin_names
21
- plugin_classes.map do |pk|
22
- pk.plugin_name
23
- end
24
- end
18
+ # All human-friendly player plugin names.
19
+ # @api private
20
+ PLUGIN_NAMES = PLUGIN_CLASSES.map(&:plugin_name).freeze
21
+
22
+ # An association of human-friendly plugin names to plugin classes.
23
+ # @api private
24
+ PLUGIN_MAP = PLUGIN_NAMES.zip(PLUGIN_CLASSES).to_h.freeze
25
25
 
26
26
  # Instantiates all configured plugins and returns them.
27
27
  # @return [Array<StubPlugin>] the instantiated plugins
28
28
  def self.load_plugins!
29
- pks = Plugin.plugin_classes.select { |pk| Config.plugin? pk.plugin_name }
29
+ pks = PLUGIN_CLASSES.select { |pk| Config.plugin? pk.plugin_name }
30
30
  pks.map { |pk| pk.new }
31
31
  end
32
-
33
- # An association of plugin names to their Class objects.
34
- PLUGIN_MAP = plugin_names.zip(plugin_classes).to_h.freeze
35
32
  end
36
33
  end
@@ -1,6 +1,8 @@
1
1
  module Muzak
2
2
  module Plugin
3
3
  # A no-op plugin that all real plugins inherit from.
4
+ # @abstract Subclass and define any of {Config::PLUGIN_EVENTS}
5
+ # as methods to implement plugins.
4
6
  class StubPlugin
5
7
  include Utils
6
8
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: muzak
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - William Woodruff
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-22 00:00:00.000000000 Z
11
+ date: 2017-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: taglib-ruby
@@ -44,6 +44,20 @@ dependencies:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
46
  version: 1.1.0
47
+ - !ruby/object:Gem::Dependency
48
+ name: vlc-client
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: 0.0.6
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: 0.0.6
47
61
  description: A library for controlling playlists and media players.
48
62
  email: william@tuffbizz.com
49
63
  executables:
@@ -77,6 +91,7 @@ files:
77
91
  - lib/muzak/player.rb
78
92
  - lib/muzak/player/mpv.rb
79
93
  - lib/muzak/player/stub_player.rb
94
+ - lib/muzak/player/vlc.rb
80
95
  - lib/muzak/playlist.rb
81
96
  - lib/muzak/plugin.rb
82
97
  - lib/muzak/plugin/stub_plugin.rb