「Qt5 开发日记」音乐播放器项目

0 程序界面布局

主要包含自定义标题栏 TitleBar、自定义播放器 PlayControlBar、自定义侧边栏 SideBar 和 主窗口界面 MusicPlayer

同时还自定义了进度条 Slider 以及弹出音量调节器 VolumePopup

目前大致的整体界面:

😭😭😭界面暂时还比较丑!


1 标题栏信号与槽

1.1 标题栏最大化/最小化窗口按钮信号与槽设计

这部分功能在创建的主窗口类 MusicPlayer 中包含以下信号与槽连接:

1
2
connect(ui->titleBar, &TitleBar::minimizeClicked, this, &MusicPlayer::onMinimizeClicked);
connect(ui->titleBar, &TitleBar::maximizeClicked, this, &MusicPlayer::onMaximizeClicked);

titleBar 是一个自定义 TitleBar 类,父类是 QWidget,类中设置了自定义信号:

1
2
3
signals:
void minimizeClicked(); // 最小化按钮点击
void maximizeClicked(); // 最大化/还原按钮点击

这 2 个自定义信号通过点击 titleBar 中的 btnMinimizebtnMaximize 发出。在 TitleBar 自定义类中的信号与槽连接设计为:

1
2
connect(ui->btnMinimize, &QPushButton::clicked, this, &TitleBar::onMinimizeClicked);
connect(ui->btnMaximize, &QPushButton::clicked, this, &TitleBar::onMaximizeClicked);

涉及的槽函数为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 发出自定义信号相关槽函数
void TitleBar::onMinimizeClicked()
{
emit minimizeClicked();
}

void TitleBar::onMaximizeClicked()
{
emit maximizeClicked();
}

// 按钮图标切换槽函数
void TitleBar::setMaximized(bool maximized)
{
// 根据最大化状态更新按钮图标
if (maximized) {
ui->btnMaximize->setText("❐"); // 还原图标
} else {
ui->btnMaximize->setText("□"); // 最大化图标
}
}

当点击 btnMinimize 时,会调用 TitleBar::onMinimizeClicked 函数,进而由 titleBar 发出自定义信号 minimizeClicked()。然后主窗口类 MusicPlayer 接收到该信号,会调用 MusicPlayer::onMinimizeClicked 函数,实现窗口的最小化。

对应的 MusicPlayer::onMinimizeClicked 处理函数如下:

1
2
3
4
void MusicPlayer::onMinimizeClicked()
{
showMinimized(); // 窗口最小化,缩到任务栏中
}

类似的逻辑,主窗口类 MusicPlayer 接收到 titleBar 发出自定义信号 maximizeClicked(),会调用 MusicPlayer::onMaximizeClicked 函数,实现窗口的最大化。

对应的 MusicPlayer::onMaximizeClicked 处理函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void MusicPlayer::onMaximizeClicked()
{
if (m_isMaximized) {
// 还原窗口,还原到先前窗口大小
setGeometry(m_normalGeometry);
m_isMaximized = false;
ui->titleBar->setMaximized(false);
} else {
// 保存当前位置和大小
m_normalGeometry = geometry();

// 最大化到屏幕可用区域(不包括任务栏)
QScreen *screen = QGuiApplication::primaryScreen();
QRect availableGeometry = screen->availableGeometry();
setGeometry(availableGeometry);

m_isMaximized = true;
ui->titleBar->setMaximized(true);
}
}
  • m_isMaximized 是 MusicPlayer 的成员变量,用于判断当前窗口是否已经处于最大化;
  • m_normalGeometry 是 MusicPlayer 中用于记录窗口大小的变量。

ui->titleBar->setMaximized(bool); 是调用 TitleBar 类的最大化按钮图标更改函数。

  • 当参数为 true 时,说明窗口被最大化,图标被改为
  • 当参数为 false 时,说明窗口不是最大化状态,图标被改为

1.2 标题栏关闭窗口、双击按钮信号与槽设计

这部分功能在创建的主窗口类 MusicPlayer 中包含以下信号与槽连接:

1
2
connect(ui->titleBar, &TitleBar::closeClicked, this, &MusicPlayer::onCloseClicked);
connect(ui->titleBar, &TitleBar::doubleClicked, this, &MusicPlayer::onMaximizeClicked);

TitleBar::closeClickedTitleBar::doubleClicked 是自定义 TitleBar 类的自定义信号,分别通过点击 titleBar 中的 btnClose 按钮和双击标题栏区域时发出:

