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.rhkb.cn/news/43261.html

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

相关文章

SQLAlchemy关键词搜索技术深度解析:从基础过滤到全文检索

在数据驱动的应用开发中&#xff0c;基于关键词的模糊查询是常见的业务需求。SQLAlchemy作为Python生态中最流行的ORM框架&#xff0c;提供了多种实现关键词搜索的技术方案。本文将从性能、适用场景和技术复杂度三个维度&#xff0c;系统对比分析SQLAlchemy中关键词搜索的最佳实…

css属性列举

介绍 CSS word-spacing 属性&#xff0c;用于指定段字之间的空间&#xff0c;例如&#xff1a; p {word-spacing:30px; }word-spacing属性增加或减少字与字之间的空白。 注意&#xff1a; 负值是允许的。 浏览器支持 表格中的数字表示支持该属性的第一个浏览器版本号。 属…

python实现股票数据可视化

最近在做一个涉及到股票数据清洗及预测的项目&#xff0c;项目中需要用到可视化股票数据这一功能&#xff0c;这里我与大家分享一下股票数据可视化的一些基本方法。 股票数据获取 在经过多次尝试后&#xff0c;发现了一个

从 JDK 11 到 JDK 17:OpenRewrite 实战 Spring Boot 升级指南

一、为什么选择 OpenRewrite 升级&#xff1f; 在 Spring Boot 项目升级 JDK 的过程中&#xff0c;我们面临两个核心痛点&#xff1a; 语法兼容性问题&#xff08;如废弃的 API、新的关键字&#xff09;依赖版本冲突&#xff08;特别是 Spring Boot 与 JDK 版本的匹配&#x…

交换技术综合实验

一、实验拓扑 二、实验要求 内网IP地址使用172.16.0.0/16分配。 SW1和SW2之间互为备份。 VRRP/STP/VLAN/Eth-trunk均使用。 所有PC通过DHCP获取IP地址。 ISP只能配置IP地址。 所有电脑可以正常访问ISP路由器。 三、实验步骤 基于172.16.0.0/16进行划分 172.16.2.0/24&…

【Linux】了解基础指令(超详细)

目录 【whoami】指令【pwd】指令【mkdir】指令【touch】指令【ls】指令文件的扩展内容 【cd】指令相对路径和绝对路径(.和..存在的原因)绝对路径相对路径 【rm】指令【man】命令【less】指令echo指令重定向操作追加重定向 cat 指令输入重定向 管道操作(组合指令)查找三剑客find…

基于改进粒子群算法的多目标分布式电源选址定容规划(附带Matlab代码)

通过分析分布式电源对配电网的影响&#xff0c;以有功功率损耗、电压质量及分布式电源总容量为优化目标&#xff0c;基于模糊理论建立了分布式电源在配电网中选址定容的多目标优化模型&#xff0c;并提出了一种改进粒子群算法进行求解。在算例仿真中&#xff0c;基于IEEE-14标准…

26_ajax

目录 了解 接口 前后端交互 一、安装服务器环境 nodejs ajax发起请求 渲染响应结果 get方式传递参数 post方式传递参数 封装ajax_上 封装ajax下 了解 清楚前后端交互就可以写一些后端代码了。小项目 现在写项目开发的时候都是前后端分离 之前都没有前端这个东西&a…

OJ题:移动零

