4.1 音乐加载
功能概述
该部分实现了从本地磁盘加载音乐文件到程序中,并在界面上显示的功能。通过QFileDialog
类创建文件选择对话框,用户可选择多个音乐文件,程序筛选出有效音频文件后,交由MusicList
类管理,并更新到本地音乐页面显示。
核心实现步骤
1. 使用QFileDialog创建文件对话框
通过QFileDialog
类实现文件选择功能,设置对话框的标题、打开模式、文件筛选条件等:
QFileDialog fileDialog(this);
fileDialog.setWindowTitle("添加本地音乐"); // 设置对话框标题
fileDialog.setAcceptMode(QFileDialog::AcceptOpen); // 设置为打开文件模式
fileDialog.setFileMode(QFileDialog::ExistingFiles); // 允许选择多个现有文件
2. 筛选音频文件(MIME类型过滤)
使用QMimeDatabase
检测文件类型,仅加载支持的音频格式(如MP3、FLAC):
QStringList mimeList;
mimeList << "audio/mpeg" << "audio/flac"; // 支持的MIME类型
fileDialog.setMimeTypeFilters(mimeList); // 设置文件过滤器
3. 设置默认打开目录
指定对话框默认打开的目录,方便用户快速找到音乐文件:
QDir dir(QDir::currentPath());
dir.cdUp(); // 切换到上一级目录
QString musicPath = dir.path() + "/QQMusic/musics/";
fileDialog.setDirectory(musicPath); // 设置默认目录
4. 处理用户选择的文件
用户点击“打开”后,获取选中的文件路径列表,切换到本地音乐页面,并将文件交由MusicList
类解析和管理:
if (fileDialog.exec() == QFileDialog::Accepted) {
ui->stackedWidget->setCurrentIndex(4); // 切换到本地音乐页面(索引4)
QList<QUrl> urls = fileDialog.selectedUrls(); // 获取选中的文件URL
musicList.addMusicByUrl(urls); // 交由MusicList处理
ui->localPage->reFresh(musicList); // 更新本地音乐列表显示
}
关键类与方法
QFileDialog类
- 作用:创建文件选择对话框,支持用户选择单个或多个文件。
- 核心方法:
setAcceptMode()
:设置对话框模式(打开/保存)。setFileMode()
:设置文件选择模式(单个文件、多个文件、目录等)。setMimeTypeFilters()
:通过MIME类型过滤文件,确保仅显示音频文件。selectedUrls()
:获取用户选中的文件URL列表。
QMimeDatabase类
- 作用:检测文件的MIME类型,验证是否为音频文件。
- 核心方法:
mimeTypeForFile()
:获取文件的MIME类型,用于过滤无效文件(如非音频文件)。
代码逻辑详解
void QQMusic::on_addLocal_clicked() {
QFileDialog fileDialog(this);
fileDialog.setWindowTitle("添加本地音乐");
fileDialog.setAcceptMode(QFileDialog::AcceptOpen);
fileDialog.setFileMode(QFileDialog::ExistingFiles);
// 设置MIME过滤器,仅显示MP3和FLAC文件
QStringList mimeList;
mimeList << "audio/mpeg" << "audio/flac";
fileDialog.setMimeTypeFilters(mimeList);
// 设置默认目录
QDir dir(QDir::currentPath());
dir.cdUp();
QString musicPath = dir.path() + "/QQMusic/musics/";
fileDialog.setDirectory(musicPath);
if (fileDialog.exec() == QFileDialog::Accepted) {
// 切换到本地音乐页面
ui->stackedWidget->setCurrentIndex(4);
// 获取选中的文件URL
QList<QUrl> urls = fileDialog.selectedUrls();
// 将文件添加到MusicList中
musicList.addMusicByUrl(urls);
// 更新本地音乐页面显示
ui->localPage->reFresh(musicList);
}
}
功能扩展点
- 支持更多音频格式:在MIME过滤器中添加更多支持的类型(如
audio/wav
)。 - 进度提示:在加载大量文件时显示进度条,提升用户体验。
- 重复文件检测:通过文件路径或MD5校验避免重复加载同一文件。
通过以上步骤,实现了本地音乐文件的加载、筛选和界面显示,为后续的播放、收藏、历史记录等功能奠定了数据基础。
4.2 MusicList类详解
一、类的定义与核心作用
MusicList类
是项目中管理音乐列表的核心数据结构,主要负责以下功能:
- 统一管理音乐对象:存储所有加载的音乐文件(
Music
实例),支持添加、查找、筛选等操作。 - 格式筛选与唯一性保证:过滤无效文件格式,避免重复加载相同音乐。
- 与数据库交互:实现音乐信息的持久化存储与读取(后续结合数据库模块)。
二、核心成员变量
成员变量 | 类型 | 说明 |
---|---|---|
musicList | QVector<Music> | 存储所有音乐对象,利用QVector 的顺序存储和快速访问特性。 |
musicPaths | QSet<QString> | 记录已加载音乐的文件路径,避免重复添加同一文件(基于路径唯一性)。 |
三、核心功能实现
1. 音乐文件加载与格式筛选
- 功能:从文件路径列表中筛选有效音频文件(MP3/FLAC),创建
Music
对象并添加到列表。 - 实现步骤:
- 遍历文件路径:通过
addMusicByUrl
方法接收QList<QUrl>
类型的文件路径。 - MIME类型检测:使用
QMimeDatabase
检测文件类型,仅保留audio/mpeg
(MP3)和audio/flac
(FLAC)格式。QMimeDatabase db; QMimeType mime = db.mimeTypeForFile(musicUrl.toLocalFile()); if (mime.name() != "audio/mpeg" && mime.name() != "audio/flac") continue;
- 唯一性检查:通过
musicPaths
集合判断文件是否已加载,避免重复添加。if (musicPaths.contains(musicPath)) continue; musicPaths.insert(musicPath);
- 创建Music对象:为有效文件创建
Music
实例,自动解析元数据(名称、歌手、专辑等)。
- 遍历文件路径:通过
2. 高效查找与遍历支持
- 通过
musicId
查找音乐:未来可添加findMusicById
方法(文档中后续扩展),通过遍历musicList
匹配唯一ID,确保快速定位音乐对象。 - 范围遍历支持:重载
begin()
和end()
方法,支持范围for
循环遍历,方便与界面模块(如CommonPage
)交互。iterator begin() { return musicList.begin(); } iterator end() { return musicList.end(); }
3. 数据持久化准备(后续扩展)
- 数据库表映射:后续可通过
MusicList
将音乐信息写入数据库(如musicInfo
表),包含musicId
、名称、歌手、路径、收藏状态等字段。 - 批量操作:提供
writeToDB()
和readFromDB()
方法,实现程序退出时保存音乐列表、启动时恢复数据(见文档6.3节数据库部分)。
四、关键代码示例
// MusicList.h 核心声明
class MusicList {
public:
void addMusicByUrl(const QList<QUrl>& urls); // 加载音乐文件
iterator begin(); iterator end(); // 支持范围遍历
private:
QVector<Music> musicList; // 存储音乐对象
QSet<QString> musicPaths; // 已加载文件路径集合(避免重复)
};
// MusicList.cpp 添加音乐逻辑
void MusicList::addMusicByUrl(const QList<QUrl>& urls) {
for (const auto& url : urls) {
QString musicPath = url.toLocalFile();
if (musicPaths.contains(musicPath)) continue; // 跳过已加载文件
QMimeDatabase db;
QMimeType mime = db.mimeTypeForFile(musicPath);
if (mime.name() != "audio/mpeg" && mime.name() != "audio/flac") continue; // 筛选有效格式
Music music(url); // 创建Music对象,自动解析元数据
musicList.push_back(music);
musicPaths.insert(musicPath); // 记录路径
}
}
五、与其他模块的协作
- 与
Music类
协作:- 每个音乐文件对应一个
Music
对象,MusicList
存储这些对象,依赖Music
类解析元数据(如parseMediaMetaData
方法)和标记状态(isLike
、isHistory
)。
- 每个音乐文件对应一个
- 与界面交互:
CommonPage
页面(如“本地下载”)通过MusicList
筛选对应条件的歌曲(如未被收藏的本地文件),更新界面显示。
- 与数据库交互:
- 程序启动时通过
readFromDB()
从数据库加载历史数据,退出时通过writeToDB()
保存当前状态,确保数据持久化。
- 程序启动时通过
六、设计优势
- 格式兼容性:通过MIME类型检测,支持多种音频格式扩展(可新增支持
audio/wav
等)。 - 性能优化:使用
QSet
实现O(1)时间复杂度的重复文件检测,避免冗余加载。 - 代码复用:统一管理音乐数据,为“我喜欢”“最近播放”等功能提供底层数据支持,减少重复逻辑。
通过MusicList类
,项目实现了对音乐文件的高效管理、格式筛选和状态维护,为后续播放控制、界面显示和数据持久化奠定了基础。
4.3 Music类解析
一、类的定义与核心作用
Music类
是项目中描述单个音乐文件的核心数据模型,负责封装音乐的元数据(如名称、歌手、专辑等)、状态信息(收藏、历史播放)及文件路径,是音乐管理、播放控制和数据持久化的基础。具体作用包括:
- 数据封装:统一管理音乐文件的基本信息和状态,便于界面显示和逻辑处理。
- 元数据解析:自动提取音乐文件的元数据(标题、作者等),处理缺失信息并设置默认值。
- 状态标记:支持标记音乐是否被收藏(
isLike
)或播放过(isHistory
),为“我喜欢”“最近播放”等功能提供数据支持。 - 唯一性保证:通过UUID生成唯一标识,避免重复添加同一首歌曲。
二、核心成员变量
成员变量 | 类型 | 说明 |
---|---|---|
isLike | bool | 标记音乐是否被收藏(默认false ),界面通过此值显示“小心心”图标。 |
isHistory | bool | 标记音乐是否被播放过(默认false ),用于“最近播放”页面筛选数据。 |
musicName | QString | 歌曲名称,解析自文件元数据或文件名,默认“歌曲未知”。 |
singerName | QString | 歌手名称,支持多歌手用逗号分隔,默认“歌手未知”。 |
albumName | QString | 专辑名称,默认“专辑名未知”。 |
duration | qint64 | 歌曲总时长(毫秒),用于播放进度和时长显示。 |
musicId | QString | 唯一标识(UUID生成),确保同一歌曲多次加载时视为同一对象,避免重复。 |
musicUrl | QUrl | 歌曲文件在磁盘中的路径,用于播放和数据库存储。 |
三、构造函数与初始化
-
默认构造函数
Music::Music() : isLike(false), isHistory(false) {}
- 初始化收藏和历史状态为未标记状态。
-
带路径的构造函数
Music::Music(const QUrl &url) : isLike(false), isHistory(false), musicUrl(url) { musicId = QUuid::createUuid().toString(); // 生成UUID保证唯一性 parseMediaMetaData(); // 解析元数据(标题、歌手、专辑、时长) }
- UUID生成:使用
QUuid::createUuid()
生成唯一ID(如550e8400-e29b-41d4-a716-446655440000
),避免重复添加相同歌曲。 - 元数据解析:调用
parseMediaMetaData()
提取文件元数据,处理缺失信息(如盗版歌曲设置默认值)。
- UUID生成:使用
四、元数据解析(核心功能)
通过QMediaPlayer
解析音乐文件的元数据,处理可能缺失的信息:
void Music::parseMediaMetaData() {
QMediaPlayer player;
player.setMedia(musicUrl);
while (!player.isMetaDataAvailable()) {
QCoreApplication::processEvents(); // 保持界面响应,避免卡死
}
if (player.isMetaDataAvailable()) {
musicName = player.metaData("Title").toString().trimmed();
singerName = player.metaData("Author").toStringList().join(",").trimmed();
albumName = player.metaData("AlbumTitle").toString().trimmed();
duration = player.duration();
}
// 处理空值情况
if (musicName.isEmpty()) musicName = "歌曲未知";
if (singerName.isEmpty()) singerName = "歌手未知";
if (albumName.isEmpty()) albumName = "专辑名未知";
}
- 处理逻辑:若元数据缺失(如无标题),设置默认值(如“歌曲未知”)。
- 线程安全:通过
QCoreApplication::processEvents()
处理事件循环,确保解析时界面可交互。
五、唯一性与状态管理
-
唯一性保证
- 使用UUID作为
musicId
,通过QUuid::createUuid()
生成全局唯一标识,避免同一文件多次加载时创建重复对象。 - 在
MusicList
类中通过musicMap
哈希表(键为musicId
,值为索引)实现O(1)时间复杂度的快速查找。
- 使用UUID作为
-
状态标记
setIsLike(bool)
和setIsHistory(bool)
方法更新收藏和历史状态,界面(如ListItemBox
)通过getIsLike()
和getIsHistory()
获取状态并显示对应图标(如红色小心心表示已收藏)。
六、与其他模块的交互
-
数据库持久化
insertMusicToDB()
:将音乐信息写入数据库(见6.3.2节),包括musicId
、名称、歌手、路径、状态等字段。MusicList
类通过readFromDB()
从数据库加载歌曲时,创建Music
对象并填充数据。
-
界面显示
CommonPage
页面(如“我喜欢”)通过Music
的get
方法获取歌曲名称、歌手、专辑等信息,填充到ListItemBox
中显示。- 收藏功能:点击“小心心”时,调用
setIsLike(true)
标记歌曲,并更新数据库和界面。
-
播放控制
- 提供
musicUrl
给QMediaPlayer
加载音频文件,支持播放、暂停、进度调节等操作。
- 提供
七、关键代码示例
// 生成唯一UUID
musicId = QUuid::createUuid().toString();
// 解析元数据并处理空值
if (player.metaData("Title").toString().isEmpty()) {
musicName = "歌曲未知";
}
// 状态标记
void setIsLike(bool isLike) { this->isLike = isLike; }
bool getIsLike() { return isLike; }
八、总结
Music类
是项目的核心数据载体,实现了以下核心功能:
- 数据封装:统一管理歌曲元数据和状态,确保信息一致性。
- 唯一性:通过UUID避免重复加载,提升数据管理效率。
- 元数据处理:自动解析文件信息,处理异常情况,保证界面正确显示。
- 持久化支持:与数据库交互,实现歌曲信息的保存和恢复。
该类的设计为“我喜欢”“最近播放”等功能提供了底层数据支持,是音乐管理、播放控制和界面显示的基础。
4.4 音乐分类
一、功能概述
QQMusic通过CommonPage
类实现“我喜欢”“本地下载”“最近播放”三个页面的音乐分类显示。核心逻辑是通过枚举类型区分页面类型,结合音乐对象的状态标记(如是否收藏、是否播放过)进行数据过滤,并在界面上动态更新对应列表。
二、页面类型枚举定义
在CommonPage
类中定义枚举PageType
,明确区分三种页面类型,确保每个页面知道自己需要显示的音乐类型:
enum PageType {
LIKE_PAGE, // 我喜欢页面
LOCAL_PAGE, // 本地下载页面
HISTORY_PAGE // 最近播放页面
};
- 作用:通过枚举值(如
LIKE_PAGE
)标记当前页面类型,后续根据类型过滤音乐数据。
三、核心成员变量
成员变量 | 类型 | 说明 |
---|---|---|
pageType | PageType | 标记当前页面属于哪种类型(如LIKE_PAGE ),决定数据过滤逻辑。 |
musicListOfPage | QVector<QString> | 存储当前页面的音乐ID列表(仅保存musicId ,而非完整对象,节省内存)。 |
四、初始化与类型设置
在QQMusic
的initUi()
方法中,为三个页面设置类型和初始UI:
ui->likePage->setMusicListType(PageType::LIKE_PAGE);
ui->likePage->setCommonPageUI("我喜欢", ":/images/ilikebg.png");
ui->localPage->setMusicListType(PageType::LOCAL_PAGE);
ui->localPage->setCommonPageUI("本地音乐", ":/images/localbg.png");
ui->recentPage->setMusicListType(PageType::HISTORY_PAGE);
ui->recentPage->setCommonPageUI("最近播放", ":/images/recentbg.png");
setMusicListType
:设置页面类型,触发后续数据过滤逻辑。setCommonPageUI
:设置页面标题和背景图片,统一页面风格。
五、音乐数据过滤逻辑
在CommonPage::addMusicToMusicPage
方法中,根据pageType
过滤MusicList
中的音乐:
void CommonPage::addMusicToMusicPage(MusicList &musicList) {
musicListOfPage.clear(); // 清空旧数据,避免重复
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;
}
}
}
- 过滤逻辑:
- 本地下载:直接添加所有本地加载的音乐(无过滤)。
- 我喜欢:仅添加
isLike
为true
的音乐。 - 最近播放:仅添加
isHistory
为true
的音乐。
六、界面数据更新
在CommonPage::reFresh
方法中,根据过滤后的musicListOfPage
更新界面显示:
- 遍历音乐ID:通过
musicId
从MusicList
中查找对应的Music
对象。 - 创建列表项:为每个音乐创建
ListItemBox
,设置歌曲名称、歌手、专辑和收藏状态(小心心图标)。
for (auto musicId : musicListOfPage) {
auto it = musicList.findMusicById(musicId);
if (it != musicList.end()) {
ListItemBox* item = new ListItemBox(ui->pageMusicList);
item->setMusicName(it->getMusicName());
item->setSinger(it->getSingerName());
item->setAlbumName(it->getAlbumName());
item->setLikeIcon(it->getIsLike()); // 设置收藏状态图标
// 添加到QListWidget
QListWidgetItem* listItem = new QListWidgetItem(ui->pageMusicList);
listItem->setSizeHint(QSize(ui->pageMusicList->width(), 45));
ui->pageMusicList->setItemWidget(listItem, item);
}
}
七、信号与槽关联
通过信号槽机制,当用户点击“播放全部”或双击歌曲时,通知QQMusic
类更新播放列表:
- 播放全部按钮:
// CommonPage中播放全部按钮的信号发射
connect(ui->playAllBtn, &QPushButton::clicked, this, [=]() {
emit playAll(pageType); // 发射当前页面类型,告知QQMusic播放对应页面的音乐
});
- QQMusic处理逻辑:
// QQMusic中处理播放全部的槽函数
void QQMusic::onPlayAll(PageType pageType) {
CommonPage* page = getPageByType(pageType); // 根据类型获取对应页面
page->addMusicToPlayer(musicList, playList); // 将页面音乐添加到播放列表
player->play(); // 开始播放
}
八、核心优势
- 代码复用:
- 通过
CommonPage
统一管理三个页面的布局和逻辑,仅通过pageType
区分功能,减少重复代码(如列表显示、按钮交互)。
- 通过
- 数据隔离:
- 每个页面仅存储对应音乐的
musicId
,通过唯一ID关联完整Music
对象,高效且节省内存。
- 每个页面仅存储对应音乐的
- 灵活过滤:
- 利用
Music
类的isLike
和isHistory
属性,轻松实现不同页面的筛选(如仅显示收藏或历史播放的音乐)。
- 利用
- 界面统一:
- 使用自定义控件
ListItemBox
统一显示格式,确保三个页面的视觉和交互一致,提升用户体验。
- 使用自定义控件
九、总结
音乐分类功能通过枚举标记页面类型,结合数据过滤和界面更新逻辑,实现了“我喜欢”“本地下载”“最近播放”的高效管理。核心在于通过CommonPage
复用代码,利用状态标记筛选数据,确保不同页面快速展示对应内容,同时通过信号槽机制与播放模块联动,提升整体功能的一致性和可维护性。