DEV Community

Ojalla
Ojalla

Posted on

I created a CLI-Music Player in Rust!

Building a Command-Line Music Player in Rust

Introduction

I built a Rust CLI Music Player that allows users to play, pause, stop, and manage their music files directly from the terminal. It supports basic playback controls, volume adjustment, and song listing.

This walkthrough will cover:

  • The dependencies used
  • How the music player was implemented
  • How to run the application
  • A guide on available commands

Dependencies

I used the following Rust crates:

  1. clap – For command-line argument parsing.
  2. colored – For colored terminal output.
  3. rodio – For audio playback.
  4. ctrlc – To handle Ctrl+C for graceful exit.

To install them, run:

cargo add clap colored rodio ctrlc
✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน ✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน

How the CLI Music Player Works

The music player follows a command-line workflow where users can:

  • Load songs from a specified directory.
  • List available songs.
  • Play, pause, resume, stop playback.
  • Adjust volume.

1. Command-Line Interface (CLI) Configuration

The application uses clap to handle command-line arguments. Users must specify a music directory:

fn cli_config() -> Command {
    Command::new("musicplayer")
        .version("0.1.0")
        .author("ojalla")
        .about("Command-line music player")
        .arg(
            Arg::new("music-dir")
                .short('d')
                .long("dir")
                .value_name("DIRECTORY")
                .help("Sets the music directory")
                .required_unless_present("how-to"),
        )
        .arg(
            Arg::new("how-to")
                .long("how-to")
                .help("Shows operation commands and how to use the application.")
                .action(clap::ArgAction::SetTrue),
        )
}
✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน ✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน

This ensures that users provide a valid music directory or request help using --how-to.


2. Handling User Input

The player reads user commands in a loop:

fn input() -> String {
    let mut user_input = String::new();

    print!("{}", "musicplayer> ".cyan().bold());
    io::stdout().flush().expect("Failed To Flush Output");

    io::stdin()
        .read_line(&mut user_input)
        .expect("Error Getting User Input");

    user_input.trim().to_string()
}
✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน ✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน

This function displays a colored prompt and waits for user input.


3. Implementing the Music Player

I created a CliPlayer struct to store the player’s state:

struct CliPlayer {
    sink: rodio::Sink,
    stream: rodio::OutputStream,
    stream_handle: OutputStreamHandle,
    is_playing: bool,
    is_paused: bool,
    main_dir: Option<String>,
    current_file: Option<String>,
    last_input: Option<String>,
    available_songs: Option<HashMap<i32, DirEntry>>,
    start_time: Option<Instant>,
}
✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน ✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน

This struct maintains:

  • The audio sink and output stream (rodio).
  • The directory containing music files.
  • The current playing song and status.

4. Loading Songs from a Directory

When the application starts, it loads music files from the given directory:

fn load_songs(&mut self) -> io::Result<()> {
    let mut index = 1;
    if let Some(dir) = &self.main_dir {
        if let Some(sound_map) = &mut self.available_songs {
            for entry in read_dir(dir)? {
                let entry = entry?;
                if entry.path().is_file() {
                    sound_map.insert(index, entry);
                    index += 1;
                }
            }
        }
    }
    Ok(())
}
✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน ✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน

This function reads all files in the specified directory and maps them to numbers for easy selection.


5. Playing a Song

To play a song, the application reads the file, decodes it, and plays it using rodio:

pub fn play(&mut self, sound_index: i32) -> Result<(), Box<dyn std::error::Error>> {
    if self.is_playing {
        self.sink.stop();
        self.sink = Sink::try_new(&self.stream_handle)?;
    }

    if let Some(sound_map) = &self.available_songs {
        if let Some(song) = sound_map.get(&sound_index) {
            let file = BufReader::new(File::open(song.path())?);
            let source = Decoder::new(file)?;
            self.sink.set_volume(1.0);
            self.sink.append(source.convert_samples::<f32>());
            self.is_playing = true;
            self.is_paused = false;
            self.current_file = Some(song.file_name().to_string_lossy().to_string());
            self.start_time = Some(Instant::now());
            println!("{}: Playing {}", "Now playing".green().bold(), self.current_file.as_ref().unwrap().blue());
            Ok(())
        } else {
            Err(format!("{}: Invalid song index", "Error".red()).into())
        }
    } else {
        Err("No songs available".into())
    }
}
✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน ✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน

