使用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_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
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);
}