前言
这是一个音乐播放器小项目,博主在学习QT一个月之后的练手成果,项目代码还是很不成熟,各个框架之间并没有按照最开始希望的MVC思想进行划分,所以大家看看就好,代码开源,轻喷,希望和大家一起成长进步。
gitee链接:musicPlayer: 存放音乐播放器项目代码和文档解释
B站链接:佳肴219的个人空间-佳肴219个人主页-哔哩哔哩视频
音乐播放器需求分析
播放界面
- 需要支持mp3,wav,wma音频文件播放。
- 需要显示播放信息,包括当前播放时间,总时间,播放状态,当前歌曲Index/总歌曲数量,歌曲ID3信息,如果ID3不存在,则显示。
- 需要显示歌曲播放进度条,进度条可以进行拖动选择播放时间。
- 需要有控制按钮,上一曲,下一曲,播放,暂停,播放模式,进入列表。
- 需要实现长按上一曲,下一曲实现快退,快进功能。
- 需要实现4种循环模式,单曲循环,目录循环,全部循环,随机播放。
- 点击列表,可以进入列表界面。
列表界面
- 需要实现文件列表,读职电脑/Music根目录下所有mp3,wav,wma文件,及其包含有mp3,wav,wma文件的文件夹,过滤其他无效文件,过滤不包含有mp3,wav,wma文件的文件夹,过滤空文件夹。
- 点击文件可以进行当前曲目播放,并且进入到播放界面。
- 当前播放的曲目,在文件列表中需要高亮显示或者增加特定标识,并且随着切曲的动作,及时更新当前曲目的高亮状态,如果点击当前播放的曲目则只是跳转到播放界面,不做重新播放处理。
- 点击文件夹可以进入当前文件夹目录,显示当前文件夹目录下所有的mp3,wav,wma文件,及其包含有mp3,wav,wma文件的文件夹。
- 需要有返回按钮,在非根目录点击返回按钮,返回上一级目录,如果在根目录点击返回,则返回到播放界面。
注意点
- 如果是某些无效的mp3,wav,wma文件播放,比如tx文件修改后缀改为mp3,出现播放错误,注意增加友好提示信息。
- 单曲循环自动播放时候循环播放当前曲目,如果用户点击上下曲需要能切到上下曲,当前曲目播放完毕后,自动继续循环当前曲目。
- 进度条拖动过程中,如果时间有更新,不要更新进度条,否则会出现进度条乱跳的情况。
高级功能
- 可以实现专辑图片显示。
- 实现歌词显示。
- 有专辑图片的曲目,将专辑图片虚化后转为墙纸显示。
- 可以实现文件列表按专辑,歌手分类。
- 使用线程后台加载文件列表。
自由扩展
- 快进快退的时候记录按下按钮的时间来实现,按下事件越久,快进的步长越大
详细分析功能需求的代码实现:
播放界面
- 需要支持mp3,wav,wma音频文件播放。
cpp
private:
QMediaPlayer *m_player = nullptr; //播放控制
- 需要显示播放信息,包括当前播放时间,总时间,播放状态,当前歌曲Index/总歌曲数量,歌曲ID3信息,如果ID3不存在,则显示。
- QMediaPlayer
- 自定义实现类
- 需要显示歌曲播放进度条,进度条可以进行拖动选择播放时间。
cpp
QObject::connect(ui->progressBar, &QSlider::sliderMoved, this, &MainWindow::onSliderMoved);
// 滑块拖动时仅更新预览时间
void MainWindow::onSliderMoved(int value)
{
ui->currentTime->setText(formatTime(value)); // 显示拖动时间
updateLyricDisplay(value); // 更新歌词显示
}
- 需要有控制按钮,上一曲,下一曲,播放,暂停,播放模式,进入列表。
- 不同按钮不同功能,connect解决
cpp
void MainWindow::initBtn()
{
setButtonStyle(ui->prevBtn, ":/icon/prev.png");
setButtonStyle(ui->playBtn, ":/icon/play.png");
setButtonStyle(ui->nextBtn, ":/icon/next.png");
...
QObject::connect(ui->playBtn, &QPushButton::clicked, playControl, &PlayControl::handlePlaySlot);
QObject::connect(ui->modeBtn, &QPushButton::clicked, playControl, &PlayControl::handleModeSlot);
QObject::connect(ui->nextBtn, &QPushButton::clicked, this, &MainWindow::handleNextSlot0);
...
}
//设置按钮样式
void MainWindow::setButtonStyle(QPushButton *button, const QString &filename)
{
//按钮设置大小
button->setFixedSize(40, 40);
//设置图标
button->setIcon(QIcon(filename));
//修改图标大小
button->setIconSize(QSize(button->width(), button->height()) );
//设置背景为透明
button->setStyleSheet("background-color:transparent");
}
- 需要实现长按上一曲,下一曲实现快退,快进功能。
- 鼠标事件过滤器,区分点击和长按
cpp
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == ui->prevBtn || obj == ui->nextBtn)//确认按钮为上下首按钮
{
if (event->type() == QEvent::MouseButtonPress)
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
if (mouseEvent->button() == Qt::LeftButton)
{
currentSeekButton = qobject_cast<QPushButton*>(obj);
longPressTimer->start(500); // 启动长按定时器
return false; // 允许事件继续传递,确保clicked信号触发
}
}
else if (event->type() == QEvent::MouseButtonRelease)
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
if (mouseEvent->button() == Qt::LeftButton)
{
longPressTimer->stop();
if (isSeeking)
{
// 处理长按结束逻辑
seekTimer->stop();
isSeeking = false;
currentSeekButton = nullptr;
playControl->getMediaPlayer()->setVolume(originalVolume);
holdStartTime = 0;
return true; // 阻止clicked信号触发
}
return false; // 允许clicked信号触发
}
}
}
return QMainWindow::eventFilter(obj, event);//其他事件
}
- 需要实现4种循环模式,单曲循环,目录循环,全部循环,随机播放。
- 全部循环遍历根目录下面所有文件和子目录,将读取到的有效音频文件全部加入list中
- 目录循环则是遍历当前目录,将音频文件和文件夹区分加入list
cpp
void MusicModel::loadMusicDir(const QString &path)
{
QDir dir(path);
QString canonicalPath = dir.canonicalPath();
// 只在非根目录时添加返回上级目录项
if (canonicalPath != QDir(m_musicDirPath).canonicalPath //规范化路径比对
{//添加返回项
QDir parentDir = dir;
if (parentDir.cdUp())//判断上级目录
{
QListWidgetItem *upItem = new QListWidgetItem("..");
upItem->setData(Qt::UserRole, parentDir.path());
upItem->setData(Qt::UserRole + 1, "folder");
emit addMusicListElement(upItem);
}
}
// 添加音频文件
QStringList filters = {"*.mp3", "*.wav", "*.wma"};
QFileInfoList files = dir.entryInfoList(filters, QDir::Files);
for (const QFileInfo &file : files) {
QListWidgetItem *item = new QListWidgetItem(file.fileName());
QString absPath = file.absoluteFilePath();
// 获取ID3信息
QMap<QString, QString> tags = ID3V2Reader::getTags(absPath);
QString artist = tags.value("TPE1", "未知艺术家");
QString album = tags.value("TALB", "未知专辑");
// 设置扩展数据
item->setData(Qt::UserRole, absPath); // 原始路径
item->setData(Qt::UserRole + 1, "file"); // 类型
item->setData(Qt::UserRole + 2, artist); // 艺术家
item->setData(Qt::UserRole + 3, album); // 专辑
emit addMusicListElement(item);
}
..
}
- 点击列表,可以进入列表界面。
- 一个按钮
列表界面
- 需要实现文件列表,读职电脑/Music根目录下所有mp3,wav,wma文件,及其包含有mp3,wav,wma文件的文件夹,过滤其他无效文件,过滤不包含有mp3,wav,wma文件的文件夹,过滤空文件夹。
- 点击文件可以进行当前曲目播放,并且进入到播放界面。
cpp
QObject::connect(ui->musicList, &QListWidget::itemDoubleClicked, this, &MainWindow::handleItemDoubleClicked);
- 当前播放的曲目,在文件列表中需要高亮显示或者增加特定标识,并且随着切曲的动作,及时更新当前曲目的高亮状态,如果点击当前播放的曲目则只是跳转到播放界面,不做重新播放处理。
cpp
ui->musicList->setStyleSheet(
"QListWidget {"
" border: none;"
" border-radius: 20px;"
" background-color: rgba(255, 255, 255, 0.5);"
" font-size: 16px;" // 字体尺寸
"}"
"QListWidget::item {"
" height: 80px;" // 行高
" padding: 8px 10px;" // 内边距
" border-bottom: 1px solid rgba(0,0,0,0.1);"
" text-overflow: ellipsis;" // 文本过长显示省略号
"}"
"QListWidget::item:hover {"
" background-color: rgba(255,255,255,0.7);"
"}"
"QListWidget::item:selected {" // 选中项样式
" background-color: #dcdcdc;" // 设置选中项背景色为灰色
"}"
- 点击文件夹可以进入当前文件夹目录,显示当前文件夹目录下所有的mp3,wav,wma文件,及其包含有mp3,wav,wma文件的文件夹。
- loadAllFromRootDirectory
cpp
//目录遍历工具
//QDirIterator::Subdirectories 参数 迭代器递归遍历所有子目录。
//深度优先 遍历所有文件和子目录
QDirIterator it(rootPath, QDirIterator::Subdirectories);
while (it.hasNext()) { //逐个获取目录中的条目
QString path = it.next();
if (isAudioFile(path)) { // 检查扩展名 .mp3
audioFiles << path;
}
}
- 需要有返回按钮,在非根目录点击返回按钮,返回上一级目录,如果在根目录点击返回,则返回到播放界面。
注意点
- 如果是某些无效的mp3,wav,wma文件播放,比如tx文件修改后缀改为mp3,出现播放错误,注意增加友好提示信息。
cpp
QDir targetDir(path);
// 检查路径是否存在
if (!targetDir.exists()) {
emit showMessageRequested("错误", "路径不存在", "warning");
return;
}
- 单曲循环自动播放时候循环播放当前曲目,如果用户点击上下曲需要能切到上下曲,当前曲目播放完毕后,自动继续循环当前曲目。
cpp
QObject::connect(playControl, &PlayControl::playbackFinished, this, &MainWindow::handleNextSlot3);
QObject::connect(ui->nextBtn, &QPushButton::clicked, this, &MainWindow::handleNextSlot0);
void MainWindow::handleNextSlot3()
{
//当前播放的音频文件位置
int currentRow = ui->musicList->currentRow();
int count = ui->musicList->count();
if(playControl->getMode() != CIRCLE_MODE)
emit emitcurrentRow(currentRow, count);
else
emit emitcurrentRow(currentRow-1, count);
}
//处理下一首按钮
void PlayControl::handleNextSlot1(int currentRow, int count)
{
int nextRow = 0;
switch(m_mode)
{
case CURRENT_DIR_ORDER_MODE:
nextRow = (currentRow + 1) % count;
break;
case GLOBAL_ORDER_MODE:
nextRow = (currentRow + 1) % count;
break;
case RANDOM_MODE:
{
int cnt = 0;
do
{
nextRow = rand() % count;
cnt++;
}while(nextRow != currentRow && cnt <= 3);//防止单曲
break;
}
case CIRCLE_MODE:
nextRow = (currentRow + 1) % count;
break;
}
emit uiSetCurrentRow(nextRow, 1);
}
- 进度条拖动过程中,如果时间有更新,不要更新进度条,否则会出现进度条乱跳的情况。
cpp
// 滑块按下时标记开始拖动
connect(ui->progressBar, &QSlider::sliderPressed, this, &MainWindow::onsliderPressed);
// 拖动滑块时显示预览时间
connect(ui->progressBar, &QSlider::sliderMoved, this, &MainWindow::onSliderMoved);
// 滑块释放时跳转位置
connect(ui->progressBar, &QSlider::sliderReleased, this, &MainWindow::onSliderReleased);
//处理音乐位置的变化
connect(playControl->getMediaPlayer(), &QMediaPlayer::positionChanged, this, &MainWindow::handlePositionSlot);
}
// 滑块释放时跳转到目标位置
void MainWindow::onSliderReleased() {
QMediaPlayer *player = playControl->getMediaPlayer();
// 获取滑块最终值并设置位置
player->setPosition(ui->progressBar->value());
m_isDraggingSlider = false; // 结束拖动标记
}
// 滑块拖动时更新预览时间
void MainWindow::onSliderMoved(int value) {
ui->currentTime->setText(formatTime(value));
}
void MainWindow::handlePositionSlot(int position) {
if (!m_isDraggingSlider) { // 仅当非拖动时更新进度
ui->progressBar->setValue(position);
ui->currentTime->setText(formatTime(position));
}
}
void MainWindow::onsliderPressed()
{
m_isDraggingSlider = true;
}
高级功能
- 可以实现专辑图片显示。
cpp
QVariantMap metaData;
foreach (const QString &key, metaDataKeys) {
metaData.insert(key, playControl->getMediaPlayer()->metaData(key));
}
..
// 封面键
if (metaData.contains("ThumbnailImage")) {
coverData = metaData.value("ThumbnailImage");
} else if (metaData.contains("CoverArtImage")) {
coverData = metaData.value("CoverArtImage");
}
if (coverPixmap.isNull()) {
// 无封面
ui->coverLabel->setPixmap(
QPixmap(":/Img/dog.jpg")
.scaled(ui->coverLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)
);
ui->coverLabel->setText("");
} else {
// 有封面
ui->coverLabel->setPixmap(
coverPixmap.scaled(ui->coverLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)
);
}
- 实现歌词显示。
cpp
void MainWindow::loadLyrics(const QString &musicPath)
{
..
QTextStream stream(&lrcFile);
stream.setAutoDetectUnicode(false);
QTextCodec *codec = QTextCodec::codecForName("GB18030");
stream.setCodec(codec);
// 正则表达式匹配时间标签和歌词
static QRegularExpression timeRegex(R"(\[(\d{2}):(\d{2})\.(\d{2,3})\](.*))");
..
}
- 有专辑图片的曲目,将专辑图片虚化后转为墙纸显示。
(未实现)
- 可以实现文件列表按专辑,歌手分类。
cpp
void MainWindow::handleSingerSlot()
{
if (!isSingerMode) {
// 进入歌手分类模式
QMap<QString, QList<QListWidgetItem*>> singerMap;
// 遍历所有项并收集歌手信息
for (int i = 0; i < ui->musicList->count(); ++i) {
QListWidgetItem *item = ui->musicList->item(i);
QString type = item->data(Qt::UserRole + 1).toString();
if (type == "file") {
QString singer = item->data(Qt::UserRole + 2).toString();
if (singer.isEmpty()) singer = "未知歌手";
// 克隆项以避免原项被删除
QListWidgetItem *newItem = new QListWidgetItem(*item);
singerMap[singer].append(newItem);
}
}
// 清空列表并重新排列
ui->musicList->clear();
QStringList singers = singerMap.keys();
std::sort(singers.begin(), singers.end());
foreach (const QString &singer, singers) {
// 添加歌手标题项
QListWidgetItem *header = new QListWidgetItem("歌手: " + singer);
header->setData(Qt::UserRole + 1, "singer_header");
header->setFlags(header->flags() & ~Qt::ItemIsSelectable);
header->setBackground(QColor(240, 240, 240));
ui->musicList->addItem(header);
// 添加歌曲项
foreach (QListWidgetItem *song, singerMap[singer]) {
ui->musicList->addItem(song);
}
}
isSingerMode = true;
} else {
// 返回原始模式
isSingerMode = false;
if (playControl->getMode() == GLOBAL_ORDER_MODE) {
musicModel->loadAllFromRootDirectory();
} else {
musicModel->loadFromDirectory(musicModel->currentPath());
}
}
}
- 使用线程后台加载文件列表。
(未实现)
- 自由扩展。
补充知识
歌曲ID3信息
ID3就是表示MP3的身份。
ID3是一种metadata容器,多应用于MP3格式的音频文件中。ID3,一般是位于一个mp3文件的开头或末尾的若干字节内,附加了关于该mp3的歌手,标题,专辑名称,年代,风格等信息,该信息就被称为ID3信息。
ID3信息分为V1和V2版,目前主流V2。V2版ID3信息在MP3文件的开头,V1版在文件结尾。
ID3官网:id3.org/
遇到的问题与解决
- QPushButton添加图片之后显示不全。

cpp
//设置按钮样式
void MainWindow::setButtonStyle(QPushButton *button, const QString &filename)
{
button->setFixedSize(50, 50);
//设置图标
button->setIcon(QIcon(filename));
//修改图标大小
button->setIconSize(QSize(button->width(), button->height()) );
button->setStyleSheet("background-color:transparent");
}
原因:使用水平布局之后没有调整水平布局的框大小导致图片无法显示完全

- 版本不同,函数名不同。QT6播放音频文件的方式与QT5不同,明确分离音频输出设备(
<font style="color:rgba(0, 0, 0, 0.9);background-color:rgb(243, 243, 243);">QAudioOutput</font>
)和播放控制(<font style="color:rgba(0, 0, 0, 0.9);background-color:rgb(243, 243, 243);">QMediaPlayer</font>
)
cpp
// QT5需要直接使用QMediaPlayer的setMedia方法
QMediaPlayer *player = new QMediaPlayer;
player->setMedia(QUrl::fromLocalFile("audio.mp3"));
player->play();
// QT6必须单独创建QAudioOutput
QAudioOutput *audioOutput = new QAudioOutput;
QMediaPlayer *player = new QMediaPlayer;
player->setAudioOutput(audioOutput); // 必须绑定音频输出设备
player->setSource(QUrl::fromLocalFile.mp3"));
player->play();
播放状态监听 \ QT6mediaStatusChanged()<font style="color:rgba(0, 0, 0, 0.9);background-color:rgb(243, 243, 243);">信号,改用</font>
playbackStateChanged()`:
cpp
// QT5
connect(player, &QMediaPlayer::mediaStatusChanged, [](QMediaPlayer::MediaStatus status){...});
// QT6
connect(player, &QMediaPlayer::playbackStateChanged, [](QMediaPlayer::PlaybackState state){...});
// 创建播放器和音频输出
QMediaPlayer *player = new QMediaPlayer;
QAudioOutput *audioOutput = new QAudioOutput;
player->setAudioOutput(audioOutput);
// 检查播放状态
void checkPlayStatus() {
if (player->playbackState() == QMediaPlayer::PlayingState) {
qDebug() << "当前正在播放";
} else {
qDebug() << "播放暂停";
}
}
- 播放状态无需转换就可以自动播放下一首,为什么不用暂停?
cpp
//播放音乐
void MainWindow::startPlayMusic()
{
#ifdef DEBUG
qDebug() << ui->musicList->currentItem()->text();
#endif
if (!ui->musicList->currentItem()) {
QMessageBox::warning(this, "错误", "未选中任何音乐文件");
return;
}
// 直接从 UserRole 中获取完整路径
QString musicAbsPath = ui->musicList->currentItem()->data(Qt::UserRole).toString();
m_player->setSource(QUrl::fromLocalFile(musicAbsPath));
handlePlaySlot(); //为什么不用暂停?
}
//处理播放按钮
void PlayControl::handlePlaySlot()
{
int flag = 0;
// 获取当前播放状态
QMediaPlayer::State state = m_player->state();
if(state == QMediaPlayer::PlayingState)
{
m_player->pause();
flag = 0;
emit changePlayIcon(flag);
qDebug() << "停止播放音乐";
}
else
{
m_player->play();
flag = 1;
emit changePlayIcon(flag);
qDebug() << "开始播放音乐";
}
}
//播放音乐
void PlayControl::startPlayMusic(QString musicAbsPath)
{
m_player->setMedia(QUrl::fromLocalFile(musicAbsPath));
m_player->play();
//修改图标
emit changePlayIcon(1);
qDebug() << "[播放控制] 请求播放状态:" << m_player->state(); //x
}
cpp
//当调用 m_player->setSource(QUrl) 时,QMediaPlayer 会自动执行以下动作:
m_player->setSource(newUrl); // 内部会触发以下操作:
-> 停止当前播放(如果正在播放)
-> 清除当前媒体内容
-> 加载新资源
-> 状态变为 `StoppedState`
- 在使用信号槽的时候需要注意确保信号槽先连接,再进行文件读取之类的相关操作
cpp
//原先
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
playControl = new PlayControl(this);
musicModel = new MusicModel(this);
init();
}
//初始化
void MainWindow::init()
{
setWindowTitle("音乐播放器");
setFixedSize(1020, 720);
setBackGround(":/Img/backgrou4.png");
initBtn();
initConnect();
}
//初始化信号和槽
void MainWindow::initConnect()
{
//处理播放按钮的变化
QObject::connect(playControl, &PlayControl::changePlayIcon, this, &MainWindow::handleChangePlayIcon);
//处理读取文件夹后添加到UI的MusicList里面
QObject::connect(musicModel, &MusicModel::addMusicListElement, this, &MainWindow::handleAddMusicListElement);
}
//ui添加对应的元素
void MainWindow::handleAddMusicListElement(QListWidgetItem *item)
{
ui->musicList->addItem(item);
}
//model
MusicModel::MusicModel(QObject *parent)
: QObject{parent}
{
//设置音乐文件夹路径
QString musicDir = "D:\\download\\music\\";
loadMusicDir(musicDir);
}
void MusicModel::loadMusicDir(const QString &filename)
{
QDir dir(filename);
if(!dir.exists())
{
QMessageBox::warning(nullptr, "error", "文件路径不存在"); //无法绑定this,可能是因为this不是一个widgets类型
return;
}
QFileInfoList fileList = dir.entryInfoList(QDir::Files);
for(auto element : fileList)
{
//判断文件后缀是否符合要求
if(element.suffix().toLower() == "mp3" ||
element.suffix().toLower() == "wav" ||
element.suffix().toLower() == "wma")
{
// 添加带后缀的文件名到列表
QListWidgetItem *item = new QListWidgetItem(element.fileName());
// 存储文件的绝对路径到 UserRole
item->setData(Qt::UserRole, element.absoluteFilePath());
//发送信号让ui添加对应的元素
emit addMusicListElement(item);
}
}
// //默认第一行
// ui->musicList->setCurrentRow(0);
}
修改后
cpp
//初始化
void MainWindow::init()
{
setWindowTitle("音乐播放器");
setFixedSize(1020, 720);
setBackGround(":/Img/backgrou4.png");
initBtn();
initConnect();
// 后加载文件
musicModel->loadFromDirectory("D:\\download\\music\\");
}
void MusicModel::loadFromDirectory(const QString &path)
{
loadMusicDir(path);
}
- 部分音频文件无法播放,QT报错提示,版本问题
qt.multimedia.ffmpeg: Using Qt multimedia with FFmpeg version 7.1 LGPL version 2.1 or later
许可证导致的编解码器禁用,LGPL 版本的 FFmpeg 默认禁用 GPL 代码和部分专利编解码器(如 Fraunhofer MP3)。若音频文件依赖这些编解码器,会导致播放失败。
解决办法,规避,转换QT5版本后正常运行
- 在拖动滑块的时候,音频文件快速播放导致杂音
cpp
//拖动滑块,对应改变音乐播放的位置
QObject::connect(ui->progressBar, &QSlider::sliderMoved, playControl->getMediaPlayer(), &QMediaPlayer::setPosition);
cpp
// 滑块按下时标记开始拖动
connect(ui->progressBar, &QSlider::sliderPressed, this, &MainWindow::onsliderPressed);
// 拖动滑块时显示预览时间
connect(ui->progressBar, &QSlider::sliderMoved, this, &MainWindow::onSliderMoved);
// 滑块释放时跳转位置
connect(ui->progressBar, &QSlider::sliderReleased, this, &MainWindow::onSliderReleased);
//处理音乐位置的变化
connect(playControl->getMediaPlayer(), &QMediaPlayer::positionChanged, this, &MainWindow::handlePositionSlot);
}
// 滑块释放时跳转到目标位置
void MainWindow::onSliderReleased() {
QMediaPlayer *player = playControl->getMediaPlayer();
// 获取滑块最终值并设置位置
player->setPosition(ui->progressBar->value());
m_isDraggingSlider = false; // 结束拖动标记
}
// 滑块拖动时更新预览时间
void MainWindow::onSliderMoved(int value) {
ui->currentTime->setText(formatTime(value));
}
void MainWindow::handlePositionSlot(int position) {
if (!m_isDraggingSlider) { // 仅当非拖动时更新进度
ui->progressBar->setValue(position);
ui->currentTime->setText(formatTime(position));
}
}
void MainWindow::onsliderPressed()
{
m_isDraggingSlider = true;
}
- 不会跳过文件夹
c++
void MainWindow::mySetCurrentRow(int row, int direction) //direction = 1 为下一首,=0 为上一首
{
const int totalItems = ui->musicList->count();
if (totalItems == 0) return;
int step = (direction == 1) ? 1 : -1;
while (true) {
if (row < 0) {
row = totalItems - 1;
} else if (row >= totalItems) {
row = 0;
}
QListWidgetItem* item = ui->musicList->item(row);
QString type = item->data(Qt::UserRole + 1).toString();
// 跳过文件夹和分类标题
if (type != "folder" && type != "singer_header") {
break;
}
row += step;
}
ui->musicList->setCurrentRow(row);
if (!ui->musicList->currentItem()) return;
QString musicAbsPath = ui->musicList->currentItem()->data(Qt::UserRole).toString();
// 先加载歌词
loadLyrics(musicAbsPath);
// 等待UI刷新(添加小延迟确保歌词加载完成)
QCoreApplication::processEvents();
// 然后发送播放信号
emit emitCurrentMusicAbsPath(musicAbsPath);
}
其他设计
快进快退的时候记录按下按钮的时间来实现,按下事件越久,快进的步长越大