Endsieg77's Studio.

C++组WOC--QMediaPlayer

2021/01/25 Share

经过多日摸鱼,我终于摆脱了前端的泥淖,今天是时候开始重拾我心爱的C艹了。


One


呃,学长给的这demo看起来有一点点简陋。但至少可以播放。image-20210125073347065

算了,先来看看他的代码实现吧。

image-20210125073711079

显然,mainwindow里面放的是主界面,playerlist是导歌的组件


playerlist


PlayerList类定义了信号/槽函数如下:

1
2
3
4
signals:

public slots:
void openFileButtonClicked();

从名字看是点击打开文件按钮之后的事件。

不妨进入源码:

首先是PlayerList类的构造函数:(这里我自行添加了注释)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
PlayerList::PlayerList(QWidget *parent) : QWidget(parent)
{
// 设置大小
this -> resize(200, 600);
// openFile是一个QPushButton,用来提交文件
openFile = new QPushButton(this);
// 设置文本,tr()函数用于i18n
openFile -> setText(tr("添加文件"));
//openFile -> move(0, 0);
openFile -> show();
// 音乐列表 类型QPlainTextEdit
musicList = new QPlainTextEdit(this);
musicList -> resize(200, 580);
musicList -> move(0, 20);
musicList -> setEnabled(false);
musicList -> show();
// 播放器,和播放列表(类似一个存放待播放音乐的数组?)
player = new QMediaPlayer(this);
playlist = new QMediaPlaylist(this);
playlist->setPlaybackMode(QMediaPlaylist::Loop);//设置循环模式
player->setPlaylist(playlist);
// 将打开文件按钮与openFileButtonClicked函数连接
connect(this->openFile, &QPushButton::clicked, this, &PlayerList::openFileButtonClicked);
}

然后是openFileButtonClicked

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void PlayerList::openFileButtonClicked(){
QString curPash=QDir::currentPath();
QString dlgTitle="选择音频文件";
QString filter=
"音频文件(*.mp3 *.wav *.wma)mp3文件(*.mp3);;wav文件(*.wav);;wma文件(*.wma);;所有文件(*.*)";
// 开启打开文件窗口
fileList = QFileDialog::getOpenFileNames(this,dlgTitle,curPash,filter);
if(fileList.count()<1)
return;
// 支持打开多个文件,这里有问题!同一个文件可以多次打开
for(int i = 0;i<fileList.count();i++)
{
musicList->appendPlainText(fileList.at(i));
//将选择的文件显示在文本框上
}
for(int i = 0;i<fileList.count();i++)
{
QString aFile = fileList.at(i);
playlist->addMedia(QUrl::fromLocalFile(aFile));
QFileInfo fileInfo(aFile);
}
if(player->state()!=QMediaPlayer::PlayingState)
{
playlist->setCurrentIndex(0);
}
//播放
player->play();
}

mainwindow


MainWindow构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
list = new PlayerList(this);
list -> move(600, 0);
list -> show();
// 连接槽函数和事件
connect(list->player, &QMediaPlayer::positionChanged, this, &MainWindow::onPositionChanged);
connect(list->player, &QMediaPlayer::durationChanged, this, &MainWindow::onDurationChanged);
//进度条
}

// 两个进度条相关的函数
void MainWindow::onDurationChanged(qint64 duration){
ui->horizontalSlider->setMaximum(duration); //设置进度条最大值 也就是歌曲时长 ms
int secs = duration/1000; //全部秒数
int mins = secs/60;//分
secs = secs % 60;//秒
// 修改总共的时间,asprintf格式化取得字符串
durationTime = QString::asprintf("%d:%d",mins,secs);
// 更改ui的字符串显示
ui->label->setText(positionTime+"/"+durationTime);
}

void MainWindow::onPositionChanged(qint64 position){
if(ui->horizontalSlider->isSliderDown())
return;//如果手动调整进度条,则不处理
ui->horizontalSlider->setSliderPosition(position);
// 计算经过的时间
int secs = position/1000;
int mins = secs/60;
secs = secs % 60;
positionTime = QString::asprintf("%d:%d",mins,secs);
ui->label->setText(positionTime+"/"+durationTime);
}

// 这个构造函数被暗地里调用 因此是private
void MainWindow::on_horizontalSlider_sliderReleased()
{
list->player->setPosition(ui->horizontalSlider->value());
}

好的,源码分析就到这里。

定个小目标,今天弄成这样:

过会就开工!


Two Records


