Intro
I love to listen to music when I work, here are some projects I'm a big fan of : mpd and mopidy.
So I had the idea to create a Music Player daemon in Rust, it could be interesting because it will be easier to install and we will have only one executable without dependencies.
Architecture
I started by writing a very simple playback engine, which takes an audio file and decodes it.
let mut hint = Hint::new();
let path = Path::new(song);
// Provide the file extension as a hint.
if let Some(extension) = path.extension() {
if let Some(extension_str) = extension.to_str() {
hint.with_extension(extension_str);
}
}
let source = Box::new(File::open(path).unwrap());
// Create the media source stream using the boxed media source from above.
let mss = MediaSourceStream::new(source, Default::default());
let symphonia_decoder = |mss: MediaSourceStream, hint| {
SymphoniaDecoder::new(mss, hint).map(|mut decoder| {
Box::new(decoder) as Decoder
})
};
let decoder_type = symphonia_decoder(mss, hint);
let mut decoder = match decoder_type {
Ok(decoder) => decoder,
Err(e) => {
error!("Failed to create decoder: {}", e);
return;
}
};
let mut sink = backend(None, audio_format);
sink.start();
loop {
match decoder.next_packet() {
Ok(result) => {
if let Some((ref _packet_position, packet, channels, sample_rate)) = result {
match packet.samples() {
Ok(_) => {
let mut converter =
Converter::new(Some(mk_ditherer::<TriangularDitherer>));
sink.write(packet, channels, sample_rate, &mut converter);
}
Err(e) => {
error!("Failed to decode packet: {}", e);
}
}
}
}
Err(e) => {
error!("Failed to decode packet: {}", e);
}
};
}
Then I added a gRPC API to control the player remotely and an audio file scanner that extracts the metadata and the path and organizes them in a small SQLite database.
How it works?
The final application is a single executable without dependencies but which is both a server (Music Player Daemon + gRPC API + GraphQL API) and a client (CLI).
If you run it for the first time it will start the server API and if you open another terminal and start another one, it will behave like a client.
Project Setup
# Install dependencies
brew install protobuf # macOS
sudo apt-get install -y libasound2-dev protobuf-compiler # Ubuntu/Debian
choco install protoc # Windows using Chocolatey Package Manager
# Compile
git clone https://github.com/tsirysndr/music-player.git
cd music-player/webui/musicplayer
nvm install # install node version specified in .nvmrc (optional on windows)
npm install -g yarn
yarn install && yarn build # build webui
cd ../..
cargo install --path .
Usage
USAGE:
music-player [SUBCOMMAND]
OPTIONS:
-h, --help Print help information
-V, --version Print version information
SUBCOMMANDS:
albums List all albums
artists List all artists
help Print this message or the help of the given subcommand(s)
next Play the next song
pause Pause the current song
open Open audio file
playlist Manage playlists
prev Play the previous song
queue Manage the queue
play Resume the current song
scan Scan music library: $HOME/Music
search Search for a song, album, artist or playlist
stop Stop the current song
tracks List all tracks
If you are interested in the project, I invite you to visit the github repo https://github.com/tsirysndr/music-player/ , there are still a lot of features that I haven't done yet
Top comments (2)
Looks great!
Nice one ❤️