1
connect(ui->btnClose, &QPushButton::clicked, this, &TitleBar::onCloseClicked);
1
2
3
4
void TitleBar::onCloseClicked()
{
emit closeClicked(); // 发出自定义关闭窗口信号
}

重写一下 mouseDoubleClickEvent 事件,使其在鼠标左键双击当前组件(标题栏)时,可以发出自定义 doubleClicked() 信号,进而实现双击标题栏来回切换窗口最大化和非最大化状态:

1
2
3
4
5
6
7
void TitleBar::mouseDoubleClickEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
emit doubleClicked();
}
QWidget::mouseDoubleClickEvent(event);
}

2 添加音乐信号与槽

2.1 导入文件夹

在窗口类 MusicPlayer 中,有一个 btnImportFolder 按钮,用于导入文件夹中所有的音频文件。相关信号与槽连接如下:

1
connect(ui->btnImportFolder, &QPushButton::clicked, this, &MusicPlayer::onImportFolder);

调用的槽函数 MusicPlayer::onImportFolder

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
void MusicPlayer::onImportFolder()
{
QString folderPath = QFileDialog::getExistingDirectory(
this,
"选择音乐文件夹",
"",
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks
);

if (!folderPath.isEmpty()) {
QDir dir(folderPath);
dir.setSorting(QDir::Name);
QStringList filters;
filters << "*.mp3" << "*.wav" << "*.flac" << "*.ogg" << "*.m4a" << "*.aac" << "*.wma" << "*.opus";
QStringList files = dir.entryList(filters, QDir::Files);

m_playlist.clear();
for (const QString &fileName : files) {
m_playlist.append(dir.absoluteFilePath(fileName));
}

if (!m_playlist.isEmpty()) {
m_currentIndex = 0;
QList<MusicInfo> list;
for (const QString &path : m_playlist) {
list.append(MusicListModel::fromFilePath(path));
}
ui->musicListView->setMusicList(list); // 设置音乐列表
m_metadataLoader->loadMetadataForFiles(m_playlist);
loadAndPlayMusic(m_playlist.at(m_currentIndex));
}
}
}

当点击这个按钮时,会弹出一个选择文件夹的窗口,并过滤 .mp3、.wav 等格式的音频文件。在 MusicPlayer 类中,有一个 QStringList 类型的 m_playerlist 列表,用于临时存储加载的音乐列表。这些经过过滤的音频文件路径被加入到 m_playerlist 中。

ui->musicListView->setMusicList(list); // 设置音乐列表
m_metadataLoader->loadMetadataForFiles(m_playlist);
这部分是有关音乐列表和音乐元数据显示的,后续有解析

m_currentIndex 用于记录播放的索引(播放到第几首音乐)。

当加载完音乐数据后,会调用 loadAndPlayMusic 播放加载的第一首音乐。loadAndPlayMusic 函数如下:

1
2
3
4
5
void MusicPlayer::loadAndPlayMusic(const QString &filePath)
{
m_player->setMedia(QMediaContent(QUrl::fromLocalFile(filePath)));
m_player->play();
}

loadAndPlayMusic 函数会加载指定路径 filePath 的音乐文件音频数据,然后开始播放。

2.2 添加音乐

添加音乐其实和导入音乐文件夹差不多,区别在于加载的是打开的文件。可以批量选择一些文件打开加载到音乐列表中。

在窗口类中相关信号与槽连接:

1
connect(ui->btnImportFiles, &QPushButton::clicked, this, &MusicPlayer::onImportFiles);

MusicPlayer::onImportFiles 函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void MusicPlayer::onImportFiles()
{
QStringList fileNames = QFileDialog::getOpenFileNames(
this,
"选择音频文件",
"",
"音频文件 (*.mp3 *.wav *.flac *.ogg *.m4a *.aac *.wma *.opus);;MP3 文件 (*.mp3);;WAV 文件 (*.wav);;FLAC 文件 (*.flac);;所有文件 (*)"
);

if (!fileNames.isEmpty()) {
m_playlist = fileNames;
m_currentIndex = 0;
QList<MusicInfo> list;
for (const QString &path : m_playlist) {
list.append(MusicListModel::fromFilePath(path));
}
ui->musicListView->setMusicList(list);
m_metadataLoader->loadMetadataForFiles(m_playlist);
loadAndPlayMusic(m_playlist.at(m_currentIndex));
}
}

3 播放控制栏信号与槽

3.1 音乐的播放和暂停

