前言html
這一節實現一個簡易的音樂播放器,其音樂播放的核心功能是採用Qt支持的Phonon框架,該框架在前一篇博文Qt學習之路_13(簡易俄羅斯方塊) 中已經使用過了,在俄羅斯方塊中主要是用來設置背景音樂和消行的聲音的。這裏用這個框架一樣是用來播放,暫停等多媒體的各類控制功能,另外該框架能夠自動獲取音頻文件的一些信息,這樣咱們在設計播放列表時能夠獲取這些信息,好比歌手名,專輯名,時長,文件名等等。程序中桌面歌詞的實現是繼承了QLabel類,而後使用3層文本顯示,最上面一層採用漸進顯示的方式來達到歌詞播放的動態效果。linux
實驗的參考資料爲http://www.yafeilinux.com/ 網站上yafei做者提供的代碼,本人只是看懂其源碼而後本身敲了一遍,代碼作了稍微的改變,其設計方法和技巧全是原創做者yafei的功勞。c++
開發環境:WindowsXP+Qt4.8.2+QtCreator2.5.1正則表達式
實驗說明網絡
本實驗沒有使用QtDesigner來設計界面,其界面而是直接採用c++代碼來寫的。下面分如下幾個方面來介紹本實驗的實現過程當中應該注意的知識點:框架
播放界面設計部分:less
由於主界面的設計是從QWidget類繼承而來,可是本程序卻沒有使用界面設計工具來設計界面,而是直接使用c++代碼完成。dom
在界面設計時,首先通常是設置窗口的標題,尺寸,圖標等。而後而後本程序時在主界面上面添加了2個工具欄和一個標題欄,這3個欄目構成了播放器的主界面,主界面採用的是垂直佈局,即QVBoxLayout. 2個工具欄分別爲QAction,裏面可使用addAction ()方法直接插入action或者使用addWidget()方法插入widget。對action能夠設置其快捷鍵,提示文本,圖標,響應槽函數等。對於widget能夠設置其顯示內容,提示文本,尺寸屬性,對其方式,若是外加網絡鏈接,則也能夠設置其是否連接到外部等。ide
在播放媒體文件時,媒體對象MediaObject會在指定的時間間隔發送tick()信號,這個時間間隔可使用setTrickInterval()函數來進行設置。tick()中的參數time指定了媒體對象在媒體流中的當前時間位置,單位是毫秒。程序中關聯了這個信號,其主要目的是爲了得到當前的播放時間。函數
能夠直接調用媒體播放文件的totalTime方法實現統計媒體文件的總播放時長,單位爲毫秒,而後能夠將其轉換保存在QTime對象中,直接使用toString()函數來指定其形式。
媒體對象的各類狀態:
當建立了媒體對象後,它就會處於LoadingState狀態,只有使用createPath()爲其設置了Path,再使用setCurrentSource()爲其設置了當前媒體源之後,媒體對象纔會進入StoppedState狀態。若是在設置了媒體源以後當即調用了play()函數,那麼媒體對象就不會進入StoppedState狀態了,而是直接進入PlayingState狀態。
每當媒體對象的狀態發生改變時,就會自動發射stateChanged()信號,這裏綁定信號後,就能夠用這些狀態來進行一些有關的設置。
播放列表:
程序中sources爲打開的因此音頻文件列表,playlist爲音樂播放列表表格對象。程序中並無直接使用meidaObject對象來獲取音頻文件信息,而是建立了新的MedioObject類對象meta_information_resolver做爲元數據的解析器。由於只有在LoadingState完成後才能得到元數據,因此能夠先調用解析器的setCurrentSource()函數爲其設置一個媒體源,而後關聯它的stateChanged()信號,等其進入到StoppedState狀態再進行元數據的解析。
桌面歌詞:
程序中實現桌面歌詞設計是類MyLrc,繼承QLabel類。桌面歌詞的顯示首先須要將部件的背景設置爲透明色,而後從新實現其重繪事件處理函數來自定義文本的顯示,這裏可使用漸變填充來實現多彩的文字。而後再使用定時器,在已經繪製的歌詞上面再繪製一個不斷變寬的相同的歌詞來實現歌詞的動態播放效果。所以程序中的歌詞共繪製了3遍,第一遍是深黑色,在最底層;第2遍是漸變填充的歌詞,爲正常顯示所用;第3次繪製的是用於遮罩用,實現動態效果。
歌詞的解析都在resolve_lrc()函數中實現的,利用正則表達式來獲取歌曲文件中的各類信息,通常的歌詞文件以.lrc後綴結尾,歌詞文件的格式以下所示:
關於歌詞的解析部分詳見代碼部分。
系統圖標的設計:
通常的音樂播放器都會有一個系統托盤圖標,這樣就能夠在播放歌曲的時候將主界面最小化到系統托盤圖標了。 Qt中是經過QSystemTrayIcon類來實現系統托盤圖標的,而且能夠很容易在該圖標上添加菜單,設置工具欄提示,顯示消息和處理各類交互等。
知識點總結
Qt知識點總結:
QAction對象使用setText()方法時,若是在對象的構造函數中已經有了其文字顯示,那麼action上面顯示的就是構造函數中的text文本。這裏的setText文本有2個做用,第一個是若是該action對應到了菜單欄中,則菜單欄會自動將其顯示出來;第二個時若是構造函數中沒有設置文本內容,則該action會顯示setText()方法設置的內容,固然了,若是action設置了圖標,該文本內容就被覆蓋了,退化爲文本提示了。
cellClicked(int, int)信號是當表格中的一個cell單元被單擊時發出的。它的兩個參數分別爲表格中cell的行號和列號。
可使用frameGeometry()來得到程序中的主界面,而後該界面的定位函數能夠得到與主界面的相對位置,好比說frameGeometry().bottomLeft()就是得到主界面的左下方的位置。
當本身定義了的一個類,該類有對應的頭文件和源文件。若是在第二個類的頭文件中藥使用到第一個類,則能夠不用包含第一個類的頭文件,直接用class關鍵字聲明就能夠了,在第二個類的源文件中則須要包含第一個類的頭文件,由於這裏須要使用第一個類對象的成員方法。
Qt中正則表達式爲類QRegExp,正則表達式是指一個用來描述或者匹配一系列符合某個句法規則的字符串的單個字符串。好比說程序中的QRegExp rx("\\[\\d{2}:\\d{2}\\.\\d{2}\\]");其實就是表示歌詞文件前面的格式,好比[00:05.54]。表達式中的d{2}表示匹配2個數字。
Qt中常見的類的繼承總結:
若是須要設計界面,且須要菜單欄,工具欄,狀態欄等,通常繼承QMainWidget類。
若是須要界面,不須要菜單欄,工具欄,狀態欄等,通常繼承QDialog類。
若是須要使用自定義視圖來畫圖形,則能夠繼承QAbstractItem類。
若是須要本身設計場景,好比遊戲開發的時候,能夠繼承QGraphicsView類。
若是須要本身製做一個小圖形視圖,能夠考慮繼承QGraphicsObject類,當將這些小視圖構成一個視圖組時,該組的類能夠繼承QGraphicsItemGroup類和QObject類。
通常的界面設計也能夠繼承QWidget類。
通常的文本類能夠繼承QLabel,好比本實驗的桌面歌詞類MyLrc。
實驗結果
該實驗有打開播放文件,播放按鈕,暫停按鈕,選擇上一首歌按鈕,選擇下一首歌按鈕,顯示播放列表,單擊播放列表實現歌曲播放,動態顯示桌面歌詞,顯示歌曲總時長和已播放時長,調節音樂音量,最小化到系統托盤等功能,其截圖效果以下所示:
實驗主要部分代碼及註釋
mywidget.h:
#ifndef MYWIDGET_H #define MYWIDGET_H
#include <QWidget> #include <Phonon> #include <QSystemTrayIcon>
class QLabel; class MyPlaylist; class MyLrc;
namespace Ui { class MyWidget; }
class MyWidget : public QWidget { Q_OBJECT public: explicit MyWidget(QWidget *parent = 0); ~MyWidget(); private: Ui::MyWidget *ui; void InitPlayer(); Phonon::MediaObject *media_object; QAction *play_action; QAction *stop_action; QAction *skip_backward_action; QAction *skip_forward_action; QLabel *top_label; QLabel *time_label;
MyPlaylist *playlist; Phonon::MediaObject *meta_information_resolver; QList<Phonon::MediaSource> sources; void change_action_state();
MyLrc *lrc; QMap<qint64, QString> lrc_map; void resolve_lrc(const QString &source_file_name);
QSystemTrayIcon *tray_icon;
private slots: void UpdateTime(qint64 time); void SetPaused(); void SkipBackward(); void SkipForward(); void OpenFile(); void SetPlayListShown(); void SetLrcShown();
void StateChanged(Phonon::State new_state, Phonon::State old_state); void SourceChanged(const Phonon::MediaSource &source); void AboutToFinish(); void MetaStateChanged(Phonon::State new_state, Phonon::State old_state); void TableClicked(int row); void ClearSources();
void TrayIconActivated(QSystemTrayIcon::ActivationReason activation_reason);
protected: void closeEvent(QCloseEvent *);
};
#endif // MYWIDGET_H myplaylist.h: #ifndef MYPLAYLIST_H #define MYPLAYLIST_H #include <QTableWidget> class MyPlaylist : public QTableWidget { Q_OBJECT public: explicit MyPlaylist(QWidget *parent = 0); signals: void play_list_clean(); public slots: protected: void contextMenuEvent(QContextMenuEvent *); void closeEvent(QCloseEvent *); private slots: void clear_play_list(); }; #endif // MYPLAYLIST_H myplaylist.cpp: #include "myplaylist.h" #include <QContextMenuEvent> #include <QMenu> MyPlaylist::MyPlaylist(QWidget *parent) : QTableWidget(parent) { setWindowTitle(tr("播放列表")); //設置爲一個獨立的窗口,且只有一個關閉按鈕 setWindowFlags(Qt::Window | Qt::WindowTitleHint); resize(400, 400); setMaximumWidth(400); setMinimumWidth(400);//固定窗口大小 setRowCount(0);//初始的行數爲0 setColumnCount(3);//初始的列數爲1 //設置第一個標籤 QStringList list; list << tr("標題") << tr("歌手") << tr("長度"); setHorizontalHeaderLabels(list); setSelectionMode(QAbstractItemView::SingleSelection);//設置只能選擇單行 setSelectionBehavior(QAbstractItemView::SelectRows); setShowGrid(false);//設置不顯示網格 } void MyPlaylist::clear_play_list() { while(rowCount()) removeRow(0); emit play_list_clean();//刪除完後,發送清空成功信號 } void MyPlaylist::contextMenuEvent(QContextMenuEvent *event) { QMenu menu; menu.addAction(tr("清空列表"), this, SLOT(clear_play_list()));//能夠直接在這裏指定槽函數 menu.exec(event->globalPos());//返回鼠標指針的全局位置 } void MyPlaylist::closeEvent(QCloseEvent *event) { if(isVisible()) { hide(); event->ignore();//清零接收標誌 } } mylrc.h: #ifndef MYLRC_H #define MYLRC_H #include <QLabel> class QTimer; class MyLrc : public QLabel { Q_OBJECT public: explicit MyLrc(QWidget *parent = 0); void start_lrc_mask(qint64 intervaltime); void stop_lrc_mask(); protected: void paintEvent(QPaintEvent *); void mousePressEvent(QMouseEvent *ev); void mouseMoveEvent(QMouseEvent *ev); void contextMenuEvent(QContextMenuEvent *ev); signals: public slots: private slots: void timeout(); private: QLinearGradient linear_gradient; QLinearGradient mask_linear_gradient; QFont font; QTimer *timer; qreal lrc_mask_width; qreal lrc_mask_width_interval; QPoint offset; }; #endif // MYLRC_H mylrc.cpp: #include "mylrc.h" #include <QPainter> #include <QTimer> #include <QMouseEvent> #include <QContextMenuEvent> #include <QMenu> MyLrc::MyLrc(QWidget *parent) : QLabel(parent) { //FramelessWindowHint爲無邊界的窗口 setWindowFlags(Qt::Window | Qt::FramelessWindowHint); setAttribute(Qt::WA_TranslucentBackground); setText(tr("簡易音樂播放器")); // 固定顯示區域大小 setMaximumSize(800, 60); setMinimumSize(800, 60); //歌詞的線性漸變填充 linear_gradient.setStart(0, 10);//填充的起點座標 linear_gradient.setFinalStop(0, 40);//填充的終點座標 //第一個參數終點座標,相對於咱們上面的區域而言,按照比例進行計算 linear_gradient.setColorAt(0.1, QColor(14, 179, 255)); linear_gradient.setColorAt(0.5, QColor(114, 232, 255)); linear_gradient.setColorAt(0.9, QColor(14, 179, 255)); // 遮罩的線性漸變填充 mask_linear_gradient.setStart(0, 10); mask_linear_gradient.setFinalStop(0, 40); mask_linear_gradient.setColorAt(0.1, QColor(222, 54, 4)); mask_linear_gradient.setColorAt(0.5, QColor(255, 72, 16)); mask_linear_gradient.setColorAt(0.9, QColor(222, 54, 4)); // 設置字體 font.setFamily("Times New Roman"); font.setBold(true); font.setPointSize(30); // 設置定時器 timer = new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(timeout())); lrc_mask_width = 0; lrc_mask_width_interval = 0; } // 開啓遮罩,須要指定當前歌詞開始與結束之間的時間間隔 void MyLrc::start_lrc_mask(qint64 intervaltime) { // 這裏設置每隔30毫秒更新一次遮罩的寬度,由於若是更新太頻繁 // 會增長CPU佔用率,而若是時間間隔太大,則動畫效果就不流暢了 qreal count = intervaltime / 30; // 獲取遮罩每次須要增長的寬度,這裏的800是部件的固定寬度 lrc_mask_width_interval = 800 / count; lrc_mask_width = 0; timer->start(30); } void MyLrc::stop_lrc_mask() { timer->stop(); lrc_mask_width = 0; update(); } void MyLrc::paintEvent(QPaintEvent *) { QPainter painter(this); painter.setFont(font); // 先繪製底層文字,做爲陰影,這樣會使顯示效果更加清晰,且更有質感 painter.setPen(QColor(0, 0, 0, 200)); painter.drawText(1, 1, 800, 60, Qt::AlignLeft, text());//左對齊 // 再在上面繪製漸變文字 painter.setPen(QPen(linear_gradient, 0)); painter.drawText(0, 0, 800, 60, Qt::AlignLeft, text()); // 設置歌詞遮罩 painter.setPen(QPen(mask_linear_gradient, 0)); painter.drawText(0, 0, lrc_mask_width, 60, Qt::AlignLeft, text()); } //左擊操做 void MyLrc::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) offset = event->globalPos() - frameGeometry().topLeft(); } void MyLrc::mouseMoveEvent(QMouseEvent *event) { //移動鼠標到歌詞上時,會顯示手型 //event->buttons()返回鼠標點擊的類型,分爲左擊,中擊,右擊 //這裏用與操做表示是左擊 if (event->buttons() & Qt::LeftButton) { setCursor(Qt::PointingHandCursor); //實現移動操做 move(event->globalPos() - offset); } } //右擊事件 void MyLrc::contextMenuEvent(QContextMenuEvent *event) { QMenu menu; menu.addAction(tr("隱藏"), this, SLOT(hide())); menu.exec(event->globalPos());//globalPos()爲當前鼠標的位置座標 } void MyLrc::timeout() { //每隔一段固定的時間籠罩的長度就增長一點 lrc_mask_width += lrc_mask_width_interval; update();//更新widget,可是並不當即重繪,而是安排一個Paint事件,當返回主循環時由系統來重繪 } main.cpp: #include <QApplication> #include <QTextCodec> #include "mywidget.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); QTextCodec::setCodecForTr(QTextCodec::codecForLocale()); MyWidget w; w.show(); return a.exec(); }