使用Qt6实现简易音乐播放器,效果如下:
github:
Gabriel-gxb/VideoPlayer: qt6实现简易视频播放器
一、整体架构
该代码整体架构围绕着MainWindow类构建一个媒体播放器相关的应用程序。
主要组件
(一)界面组件(通过UI文件关联)
1.ui成员变量:由Ui::MainWindow生成的界面相关对象,通过setupUi函数在MainWindow构造函数中进行初始化设置。这个界面包含了如slider_volume(音量滑块)、slider_progress(进度滑块)、video_widget(视频显示部件)、pushButton_start(开始按钮)、pushButton_full(全屏按钮)、label_time(时间显示标签)等多个用于交互和显示的控件。
(二)媒体播放组件
1.player对象:QMediaPlayer类型,是整个媒体播放功能的核心。在MainWindow构造函数中创建,并设置了音频输出(QAudioOutput对象)和视频输出(ui->video_widget)。
2.QAudioOutput对象:用于处理音频输出相关功能,与player关联,在MainWindow构造函数中创建并配置。
(三)事件处理相关
1.事件过滤器(eventFilter函数):在MainWindow类中实现。用于处理特定对象(ui->slider_volume、ui->slider_progress、ui->video_widget)的特定事件(鼠标移动、键盘按键等)。
2.信号 - 槽连接(connect函数):用于连接player对象的多个信号(durationChanged、positionChanged、playbackStateChanged)到MainWindow类中的lambda表达式,实现对媒体播放状态变化的响应并更新界面相关显示。
架构中的交互关系
(一)界面与媒体播放组件的交互
1.在MainWindow构造函数中,将player的视频输出设置为ui->video_widget,从而建立了界面显示部件与媒体播放核心的关联。
2.通过多个connect函数建立的信号 - 槽连接,实现了媒体播放状态(时长、当前位置、播放状态等)到界面显示(如滑块位置、标签文本等)的更新。
(二)事件处理与其他组件的交互
1.在eventFilter函数中,针对不同的被观察对象(ui->slider_volume、ui->slider_progress、ui->video_widget)的特定事件进行处理。
2.当ui->slider_volume发生鼠标移动事件时,获取滑块值并在鼠标位置显示工具提示,实现了界面操作与用户提示信息的交互。
3.对于ui->slider_progress的鼠标移动事件,根据player的播放源情况,在鼠标位置显示播放位置和总时长信息,关联了进度滑块操作与媒体播放信息。
4.当ui->video_widget有键盘按键事件时,根据不同的按键(如Esc键和Space键)执行相应操作,如退出全屏、暂停/播放媒体等,将键盘操作与视频显示及媒体播放操作相联系。
二、基本布局
这里使用QVideoWidget来显示视频,需要包含MultimediaWidgets模块,在ui界面中放置一个QWidget组件,然后将其提升为QVideoWidget。
三、代码注释
.h代码:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>class QMediaPlayer;QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();private slots:void on_pushButton_volume_clicked(bool checked);void on_pushButton_open_clicked();void on_pushButton_start_clicked(bool checked);void on_pushButton_full_clicked(bool checked);void on_slider_volume_valueChanged(int value);void on_slider_progress_sliderMoved(int position);void on_slider_progress_sliderPressed();void on_comboBox_currentIndexChanged(int index);private:Ui::MainWindow *ui;QMediaPlayer *player;QString m_durationTime;QString m_positionTime;// QWidget interface// QObject interface
public:virtual bool eventFilter(QObject *watched, QEvent *event) override;// QWidget interface
};#endif // MAINWINDOW_H
.cpp代码:
/ 包含主窗口类的头文件
#include "mainwindow.h"
// 包含主窗口类的UI头文件
#include "./ui_mainwindow.h"// 包含音频输出相关的头文件
#include <QAudioOutput>
// 包含文件对话框相关的头文件
#include <QFileDialog>
// 包含键盘事件相关的头文件
#include <QKeyEvent>
// 包含列表视图相关的头文件
#include <QListView>
// 包含工具提示相关的头文件
#include <QToolTip>
// 包含目录操作相关的头文件
#include <qdir.h>
// 包含媒体播放器相关的头文件
#include <qmediaplayer.h>// 主窗口类的构造函数
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{// 设置UI界面ui->setupUi(this);// 创建一个QMediaPlayer对象,指定this(主窗口)为父对象player = new QMediaPlayer(this);// 创建一个QAudioOutput对象,指定this为父对象QAudioOutput *audioOutput = new QAudioOutput(this);// 将音频输出设置给媒体播放器player->setAudioOutput(audioOutput);// 将视频输出设置为video_widget(应该是一个用于显示视频的部件)player->setVideoOutput(ui->video_widget);// 为音量滑块安装事件过滤器,事件将被MainWindow类处理(this指向MainWindow类实例)ui->slider_volume->installEventFilter(this);// 为进度滑块安装事件过滤器ui->slider_progress->installEventFilter(this);// 为视频部件安装事件过滤器ui->video_widget->installEventFilter(this);// 连接媒体播放器的durationChanged信号到一个lambda表达式connect(player,&QMediaPlayer::durationChanged,this,[&](qint64 duration){// 设置进度滑块的最大值为媒体的总时长ui->slider_progress->setMaximum(duration);// 将总时长(毫秒)转换为秒int sec = duration/1000;// 将秒转换为分钟int min = sec/60;// 计算剩余的秒数sec %= 60;// 将总时长格式化为"mm:ss"的字符串m_durationTime = QTime(0,min,sec).toString("mm:ss");// 在标签上显示当前播放位置和总时长ui->label_time->setText(m_positionTime+"/"+m_durationTime);});// 连接媒体播放器的positionChanged信号到一个lambda表达式connect(player,&QMediaPlayer::positionChanged,this,[&](qint64 position){// 设置进度滑块的当前值为媒体的播放位置ui->slider_progress->setValue(position);// 将播放位置(毫秒)转换为秒int sec = position/1000;// 将秒转换为分钟int min = sec/60;// 计算剩余的秒数sec %= 60;// 将播放位置格式化为"mm:ss"的字符串m_positionTime = QTime(0,min,sec).toString("mm:ss");// 在标签上更新显示当前播放位置和总时长ui->label_time->setText(m_positionTime+"/"+m_durationTime);});// 连接媒体播放器的playbackStateChanged信号到一个lambda表达式connect(player,&QMediaPlayer::playbackStateChanged,this,[&](QMediaPlayer::PlaybackState newState){// 如果播放状态为停止状态if(newState == QMediaPlayer::StoppedState){// 取消开始按钮的选中状态ui->pushButton_start->setChecked(false);// 设置开始按钮的图标为播放图标ui->pushButton_start->setIcon(QIcon(":/D:/Apps/qIcon/play.png"));}});
}// 主窗口类的析构函数
MainWindow::~MainWindow()
{// 释放UI对象的内存delete ui;
}// 事件过滤器函数,用于处理特定对象的特定事件
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{// 如果被观察的对象是音量滑块并且事件类型是鼠标移动事件if(watched == ui->slider_volume && event->type() == QEvent::MouseMove){// 将事件转换为鼠标事件QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);// 获取音量滑块的当前值int value = static_cast<QSlider*>(watched)->value();// 在鼠标位置显示音量滑块的值QToolTip::showText(mouseEvent->globalPosition().toPoint(),QString::number(value));}// 如果被观察的对象是进度滑块并且事件类型是鼠标移动事件if(watched == ui->slider_progress && event->type() == QEvent::MouseMove){QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);// 如果媒体播放器的播放源不为空if(!player->source().isEmpty())// 在鼠标位置显示当前播放位置和总时长QToolTip::showText(mouseEvent->globalPosition().toPoint(),m_positionTime+"/"+m_durationTime);}// 如果被观察的对象是视频部件并且事件类型是键盘按下事件if(watched == ui->video_widget && event->type() == QEvent::KeyPress){QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);// 如果按下的键是Esc键if(keyEvent->key() == Qt::Key_Escape){// 如果视频部件处于全屏状态if(ui->video_widget->isFullScreen()){// 取消视频部件的全屏状态ui->video_widget->setFullScreen(false);// 取消全屏按钮的选中状态ui->pushButton_full->setChecked(false);}}// 如果按下的键是空格键并且视频部件处于全屏状态if(keyEvent->key() == Qt::Key_Space && ui->video_widget->isFullScreen()){// 如果媒体播放器正在播放if(player->isPlaying()){// 设置开始按钮的图标为播放图标ui->pushButton_start->setIcon(QIcon(":/D:/Apps/qIcon/play.png"));// 取消开始按钮的选中状态ui->pushButton_start->setChecked(false);// 暂停媒体播放器player->pause();}else{// 设置开始按钮的图标为暂停图标ui->pushButton_start->setIcon(QIcon(":/D:/Apps/qIcon/pause.png"));// 设置开始按钮为选中状态ui->pushButton_start->setChecked(true);// 开始播放媒体播放器player->play();}}}// 调用父类(QMainWindow)的事件过滤器函数,继续处理其他事件return QMainWindow::eventFilter(watched,event);
}
// 在MainWindow类中的槽函数,当音量按钮(pushButton_volume)被点击时触发
void MainWindow::on_pushButton_volume_clicked(bool checked)
{// 根据按钮的选中状态(checked)来设置音量滑块(slider_volume)是否可见ui->slider_volume->setVisible(checked);
}// 在MainWindow类中的槽函数,当开始按钮(pushButton_start)被点击时触发
void MainWindow::on_pushButton_start_clicked(bool checked)
{// 如果按钮被选中(checked为true)if(checked){// 设置开始按钮的图标为暂停图标(pause.png)ui->pushButton_start->setIcon(QIcon(":/D:/Apps/qIcon/pause.png"));// 调用player对象的play函数开始播放(这里的player应该是一个用于播放的对象,如QMediaPlayer之类的)player->play();}else{// 如果按钮未被选中,设置开始按钮的图标为播放图标(play.png)ui->pushButton_start->setIcon(QIcon(":/D:/Apps/qIcon/play.png"));// 调用player对象的pause函数暂停播放player->pause();}
}// 在MainWindow类中的槽函数,当全屏按钮(pushButton_full)被点击时触发
void MainWindow::on_pushButton_full_clicked(bool checked)
{// 根据按钮的选中状态(checked)来设置视频窗口(video_widget)是否全屏ui->video_widget->setFullScreen(checked);
}// 在MainWindow类中的槽函数,当音量滑块(slider_volume)的值改变时触发
void MainWindow::on_slider_volume_valueChanged(int value)
{// 设置播放对象(player)的音频输出音量,将滑块的值(0 - 100)转换为0.0 - 1.0之间的数值player->audioOutput()->setVolume(value/100.0);// 如果滑块的值为0,表示静音,设置音量按钮的图标为静音图标(静音.png)if(value==0)ui->pushButton_volume->setIcon(QIcon(":/D:/Apps/qIcon/静音.png"));else// 如果滑块的值不为0,设置音量按钮的图标为音量图标(音量.png)ui->pushButton_volume->setIcon(QIcon(":/D:/Apps/qIcon/音量.png"));
}// 在MainWindow类中的槽函数,当打开按钮(pushButton_open)被点击时触发
void MainWindow::on_pushButton_open_clicked()
{// 获取当前路径QString curPath = QDir::currentPath();// 设置文件对话框的标题为"打开文件"QString title = "打开文件";// 设置文件过滤器,这里只显示*.mp4文件QString filter = "*mp4";// 设置文件对话框的选项,这里表示不使用本地对话框(DontUseNativeDialog)QFileDialog::Options options = QFileDialog::DontUseNativeDialog;// 弹出文件对话框,获取用户选择的文件路径,返回值存储在afile中QString afile = QFileDialog::getOpenFileName(this,curPath,title,filter,nullptr,options);// 如果用户没有选择文件(返回的文件路径为空字符串),直接返回,不进行后续操作if(afile.isEmpty())return;// 设置播放对象(player)的播放源为用户选择的本地文件(将本地文件路径转换为QUrl格式)player->setSource(QUrl::fromLocalFile(afile));// 开始播放player->play();// 设置开始按钮为选中状态,并设置其图标为暂停图标(pause.png)ui->pushButton_start->setChecked(true);ui->pushButton_start->setIcon(QIcon(":/D:/Apps/qIcon/pause.png"));
}// 在MainWindow类中的槽函数,当进度滑块(slider_progress)被移动时触发
void MainWindow::on_slider_progress_sliderMoved(int position)
{// 设置播放对象(player)的播放位置为滑块的当前位置player->setPosition(position);
}// 在MainWindow类中的槽函数,当进度滑块(slider_progress)被按下时触发
void MainWindow::on_slider_progress_sliderPressed()
{// 设置播放对象(player)的播放位置为进度滑块(slider_progress)的当前值player->setPosition(ui->slider_progress->value());
}// 在MainWindow类中的槽函数,当组合框(comboBox)的当前索引改变时触发
void MainWindow::on_comboBox_currentIndexChanged(int index)
{// 获取组合框中当前选中项的文本QString str = ui->comboBox->itemText(index);// 将文本去掉最后一个字符(可能是单位之类的)后转换为双精度浮点数qreal value = str.left(str.size()-1).toDouble();// 设置播放对象(player)的播放速率为转换后的数值player->setPlaybackRate(value);
}