crystal_mpd
Version, currently 1.1.134 versions
- 1.2.1latestJun 2, 2026
- 1.2.0not indexedJun 24, 2026
- 1.1.1not indexedJun 24, 2026
- 1.1.0not indexedJun 24, 2026
- 1.0.3not indexedJun 24, 2026
- 1.0.2not indexedJun 24, 2026
- 1.0.1not indexedJun 24, 2026
- 1.0.0not indexedJun 24, 2026
- 0.19.0not indexedJun 24, 2026
- 0.18.0not indexedJun 24, 2026
- 0.17.0not indexedJun 24, 2026
- 0.16.0not indexedJun 24, 2026
- 0.15.3not indexedJun 24, 2026
- 0.15.2not indexedJun 24, 2026
- 0.15.1not indexedJun 24, 2026
- 0.15.0not indexedJun 24, 2026
- 0.14.0not indexedJun 24, 2026
- 0.13.0not indexedJun 24, 2026
- 0.12.0not indexedJun 24, 2026
- 0.11.0not indexedJun 24, 2026
- 0.10.1not indexedJun 24, 2026
- 0.10.0not indexedJun 24, 2026
- 0.9.2not indexedJun 24, 2026
- 0.9.1not indexedJun 24, 2026
- 0.9.0not indexedJun 24, 2026
- 0.8.0not indexedJun 24, 2026
- 0.7.0not indexedJun 24, 2026
- 0.6.0not indexedJun 24, 2026
- 0.5.1not indexedJun 24, 2026
- 0.5.0not indexedJun 24, 2026
- 0.4.0not indexedJun 24, 2026
- 0.3.0not indexedJun 24, 2026
- 0.2.0not indexedJun 24, 2026
- 0.1.0not indexedJun 24, 2026
github.com/mamantoha/crystal_mpd
A Crystal wrapper for the Music Player Daemon (MPD) protocol.
Nothing has been indexed for 1.1.1 yet. The tag is recorded, its shard.yml has not been read, so the manifest and dependency list below are empty because they are unknown rather than because they are absent.
Installation
# Add this to your shard.yml
dependencies:
crystal_mpd:
github: mamantoha/crystal_mpd
version: ~> 1.1.1Then run:
shards installshard.yml
No shard.yml has been indexed for 1.1.1. You can read it on the repository.
Dependencies
Unknown: the shard.yml for this version has not been read yet.
README
This README is the one indexed from the repository at its latest ref, not from the tag for this version.
MPD::Client
Concurrent Music Player Daemon client written entirely in Crystal.
Main features
- High-level wrapper for common MPD commands
- Filtering DSL for search/find commands
- Crystal range support for playlist and database commands
- Command list helpers
- Status polling callbacks with per-event and global handlers
- Idle callbacks for MPD subsystem changes
- Optional supervised reconnects for callback and idle listeners
- Binary response support for album art and embedded pictures
- Stored playlist, sticker, output, and protocol commands
- Client-to-client messaging through MPD channels
- Logging and reconnect handling for IO errors
Installation
Add this to your application's shard.yml:
dependencies:
crystal_mpd:
github: mamantoha/crystal_mpd
Usage
require "crystal_mpd"
All functionality is contained in the MPD::Client class.
Creating an instance of this class is as simple as:
client = MPD::Client.new("localhost", 6600)
You can also omit the host and port, and it will use the defaults:
client = MPD::Client.new("localhost")
client = MPD::Client.new
You can connect to a local socket (UNIX domain socket), specify an absolute path:
client = MPD::Client.new("/run/mpd/socket")
If a password specified for access to MPD:
client = MPD::Client.new("localhost", password: "password")
The client library can be used as follows:
puts client.version # print the mpd version
client.play(2) # begins playing the playlist at song number 2
puts client.status # print the current status of the player and the volume level
client.close # send the close command
client.disconect # disconnect from the server
Check MPD::Client source for supported commands.
To use all crystal_mpd functions you should use the latest stable MPD version (0.24.x).
Command lists
Command lists documentation.
To facilitate faster adding of files etc. you can pass a list of commands all at once using a command list.
The command list begins with command_list_ok_begin and ends with command_list_end.
It does not execute any commands until the list has ended.
The return value is whatever the return for a list of commands is. On success for all commands, OK is returned.
If a command fails, no more commands are executed and the appropriate ACK error is returned.
If command_list_ok_begin is used, list_OK is returned for each successful command executed in the command list.
client.command_list_ok_begin # start a command list
client.update # insert the update command into the list
client.status # insert the status command into the list
client.command_list_end # result will be a Array with the results
or
client.with_command_list do
client.update
client.status
end
Ranges
Ranges documentation.
Some MPD commands (e.g. move, delete, load, shuffle, playlistinfo) support integer ranges in the format START:END, specifying a slice of songs. This is handled in crystal_mpd via MPD::Range, which supports both inclusive (1..10) and exclusive (1...10) ranges.
Note: MPD treats END as exclusive, so we internally adjust inclusive ranges to match this behavior.
Also note that in MPD, song indexes start at 0 — the same as in Crystal.
# Move first 3 songs to position 10, 11, and 12
client.move(0..2, 10)
# Delete songs 0 and 1 (but NOT 2)
client.delete(0...2)
# Delete songs 0, 1, and 2
client.delete(0..2)
End-less ranges also span to the end of the list:
# Delete all songs from the playlist starting from index 10
client.delete(10..)
# or using exclusive range (same effect)
client.delete(10...)
Begin-less ranges default the start to 0:
# Delete songs 0, 1, and 2
client.delete(..2)
# Delete songs 0 and 1
client.delete(...2)
Filters
Filters documentation
All commands which search for songs (playlistsearch, playlistfind, searchaddpl, searchcount, searchplaylist, list, count, find, search, findadd, searchadd) share a common filter syntax.
The find commands are case sensitive, which search and related commands ignore case.
client.search("(any =~ 'crystal')")
client.searchaddpl("alt_rock", "(genre == 'Alternative Rock')", sort: "-ArtistSort", window: (5..10))
client.list("filename", "((artist == 'Linkin Park') AND (date == '2003'))")
Build MPD query expressions in Crystal
The MPD::Filter class helps you construct complex MPD filter expressions using a fluent and chainable DSL — fully compatible with MPD filter syntax.
You can build expressions using chainable methods like #eq, #contains, #not_eq, and logical #not.
filter =
MPD::Filter
.eq("Artist", "Linkin Park")
.contains("Album", "Meteora")
.not_eq("Title", "Numb")
.sort("Track")
.window(..10)
client.find(filter)
This is equivalent to:
expression = "((Artist == 'Linkin Park') AND (Album contains 'Meteora') AND (Title != 'Numb'))"
client.find(expression, sort: "Track", window: ..10)
You can also use this block-based filter DSL like:
client.search do |filter|
filter
.eq(:artist, "Linkin Park")
.match(:album, "Meteora.*")
.not_eq(:title, "Numb")
.sort(:track)
.window(..10)
end
Supported methods
| Method | MPD Equivalent |
|---|---|
eq(tag, value) | (tag == 'value') |
not_eq(tag, value) | (tag != 'value') |
match(tag, value) | (tag =~ 'value') |
not_match(tag, value) | (tag !~ 'value') |
eq_cs(tag, value) | (tag eq_cs 'value') |
eq_ci(tag, value) | (tag eq_ci 'value') |
not_eq_cs(tag, value) | (!(tag eq_cs 'value')) |
not_eq_ci(tag, value) | (!(tag eq_ci 'value')) |
contains(tag, value) | (tag contains 'value') |
not_contains(tag, value) | (!(tag contains 'value')) |
contains_cs(tag, value) | (tag contains_cs 'value') |
contains_ci(tag, value) | (tag contains_ci 'value') |
not_contains_cs(tag, value) | (!(tag contains_cs 'value')) |
not_contains_ci(tag, value) | (!(tag contains_ci 'value')) |
starts_with(tag, value) | (tag starts_with 'value') |
not_starts_with(tag, value) | (!(tag starts_with 'value')) |
starts_with_cs(tag, value) | (tag starts_with_cs 'value') |
starts_with_ci(tag, value) | (tag starts_with_ci 'value') |
not_starts_with_cs(tag,val) | (!(tag starts_with_cs 'value')) |
not_starts_with_ci(tag,val) | (!(tag starts_with_ci 'value')) |
not(filter) | (!...) |
Chaining multiple filters implies logical AND.
Negate an expression with #not.
inner = MPD::Filter.eq("Genre", "Pop")
outer = MPD::Filter.not(inner)
# => "(!(Genre == \"Pop\"))"
which is equivalent to
MPD::Filter.not_eq("Genre", "Pop")
# => "(Genre != \"Pop\")"
Callbacks
Callbacks are a simple way to make your client respond to events, rather that have to continuously ask the server for updates. This is done by having a background thread continuously check the server for changes.
To make use of callbacks, you need to:
-
Create a MPD client instance with callbacks enabled.
client = MPD::Client.new(with_callbacks: true) -
Setup a callback to be called when something happens.
client.on :state do |state| puts "[#{Time.local}] State was change to #{state}" end
crystal_mpd supports callbacks for any of the keys returned by MPD::Client#status.
Here's the full list of events:
:partition:volume:repeat:random:single:consume:playlist:playlistlength:state:song:songid:nextsong:nextsongid:time:elapsed:duration:bitrate:xfade:mixrampdb:mixrampdelay:audio:updating_db:error:lastloadedplaylist
client = MPD::Client.new(with_callbacks: true)
client.callbacks_timeout = 2.seconds
client.on :state do |state|
puts "[#{Time.local}] State was change to #{state}"
end
client.on :song do
if (current_song = client.currentsong)
puts "[#{Time.local}] 🎵 #{current_song["Artist"]} - #{current_song["Title"]}"
end
end
# Keep the program running
loop { sleep 1.second }
The above will connect to the server like normal, but this time it will create a new fiber that loops until you issue an exit. This loop checks the server, then sleeps for 2 seconds, then loops.
In addition to registering individual event listeners using #on, the MPD client also supports a global callback listener using #on_callback.
This method allows you to handle all events in a single block and react based on the event type.
client = MPD::Client.new(with_callbacks: true)
client.on_callback do |event, value|
case event
when .state?
puts "State changed to #{value}"
when .song?
puts "Now playing: #{value}"
when .repeat?
puts "Repeat mode: #{value == "1" ? "On" : "Off"}"
else
puts "[#{event}] → #{value}"
end
end
# Keep the program running
loop { sleep 1.second }
You can combine #on_callback with specific #on handlers. For example:
client.on(:state) { |val| puts "STATE: #{val}" }
client.on_callback do |event, value|
puts "[ALL EVENTS] #{event} => #{value}"
end
Reconnect handling
By default, callback and idle listener fibers stop when the MPD connection is lost. Long-running clients can opt into supervised reconnects:
client = MPD::Client.new(
with_callbacks: true,
reconnect_policy: MPD::Client::ReconnectPolicy::Forever
)
client.reconnect_interval = 2.seconds
client.on_connection_error do |error|
puts "MPD unavailable: #{error.message}"
end
client.on_disconnect do |_error|
puts "MPD disconnected"
end
client.on_reconnect do
puts "MPD reconnected"
end
Use ReconnectPolicy::Never when a script should stop after a connection error.
Use ReconnectPolicy::Forever for GUI clients, daemons, or other long-running
programs that should survive MPD restarts.
Idle callbacks
MPD also supports the idle command for subsystem-level notifications such as
player, playlist, stored_playlist, database, and mixer.
#on_idle starts a background fiber that repeatedly waits for idle events and
passes the changed subsystem names to your callback.
idle_client = MPD::Client.new(
reconnect_policy: MPD::Client::ReconnectPolicy::Forever
)
idle_client.on_idle(["player", "playlist", "stored_playlist"]) do |events|
if events.includes?("stored_playlist")
puts "Stored playlists changed"
end
if events.includes?("player")
puts "Playback state changed"
end
end
# Keep the program running
loop { sleep 1.second }
The MPD idle command blocks the connection while it waits for changes. For GUI
apps and other programs that need to send commands while listening, create a
dedicated MPD::Client for #on_idle.
#on_idle uses the client's reconnect policy by default. You can also override
it for a specific listener:
idle_client.on_idle(["output"], reconnect_policy: MPD::Client::ReconnectPolicy::Never) do |events|
puts events
end
Binary responses
Some commands can return binary data.
client = MPD::Client.new
if (current_song = client.currentsong)
if (response = client.albumart(current_song["file"]))
data, binary = response
# data # => {"size" => "30219", "type" => "image/jpeg", "binary" => "5643"}
extension = MIME.extensions(data["type"]).first? || ".png"
file = File.open("cover#{extension}", "w")
file.write(binary.to_slice)
end
end
The above will locate album art for the current song and save image to cover.jpg file.
Client-to-Client communication
crystal_mpd supports MPD's built-in client-to-client messaging system via channels.
This allows clients to exchange messages in real time through the MPD server.
Supported Methods
client.subscribe("my_channel") # Subscribes to a channel
client.unsubscribe("my_channel") # Unsubscribes from a channel
client.channels # Returns a list of all existing channels
client.readmessages # Reads messages sent to subscribed channels
client.sendmessage("my_channel", "Hi!") # Sends a message to a specific channel
Example
client.subscribe("notifications")
# Somewhere else, another client sends a message
client.sendmessage("notifications", "System update available")
# The first client reads the message
messages = client.readmessages
puts messages
# => [{"channel" => "notifications", "message" => "System update available"}]
Logging
require "crystal_mpd"
client = MPD::Client.new
MPD::Log.level = :debug
MPD::Log.backend = ::Log::IOBackend.new
Development
Install dependencies:
shards
To run test:
crystal spec
Who's using MPD::Client
If you're using MPD::Client and would like to have your application added to this list, just submit a PR!
- cryMPD - control MPD audio playing in the browser
- webview-mpd-player - a lightweight MPD client built with Crystal and Webview
- mpd-qt6 - a desktop MPD client written in Crystal using Qt6 bindings
- mpd-gtk4 - a desktop MPD client written in Crystal using GTK4 bindings
- mpd-libui - a desktop MPD client written in Crystal using the UIng (libui-ng) GUI library
Contributing
- Fork it (https://github.com/mamantoha/crystal_mpd/fork)
- Create your feature branch (git checkout -b my-new-feature)
- Commit your changes (git commit -am 'Add some feature')
- Push to the branch (git push origin my-new-feature)
- Create a new Pull Request
Contributors
- mamantoha Anton Maminov - creator, maintainer
License
Copyright: 2018-2026 Anton Maminov (anton.maminov@gmail.com)
This library is distributed under the MIT license. Please see the LICENSE file.
Documentation
Built from the current release. The first visit to a release nobody has asked for starts its build.
Links
This release
- Version
1.1.1- Tagged
- Jun 24, 2026
- Commit
a02b2734ac24- Indexed
- not yet
Dependents
Repository
github.com/mamantoha/crystal_mpd
Metadata
- Created
- Aug 12, 2026
- Updated
- Sep 29, 2026
- Synced
- Sep 29, 2026
- Versions
- 34