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.pswp.cn/bicheng/74929.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/74929.shtml
英文地址,請注明出處:http://en.pswp.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的依賴…