当前位置: 首页 > news >正文

用【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
  1. 运行项目:
  2. cargo run
  3. 进入命令行界面后使用快捷键控制:

功能

Space

暂停 / 继续

n

下一首

Esc

退出播放器

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

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

http://www.dtcms.com/a/619632.html

相关文章:

  • 读毛选品人生00
  • 济南网站关键词优化公司合肥做公司网站
  • 网站设计师网站什么是网络营销的市场细分
  • Redis学习(day1)
  • 使用Nginx正向代理让内网主机通过外网主机访问互联网
  • 网页设计个人网站心得体会长沙seo代理商
  • 专做热血电影的网站玉树营销网站建设哪家好
  • 14.1 模型微调实战:如何用业务数据优化大模型性能?
  • 首钢水钢赛德建设有限公司网站门户网站内容管理系统
  • 安徽宿州住房与建设网站网站备案 时间
  • 网站建设具体步骤网站设置访问权限
  • 茶叶网站策划书app营销网站模板
  • 做网站公司怎么做哈尔滨建筑专业网站
  • c++零基础通关教程《第四课》
  • 网站seo外包价格北京ui网页设计培训
  • 邹城做网站wordpress 模版仿米拓
  • 一般购物网站项目wordpress主题上传怎么用
  • C语言中共享内存完整示例及函数详解
  • 创新网站建设论文wordpress 中文 插件
  • 在工商局网站如果做注销公告11电影网
  • 南头专业外贸网站建设公司绿色郑州网站
  • 餐饮酒店网站建设网页传奇网址
  • 网站建设 客户评价泰州网站专业制作
  • 电子商务网站概念食品网站建设目的
  • 携程前端开发200道题面经及参考答案(上)
  • 深圳最好的网站建设公司排名建站公司前景
  • 郑州免费网站制作沈阳网络公司排名
  • 易捷网站内容管理系统漏洞太原网站搜索优化
  • 网站投注建设网页设计作业制作个人网站
  • 软件公司网站模板下载广告设计制作属于什么行业