Qt 音乐播放器项目

具体代码见:https://gitee.com/Suinnnnnn/MusicPlayer

文章目录

  • [0. 预备](#0. 预备)
  • [1. 界面](#1. 界面)
    • [1.1 各部位长度](#1.1 各部位长度)
    • [1.2 ui文件](#1.2 ui文件)
    • [1.3 窗口前置设置](#1.3 窗口前置设置)
    • [1.4 设置QSS](#1.4 设置QSS)
  • [2. 自定义控件](#2. 自定义控件)
    • [2.1 按钮](#2.1 按钮)
    • [2.2 推荐页面](#2.2 推荐页面)
    • [2.3 CommonPage](#2.3 CommonPage)
    • [2.4 滑杆](#2.4 滑杆)
  • [3. 音乐管理](#3. 音乐管理)
  • [4. 歌词界面](#4. 歌词界面)
    • [4.1 ui文件](#4.1 ui文件)
    • [4.2 LrcPage.h文件](#4.2 LrcPage.h文件)
  • [5. 音乐播放控制 + 持久化控制](#5. 音乐播放控制 + 持久化控制)

0. 预备

各个类的作用:

ButtonForm: 自定义了按钮,其包含了图片,文字和动画效果
CommonPage:自定义页面,被"我的"之后的三个页面使用
CommonPageItem:歌曲的每一行就是一个该类的对象,包括编号,歌曲名,专辑名,时间,是否喜欢
CommonSlider:音乐进度条使用,表明当前播放到哪里,支持seek功能
CustomPlaylist:简单实现了在Qt6中删除的QMediaPlaylist
LrcPage:歌词页面
Music:用户直接交互的页面
MusicInfo:保存歌曲信息的类
MusicList:组织MusicInfo的类
RecBoxItemRecommendBox中的一个个单元
RecommendBox:推荐界面的简单实现
VolumBox:音量控制条,支持seek功能

1. 界面

1.1 各部位长度

1.2 ui文件

1.3 窗口前置设置

music.cpp如下

cpp 复制代码
#include "music.h"
#include <QGraphicsDropShadowEffect>
#include <QMouseEvent>
#include "ui_music.h"

Music::Music(QWidget* parent)
    : QWidget(parent)
    , ui(new Ui::Music)
{
    ui->setupUi(this);
    initUI();
}

void Music::initUI()
{
    // 设置无边框
    this->setWindowFlag(Qt::FramelessWindowHint);
    // 设置窗体透明,不然看不到阴影效果
    this->setAttribute(Qt::WA_TranslucentBackground);
    // 添加阴影效果
    QGraphicsDropShadowEffect* shadowEffect = new QGraphicsDropShadowEffect(this);
    shadowEffect->setOffset(0, 0);           // 设置阴影距离
    shadowEffect->setColor(QColor(0, 0, 0)); // 设置阴影颜色
    shadowEffect->setBlurRadius(100);         // 设置阴影模糊半径
    this->setGraphicsEffect(shadowEffect);   // 给 QWidget设置阴影
}

void Music::mousePressEvent(QMouseEvent* event)
{
    // 如果按下左键,记录dragPos
    if (event->button() == Qt::LeftButton) {
        QPoint p1     = event->globalPosition().toPoint();
        QPoint p2     = geometry().topLeft();
        this->dragPos = p1 - p2;
        // qDebug() << dragPos;
        return;
    }
    // 其余事件让父类处理
    QWidget::mousePressEvent(event);
}

void Music::mouseMoveEvent(QMouseEvent* event)
{
    // 如果按下左键并移动,那么就让窗口移动
    if (event->buttons() == Qt::LeftButton) {
        QPoint p1 = event->globalPosition().toPoint();
        QPoint p2 = this->dragPos;
        this->move(p1 - p2);
        // qDebug() << "dragPos: " << p2 << "\nglobalPos: " << p1;
        return;
    }
    // 其余事件让父类处理
    QWidget::mouseMoveEvent(event);
}

Music::~Music()
{
    // qDebug() << "Music::~Music() called";
    delete ui;
}

void Music::on_close_btn_clicked()
{
    // 关闭窗口
    this->close();
}

1.4 设置QSS

widget.ui中删除部分空间的背景颜色,设置QSS。

2. 自定义控件

2.1 按钮

自定义一个ButtonForm,由几个QWidgetQLabel组成

buttonform.h

cpp 复制代码
#ifndef BUTTONFORM_H
#define BUTTONFORM_H

#include <QFrame>
#include <QPropertyAnimation>
namespace Ui
{
class ButtonForm;
}

class ButtonForm : public QFrame
{
    Q_OBJECT
public:
    explicit ButtonForm(QWidget* parent = nullptr);
    void set_id_and_icon_and_text(int id, const QString& icon, const QString& text);
    void clear_backgrond_color() const;
    int  get_page_id() const;
    void show_animation();
    void hide_animation();
    ~ButtonForm();
protected:
    void mousePressEvent(QMouseEvent* event);

private:
    void                set_animation_and_start(QPropertyAnimation* animation, int duration,
                                                QRect rect1, QRect rect2, QRect rect3, int loopCount);
    Ui::ButtonForm*     ui;
    int                 pageID;          // 当前页面的ID, 用于翻页
    QPropertyAnimation* line1_animation; // 动画
    QPropertyAnimation* line2_animation;
    QPropertyAnimation* line3_animation;
    QPropertyAnimation* line4_animation;
    QPropertyAnimation* line5_animation;
signals:
    void clicked(int pageID);
};

#endif // BUTTONFORM_H

接着将music.ui中的左侧QWidget替换为ButtonForm

运行结果如下,点击不同的按钮可以跳转到不同的QWidget,且按钮有动画效果

2.2 推荐页面

music.ui中的recommend_page下添加一个QScrollArea类型的scrollArea,里面有三个QLabel和两个RecommendBox

RecommendBox.ui如下

RecommendBox.h

cpp 复制代码
#ifndef RECOMMENDBOX_H
#define RECOMMENDBOX_H

#include <qjsonarray.h>
#include <QWidget>

namespace Ui
{
class RecommendBox;
}

class RecommendBox : public QWidget
{
    Q_OBJECT

public:
    explicit RecommendBox(QWidget* parent = nullptr);
    void init_rec_box(QJsonArray pics);
    void set_col_and_row(int row, int col);
    ~RecommendBox();

private slots:
    void on_left_btn_clicked();
    void on_right_btn_clicked();
private:
    void              add_rec_item();
    void              del_rec_item();
    Ui::RecommendBox* ui;
    int               row           = 2;
    int               column        = 5;
    int               current_group = 0; // 当前是第几组
    int               group_count;       // 一共有多少组
    QJsonArray        pics_path_and_text;
};

#endif // RECOMMENDBOX_H

RecBoxItem.ui如下

RecBoxItem.h如下

cpp 复制代码
#ifndef RECBOXITEM_H
#define RECBOXITEM_H

#include <QPropertyAnimation>
#include <QWidget>

namespace Ui
{
class RecBoxItem;
}

class RecBoxItem : public QWidget
{
    Q_OBJECT

public:
    explicit RecBoxItem(QWidget* parent = nullptr);
    bool eventFilter(QObject* watched, QEvent* event);
    void set_icon_and_text(const QString& icon, const QString& text);
    ~RecBoxItem();

private:
    Ui::RecBoxItem*                                                         ui;
    std::function<void(QPropertyAnimation*, int, QRect, QRect, QRect, int)> set_animation_and_start;
};

#endif // RECBOXITEM_H

运行结果如下,支持换页,而且选中图片会有动画效果

2.3 CommonPage

由于需要上传文件,所以现在把之前的我的播客改为上传音乐

CommonPage被播客, 我喜欢, 最近播放, 上传音乐所使用。

下面是CommonPage.ui

下面是CommonPage.h(目前)

cpp 复制代码
#ifndef COMMONPAGE_H
#define COMMONPAGE_H

#include <QFrame>

namespace Ui
{
class CommonPage;
}

class CommonPage : public QFrame
{
    Q_OBJECT

public:
    explicit CommonPage(QWidget* parent = nullptr);
    void init_my_like();
    void init_podcast();
    void init_recent_play();
    void init_upload_music();
    ~CommonPage();
private:
    void            set_podcast();
    void            reset_up_widget(const QString& title = "xxx");
    Ui::CommonPage* ui;
};

#endif // COMMONPAGE_H

CommonPage使用了CommonPageItem,下面是CommonPageItem.ui

CommonPageItem.h如下

cpp 复制代码
#ifndef COMMONPAGEITEM_H
#define COMMONPAGEITEM_H
#include <QFrame>

namespace Ui
{
class CommonPageItem;
}

class CommonPageItem : public QFrame
{
    Q_OBJECT

public:
    explicit CommonPageItem(QWidget* parent = nullptr);
    const QRect& get_page_item_geometry() const;
    void         set_num(int num);
    void         enterEvent(QEnterEvent* event);
    void         leaveEvent(QEvent* event);
    void         mouseDoubleClickEvent(QMouseEvent* event);
    ~CommonPageItem();

private:
    void                set_background_color(const QString& color);
    bool                is_played() const;
    int                 play_num = -1;
    Ui::CommonPageItem* ui;
};

#endif // COMMONPAGEITEM_H

2.4 滑杆

CommonSlider.ui,音乐进度条使用

CommonSlider.h

cpp 复制代码
#ifndef COMMONSLIDER_H
#define COMMONSLIDER_H

#include <QFrame>

namespace Ui
{
class CommonSlider;
}


/* 愿意是想让进度条和音量条都用该类,后来发现不如再写一个类。此类仅用于进度条 */
class CommonSlider : public QFrame
{
    Q_OBJECT

public:
    explicit CommonSlider(QWidget* parent = nullptr);
    void init_progress_bar();
    void init_volume_btn();
    ~CommonSlider();

private:
    Ui::CommonSlider* ui;
};

#endif // COMMONSLIDER_H

VolumeBox.ui,音量按钮使用

VolumeBox.h

cpp 复制代码
#ifndef VOLUMEBOX_H
#define VOLUMEBOX_H

#include <QFrame>

namespace Ui
{
class VolumeBox;
}

class VolumeBox : public QFrame
{
    Q_OBJECT

public:
    explicit VolumeBox(QWidget* parent = nullptr);
    ~VolumeBox();
private:
    Ui::VolumeBox* ui;
};

#endif // VOLUMEBOX_H

music.cpp中给音量按钮添加事件过滤器

cpp 复制代码
bool Music::eventFilter(QObject* obj, QEvent* event)
{
    if (obj == ui->play_volume_btn || obj == volume_box) {
        if (event->type() == QEvent::Enter) {
            // 鼠标进入时显示 volume_box
            QPoint btnPos    = ui->play_volume_btn->mapToGlobal(QPoint(0, 0)); // 获取全局坐标
            QPoint targetPos = btnPos - QPoint(10, volume_box->height());      // 按钮上方
            volume_box->move(targetPos);
            volume_box->show();
        } else if (event->type() == QEvent::Leave) {
            // 鼠标离开时隐藏 VolumeBox (如果超过了300ms)
            hide_timer->start(300);
        }
    }
    return QWidget::eventFilter(obj, event);
}

3. 音乐管理

设置此按钮的槽函数

点击后需要添加我们选中的文件,使用QFileDialog。使用MusicInfo类保存所有音乐的信息。使用MusicList类来管理这些Music

MusicInfo.h,储存音乐信息

cpp 复制代码
#ifndef MUSICINFO_H
#define MUSICINFO_H

#include <QString>
#include <QUrl>
#include <QUuid>

class MusicInfo
{
public:
    MusicInfo(const QUrl& url);
    // 禁止拷贝(根据需要实现移动语义)
    MusicInfo(const MusicInfo&)            = delete;
    MusicInfo& operator=(const MusicInfo&) = delete;
    /* 必须声明移动构造函数,作用如下
     * 1. 明确告知编译器生成移动构造函数
     * 2. 允许容器使用移动语义操作对象
     * 3. 保留类的不可拷贝特性
     * */
    MusicInfo(MusicInfo&&)            = default;
    MusicInfo& operator=(MusicInfo&&) = default;
    bool       is_valid();
    void       parse_metadata();
    QUuid      getUuid() const;

    bool    getIs_like() const;
    void    setIs_like(bool newIs_like);
    bool    getIs_recent_play() const;
    void    setIs_recent_play(bool newIs_recent_play);
    bool    getIs_valid_flag() const;
    void    setIs_valid_flag(bool newIs_valid_flag);
    QString getMusic_name() const;
    QString getAlbum_name() const;
    qint64  getMusic_time() const;

private:
    void    check_valid();
    QUuid   uuid;                   // uuid, 防止重复
    QUrl    url;                    // 歌曲的URL
    QString music_name;             // 音乐名称
    QString album_name;             // 专辑名称
    qint64  music_time;             // 音乐持续时间(ms)
    bool    is_like        = false; // 是否喜欢
    bool    is_recent_play = false; // 是否是最近播放
    bool    is_valid_flag  = false; // 是否是音频文件
};

#endif                              // MUSICINFO_H

MusicList.h,管理一个个MusicInfo

cpp 复制代码
#ifndef MUSICINFO_H
#define MUSICINFO_H

#include <QString>
#include <QUrl>
#include <QUuid>

class MusicInfo
{
public:
    MusicInfo(const QUrl& url);
    // 禁止拷贝(根据需要实现移动语义)
    MusicInfo(const MusicInfo&)            = delete;
    MusicInfo& operator=(const MusicInfo&) = delete;
    /* 必须声明移动构造函数,作用如下
     * 1. 明确告知编译器生成移动构造函数
     * 2. 允许容器使用移动语义操作对象
     * 3. 保留类的不可拷贝特性
     * */
    MusicInfo(MusicInfo&&)            = default;
    MusicInfo& operator=(MusicInfo&&) = default;
    bool       is_valid();
    void       parse_metadata();
    QUuid      getUuid() const;

    bool    getIs_like() const;
    void    setIs_like(bool newIs_like);
    bool    getIs_recent_play() const;
    void    setIs_recent_play(bool newIs_recent_play);
    bool    getIs_valid_flag() const;
    void    setIs_valid_flag(bool newIs_valid_flag);
    QString getMusic_name() const;
    QString getAlbum_name() const;
    qint64  getMusic_time() const;

private:
    void    check_valid();
    QUuid   uuid;                   // uuid, 防止重复
    QUrl    url;                    // 歌曲的URL
    QString music_name;             // 音乐名称
    QString album_name;             // 专辑名称
    qint64  music_time;             // 音乐持续时间(ms)
    bool    is_like        = false; // 是否喜欢
    bool    is_recent_play = false; // 是否是最近播放
    bool    is_valid_flag  = false; // 是否是音频文件
};

#endif                              // MUSICINFO_H

Music.h中增加处理点击爱心按钮的槽函数

cpp 复制代码
void handle_item_like_btn_clicked(bool is_like, const QUuid& uuid);

4. 歌词界面

解析.lrc文件

4.1 ui文件

4.2 LrcPage.h文件

cpp 复制代码
#ifndef LRCPAGE_H
#define LRCPAGE_H
#include <QFrame>
#include <QPropertyAnimation>

namespace Ui
{
class LrcPage;
}

// 歌词的一行
struct LyricLine {
    LyricLine(qint64 time_p, QString text_p)
        : time(time_p), text(text_p)
    {
    }
    qint64  time; // 时间(ms)
    QString text; // 歌词
};

class LrcPage : public QFrame
{
    Q_OBJECT

public:
    explicit LrcPage(QWidget* parent = nullptr);
    bool    parse_lrc_file(const QString& lrc_filename);
    void    push_line_to_lrc_vector(qint64 time, QString text);
    QString get_lrc_filename_by_url(const QUrl& url);
    void    set_ui_label(qint64 time);
    void    set_name_and_singer(const QString& music, const QString& singer);
    void    clear_lyric_lines();
    ~LrcPage();
private slots:
    void on_quit_clicked();
private:
    int                 get_lyric_lines_index_by_time(qint64 time);
    QString             get_lyric_lines_line_by_index(int index);
    Ui::LrcPage*        ui;
    QPropertyAnimation* lrc_animation; // 歌词界面隐藏动画
    QVector<LyricLine>  lyric_lines;   // 歌词的所有内容,由一行行的结构体组成
};

#endif                                 // LRCPAGE_H

5. 音乐播放控制 + 持久化控制

使用QMediaPlayer类,由于Qt6中删除了QPlayList类,所以我自定义了一个CustomPlayList

cpp 复制代码
#ifndef CUSTOMPLAYLIST_H
#define CUSTOMPLAYLIST_H

#include <QList>
#include <QMediaPlayer>
#include <QObject>
#include <QRandomGenerator>
#include <QUrl>

class CustomPlaylist : public QObject
{
    Q_OBJECT

public:
    enum PlaybackMode {
        Sequential, // 顺序播放
        SingleLoop, // 单曲循环播放
        Random      // 随机播放
    };
    Q_ENUM(PlaybackMode)

    explicit CustomPlaylist(QMediaPlayer* player, QObject* parent = nullptr);

    // 基本操作
    void addMedia(const QUrl& url);
    void removeMedia(int index);
    void clear();
    void next();
    void previous();
    void setCurrentIndex(int index);

    // 获取信息
    int  currentIndex() const;
    QUrl currentMedia() const;
    int  mediaCount() const;

    // 播放模式
    PlaybackMode playbackMode() const;
    void         setPlaybackMode(PlaybackMode mode);

signals:
    void currentIndexChanged(int index);
    void mediaAdded(int index);
    void mediaRemoved(int index);
    void playbackModeChanged(PlaybackMode mode);

private slots:
    void handleMediaStatusChanged(QMediaPlayer::MediaStatus status);

private:
    QList<QUrl>   m_mediaList;         // 存储媒体项
    int           m_currentIndex = -1; // 当前播放索引
    PlaybackMode  m_playbackMode = Sequential;
    QMediaPlayer* m_player;            // 关联的 QMediaPlayer
    QList<int>    m_randomOrder;       // 随机播放顺序缓存

    void updateRandomOrder();          // 更新随机播放顺序
    int  getNextIndex() const;         // 根据模式获取下一个索引
    int  getPreviousIndex() const;     // 根据模式获取上一个索引
};
#endif                                 // CUSTOMPLAYLIST_H

使用QSQLITE数据库,用于持久化操作,下面是music.h文件

cpp 复制代码
#ifndef MUSIC_H
#define MUSIC_H

#include <QAudioOutput>
#include <QJsonArray>
#include <QMediaPlayer>
#include <QPropertyAnimation>
#include <QSqlDatabase>
#include <QWidget>
#include "CommonPage.h"
#include "CustomPlaylist.h"
#include "LrcPage.h"
#include "VolumeBox.h"

QT_BEGIN_NAMESPACE
namespace Ui
{
class Music;
}
QT_END_NAMESPACE

class Music : public QWidget
{
    Q_OBJECT

public:
    Music(QWidget* parent = nullptr);
    void       initUI();
    void       initPlayer();
    void       initStaticToolTip();
    void       initSqlLite();
    void       initMusicList();
    void       connect_signals_and_slots();
    void       mousePressEvent(QMouseEvent* event);
    void       mouseMoveEvent(QMouseEvent* event);
    bool       eventFilter(QObject* obj, QEvent* event);
    void       play_music_from_index(CommonPage* page, int index);
    QJsonArray get_random_recommend_pic() const;
    ~Music();
private slots:
    // 形如on_xxx是ui文件生成的, 其余是自己写的
    void handle_button_form_clicked(int pageID);
    void handle_item_like_btn_clicked(bool is_like, const QUuid& uuid);
    void handle_upload_music();
    void handle_play_state_changed(QMediaPlayer::PlaybackState new_state);
    void handle_play_mode_changed(CustomPlaylist::PlaybackMode mode);
    void handle_play_all_music_btn_clicked(CommonPage* page);
    void handle_double_clicked_song(CommonPage* page, int index);
    void handle_current_index_changed(int new_index);
    void handle_volume_changed(int volume);
    void handle_duration_changed(qint64 duration);
    void handle_position_changed(qint64 duration);
    void handle_slider_pos_changed(double ratio);
    void handle_meta_data_changed();
    void on_close_btn_clicked();
    void on_stop_btn_clicked();
    void on_next_song_btn_clicked();
    void on_prev_song_btn_clicked();
    void on_play_mode_btn_clicked();
    void on_play_volume_btn_clicked();
    void on_my_like_btn_clicked();
    void on_lrc_btn_clicked();
    void on_music_pic_btn_clicked();
    void on_min_btn_clicked();
private:
    void                      hide_volume_box();
    static QString            transform_million_second(qint64 msecond);
    void                      set_like_btn_icon(bool is_like);
    MusicList::const_iterator get_now_play_music_info_by_index(int index);
    void                      quit_progress();
private:
    Ui::Music* ui;
    // 由于窗口没有了默认边框,所以要想移动,需要有该属性,其值为 全局的鼠标位置-widget左上位置
    QPoint              dragPos;
    VolumeBox*          volume_box;
    QTimer*             hide_timer;
    QMediaPlayer*       player;
    CustomPlaylist*     play_list;
    QAudioOutput*       audio_output;
    CommonPage*         play_page;       // 当前播放
    bool                is_mute = false; // 是否静音, false表示没被静音
    qint64              total_duration;  // 当前音乐的总时长
    LrcPage*            lrc_page;        // 歌词页面
    QPropertyAnimation* lrc_animation;   // 歌词界面显示动画
    QSqlDatabase        db;              // 数据库驱动
};
#endif                                   // MUSIC_H
相关推荐
审计侠11 分钟前
Go语言-初学者日记(八):构建、部署与 Docker 化
开发语言·后端·golang
kovlistudio12 分钟前
红宝书第二十九讲:详解编辑器和IDE:VS Code与WebStorm
开发语言·前端·javascript·ide·学习·编辑器·webstorm
东方珵蕴28 分钟前
Logo语言的区块链
开发语言·后端·golang
明月醉窗台44 分钟前
Qt 入门 1 之第一个程序 Hello World
开发语言·c++·qt
Craaaayon1 小时前
Java八股文-List集合
java·开发语言·数据结构·list
幻想趾于现实1 小时前
C# Winform 入门(11)之制作酷炫灯光效果
开发语言·c#·winform
hy____1231 小时前
类与对象(中)(详解)
开发语言·c++
wen__xvn1 小时前
c++STL入门
开发语言·c++·算法
2301_794461571 小时前
多线程编程中的锁策略
java·开发语言
XYN612 小时前
【嵌入式学习3】基于python的tcp客户端、服务器
服务器·开发语言·网络·笔记·python·学习·tcp/ip