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

发布于:2025-03-30 ⋅ 阅读:(38) ⋅ 点赞:(0)

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

目录

一. 保存本地添加的歌曲

1. 使用QSettings

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

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

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

2. 使用数据库存储

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

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

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

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

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

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

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

3. 可能出现的问题

二. 保存在线搜索的歌曲

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

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

三、运行结果


一. 保存本地添加的歌曲

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;
}

三、运行结果