用【rust】实现命令行音乐播放器

一、项目简介

本项目实现了一个基于 Rust 的 命令行音乐播放器 ------ RustTune。 它支持播放本地 .mp3 / .flac / .wav 文件, 在终端中实时显示播放信息,并通过键盘快捷键进行控制。

主要特性:

  • 支持多格式音乐播放(mp3、flac、wav)
  • 实时暂停 / 恢复播放(无延迟)
  • 切换下一首歌曲
  • 自动扫描 music/ 目录
  • 终端界面(TUI)显示当前播放状态

这不仅是一个好玩的命令行小工具, 更是一次完整练习 Rust 多线程、音频流处理、消息通信、终端 UI 控制 的绝佳项目。


二、项目结构

复制代码
rusttune/
├── Cargo.toml
└── src/
    ├── main.rs
    ├── player.rs
    ├── tui_app.rs
    └── utils.rs

三、依赖配置(Cargo.toml)

复制代码
[package]
name = "rusttune"
version = "0.1.0"
edition = "2021"

[dependencies]
rodio = "0.17"            # 音频播放
walkdir = "2.5"           # 扫描文件目录
crossterm = "0.26"        # 终端事件控制
tui = "0.19"              # 终端 UI 渲染
chrono = "0.4"
rand = "0.8"
crossbeam-channel = "0.5" # 线程间通信

四、核心实现思路