这部分功能在窗口类 MusicPlayer 中包含如下信号与槽链接:

1
2
3
4
5
6
7
connect(ui->playControlBar, &PlayControlBar::playPauseClicked, this, [this](){
if(m_player->state() == QMediaPlayer::PlayingState){
m_player->pause();
}else{
m_player->play();
}
});

m_player 是创建的窗口类中一个 QMediaPlayer 类型的播放器成员变量,用于加载音频,进行音频的播放。

playControlBar 是一个自定义 PlayControlBar 类。PlayControlBar::playPauseClicked 信号在点击 playControlBar 中的 btnPlayPause 按钮时发出。

在 PlayControlBar 中相关的信号与槽连接如下:

1
connect(ui->btnPlayPause, &QPushButton::clicked, this, &PlayControlBar::onPlayPauseClicked);
1
2
3
4
void PlayControlBar::onPlayPauseClicked()
{
emit playPauseClicked();
}

当点击这个播放控制栏中的 btnPlayPause 按钮时,会发出 QPushButton::clicked 信号,然后当前播放控制栏会调用 PlayControlBar::onPlayPauseClicked() 函数。这个函数的行为就是发出自定义 PlayControlBar::playPauseClicked 信号。

上述这些信号与槽连接实现了点击 播放控制栏中的 btnPlayPause 按钮 来控制播放器 播放状态 和 暂停状态的切换。


在 PlayControlBar 类中,还有一个用于切换 btnPlayPause 按钮图标的函数。当播放器 m_player 处于播放状态时,点击按钮后需要播放器暂停,此时需要切换成暂停的图标;反之亦然。

用于切换 btnPlayPause 按钮图标的成员函数 PlayControlBar::updatePlayState 如下:

1
2
3
4
5
6
7
8
9
void PlayControlBar::updatePlayState(bool isPlaying)
{
// 更新播放/暂停按钮图标
if (isPlaying) {
ui->btnPlayPause->setIcon(QIcon(":/icons/play.svg"));
} else {
ui->btnPlayPause->setIcon(QIcon(":/icons/pause.svg"));
}
}

在窗口的 m_player 成员发生状态变化时,需要调用函数以切换 playControlBar 中的 btnPlayPause 按钮的图标。涉及的信号与槽连接如下:

1
2
3
4
connect(m_player, &QMediaPlayer::stateChanged, this, [this](QMediaPlayer::State state){
ui->playControlBar->updatePlayState(state == QMediaPlayer::PlayingState);
ui->musicListView->setCurrentPlaying(m_currentIndex, state == QMediaPlayer::PlayingState);
});

ui->musicListView->setCurrentPlaying(m_currentIndex, state == QMediaPlayer::PlayingState); 是和音乐列表相关的内容,后续会有解析。

窗口的 m_player 成员发生状态变化发出 QMediaPlayer::stateChanged 信号,携带一个 QMediaPlayer::State 类型的状态参数,然后调用 playControlBarupdatePlayState 成员函数,以切换播放控制栏中的播放按钮图标。


3.2 播放模式的切换

playControlBar 中有一个切换播放模式的按钮 btnPlayMode,可以切换三种播放模式,包括 顺序播放(默认)、随机播放 和 单曲循环。对应的信号与槽连接如下:

1
connect(ui->btnPlayMode, &QPushButton::clicked, this, &PlayControlBar::onPlayModeClicked);

点击按钮会调用函数 PlayControlBar::onPlayModeClicked,该函数会发出自定义信号:

1
2
3
4
void PlayControlBar::onPlayModeClicked()
{
emit playModeClicked();
}

playControlBar 发出的自定义信号被主窗口接收,并调用对应的播放模式切换函数:

1
connect(ui->playControlBar, &PlayControlBar::playModeClicked, this, &MusicPlayer::onPlayModeClicked);

MusicPlayer::onPlayModeClicked 函数内容如下:

1
2
3
4
5
void MusicPlayer::onPlayModeClicked()
{
m_playbackMode = static_cast<PlaybackMode>((m_playbackMode + 1) % 3);
updatePlayModeIcon();
}
  • m_playbackModeMusicPlayer 类中的 PlaybackMode 类(一个自定义枚举类)成员变量(数值从 0~2);

  • PlaybackMode 自定义枚举类如下:

    1
    2
    3
    4
    5
    6
    // 播放模式
    enum PlaybackMode {
    SequentialLoop, // 顺序循环
    SingleLoop, // 单曲循环
    Shuffle // 随机
    };
  • updatePlayModeIcon() 用于更新播放模式按钮的图标:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    void MusicPlayer::updatePlayModeIcon()
    {
    QIcon icon;
    QString tooltip;
    switch (m_playbackMode) {
    case SequentialLoop:
    icon = QIcon(":/icons/list_loop.svg");
    tooltip = tr("顺序循环");
    break;
    case SingleLoop:
    icon = QIcon(":/icons/single_loop.svg");
    tooltip = tr("单曲循环");
    break;
    case Shuffle:
    icon = QIcon(":/icons/random.svg");
    tooltip = tr("随机播放");
    break;
    }
    ui->playControlBar->setPlayModeIcon(icon, tooltip);
    }

    在该函数中,会先根据 m_playbackMode 判断需要更改的图标和按钮提示是什么样的,然后再调用 playControlBarsetPlayModeIcon 成员函数进行更改。

    1
    2
    3
    4
    5
    6
    7
    8
    void PlayControlBar::setPlayModeIcon(const QIcon &icon, const QString &tooltip)
    {
    ui->btnPlayMode->setIcon(icon); // 修改按钮图标
    ui->btnPlayMode->setIconSize(QSize(20, 20));
    if (!tooltip.isEmpty()) {
    ui->btnPlayMode->setToolTip(tooltip); // 修改按钮提示
    }
    }

3.3 上一首/下一首音乐

PlayControlBar 类中,对应的信号与槽连接如下:

1
2
connect(ui->btnPrevious, &QPushButton::clicked, this, &PlayControlBar::onPreviousClicked);
connect(ui->btnNext, &QPushButton::clicked, this, &PlayControlBar::onNextClicked);

当点击播放控制栏中的上一首按钮 btnPrevious / 下一首按钮 btnNext,会调用 PlayControlBar::onPreviousClicked / PlayControlBar::onNextClicked 函数。这两个函数会发出自定义信号,在主窗口 MusicPlayer 中被接收再进行后续的上一首/下一首音乐的处理:

1
2
3
4
5
6
7
8
9
void PlayControlBar::onPreviousClicked()
{
emit previousClicked();
}

void PlayControlBar::onNextClicked()
{
emit nextClicked();
}

MusicPlayer 类中,对应的信号与槽连接如下:

1
2
connect(ui->playControlBar, &PlayControlBar::previousClicked, this, &MusicPlayer::playPrevious);
connect(ui->playControlBar, &PlayControlBar::nextClicked, this, &MusicPlayer::playNext);

MusicPlayer::playPrevious 函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void MusicPlayer::playPrevious()
{
if (m_playlist.isEmpty()) return;

switch (m_playbackMode) {
case SequentialLoop:
m_currentIndex--;
if (m_currentIndex < 0) m_currentIndex = m_playlist.size() - 1;
break;
case SingleLoop:
m_currentIndex--;
if (m_currentIndex < 0) m_currentIndex = m_playlist.size() - 1;
break;
case Shuffle:
if (m_playlist.size() == 1) break;
m_currentIndex = QRandomGenerator::global()->bounded(m_playlist.size());
break;
}
loadAndPlayMusic(m_playlist.at(m_currentIndex));
}
  • 当播放模式为顺序播放或者单曲循环时,通过 m_currentIndex-- 来改变播放的索引;
  • 当播放模式为随机播放时,会通过 QRandomGenerator 随机生成一个小于 m_playlist.size() 的索引大小。

最后调用 loadAndPlayMusic 加载对应索引的音乐文件路径并播放音乐。

MusicPlayer::playNext 函数如下,基本思路也是差不多的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void MusicPlayer::playNext()
{
if (m_playlist.isEmpty()) return;

switch (m_playbackMode) {
case SequentialLoop:
m_currentIndex++;
if (m_currentIndex >= m_playlist.size()) m_currentIndex = 0;
break;
case SingleLoop:
m_currentIndex++;
if (m_currentIndex >= m_playlist.size()) m_currentIndex = 0;
break;
case Shuffle:
if (m_playlist.size() == 1) break;
m_currentIndex = QRandomGenerator::global()->bounded(m_playlist.size());
break;
}
loadAndPlayMusic(m_playlist.at(m_currentIndex));
}

3.4 音量的控制

在播放控制栏 playControlBar 中,有一个按钮 btnVolume。点击这个按钮会弹出一个自定义音量调节弹窗,这个音量调节弹窗是一个自定义 VolumePopup 类的成员,在 playControlBar 构造函数中被初始化:

