当前位置: 首页 > article >正文

Qt实现简易音乐播放器

 使用Qt6实现简易音乐播放器,效果如下:

github:

Gabriel-gxb/MusicPlayer: qt6实现简易音乐播放器

    

一、整体架构

基于Qt框架构建

整个音乐播放器程序以Qt框架为基础进行开发。Qt提供了丰富的类库和工具,方便开发者构建图形用户界面(GUI)以及处理多媒体等功能。

以MainWindow类为核心

  1.1. 功能集成点
      1.MainWindow类是程序的核心,集成了音乐播放器的各种功能逻辑,包括界面显示、用户交互处理、多媒体播放控制等。

  2.2. 继承关系
      1.继承自QMainWindow,这使得MainWindow能够利用QMainWindow的特性,如默认的菜单栏、工具栏、状态栏布局等。通过这种继承关系,可以在MainWindow类中方便地添加自定义的界面元素和功能。

功能模块划分

  1.1. 多媒体播放模块
      1.以QMediaPlayer类为中心,该类负责音乐文件的播放操作。同时创建QAudioOutput对象与QMediaPlayer关联,以处理音频输出相关的功能,如设置音量等。

  2.2. 界面显示模块
      1.包含各种QWidget相关的界面元素,如QPushButton(按钮)、QListWidget(列表部件)、QLabel(标签)、QSlider(滑块)等。这些界面元素用于展示音乐信息(如歌曲名称、播放时间、音量等),以及提供用户交互操作(如播放、暂停、添加文件、切换歌曲、调整音量等)。
      2.利用paintEvent函数绘制界面背景等自定义的绘制操作。

  3.3. 用户交互模块
      1.通过信号与槽机制将界面元素的交互事件(如按钮点击、滑块移动等)与相应的功能函数连接起来。例如,当用户点击播放按钮时,会触发on_pushButton_start_clicked函数来控制音乐的播放或暂停;当滑块移动时,会触发相应的sliderMoved或valueChanged函数来调整音乐播放的位置或音量等。

  4.4. 数据管理模块
      1.在程序中通过QListWidgetItem存储音乐文件相关信息,如文件名和文件路径(以QUrl形式存储在Qt::UserRole自定义数据中)。这些数据在程序运行过程中起到了管理音乐文件列表、获取当前播放文件等作用。

    

二、基本布局

在布局中,使按钮隐藏边框的样式表内容为:

*{
	border: none;
}

使ListWidget变为透明的样式表内容为:

*{
	background-color: transparent
}

     

三、代码注释

.h文件中代码:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QtMultimedia/qmediaplayer.h>

QT_BEGIN_NAMESPACE

class QLabel;
class QListWidgetItem;

namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;

    QMediaPlayer *player;
    bool isLoop = false;

    QLabel *label_volume;
    QLabel *label_loop;

    QString m_durationTime;
    QString m_positionTime;

    // QWidget interface
protected:
    virtual void paintEvent(QPaintEvent *event) override;

private slots:
    void on_pushButton_add_clicked();
    void on_pushButton_start_clicked(bool check);
    void on_listWidget_itemClicked(QListWidgetItem *item);
    void on_listWidget_itemDoubleClicked(QListWidgetItem *item);
    void on_pushButton_volume_clicked(bool checked);
    void on_horizontalSlider_sliderMoved(int position);
    void on_horizontalSlider_sliderPressed();
    void on_verticalSlider_valueChanged(int value);
    void on_pushButton_front_clicked();
    void on_pushButton_back_clicked();
    void on_pushButton_mode_clicked(bool checked);
    void on_horizontalSlider_valueChanged(int value);
};

#endif // MAINWINDOW_H

.cpp文件中代码:

#include "mainwindow.h"
#include "./ui_mainwindow.h"

// 包含用于文件对话框操作的头文件
#include <QFileDialog>
// 包含用于绘制事件相关的头文件
#include <QPaintEvent>
// 包含用于绘图操作(如画图工具类)的头文件
#include <QPainter>
// 包含Qt多媒体相关的头文件
#include <QtMultimedia>