播放器的架构由两部分组成:

  1. 播放引擎(player.rs
    1. 管理播放线程;
    2. 使用 rodio::Sink 播放音频;
    3. 通过 crossbeam-channel 接收控制命令(暂停、下一首、退出)。
  1. 终端界面(tui_app.rs)
    1. tui-rs 绘制播放界面;
    2. crossterm 监听键盘事件;
    3. 发送用户命令到播放线程。

两部分通过共享状态和消息通道协同工作,实现即时响应。

五、完整源码

src/main.rs

复制代码
mod player;
mod tui_app;
mod utils;

use std::sync::{Arc, Mutex};
use player::Player;
use tui_app::run_tui;

fn main() {let player = Arc::new(Mutex::new(Player::new("music")));run_tui(player);
}

src/player.rs

复制代码
use rodio::{Decoder, OutputStream, Sink};
use std::{
    fs::File,
    io::BufReader,
    path::PathBuf,
    thread,
    time::Duration,
};
use crossbeam_channel::{unbounded, Sender, Receiver};
use crate::utils::scan_music_dir;

pub enum PlayerCommand {
    TogglePause,
    Next,
    Exit,
}

pub struct Player {pub playlist: Vec<PathBuf>,pub current: usize,pub cmd_tx: Sender<PlayerCommand>,
}

impl Player {pub fn new(folder: &str) -> Self {let playlist = scan_music_dir(folder);let (tx, rx) = unbounded();let player = Self { playlist, current: 0, cmd_tx: tx.clone() };
let playlist_clone = player.playlist.clone();
        thread::spawn(move || player_loop(playlist_clone, rx));
        player
    }
pub fn toggle_pause(&self) {let _ = self.cmd_tx.send(PlayerCommand::TogglePause);
    }
pub fn skip(&self) {let _ = self.cmd_tx.send(PlayerCommand::Next);
    }
pub fn stop(&self) {let _ = self.cmd_tx.send(PlayerCommand::Exit);
    }
}

fn player_loop(playlist: Vec<PathBuf>, rx: Receiver<PlayerCommand>) {if playlist.is_empty() {println!("⚠️ 没有找到音乐文件,请放到 ./music 文件夹中");return;
    }
let (stream, handle) = OutputStream::try_default().unwrap();let mut sink = Sink::try_new(&handle).unwrap();let mut current = 0;
loop {let path = &playlist[current];println!("▶️ 正在播放: {}", path.file_name().unwrap().to_string_lossy());
let file = BufReader::new(File::open(path).unwrap());let source = Decoder::new(file).unwrap();
        sink = Sink::try_new(&handle).unwrap();
        sink.append(source);
        sink.play();
loop {if let Ok(cmd) = rx.try_recv() {match cmd {
                    PlayerCommand::TogglePause => {if sink.is_paused() {println!("▶️ 继续播放");
                            sink.play();
                        } else {println!("⏸ 暂停播放");
                            sink.pause();
                        }
                    }
                    PlayerCommand::Next => {println!("⏭ 下一首");
                        sink.stop();
                        current = (current + 1) % playlist.len();break;
                    }
                    PlayerCommand::Exit => {println!("退出播放器");
                        sink.stop();return;
                    }
                }
            }
if sink.empty() {
                current = (current + 1) % playlist.len();break;
            }
            thread::sleep(Duration::from_millis(200));
        }
    }
}

src/tui_app.rs

复制代码
use crate::player::Player;
use std::io::{self, stdout};
use std::sync::{Arc, Mutex};
use crossterm::{
    event::{self, Event, KeyCode},
    terminal::{enable_raw_mode, disable_raw_mode, Clear, ClearType},
    execute,
};
use tui::{
    backend::CrosstermBackend,
    Terminal,
    widgets::{Block, Borders, Paragraph},
    layout::{Layout, Constraint, Direction},
    text::Span,
};

pub fn run_tui(player: Arc<Mutex<Player>>) {enable_raw_mode().unwrap();
    execute!(stdout(), Clear(ClearType::All)).unwrap();let backend = CrosstermBackend::new(io::stdout());let mut terminal = Terminal::new(backend).unwrap();
loop {
        terminal.draw(|f| {let size = f.size();let chunks = Layout::default()
                .direction(Direction::Vertical)
                .constraints([Constraint::Percentage(100)].as_ref())
                .split(size);
let para = Paragraph::new(Span::raw("🎶 RustTune 播放中(Space暂停/继续,n下一首,Esc退出)",
            ))
            .block(Block::default().borders(Borders::ALL).title("RustTune"));
            f.render_widget(para, chunks[0]);
        }).unwrap();
if event::poll(std::time::Duration::from_millis(200)).unwrap() {if let Event::Key(key) = event::read().unwrap() {let p = player.lock().unwrap();match key.code {
                    KeyCode::Char(' ') => p.toggle_pause(),
                    KeyCode::Char('n') => p.skip(),
                    KeyCode::Esc => {
                        p.stop();break;
                    }
                    _ => {}
                }
            }
        }
    }
disable_raw_mode().unwrap();
}

src/utils.rs

复制代码
use std::path::PathBuf;
use walkdir::WalkDir;

pub fn scan_music_dir(folder: &str) -> Vec<PathBuf> {let mut files = Vec::new();for entry in WalkDir::new(folder).into_iter().filter_map(|e| e.ok()) {if let Some(ext) = entry.path().extension() {if ext == "mp3" || ext == "flac" || ext == "wav" {
                files.push(entry.path().to_path_buf());
            }
        }
    }
    files
}

六、运行项目

  1. 在项目根目录创建一个 music/ 文件夹 放入一些 .mp3.flac.wav 文件:

    rusttune/
    ├── music/
    │ ├── song1.mp3
    │ ├── song2.flac
    │ └── song3.wav

  2. 运行项目:

  3. cargo run

  4. 进入命令行界面后使用快捷键控制:

|-------|---------|
| | 功能 |
| Space | 暂停 / 继续 |
| n | 下一首 |
| Esc | 退出播放器 |

该项目最终将落地为一款功能完备的命令行音乐播放器,以简洁直观的交互形态,全面覆盖音乐播放核心功能。项目深度发挥 Rust 语言的技术优势,既凭借其内存安全特性筑牢程序稳定性基石,又以零成本抽象与高效编译能力保障流畅的播放体验,最终呈现出一款兼具实用性与技术质感的轻量工具,充分彰显了 Rust 编程在性能与安全性上的卓越平衡。

想了解更多关于Rust语言的知识及应用,可前往华为开放原子旋武开源社区(https://xuanwu.openatom.cn/),了解更多资讯~

相关推荐
s9123601012 小时前
【Rust】使用lldb 调试core dump
前端·javascript·rust
全栈陈序员2 小时前
用Rust和Bevy打造2D平台游戏原型
开发语言·rust·游戏引擎·游戏程序
黛琳ghz2 小时前
用 Rust 从零构建高性能文件加密工具
开发语言·后端·rust
悟世君子2 小时前
Rust 开发环境搭建
开发语言·后端·rust
OlahOlah2 小时前
Go 入门实战:音乐专辑管理 API
后端
DARLING Zero two♡2 小时前
用Rust构建一个OCR命令行工具
数据库·rust·ocr
代码狂想家2 小时前
Rust时序数据库实现:从压缩算法到并发优化的实战之旅
开发语言·rust·时序数据库
黛琳ghz2 小时前
用 Rust 打造高性能 PNG 压缩服务
开发语言·后端·rust
IT闫2 小时前
Rust的内存安全与实战落地的直观解析
开发语言·安全·rust