This method:

  • Stops any currently playing track.
  • Loads and decodes the selected song.
  • Starts playback.

6. Implementing Playback Controls

The player handles commands like play, pause, resume, stop, and volume adjustment:

match command {
    InputCommands::Play => { /* Calls play() */ }
    InputCommands::Pause => {
        if self.is_playing {
            self.sink.pause();
            self.is_paused = true;
            println!("{}: Playback paused", "Info".yellow());
        }
    }
    InputCommands::Resume => {
        if self.is_paused {
            self.sink.play();
            self.is_paused = false;
            self.is_playing = true;
            println!("{}: Playback resumed", "Info".green());
        }
    }
    InputCommands::Stop => {
        if self.is_playing {
            self.sink.stop();
            self.is_playing = false;
            println!("{}: Playback stopped", "Info".red());
        }
    }
    InputCommands::Volume(vol) => {
        if (0.0..=1.0).contains(&vol) {
            self.sink.set_volume(vol);
            println!("{}: Volume set to {:.1}", "Success".green(), vol);
        } else {
            println!("{}: Volume must be 0.0 to 1.0", "Error".red());
        }
    }
    _ => println!("{}: Invalid command", "Error".red()),
}
✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน ✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน

These functions control playback using rodio::Sink methods like .play(), .pause(), .stop(), and .set_volume().


7. Displaying Available Songs

Users can list all available songs:

pub fn list(&self) {
    if let Some(sound_map) = &self.available_songs {
        println!("\n{}", "Available Songs:".green().bold());
        println!("{}", "-------------------------------".green());
        println!("{:<6} {:<}", "Index".to_string().bold(), "Filename".to_string().bold());
        for (index, entry) in sound_map {
            let filename = entry.file_name().to_string_lossy();
            if let Some(current) = &self.current_file {
                if filename == *current {
                    println!("{:<6} {:<} {}", index.to_string().green(), filename.green(), "▶".green());
                } else {
                    println!("{:<6} {:<}", index, filename);
                }
            } else {
                println!("{:<6} {:<}", index, filename);
            }
        }
        println!();
    }
}
✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน ✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน

Running the Application

  1. Clone the repository:
git clone https://github.com/Parado-xy/rust-cli-music-player
cd rust-cli-music-player
✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน ✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน
  1. Run the application with a music directory:
cargo run -- --dir /path/to/music
✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน ✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน
  1. Use the available commands:
play <number>    # Play a song
pause            # Pause playback
resume           # Resume playback
stop             # Stop playback
volume <0.0-1.0> # Adjust volume
list             # Show available songs
status           # Show playback status
exit             # Quit the player
✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน ✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน

Conclusion

This Rust CLI Music Player is a simple but powerful terminal-based music player. It utilizes rodio for audio playback, clap for argument parsing, and colored for improved UI. Future improvements could include playlist support and file format filtering.

Top comments (7)

✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน
 
igadii profile image
Idris Gadi •

This looks cool, a small feedback would be to replace --how-to command with --help or -h (short format), as most CLI users will intuitively use the help command. This could be a major UX improvement.

✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน
 
paradoxy profile image
Ojalla •

You are absolutely right.

✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน
 
silviaodwyer profile image
Silvia O'Dwyer •

This is a really cool project, wow!

✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน
 
paradoxy profile image
Ojalla •

Thanks 🙏

✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน
 
urbanisierung profile image
Adam •

That's really cool!

The link to the github repo changed in the meanwhile, right? It's now: github.com/Parado-xy/rust-cli-player

I also added the article to the next issue of the weeklyfoo newsletter.

✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน
 
best_codes profile image
BestCodes • • Edited

I had the same issue (the link was changed). It also doesn't quite work right for me on Ubuntu, but this is a very cool idea @paradoxy!

✨ อัปเดตล่าสุด: paradoxy/i created a cli music player in rust af - ประเด็นร้อน
 
best_codes profile image
BestCodes • • Edited

Never mind, I got it working by running cargo run --release -- --dir /path/to/music. :D