博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Qt学习之路_14(简易音乐播放器)
阅读量:7100 次
发布时间:2019-06-28

本文共 9816 字,大约阅读时间需要 32 分钟。

hot3.png

 

  前言

  这一节实现一个简易的音乐播放器,其音乐播放的核心功能是采用Qt支持的Phonon框架,该框架在前一篇博文 中已经使用过了,在俄罗斯方块中主要是用来设置背景音乐和消行的声音的。这里用这个框架同样是用来播放,暂停等多媒体的各种控制功能,另外该框架可以自动获取音频文件的一些信息,这样我们在设计播放列表时可以获取这些信息,比如歌手名,专辑名,时长,文件名等等。程序中桌面歌词的实现是继承了QLabel类,然后使用3层文本显示,最上面一层采用渐进显示的方式来达到歌词播放的动态效果。

  实验的参考资料为 网站上yafei作者提供的代码,本人只是看懂其源码然后自己敲了一遍,代码做了稍微的改变,其设计方法和技巧全是原创作者yafei的功劳。

  开发环境:WindowsXP+Qt4.8.2+QtCreator2.5.1

   

 

  实验说明

 本实验没有使用QtDesigner来设计界面,其界面而是直接采用c++代码来写的。下面分以下几个方面来介绍本实验的实现过程中应该注意的知识点:

  播放界面设计部分:

  因为主界面的设计是从QWidget类继承而来,但是本程序却没有使用界面设计工具来设计界面,而是直接使用c++代码完成。

  在界面设计时,首先一般是设置窗口的标题,尺寸,图标等。然后然后本程序时在主界面上面添加了2个工具栏和一个标题栏,这3个栏目构成了播放器的主界面,主界面采用的是垂直布局,即QVBoxLayout. 2个工具栏分别为QAction,里面可以使用addAction ()方法直接插入action或者使用addWidget()方法插入widget。对action可以设置其快捷键,提示文本,图标,响应槽函数等。对于widget可以设置其显示内容,提示文本,尺寸属性,对其方式,如果外加网络连接,则也可以设置其是否链接到外部等。

在播放媒体文件时,媒体对象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 
#include 
#include 
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
 sources;    void change_action_state();
    MyLrc *lrc;    QMap
 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 
class MyPlaylist : public QTableWidget{    Q_OBJECTpublic:    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 
#include 
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 
class QTimer;class MyLrc : public QLabel{    Q_OBJECTpublic:    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 
#include 
#include 
#include 
#include 
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 
#include 
#include "mywidget.h"int main(int argc, char *argv[]){    QApplication a(argc, argv);    QTextCodec::setCodecForTr(QTextCodec::codecForLocale());    MyWidget w;    w.show();        return a.exec();}

转载于:https://my.oschina.net/u/174242/blog/221823

你可能感兴趣的文章
Struts2 系列问题一
查看>>
DP(优化) UVALive 6073 Math Magic
查看>>
java 编写小工具 尝试 学习(二)
查看>>
在平台中使用JNDI 数据源
查看>>
Windows下Java环境变量配置
查看>>
JS-JavaScript类库整理 [更新中...]
查看>>
分布式缓存的面试题11
查看>>
rep insw的用法小记
查看>>
程序员节诗词
查看>>
git远程仓库
查看>>
mysql之 重建GTID下主从关系
查看>>
Oracle 表空间与数据文件
查看>>
Centos创建ftp服务器
查看>>
python爬虫-url
查看>>
阿里云 phpwind帮助文档
查看>>
关于移动端键盘弹出
查看>>
h5 左右滑动切换tab栏
查看>>
js 类型判断
查看>>
cg资讯网址
查看>>
写给那些看不懂委托的同学
查看>>