// MainWindow类的构造函数,用于初始化MainWindow对象
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
   , ui(new Ui::MainWindow)
{
    // 使用UI设计文件中的setupUi函数来初始化界面
    ui->setupUi(this);

    // 设置主窗口的大小为宽度1000,高度700
    resize(1000, 700);

    // 创建一个QMediaPlayer对象,this表示该对象的父对象为MainWindow
    player = new QMediaPlayer(this);
    // 创建一个QAudioOutput对象,与前面创建的QMediaPlayer对象关联
    QAudioOutput *audioOutput = new QAudioOutput(player);
    player->setAudioOutput(audioOutput);

    // 创建一个QLabel对象用于显示音量相关信息,初始文本包含当前音量值(从垂直滑块获取)
    label_volume = new QLabel("当前音量:" + QString("%1").arg(ui->verticalSlider->value()), this);
    // 设置标签的固定宽度为80像素
    label_volume->setFixedWidth(80);
    // 将音量标签添加到状态栏的永久部件中,使其始终显示
    ui->statusBar->addPermanentWidget(label_volume);

    // 创建一个QLabel对象用于显示循环相关信息,初始文本为"当前模式:列表循环"
    label_loop = new QLabel("当前模式:列表循环", this);
    // 设置标签的固定宽度为200像素
    label_loop->setFixedWidth(200);
    // 将循环标签添加到状态栏中
    ui->statusBar->addWidget(label_loop);

    // 设置状态栏的样式表,使状态栏中的项目没有边框
    ui->statusBar->setStyleSheet(QString("QStatusBar::item{border:0px}"));

    // 当QMediaPlayer的播放状态发生改变时(开始播放或停止播放)的连接操作
    // 当playing为true表示正在播放,false表示停止播放
    // 使用lambda表达式作为槽函数,根据播放状态更新播放按钮的选中状态和水平滑块的可用性
    connect(player, &QMediaPlayer::playingChanged, this, [&](bool playing) {
        ui->pushButton_start->setChecked(!playing);  //播放按钮状态
        ui->horizontalSlider->setEnabled(true);  //设置进度条可拖拽
    });

    // 当QMediaPlayer的播放文件发生改变时(例如切换到新的音频或视频文件)的连接操作
    // media是新的播放文件的QUrl对象
    // 使用lambda表达式作为槽函数,根据新的播放文件更新歌曲名称标签的显示内容
    connect(player, &QMediaPlayer::sourceChanged, this, [&](const QUrl &media) {
        ui->label_info->setText(media.fileName());  //设置歌曲名称
    });

    // 当QMediaPlayer的源数据发生变化时(例如音频或视频的元数据发生改变)的连接操作
    // 使用lambda表达式作为槽函数,处理元数据变化相关的操作
    connect(player, &QMediaPlayer::metaDataChanged, this, [&]() {
        // 获取元数据对象
        QMediaMetaData metaData = player->metaData();
        // 获取元数据中的缩略图图像数据(如果存在)
        QVariant metaImg = metaData.value(QMediaMetaData::ThumbnailImage);
        if (metaImg.isValid()) {
            // 将图像数据转换为QImage对象
            QImage img = metaImg.value<QImage>();
            // 将QImage转换为QPixmap对象,并根据部件宽度缩放图像
            QPixmap pix = QPixmap::fromImage(img);
            ui->label_pic->setPixmap(pix.scaledToWidth(ui->widget->width()));
        } else {
            // 如果没有有效图像,清除标签上的图像显示
            ui->label_pic->clear();
        }
    });

    // 当QMediaPlayer的播放源时长发生变化时(例如切换到新的音频或视频文件,其时长与之前不同)的连接操作
    // duration是新的播放时长(以毫秒为单位)
    // 使用lambda表达式作为槽函数,根据新的时长更新水平滑块的最大范围,并计算和显示总时长
    connect(player, &QMediaPlayer::durationChanged, this, [&](qint64 duration) {
        ui->horizontalSlider->setMaximum(duration);
        int sec = duration / 1000;
        int min = sec / 60;
        sec %= 60;
        m_durationTime = QTime(0, min, sec).toString("mm:ss");
        ui->label_time->setText(m_positionTime + "/" + m_durationTime);
    });

    // 当QMediaPlayer的播放位置发生变化时(例如播放过程中时间轴的推进)的连接操作
    // position是新的播放位置(以毫秒为单位)
    // 使用lambda表达式作为槽函数,根据新的播放位置更新水平滑块的滑块位置,并计算和显示当前播放时间
    connect(player, &QMediaPlayer::positionChanged, this, [&](qint64 position) {
        if (ui->horizontalSlider->isSliderDown())
            return;
        ui->horizontalSlider->setSliderPosition(position);
        int sec = position / 1000;
        int min = sec / 60;
        sec %= 60;
        m_positionTime = QTime(0, min, sec).toString("mm:ss");
        ui->label_time->setText(m_positionTime + "/" + m_durationTime);
    });
}