1
2
3
4
connect(ui->btnVolume, &QPushButton::clicked, this, &PlayControlBar::onVolumeButtonClicked);

// 音量弹窗
m_volumePopup = new VolumePopup(this);

PlayControlBar::onVolumeButtonClicked 函数内容如下:

1
2
3
4
5
6
7
void PlayControlBar::onVolumeButtonClicked()
{
if (m_volumePopup) {
QPoint pos = ui->btnVolume->mapToGlobal(QPoint(ui->btnVolume->width() / 2, 0));
m_volumePopup->showAt(pos);
}
}

点击 btnVolume 按钮后,会在指定的位置显示这个自定义弹窗。

VolumePopup 类自定义弹窗包含一个按钮 m_btnMute,用于调节是否静音;一个 Slider 类音量调节条 m_slider,用于调节音量。

VolumePopup 类中相关的信号与槽连接如下:

1
2
3
connect(m_btnMute, &QPushButton::toggled, this, &VolumePopup::onMuteToggled);
connect(m_slider, &QSlider::valueChanged, this, &VolumePopup::onSliderValueChanged);
connect(m_slider, &Slider::sliderClicked, this, &VolumePopup::onVolumeSliderClicked);

在这里使用的是 QPushButton::toggled 信号,使得按钮状态发生改变时,就会出发槽函数(而不是用 clicked,只能点击才会触发槽函数)。主要是因为当音量条调节为 0 时,m_btnMute 也需要改变为静音的状态。

VolumePopup::onMuteToggled 函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void VolumePopup::onMuteToggled(bool checked)
{
if (checked) {
m_volumeBeforeMute = m_slider->value();
m_slider->blockSignals(true);
m_slider->setValue(0);
m_slider->blockSignals(false);
m_btnMute->setIcon(QIcon(":/icons/slient.svg"));
m_btnMute->setToolTip(tr("取消静音"));
emit valueChanged(0); // 静音时音量设为 0
} else {
int restore = m_volumeBeforeMute > 0 ? m_volumeBeforeMute : 70;
m_slider->blockSignals(true);
m_slider->setValue(restore);
m_slider->blockSignals(false);
m_btnMute->setIcon(QIcon(":/icons/volume.svg"));
m_btnMute->setToolTip(tr("静音"));
emit valueChanged(restore); // 取消静音时恢复
}
emit muteToggled(checked);
}

VolumePopup::onMuteToggled 函数会先将 m_volumePopup 内的音量条和静音按钮进行修改,然后发出自定义 valueChangedmuteToggled 信号。这些信号在 playControlBar 中被接收:

1
2
connect(m_volumePopup, &VolumePopup::valueChanged, this, &PlayControlBar::onVolumeChanged);
connect(m_volumePopup, &VolumePopup::muteToggled, this, &PlayControlBar::onVolumeMuteToggled);

触发的槽函数如下,用于再次发出信号传递给主窗口类:

1
2
3
4
5
6
7
8
9
10
11
void PlayControlBar::onVolumeMuteToggled(bool muted)
{
emit volumeMuteToggled(muted);
}

void PlayControlBar::onVolumeChanged(int value)
{
// 更新 btnVolume 图标以反映音量状态(仅视觉,不提供静音功能)
ui->btnVolume->setIcon(value > 0 ? QIcon(":/icons/volume.svg") : QIcon(":/icons/slient.svg"));
emit volumeChanged(value);
}

在主窗口类 MusicPlayer 中接收到这些信号时,对播放器 m_player 进行修改:

1
2
3
4
5
6
7
8
connect(ui->playControlBar, &PlayControlBar::volumeChanged, this, [this](int volume){
m_player->setVolume(volume); // 调节播放器音量
});
connect(ui->playControlBar, &PlayControlBar::volumeMuteToggled, this, [this](bool muted){
if (muted) {
m_player->setVolume(0); // 设置静音
}
});

VolumePopup 类中有一个 Slider 自定义类(重写了点击轨道事件 「Qt5 开发日记」极简音乐播放 Demo 3.2 点击进度条功能的实现)音量条 m_slider,相关信号与槽连接如下:

1
2
connect(m_slider, &QSlider::valueChanged, this, &VolumePopup::onSliderValueChanged);
connect(m_slider, &Slider::sliderClicked, this, &VolumePopup::onVolumeSliderClicked);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void VolumePopup::onSliderValueChanged(int value)
{
if (value > 0) {
m_volumeBeforeMute = value;
}
updateMuteButtonFromValue(value); // 是否调节到了0,需要改为静音
emit valueChanged(value);
}

