实现功能:用数据库保存本地导入和在线搜索的歌曲记录
目录
(2)在导入音乐槽函数中,保存新添加的文件路径,保存到Settings
(4)创建musicDatabase.cpp和musicDatabase.h文件
(1)在在线搜索函数的处理数据信息返回函数中保存(在线)歌曲到数据库
一. 保存本地添加的歌曲
1. 使用QSettings
使用QSettings保存播放列表路径:记录用户添加的文件路径,每次启动时读取这些路径并重新添加到播放列表。这种方法简单,但需要注意文件路径的有效性,比如文件是否被移动或删除。
(1)在构造函数中,创建对象。
//构造函数中,创建对象
m_settings = new QSettings("TT Music", "MusicPlayer");
loadPlaylist(); // 初始化加载播放列表
(2)在导入音乐槽函数中,保存新添加的文件路径,保存到Settings
// 保存新添加的文件路径,保存到Settings
m_lastPlaylist = strMp3FileList;
m_settings->setValue("LastPlaylist", m_lastPlaylist);
(3)加载保存的播放列表,loadPlaylist()
//加载保存的播放列表
void OnlineMusicWidget::loadPlaylist() {
// 读取保存的播放列表
m_lastPlaylist = m_settings->value("LastPlaylist").toStringList();
// 清空当前播放列表
p_PlayerList->clear();
// 添加文件并更新UI
for (const QString &filePath : m_lastPlaylist) {
if (QFile::exists(filePath)) { // 验证文件有效性
p_PlayerList->addMedia(QUrl::fromLocalFile(filePath));
ui->plainTextEdit_SongList->appendPlainText(QFileInfo(filePath).fileName());
} else {
qDebug() << "警告:文件" << filePath << "不存在,已跳过";
}
}
// 保持最后添加的文件显示在文本框底部
ui->plainTextEdit_SongList->moveCursor(QTextCursor::End);
}
QSettings适合保存应用程序的配置信息,如用户偏好、最后访问的位置等,但不适合保存临时或动态生成的数据,尤其是当这些数据需要持久化存储时。
2. 使用数据库存储
(1)在构造函数中,初始化数据库。
m_musicDb = new MusicDatabase(this);
if (!m_musicDb->initialize()) {
qDebug() << "数据库初始化失败!";
}
(2)在导入音乐槽函数中,数据库存储
for (const QString &filePath : strMp3FileList) {
// 检查数据库重复(可选)
if (m_musicDb->isSongExists(filePath)) {
qDebug() << "[数据库] 歌曲已存在,跳过:" << filePath;
continue;
}else
qDebug() << "不存在:"<<filePath;
// 检查播放列表重复(关键)
if (isSongInPlaylist(filePath)) {
qDebug() << "[播放列表] 歌曲已存在,跳过:" << filePath;
continue;
}else
qDebug() << "不存在:"<<filePath;
// 1. 解析文件名中的歌曲名和歌手
QFileInfo fileInfo(filePath);
QString fileName = fileInfo.baseName(); // 示例:"富士山下 - 陈奕迅"
QStringList parts = fileName.split(" - ");
QStringList separators = {" - ","-", "_", "—",""}; // 定义可能的分隔符
foreach (const QString &sep, separators) {
if (fileName.contains(sep)) {
parts = fileName.split(sep);
break;
}
}
QString songName = "未知歌曲";
QString singer = "未知歌手";
if (parts.size() >= 2) {
songName = parts[0].trimmed();
singer = parts[1].trimmed();
} else {
songName = fileName; // 无分隔符时直接使用文件名
}
// 2. 添加到播放列表
p_PlayerList->addMedia(QUrl::fromLocalFile(filePath));
int musicindex=p_PlayerList->currentIndex();//当前音乐的索引
int currentSongid = getCurrentPlayingSongId();
// QString strSongId = QString::number(currentSongid); // 直接转换
qDebug() << "歌曲------------ID"<<currentSongid;
// 3. 保存到数据库(使用解析出的歌曲名和歌手)
// MusicDatabase::DatabaseError error = m_musicDb->addSong(filePath);
MusicDatabase::DatabaseError error = m_musicDb->saveSongToDatabase(MusicDatabase::Local, musicindex, currentSongid, filePath, songName, singer,"",0);
if (error != MusicDatabase::NoError) {
qDebug() << "添加(本地)歌曲到数据库失败:" << error;
}else
{qDebug() << "添加(本地)歌曲到数据库成功";
}
}
emit playlistUpdated(); // 添加歌曲后触发信号
}
(3)加载播放列表时从数据库读取函数
void OnlineMusicWidget::loadPlaylistFromDatabase() {
qDebug() << "进入 loadPlaylistFromDatabase()";
// 检查数据库初始化
if (!m_musicDb->initialize()) {
qDebug() << "数据库初始化失败:" ;
return;
}
// 从数据库获取播放列表路径
QStringList playlist = m_musicDb->loadPlaylist();
// p_PlayerList->clear();
// 添加调试日志
qDebug() << "查询到的歌曲数量:" << playlist.size();
// 初始化计数器
int newSongsCount = 0;
// 遍历数据库中的每首歌曲
for (const QString &filePath : playlist) {
qDebug() << "加载文件:" << filePath;
QString normalizedPath = normalizePath(filePath);
// 检查是否已存在
if (m_existingPaths.contains(normalizedPath)) {
qDebug() << "跳过重复歌曲:" << filePath;
continue;
}
// 新增歌曲:更新计数器和集合
newSongsCount++;
m_existingPaths.insert(normalizedPath);
qDebug() << "新增路径—— :" << normalizedPath;
// 添加到播放列表
p_PlayerList->addMedia(QUrl::fromLocalFile(filePath));
QFileInfo qFileInfo(filePath);
// 添加到文本框,带有序号
int index = ui->listWidget_SongList->count();
ui->listWidget_SongList->addItem(QString("%1. %2").arg(index+1).arg(qFileInfo.fileName()));
}
// 输出新增歌曲数量
qDebug() << "本次新增歌曲数量:" << newSongsCount;
qDebug() << "播放列表加载完成";
}
(4)创建musicDatabase.cpp和musicDatabase.h文件
//musicDatabase.cpp
MusicDatabase::MusicDatabase(QObject *parent) : QObject(parent) {
// 使用SQLite数据库
m_db = QSqlDatabase::addDatabase("QSQLITE");
m_db.setDatabaseName("E:/QtProjects/OnlineMusic/playlist.db"); // 使用应用程序资源文件
}
//数据库初始化
bool MusicDatabase::initialize() {
if (!m_db.open()) {
qDebug() << "数据库初始化失败:" << m_db.lastError().text();
return false;
}
// 加载播放列表和收藏列表
loadPlaylist();
// loadFavorites();
// 创建歌曲表(如果不存在)
QSqlQuery query;
QString createTableSQL =
"CREATE TABLE IF NOT EXISTS songs ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"source_type TEXT NOT NULL, "
"music_id INTEGER UNIQUE, "
"local_filepath TEXT UNIQUE, "
"name TEXT NOT NULL, "
"singer TEXT NOT NULL, "
"album TEXT, "
"duration INTEGER, "
"is_favorite BOOLEAN DEFAULT 0, "
"added_time DATETIME DEFAULT CURRENT_TIMESTAMP"
")";
if (!query.exec(createTableSQL)) {
qDebug() << "创建表失败:" << query.lastError().text();
qDebug() << "执行的 SQL:" << createTableSQL; // 打印 SQL 语句
return false;
}
qDebug() << "创建表成功!";
return true;
}
//将(本地)加载的歌曲保存到数据库(通过路径filePath)
MusicDatabase::DatabaseError MusicDatabase::addSong(const QString &filePath) {
if (!QFile::exists(filePath)) {
qDebug() << "文件不存在:" << filePath; // 添加调试输出
return FileNotFoundError; // 文件不存在
}
QSqlDatabase db = QSqlDatabase::database();
db.transaction(); // 开始事务
// 先删除旧记录(如果有)
QSqlQuery delQuery;
delQuery.prepare("DELETE FROM songs WHERE filepath = :path");
delQuery.bindValue(":path", filePath);
delQuery.exec();
// 插入新记录
QSqlQuery insertQuery;
insertQuery.prepare("INSERT INTO songs (filepath) VALUES (:path)");
insertQuery.bindValue(":path", filePath);
// // 检查数据库中是否已存在相同路径
QSqlQuery query1;
query1.prepare("SELECT COUNT(*) FROM songs WHERE filepath = :path");
query1.bindValue(":path", filePath);
if (!query1.exec() || !query1.next()) {
qDebug() << "查询失败:" << query1.lastError().text();
return QueryError;
}
int count = query1.value(0).toInt();
if (count > 0) {
db.rollback(); // 回滚事务(可选)
qDebug() << "路径已存在,跳过插入";
return NoError; // 或自定义重复错误码
}
//执行插入
QSqlQuery query;
query.prepare("INSERT INTO songs (filepath) VALUES (:path)");
query.bindValue(":path", filePath);
if (!query.exec()) {
db.rollback(); // 回滚事务
qDebug() << "插入失败:" << query.lastError().text();
return QueryError;
}
db.commit(); // 提交事务
return NoError;
}
QStringList MusicDatabase::loadPlaylist() {
QStringList paths;
QSqlQuery query;
query.prepare("SELECT local_filepath FROM songs WHERE source_type = 'Local'"); // 注意 source_type 值的大小写
if (!query.exec()) {
qDebug() << "查询失败:" << query.lastError().text();
return paths;
}
while (query.next()) {
QString path = query.value("local_filepath").toString();
qDebug() << "加载路径:" << path; // 添加调试输出
paths.append(path);
}
return paths;
}
(5)创建 playlist.db
文件的方法
playlist.db
是 SQLite 数据库文件,用于存储应用程序的本地数据(如歌曲列表、播放记录等)
方法一:手动创建
方法二:通过 Qt 代码动态创建
m_db = QSqlDatabase::addDatabase("QSQLITE");
m_db.setDatabaseName("E:/QtProjects/OnlineMusic/playlist.db");
(6)在 .qrc 文件中声明 playlist.db
- 右键点击项目 → 添加新文件 → 资源文件 →
resources.
qrc
- 打开生成的
resources.qrc
文件。 - 点击左侧的 添加现有文件。
- 选择项目目录下的
playlist.db
文件。 - 确保
prefix
设置为/
(根路径)。
(7)将 .qrc
文件添加到 Qt 项目中
在 .pro
文件中声明资源文件
RESOURCES += \
images.qrc \
music.qrc
3. 可能出现的问题
问题1:数据库里保存了之前导入的歌曲,但是程序启动时播放列表里并没有自动显示之前的歌曲,而是需要再次导入歌曲时才会把之前的歌曲全部显示。
解决方法:
方法一:在主窗口构造函数中添加自动加载,在窗口初始化阶段调用
loadPlaylistFromDatabase()。
方法一:通过
showEvent
触发加载。重写窗口的showEvent
方法,在窗口显示时自动加载:void OnlineMusicWidget::showEvent(QShowEvent *event) { QMainWindow::showEvent(event); loadPlaylistFromDatabase(); //窗口显示时自动加载 }
推荐 showEvent:
1. 更安全的初始化顺序
showEvent
在窗口控件完全初始化(如ui
指针绑定)后触发,避免在构造函数中因组件未就绪导致的崩溃。
2. 代码可维护性
- 将数据加载逻辑与 UI 初始化分离,符合单一职责原则。
-
showEvent
:专注于窗口显示时的数据加载和 UI 更新。
3. 动态刷新支持
- 通过
showEvent
,可以在窗口重新显示时自动刷新播放列表(例如用户关闭后重新打开窗口)。
4. 多窗口实例兼容性
- 如果程序中存在多个
OnlineMusicWidget
实例,showEvent
保证每个窗口独立加载自己的数据。
二. 保存在线搜索的歌曲
(1)在在线搜索函数的处理数据信息返回函数中保存(在线)歌曲到数据库
处理数据信息返回函数:当收到网络回复后,代码解析JSON数据,提取了歌曲的ID、名称、歌手等信息,并将这些信息显示在文本框中,同时将歌曲URL添加到播放列表
MusicDatabase::DatabaseError error = m_musicDb->saveSongToDatabase(MusicDatabase::Online,100, I_MusicID," ",StrMusicName, StrSingerName, "album", 100);
if (error != MusicDatabase::NoError) {
qDebug() << "添加(在线)歌曲到数据库失败:" << error;
}else
qDebug() << "添加(在线)歌曲到数据库成功";
(2)实现保存歌曲到数据库的函数
MusicDatabase::DatabaseError MusicDatabase::saveSongToDatabase(SongSource source,
const int id,
const int music_id,
const QString &filePathOrId,
const QString &name,
const QString &singer,
const QString &album = "",
int duration = 0) {
QSqlQuery query;
QString sql;
QVariantList params;
// 根据来源类型生成不同的SQL插入语句
if (source == Local) {
// 本地歌曲(使用文件路径)
sql = "INSERT INTO songs (source_type, id, music_id, local_filepath, name, singer, album, duration) "
"VALUES (:source_type, :id, :music_id, :local_filepath, :name, :singer, :album, :duration)";
query.prepare(sql);
query.bindValue(":source_type", "Local");
query.bindValue(":local_filepath", filePathOrId); // 文件路径
} else {
// 在线歌曲(使用在线ID)
sql = "INSERT INTO songs (source_type, music_id, name, singer, album, duration) "
"VALUES (:source_type, :music_id, :name, :singer, :album, :duration)";
query.prepare(sql);
query.bindValue(":source_type", "online");
query.bindValue(":music_id", filePathOrId.toInt()); // 转换为整数ID
}
// 绑定公共参数
query.bindValue(":name", name);
query.bindValue(":singer", singer);
query.bindValue(":album", album);
query.bindValue(":duration", duration);
if (!query.exec()) {
QSqlError sqlError = query.lastError();
if (sqlError.nativeErrorCode() == "19") { // SQLite 唯一性约束错误码
qDebug() << "文件路径已存在:" << filePathOrId;
return FileExistsError; // 返回 FileExistsError(值 3)
} else {
qDebug() << "保存失败:" << sqlError.text();
return QueryError; // 其他 SQL 错误
}
}
qDebug() << "保存成功:" << name << "(" << (source == Local ? "本地" : "在线") << ")";
return NoError;
}