【QQMusic项目复习笔记——音乐管理模块详解】第四章
🌹 作者: 云小逸
🤟 个人主页: 云小逸的主页
🤟 motto: 要敢于一个人默默的面对自己,强大自己才是核心。不要等到什么都没有了,才下定决心去做。种一颗树,最好的时间是十年前,其次就是现在!学会自己和解,与过去和解,努力爱自己。希望春天来之前,我们一起面朝大海,春暖花开!
🥇 专栏:
- 动态规划
- C 语言
- C++
- Java 语言
- Linux 编程
- 算法
- 待续…
文章目录
- 📚 前言
- 一、音乐加载与筛选
- 1. 使用QFileDialog加载本地音乐
- 2. MusicList类:音乐集合管理
- 3. Music类:单个音乐元数据封装
- 二、音乐分类与界面更新
- 1. CommonPage:通用页面模板
- 2. ListItemBox:音乐列表项控件
- 三、音乐收藏与持久化
- 1. 收藏逻辑:界面与数据同步
- 2. 数据库持久化:SQLite存储音乐信息
- 四、CommonPage显示不足处理
- 功能概述
- 核心处理内容及实现方法
- a. **歌曲作者对齐处理**
- b. **显示延迟问题**
- c. **移除QListWidget水平滚动条**
- d. **QListWidget选中项背景色设置**
- e. **垂直滚动条美化**
- 五、总结与扩展
- 1. 模块价值
- 2. 常见问题与解决方案
- 3. 扩展方向
- 📣 结语
📚 前言
在QQMusic这样的音乐软件里,音乐管理模块可是非常重要的。它就像是一个大管家,负责把音乐从你的电脑里找出来,整理好,然后展示给你看,还能让你把喜欢的音乐收藏起来。这篇笔记会详细地给你讲解第4章的内容,把代码和逻辑结合起来,让你轻松理解怎么实现这么厉害的音乐管理功能。
一、音乐加载与筛选
1. 使用QFileDialog加载本地音乐
void QQMusic::on_addLocal_clicked() {QFileDialog fileDialog(this);fileDialog.setWindowTitle("添加本地音乐");fileDialog.setAcceptMode(QFileDialog::AcceptOpen); // 设置为打开文件模式fileDialog.setFileMode(QFileDialog::ExistingFiles); // 允许选择多个已存在的文件// 设置MIME过滤器,只显示音频文件QStringList mimeList;mimeList << "application/octet-stream"; // 通用二进制流(可根据需求细化为mp3、flac等)fileDialog.setMimeTypeFilters(mimeList);// 设置默认打开目录QDir dir(QDir::currentPath());dir.cdUp();QString musicPath = dir.path() + "/QQMusic/musics/";fileDialog.setDirectory(musicPath);// 显示对话框,获取用户选择的文件if (fileDialog.exec() == QFileDialog::Accepted) {QList<QUrl> urls = fileDialog.selectedUrls(); // 获取选中文件的URL列表musicList.addMusicByUrl(urls); // 交给MusicList处理ui->stackedWidget->setCurrentIndex(4); // 切换到本地音乐页面}
}
- 创建文件选择对话框:
- 使用
QFileDialog
创建一个用于选择文件的对话框,将其作为当前QQMusic
对象的子对象。 - 通过
setWindowTitle
方法为对话框设置标题“添加本地音乐”,方便用户识别该对话框的用途。
- 使用
- 设置对话框模式和文件选择规则:
- 调用
setAcceptMode
方法,将对话框设置为打开文件模式,明确其功能是让用户选择要打开的文件。 - 使用
setFileMode
方法,允许用户选择多个已经存在的文件,这样用户可以一次性添加多首本地音乐。
- 调用
- 设置文件类型过滤器:
- 创建一个
QStringList
类型的mimeList
,并添加“application/octet - stream”(通用二进制流,可根据实际需求细化为mp3、flac等具体音频格式)。 - 调用
setMimeTypeFilters
方法,将mimeList
设置为对话框的MIME类型过滤器,确保用户只能看到和选择音频文件。
- 创建一个
- 设置默认打开目录:
- 获取当前工作目录,然后使用
cdUp
方法向上一级目录移动。 - 拼接出一个音乐文件所在的默认目录路径
musicPath
,并通过setDirectory
方法将其设置为对话框的默认打开目录,方便用户快速定位到音乐文件。
- 获取当前工作目录,然后使用
- 获取用户选择的文件并处理:
- 调用
exec
方法显示对话框,当用户点击“确定”按钮(即返回值为QFileDialog::Accepted
)时,获取用户选中文件的URL列表。 - 将URL列表传递给
MusicList
的addMusicByUrl
方法进行进一步处理。 - 最后,使用
ui->stackedWidget->setCurrentIndex(4)
将界面切换到本地音乐页面,让用户可以查看刚添加的音乐。
- 调用
2. MusicList类:音乐集合管理
// MusicList.h
#include <QVector>
#include <QUrl>
#include "music.h"class MusicList {
public:void addMusicByUrl(const QList<QUrl>& urls); // 从URL列表添加音乐iterator begin(); // 迭代器开始iterator end(); // 迭代器结束iterator findMusicById(const QString& musicId); // 通过UUID查找音乐
private:QVector<Music> musicList; // 存储Music对象QSet<QString> musicPaths; // 存储已加载的文件路径,避免重复加载
};// MusicList.cpp
void MusicList::addMusicByUrl(const QList<QUrl>& urls) {for (auto e : urls) {QString musicPath = e.toLocalFile();if (musicPaths.contains(musicPath)) continue; // 跳过已加载的文件musicPaths.insert(musicPath); // 记录路径防止重复QMimeDatabase mimeDB;QMimeType mimeType = mimeDB.mimeTypeForFile(musicPath);// 筛选支持的音频格式(mp3、flac等)if (mimeType.name() != "audio/mpeg" && mimeType.name() != "audio/flac") continue;Music music(e); // 创建Music对象,自动解析元数据musicList.push_back(music); // 添加到列表}
}
- 类的成员和功能概述:
MusicList
类用于管理音乐集合,包含一个QVector<Music>
类型的musicList
成员,用于存储Music
对象,以及一个QSet<QString>
类型的musicPaths
成员,用于记录已经加载过的音乐文件路径,避免重复加载。- 提供了
addMusicByUrl
方法用于从URL列表添加音乐,还提供了迭代器相关方法begin
、end
以及通过UUID查找音乐的findMusicById
方法。
- 添加音乐的具体实现:
- 在
addMusicByUrl
方法中,遍历传入的URL列表。 - 将每个URL转换为本地文件路径
musicPath
,并检查musicPaths
集合中是否已经包含该路径,如果包含则跳过该文件,避免重复添加。 - 若路径未被记录,则将其插入到
musicPaths
集合中。 - 使用
QMimeDatabase
和QMimeType
来获取文件的MIME类型,筛选出支持的音频格式(如mp3、flac),若文件类型不符合要求则跳过。 - 对于符合要求的文件,创建一个
Music
对象,该对象会自动解析音乐的元数据。 - 最后将创建好的
Music
对象添加到musicList
向量中。
- 在
3. Music类:单个音乐元数据封装
// Music.h
class Music {
public:Music(const QUrl& url); // 构造函数,自动解析元数据void parseMediaMetaData(); // 解析音乐元数据QString getLrcFilePath() const; // 获取对应的LRC歌词路径
private:QString musicId; // UUID,唯一标识音乐QString musicName, singerName, albumName; // 名称、歌手、专辑qint64 duration; // 时长(毫秒)QUrl musicUrl; // 文件路径bool isLike, isHistory; // 收藏状态、历史播放状态
};// Music.cpp
Music::Music(const QUrl& url) : musicUrl(url) {musicId = QUuid::createUuid().toString(); // 生成UUIDparseMediaMetaData(); // 解析元数据
}void Music::parseMediaMetaData() {QMediaPlayer player;player.setMedia(musicUrl);// 等待元数据加载完成while (!player.isMetaDataAvailable()) {QCoreApplication::processEvents(); // 处理事件循环,避免界面卡顿}// 提取元数据,默认值处理空数据musicName = player.metaData("Title").toString().trimmed() ?: "歌曲未知";singerName = player.metaData("Author").toStringList().join(",").trimmed() ?: "歌手未知";albumName = player.metaData("AlbumTitle").toString().trimmed() ?: "专辑名未知";duration = player.duration(); // 获取时长
}QString Music::getLrcFilePath() const {// 将音频文件后缀替换为.lrc,假设歌词文件同名QString path = musicUrl.toLocalFile();path.replace(".mp3", ".lrc").replace(".flac", ".lrc");return path;
}
- 类的成员和功能概述:
Music
类用于封装单个音乐的元数据,包含音乐的唯一标识musicId
、名称musicName
、歌手singerName
、专辑albumName
、时长duration
、文件路径musicUrl
以及收藏状态isLike
和历史播放状态isHistory
。- 提供了构造函数
Music(const QUrl& url)
用于创建对象并自动解析元数据,parseMediaMetaData
方法用于解析音乐元数据,getLrcFilePath
方法用于获取对应的LRC歌词文件路径。
- 构造函数的实现:
- 在构造函数中,将传入的URL赋值给
musicUrl
成员。 - 使用
QUuid::createUuid().toString()
生成一个唯一的UUID作为musicId
,确保每个音乐对象都有唯一标识。 - 调用
parseMediaMetaData
方法解析音乐的元数据。
- 在构造函数中,将传入的URL赋值给
- 元数据解析方法:
- 创建一个
QMediaPlayer
对象player
,并将musicUrl
设置为其播放媒体。 - 使用
while
循环等待元数据加载完成,在等待过程中调用QCoreApplication::processEvents()
处理事件循环,避免界面卡顿。 - 从
player
中提取音乐的标题、作者、专辑名等元数据,使用trimmed
方法去除字符串前后的空格,并使用空值合并运算符?:
为可能为空的元数据设置默认值(如“歌曲未知”“歌手未知”“专辑名未知”)。 - 通过
player.duration()
获取音乐的时长。
- 创建一个
- 获取歌词文件路径方法:
- 将
musicUrl
转换为本地文件路径path
。 - 使用
replace
方法将文件后缀.mp3
和.flac
替换为.lrc
,假设歌词文件与音频文件同名,返回替换后的路径。
- 将
二、音乐分类与界面更新
1. CommonPage:通用页面模板
// CommonPage.h
enum PageType { LIKE_PAGE, LOCAL_PAGE, HISTORY_PAGE }; // 页面类型枚举class CommonPage : public QWidget {
public:void setMusicListType(PageType type); // 设置页面类型void reFresh(const MusicList& musicList); // 刷新页面显示
private:PageType pageType; // 当前页面类型QVector<QString> musicListOfPage; // 本页面的音乐ID列表QListWidget* pageMusicList; // 音乐列表控件
};// CommonPage.cpp
void CommonPage::setMusicListType(PageType type) {pageType = type; // 记录页面类型// 根据类型设置页面标题和背景switch (type) {case LIKE_PAGE: setCommonPageUI("我喜欢", ":/images/ilikebg.png"); break;case LOCAL_PAGE: setCommonPageUI("本地音乐", ":/images/localbg.png"); break;case HISTORY_PAGE: setCommonPageUI("最近播放", ":/images/recentbg.png"); break;}
}void CommonPage::reFresh(const MusicList& musicList) {pageMusicList->clear(); // 清空旧数据musicListOfPage.clear(); // 清空旧ID列表// 根据页面类型筛选音乐for (auto& music : musicList) {switch (pageType) {case LOCAL_PAGE: musicListOfPage.push_back(music.getMusicId()); // 本地页面直接添加所有break;case LIKE_PAGE: if (music.getIsLike()) musicListOfPage.push_back(music.getMusicId()); // 只添加收藏的break;case HISTORY_PAGE: if (music.getIsHistory()) musicListOfPage.push_back(music.getMusicId()); // 只添加历史播放的break;}}// 添加自定义控件ListItemBox到QListWidgetfor (auto musicId : musicListOfPage) {auto it = musicList.findMusicById(musicId);ListItemBox* item = new ListItemBox();item->setMusicName(it->getMusicName());item->setSinger(it->getSingerName());item->setLikeIcon(it->getIsLike()); // 设置收藏图标QListWidgetItem* listItem = new QListWidgetItem();listItem->setSizeHint(item->size());pageMusicList->setItemWidget(listItem, item);}
}
- 类的成员和功能概述:
CommonPage
类是一个通用的页面模板,继承自QWidget
。它使用枚举PageType
来区分不同的页面类型(“我喜欢”“本地音乐”“最近播放”)。- 包含
pageType
成员记录当前页面类型,musicListOfPage
存储本页面要显示的音乐ID列表,pageMusicList
是用于显示音乐列表的控件。 - 提供了
setMusicListType
方法用于设置页面类型并根据类型设置页面标题和背景,reFresh
方法用于刷新页面显示。
- 设置页面类型的实现:
- 在
setMusicListType
方法中,将传入的PageType
类型参数赋值给pageType
成员,记录当前页面类型。 - 使用
switch
语句根据不同的页面类型调用setCommonPageUI
方法设置页面的标题和背景图片。
- 在
- 刷新页面显示的实现:
- 首先,清空
pageMusicList
控件中的旧数据和musicListOfPage
中的旧ID列表。 - 遍历
MusicList
中的所有音乐对象,根据pageType
的值进行筛选:- 对于“本地音乐”页面,将所有音乐的ID添加到
musicListOfPage
中。 - 对于“我喜欢”页面,只将收藏状态为
true
的音乐ID添加到musicListOfPage
中。 - 对于“最近播放”页面,只将历史播放状态为
true
的音乐ID添加到musicListOfPage
中。
- 对于“本地音乐”页面,将所有音乐的ID添加到
- 遍历
musicListOfPage
中的音乐ID,通过findMusicById
方法找到对应的音乐对象。 - 创建一个
ListItemBox
自定义控件,设置其显示的音乐名称、歌手和收藏图标。 - 创建一个
QListWidgetItem
,设置其大小提示为ListItemBox
的大小。 - 将
ListItemBox
设置为QListWidgetItem
的自定义控件,并添加到pageMusicList
中。
- 首先,清空
2. ListItemBox:音乐列表项控件
// ListItemBox.h
class ListItemBox : public QWidget {Q_OBJECT
public:void setMusicName(const QString& name); // 设置歌曲名称void setSinger(const QString& singer); // 设置歌手void setLikeIcon(bool isLike); // 设置收藏图标状态
signals:void setIsLike(bool isLike, QString musicId); // 收藏状态改变信号
private:bool isLike; // 当前收藏状态QPushButton* likeBtn; // 收藏按钮QString musicId; // 关联的音乐ID
};// ListItemBox.cpp
void ListItemBox::setLikeIcon(bool isLike) {this->isLike = isLike;likeBtn->setIcon(isLike ? QIcon(":/images/like_2.png") : QIcon(":/images/like_3.png")); // 切换图标
}// 点击收藏按钮时触发
void ListItemBox::on_likeBtn_clicked() {isLike = !isLike; // 取反状态emit setIsLike(isLike, musicId); // 发射信号,携带新状态和音乐ID
}
- 类的成员和功能概述:
ListItemBox
类继承自QWidget
,用于显示单个音乐列表项的信息。- 包含
isLike
成员记录当前音乐的收藏状态,likeBtn
是收藏按钮,musicId
是关联的音乐ID。 - 提供了
setMusicName
、setSinger
和setLikeIcon
方法用于设置显示的音乐名称、歌手和收藏图标状态,还定义了一个信号setIsLike
用于在收藏状态改变时发出通知。
- 设置收藏图标状态的实现:
- 在
setLikeIcon
方法中,将传入的isLike
值赋值给isLike
成员。 - 根据
isLike
的值,为likeBtn
设置不同的图标(like_2.png
表示已收藏,like_3.png
表示未收藏)。
- 在
- 处理收藏按钮点击事件的实现:
- 在
on_likeBtn_clicked
方法中,当用户点击收藏按钮时,将isLike
状态取反。 - 调用
emit
关键字发射setIsLike
信号,携带新的收藏状态和关联的音乐ID,通知其他部分更新数据。
- 在
三、音乐收藏与持久化
1. 收藏逻辑:界面与数据同步
// QQMusic.cpp
void QQMusic::onUpdateLikeMusic(bool isLike, QString musicId) {auto it = musicList.findMusicById(musicId); // 查找对应的Music对象if (it != musicList.end()) {it->setIsLike(isLike); // 更新本地状态}// 刷新所有相关页面(我喜欢、本地、历史播放)ui->likePage->reFresh(musicList);ui->localPage->reFresh(musicList);ui->recentPage->reFresh(musicList);
}// CommonPage.cpp(信号连接)
connect(ui->pageMusicList, &QListWidget::itemClicked, [=](QListWidgetItem* item) {ListItemBox* listItem = static_cast<ListItemBox*>(pageMusicList->itemWidget(item));connect(listItem, &ListItemBox::setIsLike, this, [=](bool isLike, QString musicId) {emit updateLikeMusic(isLike, musicId); // 传递给QQMusic处理});
});
- 收藏状态更新的实现:
- 在
QQMusic
类的onUpdateLikeMusic
方法中,接收收藏状态isLike
和音乐IDmusicId
作为参数。 - 使用
findMusicById
方法在musicList
中查找对应的Music
对象。 - 如果找到该对象,则调用其
setIsLike
方法更新本地的收藏状态。 - 调用所有相关页面(“我喜欢”“本地音乐”“最近播放”)的
reFresh
方法,刷新页面显示,确保界面上的收藏状态与数据一致。
- 在
- 信号传递的实现:
- 在
CommonPage
类中,通过connect
函数连接pageMusicList
的itemClicked
信号。 - 当用户点击列表项时,将列表项对应的
ListItemBox
控件转换为正确的类型。 - 再通过
connect
函数连接ListItemBox
的setIsLike
信号,当收藏状态改变时,将新的状态和音乐ID通过updateLikeMusic
信号传递给QQMusic
类进行处理。
- 在
2. 数据库持久化:SQLite存储音乐信息
// 建表语句(MusicInfo表)
CREATE TABLE IF NOT EXISTS musicInfo (id INTEGER PRIMARY KEY AUTOINCREMENT,musicId VARCHAR(200) UNIQUE, // UUID,唯一标识musicName VARCHAR(50),musicSinger VARCHAR(50),albumName VARCHAR(50),duration BIGINT, // 时长(毫秒)musicUrl VARCHAR(256), // 文件路径isLike INTEGER, // 0未收藏,1已收藏isHistory INTEGER // 0未播放,1已播放
);// Music.cpp 插入/更新数据库
void Music::insertMusicToDB() {QSqlQuery query;// 检测是否已存在query.prepare("SELECT EXISTS (SELECT 1 FROM musicInfo WHERE musicId = ?)");query.addBindValue(musicId);query.exec();query.next();bool isExists = query.value(0).toBool();if (isExists) {// 更新收藏/历史状态query.prepare("UPDATE musicInfo SET isLike = ?, isHistory = ? WHERE musicId = ?");query.addBindValue(isLike ? 1 : 0);query.addBindValue(isHistory ? 1 : 0);query.addBindValue(musicId);} else {// 插入新记录query.prepare("INSERT INTO musicInfo (musicId, musicName, musicSinger, albumName, musicUrl, duration, isLike, isHistory) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");query.addBindValue(musicId);query.addBindValue(musicName);query.addBindValue(musicSinger);// 其他字段依次绑定...}query.exec();
}
- 数据库表结构设计:
- 创建一个名为
musicInfo
的表,使用id
作为自增的主键,确保每条记录有唯一标识。 - 使用
musicId
作为UUID,唯一标识每首音乐,防止重复记录。 - 存储音乐的名称、歌手、专辑名、时长、文件路径、收藏状态和历史播放状态。
- 创建一个名为
- 插入/更新数据库的实现:
- 在
Music
类的insertMusicToDB
方法中,创建一个QSqlQuery
对象用于执行SQL语句。 - 首先执行一个查询语句,检测当前音乐记录是否已经存在于数据库中。
- 如果记录已经存在,则执行
UPDATE
语句更新收藏和历史播放状态。 - 如果记录不存在,则执行
INSERT
语句插入一条新的记录,将音乐的各项信息绑定到相应的参数位置。 - 最后执行SQL语句完成插入或更新操作。
- 在
四、CommonPage显示不足处理
功能概述
这部分主要是针对CommonPage
页面在显示音乐列表时可能会出现的各种显示问题进行优化。在显示音乐信息时,可能会存在文本对齐不整齐、显示有延迟、滚动条样式不美观等情况。通过对这些问题的处理,可以确保界面显示的一致性和美观性,提升用户的视觉体验。
核心处理内容及实现方法
a. 歌曲作者对齐处理
// 在Music类解析元数据时处理空值和空格
if (musicName.isEmpty()) {// 从文件名解析并去除空格musicName = fileName.mid(0, index).trimmed();
}
if (singerName.isEmpty()) {singerName = fileName.mid(index+1, fileName.indexOf('.')-index-1).trimmed();
}
- 问题分析:在解析音乐元数据时,如果歌曲名称、歌手或者专辑信息缺失,直接显示这些信息会导致界面上的文本出现错位,比如前后有多余的空格。
- 解决步骤:
- 检查
musicName
是否为空,如果为空,则从文件名中提取歌曲名称部分,并使用trimmed
方法去除前后的空格。 - 检查
singerName
是否为空,如果为空,则从文件名中提取歌手名称部分,并使用trimmed
方法去除前后的空格。
- 检查
b. 显示延迟问题
void CommonPage::reFresh(MusicList &musicList) {// ... 其他逻辑 ...repaint(); // 强制界面重绘,确保更新显示
}
- 问题分析:当更新音乐列表之后,界面可能不会马上刷新,需要手动触发重绘才能看到更新后的内容。
- 解决步骤:在
CommonPage
类的reFresh
方法中,在完成其他更新逻辑后,调用repaint
方法强制界面进行重绘,确保更新后的内容能够实时显示。
c. 移除QListWidget水平滚动条
CommonPage::CommonPage(QWidget *parent) : QWidget(parent), ui(new Ui::CommonPage) {ui->setupUi(this);ui->pageMusicList->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // 禁用水平滚动条
}
- 目的分析:一般情况下,歌曲信息不会超出列表的宽度,显示水平滚动条会影响界面的简洁性,因此需要将其隐藏。
- 解决步骤:在
CommonPage
类的构造函数中,调用setHorizontalScrollBarPolicy
方法,将水平滚动条的策略设置为Qt::ScrollBarAlwaysOff
,即始终关闭水平滚动条。
d. QListWidget选中项背景色设置
#pageMusicList::item:selected {background-color: #EFEFEF; // 浅灰色背景
}
- 目的分析:为了优化选中项的视觉反馈,让用户更容易识别当前选中的是哪一项,需要为选中项设置特定的背景色。
- 解决步骤:使用QSS(Qt Style Sheets)样式表,通过选择器
#pageMusicList::item:selected
选中pageMusicList
中被选中的列表项,将其背景色设置为浅灰色(#EFEFEF
)。
e. 垂直滚动条美化
QScrollBar:vertical {border: none; // 无边框width: 10px; // 宽度background-color: #FFFFFF; // 背景/* 可继续添加更多样式设置,如句柄样式等 */
}
- 目的分析:默认的垂直滚动条样式可能与整个界面的风格不一致,为了使滚动条与界面风格相匹配,需要对其进行美化。
- 解决步骤:使用QSS样式表,通过选择器
QScrollBar:vertical
选中垂直滚动条,设置其无边框、宽度为10px、背景色为白色。还可以根据需要继续添加更多的样式设置,如句柄的样式等。
五、总结与扩展
1. 模块价值
- 解耦设计:
MusicList
和Music
类将数据管理和界面显示进行了分离,就像搭积木一样,不同的功能模块相互独立。- 当需要修改数据管理部分时,不会对界面显示产生影响;反之,修改界面显示也不会影响数据管理。这使得代码的维护和扩展变得更加容易。
- 类型安全:
- 枚举
PageType
明确地定义了不同的页面类型,就像给每个页面贴上了清晰的标签。 - 避免了使用一些含义不明确的数字或字符串来表示页面类型,提高了代码的可读性和可维护性。
- 枚举
- 用户体验:
- 自定义控件
ListItemBox
为用户提供了直观的交互反馈,当点击收藏按钮时,图标会立即切换。 - 结合信号槽机制,能够实时更新界面,让用户在操作过程中感受到流畅的体验。
- 自定义控件
2. 常见问题与解决方案
问题 | 原因 | 解决方案 |
---|---|---|
重复加载同一歌曲 | 没有记录已经加载过的文件路径,导致每次都可能重复添加同一首歌 | 使用QSet 来记录已经加载过的文件路径,在添加新歌曲时先检查一下,如果已经存在就跳过 |
元数据解析失败 | 有些音乐文件可能没有元数据,或者文件格式不支持,导致无法读取歌曲名、歌手等信息 | 给这些情况设置默认值,比如“歌曲未知”“歌手未知”,同时筛选出支持的音频格式,只处理这些格式的文件 |
界面更新不同步 | 当音乐的收藏状态或者播放状态发生变化时,没有及时刷新相关的页面,导致显示的信息和实际情况不一致 | 当收藏或者播放状态变化时,强制刷新所有相关的页面,让界面显示最新的信息 |
3. 扩展方向
- 网络模块:目前的功能只能加载本地的音乐,后续可以添加网络模块,让软件能够从服务器上下载在线音乐,并且获取这些音乐的元数据和歌词,丰富音乐资源。
- 搜索功能:当前软件没有搜索功能,用户如果想听某首特定的歌曲,需要在大量的音乐列表中手动查找,非常不方便。可以添加一个搜索框,用户可以根据歌曲名或者歌手名快速定位到想听的音乐。
- 批量操作:现在只能一首一首地对音乐进行收藏、删除等操作,效率较低。可以支持多选功能,让用户能够一次选中多首音乐,然后一起进行收藏、删除等操作,提高操作效率。
📣 结语
音乐管理模块是QQMusic项目的核心基础,通过合理的类设计和信号槽机制,实现了从文件加载到界面显示的完整流程。理解这部分内容,不仅能掌握Qt文件操作、自定义控件和数据库应用,还能体会分层架构在实际项目中的应用。后续可结合播放控制模块,实现更丰富的音乐播放功能。
技术的魅力在于将复杂需求拆解为可实现的模块,每个细节的打磨都是进步的阶梯。坚持理解每一行代码的作用,终将积累出解决复杂问题的能力。
相关文章:
【QQMusic项目复习笔记——音乐管理模块详解】第四章
🌹 作者: 云小逸 🤟 个人主页: 云小逸的主页 🤟 motto: 要敢于一个人默默的面对自己,强大自己才是核心。不要等到什么都没有了,才下定决心去做。种一颗树,最好的时间是十年前,其次就是现在&…...
IPv6 技术细节 | 源 IP 地址选择 / Anycast / 地址自动配置 / 地址聚类分配
注:本文为 “IPv6 技术细节” 相关文章合集。 部分文章中提到的其他文章,一并引入。 略作重排,未整理去重。 如有内容异常,请看原文。 闲谈 IPv6 - 典型特征的一些技术细节 iteye_21199 于 2012-11-10 20:54:00 发布 0. 巨大的…...
代码随想录算法训练营day11(二叉树)
华子目录 翻转二叉树思路 对称二叉树思路 二叉树的最大深度思路 翻转二叉树 https://leetcode.cn/problems/invert-binary-tree/description/ 思路 采用递归的思路可以前序遍历和后序遍历,不能使用中序遍历 # Definition for a binary tree node. # class TreeNo…...
A Comprehensive Survey of Spoken Language Models
语音大语言模型(Spoken Language Model, SLM)正在引领人工智能领域的新一轮革新浪潮。正如文本自然语言处理从任务特定模型迈向通用大语言模型的演进,语音领域也正在经历类似转型。 为填补该领域系统性综述的空白,芝加哥大学、卡…...
深入解析 SMB 相关命令:smbmap、smbclient、netexec 等工具的使用指南
Server Message Block(SMB)协议是广泛应用于文件共享、打印机共享和进程间通信的网络协议,尤其在 Windows 环境中常见。渗透测试和网络安全审计中,SMB 是一个重要的攻击面,相关工具如 smbmap、smbclient 和 netexec 提…...
伊克罗德信息亮相亚马逊云科技合作伙伴峰会,以ECRobot 智能云迁移助手在GenAI Tech Game比赛勇夺金牌!
十年同行,共赴盛会:伊克罗德信息亮相2025亚马逊云科技Partner Summit 2025亚马逊云科技合作伙伴峰会(AWS Partner Summit)于乌镇盛大启幕,这场全球云计算领域的顶级盛会汇聚了亚马逊云科技全球核心合作伙伴、行业领袖与…...
【蓝桥杯】P12165 [蓝桥杯 2025 省 C/Java A] 最短距离
最短距离 题目描述 在一条一维的直线上,存在着 n n n 台显示器和 n n n 个电源插座。老师给小蓝布置了个任务:负责将每台显示器通过电源线与一个插座相连接(每个插座最多只能给一台显示器供电);同时,老…...
深入浅出Sentinel:分布式系统的流量防卫兵
引言 在当今的微服务架构和分布式系统中,服务间的依赖关系错综复杂,一个服务的故障可能会像多米诺骨牌一样引发整个系统的崩溃。如何有效地保护系统免受突发流量、不稳定依赖服务的影响,成为每个架构师和开发者必须面对的挑战。今天…...
vite+vue2+elementui构建之 vite.config.js
webpack版本太低,构建依赖太多,头大。 各种查阅资料,弄了一份直通构建vite构建elementUi核心文件, 构建基于开源若依vue2vue3版本改造,感谢开源,感谢若依。 package.json 地址 vitevue2elementui构建之…...
【Pandas】pandas DataFrame radd
Pandas2.2 DataFrame Binary operator functions 方法描述DataFrame.add(other)用于执行 DataFrame 与另一个对象(如 DataFrame、Series 或标量)的逐元素加法操作DataFrame.add(other[, axis, level, fill_value])用于执行 DataFrame 与另一个对象&…...
Java 后端开发环境安装
Java环境安装 1. 安装程序 Java1.8下载,由于官网下载需要登录,比较麻烦,所以我将安装文件放到了我的资源中,大家通过资源直接下载即可 jdk-8u351-windows-x64.exe 大家根据自己的电脑的配置选择适当的版本 然后一路下一步 这里…...
Azure Data Factory ETL设计与调度最佳实践
一、引言 在Azure Data Factory (ADF) 中,调度和设计ETL(抽取、转换、加载)过程需要综合考量多方面因素,以确保数据处理高效、可扩展、可靠且易于维护。以下将详细介绍相关关键考虑因素、最佳实践,并辅以具体示例说明…...
【Mybatis】MyBatisPlus的saveBatch真的是批量插入吗?深度解析与性能优化
前言 在使用MyBatis-Plus进行批量数据插入时,许多开发者会发现:即使调用saveBatch方法,数据库仍会产生大量INSERT语句。本文将深入源码揭示背后的真相,并提供3种性能优化方案,让你的批量插入速度提升10倍!…...
图像预处理-图像亮度变换
一.亮度变换 首先有两个关联的说法: 亮度调整:像素强度整体变高或者变低。 对比度调整:暗处像素强度变低,亮处像素强度变高,从而拉大中间某个区域范围的显示精度。 opencv中操作这两种变换的公式为: 对比…...
基于AI应用创业IDEA:使用百度搜索开放平台的MCP广场智能推荐MCPServices服务
基于AI应用创业IDEA:使用百度搜索开放平台的MCP广场智能推荐MCPServices服务 在当今快速发展的技术时代,人工智能(AI)已经成为推动各行各业创新的关键力量。特别是在创业领域,AI技术不仅能够帮助提升产品性能…...
URP-利用矩阵在Shader中实现物体的平移和缩放
一、平移 方法一: v.positionOS.xyz _Translate.xyz; 方法二: 利用矩阵实现平移: 二、缩放 方法一: v.positionOS.xyz * _Scale.xyz*_Scale.w; _Scale.w实现全局缩放 方法二: Shader"unity/Translation"…...
跟着尚硅谷学vue-day5
计算属性和watch监视 一.姓名案例 1.姓名案例-插值语法 <div id"root">姓:<input type"text" value"张" v-model"firstname"><br/><br/>名:<input type"text" value&q…...
猫咪如厕检测与分类识别系统系列【十三】猫咪进出事件逻辑及日志优化【下】
前情提要 家里养了三只猫咪,其中一只布偶猫经常出入厕所。但因为平时忙于学业,没法时刻关注牠的行为。我知道猫咪的如厕频率和时长与健康状况密切相关,频繁如厕可能是泌尿问题,停留过久也可能是便秘或不适。为了更科学地了解牠的如…...
【Java学习笔记】冒泡排序
冒泡排序 思想:经过一轮遍历比较,把最大的放在数组的末尾 int[] a {3, 2, 1}; for( int i 0; i < a.length-1; i){for( int j 0; j < a.length-1-i; j){if(a[j] > a[j1]){int temp a[j];a[j] a[j1];a[j1] temp;}} } for( int i 0; i &…...
【含文档+PPT+源码】基于微信小程序的校园快递平台
项目介绍 本课程演示的是一款基于微信小程序的校园快递平台,主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含:项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本套系统 3.该项目附带…...
Vue3 自定义指令完全指南
Vue3 自定义指令完全指南 目录 基本概念指令注册方式常用应用场景注意事项 基本概念 在Vue3中,自定义指令是用于直接操作DOM的重要工具。相比Vue2,Vue3的指令系统进行了优化和简化。 生命周期钩子 钩子名称对应Vue2名称触发时机createdbind元素属性…...
神经辐射场(NeRF)技术解析:3D重建与虚拟世界的未来
神经辐射场(NeRF)技术解析:3D重建与虚拟世界的未来 ——从算法突破到元宇宙基础设施的演进之路 摘要 本文通过算法演进图谱、训练流程解析、PyTorch代码实战及产业应用洞察,构建从学术创新到工程落地的完整技术框架。实验数据显…...
050_基于springboot的音乐网站
一、系统架构 前端:vue | element-ui | html | jquery | css | ajax 后端:springboot | mybatis 环境:jdk1.8 | mysql | maven | nodejs | idea 二、代码及数据 三、功能介绍 01. web端-注册 02. web端-登录 03. web…...
Django之旅:第七节--模版继承
定义母版—new.html <!DOCTYPE html> <html lang"en"><head></head><body><div>{% block contents %}{% endblock %}</div></body> </html> 继承母模板 {% extends new.html %} {% block contents %}&…...
Windows 10 上运行 Ollama 时遇到 llama runner process has terminated: exit status 2
在 Windows 10 上运行 Ollama 时遇到 llama runner process has terminated: exit status 2 错误,可能是由多种原因引起的。以下是逐步解决方案: 1. 检查 Ollama 服务状态 按 Win R 输入 services.msc,找到 Ollama 服务,确保其状…...
基于 Python(selenium) 的百度新闻定向爬虫:根据输入的关键词在百度新闻上进行搜索,并爬取新闻详情页的内容
该项目能够根据输入的关键词在百度新闻上进行搜索,并爬取新闻详情页的内容。 一、项目准备 1. 开发环境配置 操作系统:支持 Windows、macOS、Linux 等主流操作系统,本文以 Windows 为例进行说明。Python 版本:建议使用 Python 3.8 及以上版本,以确保代码的兼容性和性能。…...
YOLOX-PAI手部检测模型
YOLOX-PAI手部检测模型 # numpy > 1.20 from modelscope.pipelines import pipeline from modelscope.utils.constant import Tasksmodel_id iic/cv_yolox-pai_hand-detection hand_detection pipeline(Tasks.domain_specific_object_detection, modelmodel_id) output …...
速成GO访问sql,个人笔记
更多个人笔记:(仅供参考,非盈利) gitee: https://gitee.com/harryhack/it_note github: https://github.com/ZHLOVEYY/IT_note 本文是基于原生的库 database/sql进行初步学习 基于ORM等更多操作可以关注我…...
SkyWalking 安装与使用详细总结
SkyWalking 是一款开源的分布式应用性能监控(APM)系统,用于监控微服务、云原生应用、容器等。它支持服务跟踪、性能监控、日志分析和数据可视化。以下是 SkyWalking 的安装与使用详细步骤。 一、SkyWalking 安装 1. 下载 SkyWalking 访问 …...
产业观察:中国商飞2025.4.25
一.中国商飞简介 1.1 公司背景 中国商飞于 2008年5月11日 在上海成立,是实施国家大型飞机重大专项中大型客机项目的主体。公司由多家国有大型企业共同出资组建,包括国务院国资委、上海国盛集团、中航工业集团等。总部设在上海,拥有多个研发…...
【爬虫】DrissionPage-获取douyim用户下的视频
之前看过DrissionPage,觉得很厉害,比selenium简单,适合新手。因为盲目跟风逆向,今天看了一个DrissionPage案例直播,学习一下,真香哈。 DrissionPage官网:🛰️ 概述 | DrissionPage官…...
Kubernetes 节点 Not Ready 时 Pod 驱逐机制深度解析(下)
#作者:邓伟 文章目录 三、深度解析:源码逻辑与调优策略四、常见问题与排查五、最新动态与技术演进总结 三、深度解析:源码逻辑与调优策略 TaintManager 核心源码逻辑 (1)参数定义(kube-controller-manage…...
markdown自动标题序号,标题序号,目录处理
在vscode下有插件 markdown aheads markdown标题与目录处理插件。 使用方法:在vscode中 ctrlshiftp 输入 AHeads 选择功能。 包含以下功能: "AHeads:AddIndex": "添加标题序号" "AHeads:RemoveIndex": "移除标题…...
模式设计简介
设计模式简介 设计模式是软件开发中经过验证的最佳实践解决方案,它是针对特定问题的通用解决方案,能够帮助开发者提升代码的可维护性、可扩展性和复用性。设计模式并非具体的代码实现,而是一种解决问题的思路和方法论,它源于大量的实践经验总结,旨在解决软件开发过程中反…...
Linux字符设备驱动开发的详细步骤
1. 确定主设备号 手动指定:明确设备号时,使用register_chrdev_region()静态申请(需确保未被占用)。动态分配:通过alloc_chrdev_region()由内核自动分配主设备号(更灵活,推…...
融合注意力机制和BiGRU的电力领域发电量预测项目研究,并给出相关代码
大家好,我是微学AI,今天给大家介绍一下融合注意力机制和BiGRU的电力领域发电量预测项目研究,并给出相关代码。 文章目录 一、项目背景二、融合注意力机制和BiGRU的技术原理(一)双向门控循环单元(BiGRU&…...
基于 Netmiko 的网络设备自动化操作
学习目标 掌握 Netmiko 库的核心功能与使用场景。能够通过 Netmiko 连接多厂商设备并执行命令和配置。实现批量设备管理、配置备份与自动化巡检。掌握异常处理、日志记录与性能优化技巧。理解 Netmiko 在自动化运维体系中的角色。 1. Netmiko 简介 1.1 什么是 Netmiko Netmi…...
eclipse怎么导入junit4
JUnit 4 是 Java 编程语言里常用的单元测试框架,在 Eclipse 中导入 JUnit 4 可按以下步骤操作: 1. 新建或打开 Java 项目 如果你已经有一个 Java 项目,可直接打开它;若没有,就新建一个 Java 项目。操作步骤如下&…...
深入理解C语言函数之模拟实现strcpy()strcat()
文章目录 前言一、strcpy的模拟实现二、strcat的模拟实现总结 前言 前面我们用三种方法模拟实现了一下strlen,所以这篇文章模拟实现以下strcpy()strcat() 一、strcpy的模拟实现 首先我们去官网找到strcpy的用法和原…...
[mysql]窗口函数
目录 窗口函数: 为何要学习窗口函数,与mysql5.7实现语句对比 现在我们介绍一下窗口函数: 函数规则 1序号函数 2分布函数 3前后函数 5其他函数 总结 窗口函数: 首先数据库的迁移是非常慢的,大家学习新特性的时候要考虑自己公司的数据库版本是不是和自己学习的吻合 为何…...
IT人力外包定义-优势-服务流程介绍
IT人力外包:企业灵活用工的“技术人才蓄水池” 在数字化转型的浪潮中,企业对IT人才的需求日益增长,但招聘、培养和管理技术团队的成本和风险也随之攀升。此时,IT人力外包应运而生,成为企业高效获取技术资源的解决方案…...
Cancer Cell|scRNA-seq + scTCR + 空间多组学整合分析,揭示CD8⁺ T细胞在免疫治疗中的“双路径” | 临床问题的组学解答
Cancer Cell|scRNA-seq scTCR 空间多组学整合分析,揭示CD8⁺ T细胞在免疫治疗中的“双路径” 👋 欢迎关注我的生信学习专栏~ 如果觉得文章有帮助,别忘了点赞、关注、评论,一起学习 近日,《Cancer Cell》…...
数字人民币杠杆破局预付乱象 XBIT智能合约筑牢资金安全防线
在加密货币市场与实体经济加速融合的当下,数字人民币试点工作正以创新姿态突破传统金融边界。青岛、广州、厦门等地近期密集落地的数字人民币智能合约应用,为预付式消费领域资金监管难题提供了破局方案,而XBIT去中心化交易所平台凭借其技术优…...
React.memo 和 useMemo
现象 React 中,通常父组件的某个state发生改变,会引起父组件的重新渲染(和其他state的重新计算),从而会导致子组件的重新渲染(和其他非相关属性的重新计算) 问题一:如何避免因为某个…...
命令行指引的尝试
效果 步骤 首先初始化一个空的项目,然后安装一些依赖 npm init -y npm install inquirer execa chalk ora至于这些依赖是干嘛的,如下图所示: 然后再 package.json 中补充一个 bin 然后再根目录下新建一个 index.js , 其中的内容如下 #!/…...
《深入理解计算机系统》阅读笔记之第三章 程序的机器级表示
概述 历史观点 程序编码 数据格式 访问信息 操作数指示符 数据传送指令 压入和弹出栈数据 算术和逻辑操作 小结 本章没有细看,其中主要以C语言中的一些汇编等知识来做介绍。算是一种对C语言的相关底层知识的详细介绍吧。但是从原理上理解我觉得《编码》这本书更好理…...
解析excel中的图片
解析excel中的图片 前言一、pom依赖二、使用步骤1.示例数据2.代码如下(示例): 总结 前言 初始化数据是,需要将excel中的数据解析并插入数据库。 但是某几列存放的是图片,这时候怎么办呢。 主要解决的是:获…...
红队系列-网络安全知识锦囊-CTF(持续更新)
CTF CTF系列-AWD专题篇CTF-比赛培训基础1 CTF 介绍HTTP协议分析进阶001.CTF简介_宽字节注入高级 2018CTF——黑客大赛特训CTF-PWNPWNCTF竞赛中的主要题型之一了解CTF Capture The Flag 夺旗描述:# gets从标准输入设备读字符串函数#下面是对main函数中的汇编代码的解释:modifi…...
强化学习:基础理论与高级DQN算法及策略梯度基础
如果您想学习强化学习,我推荐David Sliver的讲座😊:RL Course by David Silver - Lecture 1: Introduction to Reinforcement Learning - YouTube 基础理论 马尔可夫决策过程(MDP) 1.MDP五元组定义 状态空间…...
如何修复宝可梦时时刻刻冒险无法正常工作
宝可梦的时时刻刻冒险模式是一项强大的功能,即使应用程序关闭,它也能追踪你的步行距离。它的工作原理是将你的步数与 iOS 上的 Apple Health 或 Android 上的 Google Fit 同步。它对于孵化宝可梦蛋和赚取好友糖果至关重要,但一旦它停止工作&a…...