双指针法 c 语言实现 void moveZeroes(int* nums, int numsSize) {int dest,cur; //创建临时指针和目标指针destcur0;//出初始化while(cur<numsSize)//遍历{if(nums[cur]!0){swap(&nums[cur],&nums[dest]);cur;dest;}else{cur;}}} 思路是建立两个指针&#xff0…

Kubernetes对象基础操作

基础操作 文章目录 基础操作一、创建Kubernetes对象1.使用指令式命令创建Deployment2.使用指令式对象配置创建Deployment3.使用声明式对象配置创建Deployment 二、操作对象的标签1.为对象添加标签2.修改对象的标签3.删除对象标签4.操作具有指定标签的对象 三、操作名称空间四、…

命悬生死线:当游戏遭遇DDoS围剿,如何用AI破局?

文章作者&#xff1a;腾讯宙斯盾DDoS防护团队 一、血色战场&#xff1a;DDoS攻击游戏产业的致命瞬间 全球黑色星期五 这是一场波及全球的“黑色星期五”&#xff0c;起初无人察觉&#xff0c;包括小林。 他刚下班到家就迫不及待打开电脑&#xff0c;准备体验期待已久的《黑神话…

【数据结构】[特殊字符] 并查集优化全解:从链式退化到近O(1)的性能飞跃 | 路径压缩与合并策略深度实战

并查集的优化 导读一、合并优化1.1 基本原理1.2 按大小合并1.3 按秩合并1.4 两种合并的区别**1.4.1 核心目标****1.4.2 数据存储****1.4.3 合并逻辑****1.4.4 树高控制****1.4.5 适用场景****1.4.6 路径压缩兼容性****1.4.7 极端案例对比****1.4.8 小结**二、查找优化2.1 路径压…

[python]基于yolov12实现热力图可视化支持图像视频和摄像头检测

YOLOv12 Grad-CAM 可视化工具 本工具基于YOLOv12模型&#xff0c;结合Grad-CAM技术实现目标检测的可视化分析&#xff0c;支持图像、视频和实时摄像头处理。 注意 该项目使用的是yolov12-1.0模型进行测试通过&#xff0c;不是使用turbo模型&#xff0c;且由于yolov12-1.0由于…

进程Kill杀死后GPU显存没有释放仍然被占用,怎么杀死僵尸进程

参考链接&#xff1a; https://blog.csdn.net/qq_37591986/article/details/131118109 使用下面的命令&#xff1a; fuser -v /dev/nvidia0 | awk {print $0} | xargs kill -9一般来说他会杀掉整个用户的所有进程。

基于飞腾/龙芯+盛科CTC7132全国产交换机解决方案

产品介绍 盛科CTC7132,内置ARM-Cortex A53 主频1.2GHz&#xff1b;支持24个千兆电口&#xff0c;24个万兆光口&#xff08;850nm多模&#xff09;&#xff0c;1个千兆管理网口&#xff0c;1个管理串口&#xff1b;支持1个百兆健康管理网口&#xff1a;用于设备端口状态、电压、…

Tesseract OCR技术初探(Python调用)

一、Tesseract OCR技术解析 1.1 核心架构与发展历程 Tesseract是由HP实验室于1985年研发的光学字符识别引擎&#xff0c;2005年由Google开源并持续维护至今。其核心技术经历了三个阶段演进&#xff1a; 传统模式&#xff08;v3.x&#xff09;&#xff1a;基于特征匹配算法&a…

自动语音识别(ASR)技术详解

语音识别&#xff08;Automatic Speech Recognition, ASR&#xff09;是人工智能和自然语言处理领域的重要技术&#xff0c;旨在将人类的语音信号转换为对应的文本。近年来&#xff0c;深度学习的突破推动语音识别系统从实验室走入日常生活&#xff0c;为智能助手、实时翻译、医…

Cursor 汉化教程

# 问题 想把 cursor 改成中文 我这里是汉化过的 # 【第一种方法】安装插件 然后重启 # 【第二种方法】Ctrl Shift P 打开配置项 然后搜索输入 Configure Display Language 点一下 切换到 zh-cn 重启 cursor 即可 重启后就好了~

用 pytorch 从零开始创建大语言模型(三):编码注意力机制

从零开始创建大语言模型&#xff08;Python/pytorch &#xff09;&#xff08;三&#xff09;&#xff1a;编码注意力机制 3 编码注意力机制3.1 建模长序列的问题3.2 使用注意力机制捕捉数据依赖关系3.3 通过自注意力关注输入的不同部分3.3.1 一个没有可训练权重的简化自注意力…

Linux之基础知识

目录 一、环境准备 1.1、常规登录 1.2、免密登录 二、Linux基本指令 2.1、ls命令 2.2、pwd命令 2.3、cd命令 2.4、touch命令 2.5、mkdir命令 2.6、rmdir和rm命令 2.7man命令 2.8、cp命令 2.9、mv命令 2.10、cat命令 2.11、echo命令 2.11.1、Ctrl r 快捷键 2…