1265 字
6 分钟
music-cli 开发
music-cli 开发日志
前言(为什么要做这个)
主要是我一个同学很喜欢听歌,而且很关注各类的音乐播放软件,但是电脑上还没有找到像手机端salt player一样很好用的本地音乐播放器,我就经常让他自己做一个,然后说着说着我自己也产生了兴趣,但是我不会前端,就做了这么一个命令行的音乐播放器(虽然说写的非常屎,现在还有很多bug,哎~ )
技术栈:Go(go 1.25.3),使用 github.com/faiface/beep 负责音频播放,github.com/dhowden/tag 读取元数据/歌词,golang.org/x/term 操作终端尺寸与 raw 模式,若干小工具包用于终端字符串宽度与 ANSI 颜色处理。
主要实现与关键点
支持的格式
- flac
- mp3
- wav(不能读取音乐标签)
文件与模块一览
main.go:入口,调用player.PageController()启动交互流程。input_handler/用户输出处理目录,包括各种界面用户输入处理的逻辑和页面切换控制器。player/:播放核心目录,包含播放、输入控制、歌词解析、进度条等:player.go:Player结构体,负责解码(mp3/flac/wav)、初始化beep流、启动播放、管理beep.Ctrl用于暂停/恢复,以及歌词加载逻辑。。lyric.go:逐字歌词解析与渲染逻辑,支持把相同时间戳的原文/译文配对并逐字高亮。process_bar.go:进度条逻辑,按终端宽度动态计算进度长度并绘制。
utils/:若干工具函数:center.go:把带颜色的字符串居中显示(使用stripansi删除 ANSI 再测宽度)。path.go:WalkDir、ListDir、PrintPathInfo等目录/分页展示辅助。
关键依赖在 go.mod 中可以看到:beep、tag、stripansi、go-runewidth 等。
播放流程
- 启动页面控制器
- 用户输入路径(
handleHomeInput),若是目录进入菜单,否则直接播放文件 - 在菜单中使用
ListDir列出音乐文件和子目录,分页显示,+下一页,-上一页,q退出,0播放当前目录所有音乐,a递归遍历目录并播放所有文件,加上r后可以随机播放 - 选择播放时创建
Player(NewPlayer(path)),Init()打开文件并根据扩展名用相应解码器(mp3.Decode、flac.Decode、wav.Decode)。 - 使用
beep.Ctrl{Streamer: p.streamer}作为控制器,在speaker.Play(beep.Seq(ctrl, beep.Callback(...)))中播放并等待播放完成信号。 - 播放期间开启两个并发协程:一个绘制进度条(
progressBar.printBar),另一个负责歌词显示(lyrics.print)。输出使用 ANSI 控制序列定位,printMu用于避免多协程输出冲突,并且会开启一个监听用户输入的协程(handlePlayInput),根据用户输入向其他协程发送信号来控制行为 - 输入监听在 raw 模式下逐字读取字节,空格控制暂停/播放,
-上一首,+下一首,q退出。
歌词实现细节
- 借助
github.com/dhowden/tag读取音频元数据中的歌词(如果存在)。 - 使用正则匹配时间戳信息和歌词信息
lyric.go实现了逐字解析:把带时间戳的行拆成若干word(包含相对时间与文本),并把相同时间戳的行配对为原文+译文(lyricPair)。- 渲染时根据播放器当前播放样本位置计算当前时间(通过
streamer.Position()与format.SampleRate换算),定位到当前行和当前字并高亮已播放的字(蓝色)和未播放字(深灰)。 - 为了解决歌词闪烁,每一行只有当变化的时候才会重新渲染(虽然直到现在当逐字歌词词变化速度过快的时候还是会闪烁,我是真服了)
进度条与终端适配
progressBar读取终端宽度(term.GetSize),减去显示当前/总时长位置后绘制进度块。- 时间计算使用
streamer.Len()与采样率换算为总时长,再通过Position()换算当前时间。 - 绘制时读取实时终端大小,并在每次绘制前清理目标行,借助
utils.Center使部分文本居中。
代码迭代时间线
- 实现了进度条和音乐播放部分
- 实现了歌词时间戳和原文的解析
- 实现了歌词的显示
- 成功解析双语歌词
- 实现歌词原文的显示
- 加上歌词译文的显示
- 同时显示上一句,当前和下一句歌词
- 实现了逐字歌词的解析
- 成功显示逐字歌词
- 能播放文件夹内容
- 能递归或者不递归播放
- 显示当前文件夹歌曲和目录
- 分页显示,可切换页数
- 能指定页数
- 显示歌曲名和作者
- 实现随机播放(单曲,递归所有,当前文件夹所有)
- 能切换目录
- 终端大小改变时刷新来防止显示混乱
- 期间还修复了各种bug,像是闪烁(现在都还没完全解决),显示混乱(并发导致的),播放wav会闪退(似乎是因为标签解析库不能解析wav文件),等等。
下一步计划
- 修复当前终端大小改变后歌曲名和作者不显示的问题(
部分信息可能已经过时
鄂公网安备42011102005849号