我用Qt写了个本地音乐播放器,踩了不少坑,今天全交代了
上个月整理电脑,发现硬盘里存了好几百首mp3,用系统自带的播放器又丑又卡。我就想:干脆自己写一个呗?反正Qt刚学完,正好练练手。
说干就干。我在职坐标学Qt那会儿,老师讲信号槽、讲数据库、讲UI布局,每节课我都跟着敲了代码,但总觉得是"散装"的知识点。直到这个项目做完,我才真正把这些东西串成了一条线。今天就把开发过程中遇到的几个关键问题和踩过的坑,跟大家唠唠。
开发环境和工具:工欲善其事,必先利其器
先说说我用的开发环境,给想复刻的同学一个参考:
- 操作系统:Windows 11,22H2版本
- IDE:Qt Creator(写Qt项目用它最顺手,代码编辑、UI设计、调试一条龙)
- 编译器:MinGW(Qt Creator自带的,不用额外装GCC)
- Qt版本:Qt 5.x(Multimedia模块在5系列里API比较稳定,资料也多)
- 构建工具:qmake(Qt自带的构建系统,写个.pro文件就能编译)
- 数据库:SQLite(Qt内置驱动,不用装额外数据库软件,数据存在本地一个.db文件里)
- 调试神器:
qDebug()(相当于C++版的printf,但更好用,直接在Qt Creator的输出窗口打印,我全程靠它排查问题) - UI设计:Qt Designer(Qt Creator内置的拖拽式界面编辑器,按钮、标签、滑动条拖进去摆好位置就行,不用手写布局代码)
说实话,Qt Creator的集成度很高,从写代码到设计界面到调试,一个软件全搞定,不用在多个工具之间来回切。在职坐标学习的时候老师也是用的这套环境,所以我上手很快,没在配置上折腾太久。
环境搭建:先把地基打好
这个项目用到了Qt的三个模块:Widgets(画界面)、Multimedia(播放音乐)、SQL(存歌曲数据)。在.pro文件里要写清楚QT += core gui multimedia sql,少一个都跑不起来。一开始我就忘了加sql,结果数据库那块怎么都连不上,查了半天才发现是配置没写全。这种低级错误,新手真的很容易犯。
另外项目里的图标资源(播放、暂停、上一首、下一首这些按钮图片)都放在icons文件夹下,通过.qrc资源文件统一管理。代码里用:/icons/play.png这种路径引用,编译后会打包进程序里,发布的时候不用单独带图片文件,很方便。
整个程序的业务流程
先说说程序从头到尾是怎么跑起来的,心里有个全局观再看代码会轻松很多:
第一步:程序启动→main.cpp里创建QApplication和MainWindow,窗口显示出来。
第二步:自动初始化→ MainWindow的构造函数里做了一大堆事:创建播放器、创建播放列表、打开数据库、从数据库读取所有歌曲、把歌曲加载到播放器和界面上、绑定各种信号槽。如果数据库是空的,就弹窗提示用户添加歌曲。
第三步:用户添加歌曲→ 点菜单"文件→添加音乐",弹出文件选择框,选好mp3文件后:提取文件名→推算歌词路径(把.mp3换成.lrc)→写入数据库→加入播放列表→更新界面。
第四步:播放歌曲→ 双击歌单里的歌,播放器开始播放。进度条跟着走、时间标签跟着更新、歌词跟着滚动高亮。
第五步:切歌→ 点上一首/下一首,或者双击歌单里别的歌。播放列表切换后自动加载新歌词,所有UI同步刷新。
第六步:程序退出→ 析构函数清理资源,数据库自动关闭。下次打开程序,歌曲还在,因为数据一直存在SQLite里。
简单说就是:启动→自动加载→添加歌曲→播放→切歌→退出,一个完整的闭环。
整个程序怎么分块?
我把程序拆成了三个类:
- Song:就是个"数据袋子",装每首歌的路径、名字、歌词路径。用了静态列表来存所有歌曲,这样别的地方随时能拿到。
- dbhelper:专门跟数据库打交道。增删查改全封装在里面,别的地方不用管SQL怎么写,调方法就行。
- MainWindow:大管家,界面交互、播放控制、歌词同步全归它管。
这种分法的好处是——改数据库逻辑不用动界面代码,改歌词显示不用管播放逻辑,各管各的。
坑点一:进度条、时间标签、歌词怎么同步?
这是整个项目最烧脑的部分。三个东西都要跟着歌曲播放进度走,但它们的更新方式完全不同。
进度条靠的是QMediaPlayer的positionChanged信号,播放器每隔一小段时间就发一次,我接到信号后算个比例,设给滑动条就行。但这里有个大坑:用户拖拽滑动条的时候,会疯狂触发位置变化信号,跟播放器自己的信号打架。解决办法是在拖拽开始时用blockSignals(true)把播放器的信号屏蔽掉,松手后再blockSignals(false)恢复。这个技巧我后来在别的项目里也用了好几次。
时间标签就是简单的数学:毫秒除以1000变秒,秒除以60变分钟,然后用QString::arg()补零格式化,拼成"03:25/05:12"这种格式。
歌词同步最麻烦。我用了个QTimer每100毫秒扫一次,拿当前播放进度去跟歌词的时间戳比对。核心逻辑就是:当前进度在哪个时间戳区间内,就高亮哪一行,同时把上一行恢复颜色,再调用scrollToItem()让当前行居中。这里要注意边界——进度条拖回去的时候,歌词也要能往回滚,所以我写了向前和向后两个方向的查找逻辑。
坑点二:歌词文件解析,没我想的那么简单
LRC歌词文件的格式是[00:12.34]你好世界,看着简单,解析起来细节很多。我的做法是逐行读,先用]分割成时间和文本两部分,再用:分割时间成分钟和秒。分钟要去掉开头的[(用mid(1)),然后转成数字。最后算成毫秒存进QMap里,这样QMap自动按时间排序,后续查找很方便。
坑在哪?如果某一行格式不规范,比如少了个],split之后数组长度不够,直接取下标就崩了。我一开始没加判断,结果碰到一个"野生"歌词文件就闪退了。后来老老实实加了长度检查才稳。
坑点三:数据怎么持久化?启动时怎么自动加载?
歌曲信息存在SQLite数据库里,程序关了再开还在。我在dbhelper构造的时候自动建表(CREATE TABLE IF NOT EXISTS),所以第一次运行就自动创建,不用手动初始化。
程序启动时,构造函数里先调db.querySong()把数据库里的歌全读出来塞进Song的静态列表,然后initialPlayList()把这个列表加载到播放器和界面上。如果数据库是空的,就弹个对话框问用户要不要直接添加歌曲。这套流程串起来,用户打开程序就能直接用了。
这里有个小坑:querySong()每次调用都是往列表后面追加,如果多次调用就会出现重复歌曲。我后来在调用前先clearList()才解决。
给想动手的同学几点建议
- 先画草图再写代码。我开工前先在纸上画了界面布局和数据流向,写的时候思路清晰很多。
- 信号槽是Qt的灵魂,一定要吃透。这个项目十几对信号槽,搞清楚谁发谁收、什么时候触发,程序就不会乱。
- 别怕踩坑,坑里才有真功夫。进度条信号冲突、歌词解析崩溃、数据重复——这些问题课本上学不到,只有自己写才会遇到。
- 学系统课程比零散看视频有用。我在职坐标学的时候,老师带着从建项目到打包一步步走,那种"完整做一遍"的体验,比自己东拼西凑看教程强太多了。
做一个完整的小项目,比刷十道算法题更能让你理解编程。加油吧!