How I built a simple music player daemon in Rust with a CLI/Web UI
Tags: rust, music, opensource, cli
Published: 2023-01-02 19:12:52

## Intro
I love to listen to music when I work, here are some projects I'm a big fan of : [mpd](https://www.musicpd.org) and [mopidy](https://mopidy.com).

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
![architecture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cpiysiwpsj9m4gs6mh56.png)

I started by writing a very simple playback engine, which takes an audio file and decodes it.

```rust
        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?

![schema](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jgj5o7yv998rjp9cmp4i.png)

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
```bash
# 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