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

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

目录

一. 保存本地添加的歌曲

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

// 保存新添加的文件路径,保存到Settingsm_lastPlaylist = strMp3FileList;m_settings->setValue("LastPlaylist", m_lastPlaylist);

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

//加载保存的播放列表
void OnlineMusicWidget::loadPlaylist() {// 读取保存的播放列表m_lastPlaylist = m_settings->value("LastPlaylist").toStringList();// 清空当前播放列表p_PlayerList->clear();// 添加文件并更新UIfor (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;}elseqDebug() << "不存在:"<<filePath;// 检查播放列表重复(关键)if (isSongInPlaylist(filePath)) {qDebug() << "[播放列表] 歌曲已存在,跳过:" << filePath;continue;}elseqDebug() << "不存在:"<<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;}elseqDebug() << "添加(在线)歌曲到数据库成功";

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

三、运行结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/74929.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

自动化发布工具CI/CD实践Jenkins常用工具和插件的使用

1、安装常用工具 名称版本备注jdkjava8代码打包所需git1.8.3.1maven3.6.3注意配置私服内容nvm0.39.3多Node.js环境管理工具Node.jsv14.18.0 / v16.17.1包管理工具yarn1.22.15包管理工具 1.1 安装jdk Jenkins 需要使用java11 及以上&#xff0c;但是代码打包依赖jdk8&#xff…

shared_ptr和 weak_ptr的详细介绍

关于 shared_ptr 和 weak_ptr 的详细介绍及使用示例&#xff1a; 1. shared_ptr&#xff08;共享所有权智能指针&#xff09; 核心特性 引用计数&#xff1a;记录当前有多少个 shared_ptr 共享同一个对象。自动释放&#xff1a;当引用计数归零时&#xff0c;自动释放对象内存…

Spring AI MCP 架构详解

Spring AI MCP 架构详解 1.什么是MCP? MCP 是一种开放协议&#xff0c;它对应用程序向大语言模型&#xff08;LLMs&#xff09;提供上下文信息的方式进行了标准化。可以把 MCP 想象成人工智能应用程序的 USB-C 接口。就像 USB-C 为将设备连接到各种外围设备和配件提供了一种…

腾讯系AI应用,可以生视频,3D模型...

以下注册手机后就可以使用了。 腾讯智影 智能抹除-在线去水印去字幕-抹除水印字幕-腾讯智影 混元&#xff08;文字&#xff0c;图片生成3D&#xff09; 腾讯混元3D 混元视频&#xff08;文字生成视频&#xff0c;可惜右下角有文字&#xff09; https://video.hunyuan.tencen…

数据结构(并查集,图)

并查集 练习版 class UnionFindSet { public:void swap(int* a, int* b){int tmp *a;*a *b;*b tmp;}UnionFindSet(size_t size):_ufs(size,-1){}int UnionFind(int x){}void Union(int x1, int x2){}//长分支改为相同节点int FindRoot(int x){}bool InSet(int x1, int x2)…

数据结构:探秘AVL树

本节重点 理解AVL树的概念掌握AVL树正确的插入方法利用_parent指针正确更新平衡因子掌握并理解四种旋转方式&#xff1a;左单旋&#xff0c;右单旋&#xff0c;左右双旋&#xff0c;右左双旋 一、AVL树的概念 AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis&…

电源系统的热设计与热管理--以反激式充电器为例

前言 反激电源常用于各种电子设备中&#xff0c;比如充电器、适配器等&#xff0c;它们通过变压器进行能量转换。高温环境可能对电子元件造成影响&#xff0c;特别是像MOSFET、二极管、变压器这样的关键部件&#xff0c;导致效率变低&#xff0c;甚至可能导致功能失效。还有安…

linux课程学习二——缓存

一.文件io与标准io的一个区别 遇到死循环可以ctrl c结束进程 使用printf输出&#xff0c;输出没有问题 用wirte输出&#xff0c;参数1&#xff0c;可以理解为上面介绍的linux标准文件描述符的1&#xff08;STDOUT&#xff09;标准输出&#xff0c;我们加上一个死循环while&…

Kafka中的消息如何分配给不同的消费者?

大家好&#xff0c;我是锋哥。今天分享关于【Kafka中的消息如何分配给不同的消费者&#xff1f;】面试题。希望对大家有帮助&#xff1b; Kafka中的消息如何分配给不同的消费者&#xff1f; 在 Kafka 中&#xff0c;消息是通过 主题&#xff08;Topic&#xff09; 进行组织的&…

Android的安全问题 - 在 Android 源码的 system/sepolicy 目录中,区分 public、private 和 vendor的目的

参考&#xff1a;Google文档 在 Android 8.0 及更高版本中自定义 SEPolicy 在 Android 源码的 system/sepolicy 目录中&#xff0c;区分 public、private 和 vendor 是为了模块化 SELinux 策略&#xff0c;并明确不同部分的访问权限和接口边界。这种设计主要基于以下原因&…

Java NIO之FileChannel 详解

关键点说明 文件打开选项&#xff1a; StandardOpenOption.CREATE - 文件不存在时创建 StandardOpenOption.READ/WRITE - 读写权限 StandardOpenOption.APPEND - 追加模式 StandardOpenOption.TRUNCATE_EXISTING - 清空已存在文件 缓冲区操作&#xff1a; ByteBuffer.wrap…

stock-pandas,一个易用的talib的替代开源库。

原创内容第841篇&#xff0c;专注智能量化投资、个人成长与财富自由。 介绍一个ta-lib的平替——我们来实现一下&#xff0c;最高价突破布林带上轨&#xff0c;和最低价突破布林带下轨的可视化效果&#xff1a; cross_up_upper stock[high].copy()# cross_up_upper 最高价突破…

JVM 面经

1、什么是 JVM? JVM 就是 Java 虚拟机&#xff0c;它是 Java 实现跨平台的基石。程序运行之前&#xff0c;需要先通过编译器将 Java 源代码文件编译成 Java 字节码文件&#xff1b;程序运行时&#xff0c;JVM 会对字节码文件进行逐行解释&#xff0c;翻译成机器码指令&#x…

【JavaScript】合体期功法——DOM(一)

目录 DOMWeb API 基本概念作用和分类 什么是 DOMDOM 树DOM 对象 获取 DOM 元素根据 CSS 选择器来获取 DOM 元素选择匹配的第一个元素选择匹配的多个元素 其他获取 DOM 元素方法 修改元素的内容对象.innerText 属性对象.innerHTML 属性案例&#xff1a;年会抽奖 修改元素属性修改…

GAMMA数据处理(十)

今天向别人请教了一个问题&#xff0c;刚无意中搜索到了一模一样的问题 不知道这个怎么解决... ok 解决了 有一个GAMMA的命令可转换 但是很奇怪 完全对不上 转换出来的行列号 不知道为啥 再试试 是因为经纬度坐标的小数点位数 de as

Java入门知识总结——章节(二)

ps&#xff1a;本章主要讲数组、二维数组、变量 一、数组 数组是一个数据容器&#xff0c;可用来存储一批同类型的数据 &#x1f511;&#xff1a;注意 类也可以是一个类的数组 public class Main {public static class Student {String name;int age; // 移除 unsignedint…

动态IP:网络世界的“变色龙”如何改变你的在线体验?

你知道吗&#xff1f;有时候我觉得动态IP就像是网络世界里的“变色龙”。它不像静态IP那样一成不变&#xff0c;而是随时在变化&#xff0c;像是一个永远在换衣服的演员。你永远不知道它下一秒会变成什么样子&#xff0c;但正是这种不确定性&#xff0c;让它变得特别有趣。想象…

从24GHz到71GHz:Sivers半导体的广泛频率范围5G毫米波产品解析

在5G技术的浪潮中&#xff0c;Sivers半导体推出了创新的毫米波无线产品&#xff0c;为通信行业带来高效、可靠的解决方案。这些产品支持从24GHz到71GHz的频率&#xff0c;覆盖许可与非许可频段&#xff0c;适应高速、低延迟的通信场景。 5G通信频段的一点事儿及Sivers毫米波射频…

aocache:AOCache 新增功能深度解析:从性能监控到灵活配置的全方位升级

最近对aocache 进行了重要升级&#xff0c;最新版本0.6.0增加了几项新功能&#xff1a;性能分析日志&#xff0c;AOCache性能分析工具&#xff0c;切入点自定义配置&#xff0c;全局配置&#xff0c;本文详细说明这几项目新功能的作用和使用方式。 一、性能分析日志 需求背景…

Java EE 进阶:MyBatis-plus

MyBatis-plus的介绍 MyBatis-plus是MyBatis的增强工具&#xff0c;在MyBatis的基础上做出加强&#xff0c;只要MyBatis有的功能MyBatis-plus都有。 MyBatis-plus的上手 添加依赖 在我们创建项目的时候&#xff0c;我们需要添加MyBatis-plus和mysql的依赖 MyBatis-plus的依赖…