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
相关推荐
isyangli_blog1 小时前
OpenDayLight (Carbon 版本) 启动与组件安装
开发语言·php
vb2008111 小时前
FastAPI APIRouter
开发语言·python
Benszen1 小时前
KVM虚拟化解决方案
开发语言·perl
会编程的土豆1 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
東雪木1 小时前
多线程与并发编程 专属复习笔记
java·开发语言·笔记·java面试
杨充2 小时前
1.3 浮点型数据设计灵魂
开发语言·python·算法
噜噜噜阿鲁~2 小时前
python学习笔记 | 11.3、面向对象高级编程-多重继承
java·开发语言
basketball6162 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang
春生野草2 小时前
反射、Tomcat执行
java·开发语言
雪的季节3 小时前
企业级 Qt 全功能项目
开发语言·数据库·qt