过会(指摸了三天然后通关了宝可梦再开始。

我发现QT的examples里就有musicplayer的样例,非常好啊!

准备先阅读一番。


QT_FORWARD_DECALARE_CLASS()

一个QT的预处理宏,

1
#define  QT_FORWARD_DECALARE_CLASS(name) class name

用来前向声明,避免不需要的include。


Q_PROPERTY(“property”, #arg)


Example:

1
2
3
/* 对于一个属性volume,靠volume函数获取其值,靠setVolume改变其值,
并且和槽函数volumeChanged连接 */
Q_PROPERTY(int volume READ volume WRITE setVolume NOTIFY volumeChanged)



QToolButton的PopupMode

链接

一般分为三种:

  • QToolButton::DelayedPopup 点击一定时间和单击立刻松开的效果有别。
  • QToolButton::MenuButtonPopup 按钮上会有一个箭头指示有弹出菜单存在。
  • QToolButton::InstantPopup 一经点击,立刻弹出。

QOverload<>::of()

当某函数有多个重载版本时,可以用该函数来指定调用哪个版本的重载函数。

多用于信号槽的连接。

例如:

1
connect(slider, &QAbstractSlider::valueChanged, label, QOverload<int>::of(&QLabel::setNum));

将Slider值改变的事件与setNum以int为参数的版本连接。


QBoxLayout

用于布局的类。

其有两个子类。

QHBoxLayout,用于水平方向布局。QVBoxLayout,用于竖直方向布局。


QStandardPaths::standardLocations()

获取系统标准中某一类型文件的存储路径列表(QStringList)。

1
fileDialog.setDirectory(QStandardPaths::standardLocations(QStandardPaths::MusicLocation).value(0, QDir::homePath()));

上面这段代码的意思是,将fileDialog的初始位置设置在标准音乐路径的索引0处(一般系统标准下只会有这一个路径),若该列表为空,则进入$HOME返回的路径。

value函数继承自QList,有两个重载版本:

  • template<class T> T QList<T>:value(int index) 返回列表中第i个元素;
  • template<class T> T QList<T>:value(int index, const T &default) 返回列表第i个元素,若列表无第i个元素,则返回default的引用

QWidget::setToolTip(const QString&)

设置Widget对象的悬停显示文本。
我们常常看到setToolTip与tr同用。这里的tr全称是translate,是用于国际化的一个函数。
More Details See This


Inside Sample Object Tree

这个样例播放器的布局,完全依赖与QBoxLayout这个类。不妨让我们看看它的层级结构。

  • MusicPlayer: public QWidget
    • controlLayout(QHBoxLayout)
      • openButton(QToolButton *, has a ToolTip “Open a file…”, connected with openFile)
      • playButton(QToolButton *, has a ToolTip “Play” or “Pause”, connected with togglePlayBack)
      • positionSlider(QSlider, Qt::Horizontal, has a ToolTip “Seek”)
      • volumeButton(QToolButton *, has a ToolTip “Adjust Volume”, a menu popping up after user clicking the button)
        • menu(QMenu, has an QWidgetAction action)
        • popup(QWidget *, set as the DefaultWidget of action)
        • popupLayout(QHBoxLayout)
          • slider(QSlider, Qt::Horizontal, triggering volumeChanged when its value changed)
          • label(QLabel, explicitly shows the volume, num changed as the slider’s value changing)

set icon of buttons

btn->setIcon(QPixmap(“:/rsc/filename”))


Pass parameters from signals to slots

信号函数的参数数量和类型注意要与槽函数的一致. 这个问题我也花了好久解决. 写这个QT真是踩一堆坑, 难受.


Into MusicPlayer::setPosition

今天解决了一个之前的疑惑.
直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void MusicPlayer::init()
{
...
/** 这里的valueChanged为什么不直接和player的setPosition connect呢? 是个好问题:
* 因为这样会造成卡顿.
* 只要sliderBar的值改变都会触发player的setPosition
* 但我们需要的是什么呢? 是手动调整sliderBar时才触发这一事件.
* 故MusicPlayer::setPosition中设置了if条件, 就是为了避免这一错误.
*/
connect(sliderBar, &QSlider::valueChanged, this, &MusicPlayer::setPosition);
...
}

void MusicPlayer::setPosition(int pos)
{
if(qAbs(player->position() - pos) > 99)
player->setPosition(pos);
}

Three 2021/2/5 Updation

image-20210205025700745

看起来有点样子了. 开心.


Four

因为有恶性bug把前一个版本改成了后一个 出来混早晚要还的.

明天早上起来改改, 准备准备收工了.


Five

关于TitleBar继承QWidget样式失效的问题

解决方法:

重写paintEvent方法.


Six

暂时不改了, 这是仓库地址: View Source Code Here

CATALOG
  1. 1. One
    1. 1.1. playerlist
    2. 1.2. mainwindow
  2. 2. Two Records
    1. 2.1. QT_FORWARD_DECALARE_CLASS()
    2. 2.2. Q_PROPERTY(“property”, #arg)
    3. 2.3. QToolButton的PopupMode
    4. 2.4. QOverload<>::of()
    5. 2.5. QBoxLayout
    6. 2.6. QStandardPaths::standardLocations()
    7. 2.7. QWidget::setToolTip(const QString&)
    8. 2.8. Inside Sample Object Tree
    9. 2.9. set icon of buttons
    10. 2.10. Pass parameters from signals to slots
    11. 2.11. Into MusicPlayer::setPosition
  3. 3. Three 2021/2/5 Updation
  4. 4. Four
  5. 5. Five
  6. 6. Six