// MainWindow类的析构函数,用于释放ui对象占用的内存
MainWindow::~MainWindow()
{
    delete ui;
}

// paintEvent函数用于处理主窗口的绘制事件
// 重写QWidget的paintEvent函数
void MainWindow::paintEvent(QPaintEvent *event)
{
    // 创建一个QPainter对象,用于在主窗口(this表示当前的MainWindow对象)上进行绘制操作
    QPainter painter(this);
    // 创建一个QPixmap对象,加载指定资源路径(":/bk",这是Qt的资源系统路径)下的图像
    QPixmap pixmap(":/bk");
    // 将图像按照主窗口的大小进行缩放,忽略宽高比并使用平滑变换
    pixmap = pixmap.scaled(size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
    // 在主窗口的坐标(0, 0)处绘制缩放后的图像
    painter.drawPixmap(0, 0, pixmap);

    // 调用父类(QMainWindow)的paintEvent函数,以确保默认的绘制操作也能执行
    return QMainWindow::paintEvent(event);
}

// 当名为pushButton_add的按钮被点击时调用此函数
void MainWindow::on_pushButton_add_clicked()
{
    // 获取当前的工作目录路径
    QString curPath = QDir::currentPath();
    // 设置文件选择对话框的标题
    QString title = "选择音频文件";
    // 设置文件选择对话框的文件过滤器,只显示扩展名为.mp3的音频文件
    QString filter = "音频文件(*.mp3)";
    // 弹出文件选择对话框,允许用户选择多个文件,返回所选文件的路径列表
    QStringList fileList = QFileDialog::getOpenFileNames(this, title, curPath, filter);
    // 如果没有选择任何文件,则直接返回,不进行后续操作
    if (fileList.size() < 1) return;

    // 遍历所选文件的路径列表
    for (int i = 0; i < fileList.size(); i++) {
        // 获取当前遍历到的文件路径
        QString aFile = fileList[i];
        // 创建一个QFileInfo对象,用于获取文件的相关信息(如文件名等)
        QFileInfo info(aFile);
        // 创建一个QListWidgetItem对象,用于在列表部件(listWidget)中显示文件名
        QListWidgetItem *item = new QListWidgetItem(info.fileName());
        // 将文件的本地路径转换为QUrl对象,并将其作为用户自定义数据(Qt::UserRole)存储在列表项中
        item->setData(Qt::UserRole, QUrl::fromLocalFile(aFile));
        // 将创建好的列表项添加到列表部件(listWidget)中
        ui->listWidget->addItem(item);
    }
}
// 当名为pushButton_start的按钮被点击时调用此函数,check表示按钮的选中状态
void MainWindow::on_pushButton_start_clicked(bool check)
{
    // 获取列表部件(listWidget)中的当前选中项
    QListWidgetItem *item = ui->listWidget->currentItem();
    // 获取当前选中项中存储的文件路径(以QUrl形式)
    auto url = item->data(Qt::UserRole).toUrl();
    // 如果播放器的当前播放源与当前选中项的文件路径不同,则设置播放器的播放源为当前选中项的文件路径
    if (player->source()!= url)
        player->setSource(url);

    // 如果按钮未被选中(即check为false,表示要开始播放)
    if (!check) {
        // 设置播放按钮的图标为暂停图标(":/D:/Apps/qIcon/pause.png")
        ui->pushButton_start->setIcon(QIcon(":/D:/Apps/qIcon/pause.png"));
        // 调用播放器的play函数开始播放
        player->play();
    }
    // 如果按钮被选中(即check为true,表示要暂停播放)
    else {
        // 设置播放按钮的图标为播放图标(":/D:/Apps/qIcon/play.png")
        ui->pushButton_start->setIcon(QIcon(":/D:/Apps/qIcon/play.png"));
        // 调用播放器的pause函数暂停播放
        player->pause();
    }
}

// 当列表部件(listWidget)中的项目被点击时调用此函数,item为被点击的项目指针
void MainWindow::on_listWidget_itemClicked(QListWidgetItem *item)
{
    // Q_UNUSED宏用于标记未使用的参数,这里表示虽然有item参数但在函数内未使用
    Q_UNUSED(item);
    // 启用播放按钮,使其可被点击
    ui->pushButton_start->setEnabled(true);
    // 启用向前按钮(pushButton_front),使其可被点击
    ui->pushButton_front->setEnabled(true);
    // 启用向后按钮(pushButton_back),使其可被点击
    ui->pushButton_back->setEnabled(true);
}
// 当列表部件(listWidget)中的项目被双击时调用此函数,item为被双击的项目指针
void MainWindow::on_listWidget_itemDoubleClicked(QListWidgetItem *item)
{
    // 获取被双击项目中存储的文件路径(以QUrl形式)
    auto url = item->data(Qt::UserRole).toUrl();
    // 设置播放器的播放源为被双击项目的文件路径
    player->setSource(url);
    // 设置播放按钮的图标为暂停图标(":/D:/Apps/qIcon/pause.png")
    ui->pushButton_start->setIcon(QIcon(":/D:/Apps/qIcon/pause.png"));
    // 调用播放器的play函数开始播放
    player->play();
}
// 当名为pushButton_volume的按钮被点击时调用此函数,checked表示按钮的选中状态
void MainWindow::on_pushButton_volume_clicked(bool checked)
{
    // 根据按钮的选中状态设置垂直滑块(verticalSlider)是否可见
    ui->verticalSlider->setVisible(checked);
}
// 当水平滑块(horizontalSlider)被移动时调用此函数,position为滑块的新位置
void MainWindow::on_horizontalSlider_sliderMoved(int position)
{
    // 设置播放器的播放位置为滑块的新位置(以毫秒为单位)
    player->setPosition(position);
}
// 当水平滑块(horizontalSlider)被按下时调用此函数
void MainWindow::on_horizontalSlider_sliderPressed()
{
    // 设置播放器的播放位置为水平滑块的当前值(以毫秒为单位)
    player->setPosition(ui->horizontalSlider->value());
}

// 当水平滑块(horizontalSlider)的值发生改变时调用此函数,value为滑块的新值
void MainWindow::on_horizontalSlider_valueChanged(int value)
{
    // 如果当前播放时间等于总播放时间
    if (m_positionTime == m_durationTime) {
        // 如果处于循环播放模式(isLoop为true)
        if (isLoop)
            // 将播放器的播放位置设置为0,即重新开始播放
            player->setPosition(0);
        else
            // 否则点击向后按钮(pushButton_back),切换到下一个音频文件(根据pushButton_back的逻辑)
            ui->pushButton_back->click();
    }
}

// 当垂直滑块(verticalSlider)的值发生改变时调用此函数,value为滑块的新值
void MainWindow::on_verticalSlider_valueChanged(int value)
{
    // 设置播放器音频输出的音量,将滑块的值转换为0到1之间的浮点数(因为音量范围是0.0到1.0)
    player->audioOutput()->setVolume(value / 100.0);
    // 更新音量标签的文本,显示当前音量值(从垂直滑块获取)
    label_volume->setText("当前音量:" + QString("%1").arg(ui->verticalSlider->value()));
    // 如果垂直滑块的值为0(即音量为0)
    if (ui->verticalSlider->value() == 0)
        // 设置音量按钮的图标为静音图标(":/D:/Apps/qIcon/静音.png")
        ui->pushButton_volume->setIcon(QIcon(":/D:/Apps/qIcon/静音.png"));
    else
        // 否则设置音量按钮的图标为音量图标(":/D:/Apps/qIcon/音量.png")
        ui->pushButton_volume->setIcon(QIcon(":/D:/Apps/qIcon/音量.png"));
}
// 当名为pushButton_front的按钮被点击时调用此函数
void MainWindow::on_pushButton_front_clicked()
{
    // 获取列表部件(listWidget)中的当前行索引
    int curRow = ui->listWidget->currentRow();
    // 如果当前行是第一行(索引为0)
    if (curRow == 0)
        // 将当前行设置为最后一行(列表部件的行数减1)
        curRow = ui->listWidget->count() - 1;
    else
        // 否则将当前行减1,即切换到上一行
        curRow--;
    // 设置列表部件的当前行为新的行索引
    ui->listWidget->setCurrentRow(curRow);
    // 获取新的当前行项目中存储的文件路径(以QUrl形式)
    auto url = ui->listWidget->item(curRow)->data(Qt::UserRole).toUrl();
    // 设置播放器的播放源为新的文件路径
    player->setSource(url);
    // 调用播放器的play函数开始播放
    player->play();
}
// 当名为pushButton_back的按钮被点击时调用此函数
void MainWindow::on_pushButton_back_clicked()
{
    // 获取列表部件(listWidget)中的当前行索引
    int curRow = ui->listWidget->currentRow();
    // 如果当前行是最后一行(索引为列表部件的行数减1)
    if (curRow == ui->listWidget->count() - 1)
        // 将当前行设置为第一行(索引为0)
        curRow = 0;
    else
        // 否则将当前行加1,即切换到下一行
        curRow++;
    // 设置列表部件的当前行为新的行索引
    ui->listWidget->setCurrentRow(curRow);
    // 获取新的当前行项目中存储的文件路径(以QUrl形式)
    auto url = ui->listWidget->item(curRow)->data(Qt::UserRole).toUrl();
    // 设置播放器的播放源为新的文件路径
    player->setSource(url);
    // 调用播放器的play函数开始播放
    player->play();
}
// 当名为pushButton_mode的按钮被点击时调用此函数,checked表示按钮的选中状态
void MainWindow::on_pushButton_mode_clicked(bool checked)
{
    // 如果按钮被选中(即checked为true)
    if (checked) {
        // 更新循环模式标签的文本为"当前模式:单曲循环"
        label_loop->setText("当前模式:单曲循环");
        // 设置模式按钮的图标为单曲循环图标(":/D:/Apps/qIcon/单曲循环.png")
        ui->pushButton_mode->setIcon(QIcon(":/D:/Apps/qIcon/单曲循环.png"));
        // 设置循环标志为true,表示处于单曲循环模式
        isLoop = true;
    }
    else {
        // 更新循环模式标签的文本为"当前模式:列表循环"
        label_loop->setText("当前模式:列表循环");
        // 设置模式按钮的图标为列表循环图标(":/D:/Apps/qIcon/列表循环.png")
        ui->pushButton_mode->setIcon(QIcon(":/D:/Apps/qIcon/列表循环.png"));
        // 设置循环标志为false,表示处于列表循环模式
        isLoop = false;
    }
}

 

 


http://www.kler.cn/a/536960.html

相关文章:

  • npm安装electron安装报错
  • Python----Python高级(并发编程:协程Coroutines,事件循环,Task对象,协程间通信,协程同步,将协程分布到线程池/进程池中)
  • Redis --- 使用Feed流实现社交平台的新闻流
  • 4.PPT:日月潭景点介绍【18】
  • 【大数据技术】搭建完全分布式高可用大数据集群(Scala+Spark)
  • SQL条件分支中的大讲究
  • 脚手架开发【实战教程】prompts + fs-extra
  • MySQL视图索引操作
  • 【Linux】Ubuntu Linux 系统 ——Android开发环境
  • linux进程通讯-信号处理介绍
  • [开源/教程]使用Ollama+ESP32实现本地对话助手(可接入deepseek等模型)
  • 基于微信平台的报刊订阅小程序的设计与实现ssm+论文源码调试讲解
  • 新注册的域名无法访问,是怎么回事?
  • “AI隐患识别系统,安全多了道“智能护盾”
  • 鸿蒙UI(ArkUI-方舟UI框架)- 设置组件导航和页面路由
  • 青少年编程与数学 02-008 Pyhon语言编程基础 24课题、正则表达式
  • MES系统对于中小型制造企业有什么价值?
  • verilog练习:8bit移位寄存器
  • 防火墙与Squid代理服务器
  • FastReport 加载Load(Stream) 模板内包含换行符不能展示
  • 【网络】应用层协议http
  • 避免样式冲突:掌握CSS选择器优先级与层叠规则的终极指南
  • Itext pdf reader解析
  • mysql的语句备份详解
  • 11.享元模式 (Flyweight)
  • windows同时安装两个不同版本的Mysql