QT音乐播放器(1):数据库保存歌曲

实现功能:用数据库保存本地导入和在线搜索的歌曲记录

目录

[一. 保存本地添加的歌曲](#一. 保存本地添加的歌曲)

[1. 使用QSettings](#1. 使用QSettings)

(1)在构造函数中,创建对象。

(2)在导入音乐槽函数中,保存新添加的文件路径,保存到Settings

(3)加载保存的播放列表,loadPlaylist()

[2. 使用数据库存储](#2. 使用数据库存储)

(1)在构造函数中,初始化数据库。

(2)在导入音乐槽函数中,数据库存储

(3)加载播放列表时从数据库读取函数

(4)创建musicDatabase.cpp和musicDatabase.h文件

[(5)创建 playlist.db 文件的方法](#(5)创建 playlist.db 文件的方法)

[(6)在 .qrc 文件中声明 playlist.db](#(6)在 .qrc 文件中声明 playlist.db)

[(7)将 .qrc 文件添加到 Qt 项目中](#(7)将 .qrc 文件添加到 Qt 项目中)

[3. 可能出现的问题](#3. 可能出现的问题)

[二. 保存在线搜索的歌曲](#二. 保存在线搜索的歌曲)

(1)在在线搜索函数的处理数据信息返回函数中保存(在线)歌曲到数据库

(2)实现保存歌曲到数据库的函数

三、运行结果


一. 保存本地添加的歌曲

1. 使用QSettings

使用QSettings保存播放列表路径:记录用户添加的文件路径,每次启动时读取这些路径并重新添加到播放列表。这种方法简单,但需要注意文件路径的有效性,比如文件是否被移动或删除。

(1)在构造函数中,创建对象。

cpp 复制代码
    //构造函数中,创建对象
    m_settings = new QSettings("TT Music", "MusicPlayer"); 
    loadPlaylist(); // 初始化加载播放列表

(2)在导入音乐槽函数中,保存新添加的文件路径,保存到Settings

cpp 复制代码
// 保存新添加的文件路径,保存到Settings
    m_lastPlaylist = strMp3FileList;
    m_settings->setValue("LastPlaylist", m_lastPlaylist);

(3)加载保存的播放列表,loadPlaylist()

cpp 复制代码
//加载保存的播放列表
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)在构造函数中,初始化数据库。

cpp 复制代码
    m_musicDb = new MusicDatabase(this);
    if (!m_musicDb->initialize()) {
        qDebug() << "数据库初始化失败!";
    }

(2)在导入音乐槽函数中,数据库存储

cpp 复制代码
     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)加载播放列表时从数据库读取函数

cpp 复制代码
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文件

cpp 复制代码
//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 代码动态创建

cpp 复制代码
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 文件中声明资源文件

cpp 复制代码
RESOURCES += \
    images.qrc \
    music.qrc

3. 可能出现的问题

问题1:数据库里保存了之前导入的歌曲,但是程序启动时播放列表里并没有自动显示之前的歌曲,而是需要再次导入歌曲时才会把之前的歌曲全部显示。

解决方法:

方法一:在主窗口构造函数中添加自动加载,在窗口初始化阶段调用 loadPlaylistFromDatabase()。

方法一:通过 showEvent 触发加载。重写窗口的 showEvent 方法,在窗口显示时自动加载:

cpp 复制代码
void OnlineMusicWidget::showEvent(QShowEvent *event) {
    QMainWindow::showEvent(event);
    loadPlaylistFromDatabase();  //窗口显示时自动加载
}

推荐 showEvent:

  1. 更安全的初始化顺序
  • showEvent 在窗口控件完全初始化(如 ui 指针绑定)后触发,避免在构造函数中因组件未就绪导致的崩溃。
  1. 代码可维护性
  • 将数据加载逻辑与 UI 初始化分离,符合单一职责原则。
  • showEvent:专注于窗口显示时的数据加载和 UI 更新。

3. 动态刷新支持

  • 通过 showEvent,可以在窗口重新显示时自动刷新播放列表(例如用户关闭后重新打开窗口)。

​4. 多窗口实例兼容性

  • 如果程序中存在多个 OnlineMusicWidget 实例,showEvent 保证每个窗口独立加载自己的数据。

二. 保存在线搜索的歌曲

(1)在在线搜索函数的 处理数据信息返回函数中保存(在线)歌曲到数据库

处理数据信息返回函数:当收到网络回复后,代码解析JSON数据,提取了歌曲的ID、名称、歌手等信息,并将这些信息显示在文本框中,同时将歌曲URL添加到播放列表

cpp 复制代码
MusicDatabase::DatabaseError error = m_musicDb->saveSongToDatabase(MusicDatabase::Online,100, I_MusicID," ",StrMusicName, StrSingerName, "album", 100);
                if (error != MusicDatabase::NoError) {
                    qDebug() << "添加(在线)歌曲到数据库失败:" << error;
                }else
                qDebug() << "添加(在线)歌曲到数据库成功";

(2)实现保存歌曲到数据库的函数

cpp 复制代码
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;
}

三、运行结果

相关推荐
学习是种信仰啊12 分钟前
QT图片轮播器实现方法二(QT实操2)
开发语言·c++·qt
疾风铸境15 分钟前
Qt5.14.2+Cmake使用mingw64位编译opencv4.5成功图文教程
开发语言·qt
nqqcat~2 小时前
STL常用算法
开发语言·c++·算法
_GR2 小时前
2022年蓝桥杯第十三届C&C++大学B组真题及代码
c语言·数据结构·c++·算法·蓝桥杯·动态规划
fengbingchun2 小时前
Qt中的事件循环
qt
虾球xz2 小时前
游戏引擎学习第195天
c++·学习·游戏引擎
熙曦Sakura3 小时前
【C++】map
前端·c++
Stardep3 小时前
算法学习11——滑动窗口——最大连续1的个数
数据结构·c++·学习·算法·leetcode·动态规划·牛客网
双叶8363 小时前
(C语言)学生信息表(学生管理系统)(基于通讯录改版)(正式版)(C语言项目)
c语言·开发语言·c++·算法·microsoft
wen__xvn3 小时前
每日一题洛谷P8716 [蓝桥杯 2020 省 AB2] 回文日期c++
c++·算法·蓝桥杯