void VolumePopup::onVolumeSliderClicked(qint64 value)
{
int v = qBound(0, static_cast<int>(value), 100);
m_slider->blockSignals(true);
m_slider->setValue(v); // 点击精确调节音量
m_slider->blockSignals(false);
if (v > 0) {
m_volumeBeforeMute = v;
}
updateMuteButtonFromValue(v);
emit valueChanged(v);
}

音量条被调节后,会发出 VolumePopup 中的自定义 valueChanged 信号,由 playControlBar 接收。


3.5 播放进度条

PlayControlBar 中有一个 progressSlider 音乐进度条,用于调节音乐的播放进度。这个进度条是一个 Slider 类,同样重写了鼠标点击事件,进而能够点击进度条位置实现播放位置的跳转。

PlayControlBar 类中的音乐进度条相关信号与槽连接如下:

1
2
3
4
5
6
7
8
connect(ui->progressSlider, &Slider::sliderClicked, this, &PlayControlBar::onSliderClicked);
connect(ui->progressSlider, &QSlider::sliderPressed, this, [this](){
m_sliderPressed = true;
});
connect(ui->progressSlider, &QSlider::sliderReleased, this, [this](){
m_sliderPressed = false;
emit progressChanged(ui->progressSlider->value());
});
  • connect(ui->progressSlider, &Slider::sliderClicked, this, &PlayControlBar::onSliderClicked); 用于音乐进度条的位置点击跳转;
  • connect(ui->progressSlider, &QSlider::sliderPressed ... 用于当点住进度条滑块时,将 m_sliderPressed 设置为正在拖拽的状态。(m_sliderPressed 用于判断当前是否正在拖拽进度条滑块)
  • connect(ui->progressSlider, &QSlider::sliderReleased ... 用于释放进度条滑块时,将 m_sliderPressed 设置为未拖拽的状态,并发出一个自定义信号 progressChanged。这个信号被主窗口 MusicPlayer 接收,然后调节 m_player 播放器的音量。

MusicPlayer 中,接收到 PlayControlBar 发出的 自定义信号 progressChanged 时,触发播放器播放位置的修改:

1
2
3
connect(ui->playControlBar, &PlayControlBar::progressChanged, this, [this](qint64 position){
m_player->setPosition(position);
});

反过来播放器播放位置的变化也要促成 playControlBar 中进度条的修改。因此,在 MusicPlayer 中需要如下信号与槽连接:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 连接播放器信号更新 UI
connect(m_player, &QMediaPlayer::durationChanged, this, [this](qint64 duration){
ui->playControlBar->updateDuration(duration);
});

connect(m_player, &QMediaPlayer::positionChanged, this, [this](qint64 position){
ui->playControlBar->updateProgress(position);
});

connect(m_player, &QMediaPlayer::stateChanged, this, [this](QMediaPlayer::State state){
ui->playControlBar->updatePlayState(state == QMediaPlayer::PlayingState);
ui->musicListView->setCurrentPlaying(m_currentIndex, state == QMediaPlayer::PlayingState);
});

对应的处理函数如下:

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
void PlayControlBar::updateDuration(qint64 duration)
{
// 设置进度条范围为实际音频时长(毫秒数)
ui->progressSlider->setRange(0, duration);
ui->lblTotalTime->setText(formatTime(duration));
ui->lblCurrentTime->setText("0:00");
}

void PlayControlBar::updateProgress(qint64 position)
{
// 拖拽时不更新进度条显示,
// 否则播放器和用于同时控制进度条滑块而导致冲突,进而导致跳动的不佳效果
if (!m_sliderPressed) {
ui->progressSlider->blockSignals(true);
ui->progressSlider->setValue(position);
ui->progressSlider->blockSignals(false);
}

ui->lblCurrentTime->setText(formatTime(position));
}

void PlayControlBar::updatePlayState(bool isPlaying)
{
// 更新播放/暂停按钮图标
if (isPlaying) {
ui->btnPlayPause->setIcon(QIcon(":/icons/play.svg"));
} else {
ui->btnPlayPause->setIcon(QIcon(":/icons/pause.svg"));
}
}


「Qt5 开发日记」音乐播放器项目
https://marisamagic.github.io/2026/03/03/20260303/
作者
MarisaMagic
发布于
2026年3月3日
许可协议