2020-2-26,项目耗时:2天
实现的功能
- 依据歌曲作者对歌曲进行分类,并以树状目录的结构显示在界面上
- 进度条实现
- 播放、暂停、音量调节
- 显示当前歌曲名
效果如下
模块划分
- 歌曲目录树模块:用来将歌曲文件以目录树的形式展现在界面上,并向外界提供当前被选中歌曲的路径信息。
- 控制面板模块:用来控制播放器的行为,以及反应播放器的状态。
- 播放器模块:播放器引擎,用来执行音乐文件的播放。
- 顶层模块:用来将以上模块组合在一起。
下面一一介绍每个模块的实现。
歌曲目录树模块
主要用到了一个QTreeWidget类,我从这个类派生出一个MusicTreeList类,关于QTreeWidget的详细说明见帮助文档。
music_tree_list.h
#ifndef MUSIC_TREE_LIST_H
#define MUSIC_TREE_LIST_H
#include "QTreeWidget"
class MusicTreeList : public QTreeWidget
{
Q_OBJECT
public:
MusicTreeList();
~MusicTreeList();
public slots:
void musicChanged(QTreeWidgetItem *item, int column);
signals:
void currentMusic(QString str);
};
#endif // MUSIC_TREE_LIST_H
music_tree_list.cpp
#include "music_tree_list.h"
#include "QRegExp"
#include "QtDebug"
#include "QDir"
MusicTreeList::MusicTreeList()
{
//设置歌曲栏目,详细用法见QTreeWidget帮助文档
this->setColumnCount(2);
QStringList headers;
headers << QObject::tr("歌名") << QObject::tr("发行时间");
this->setHeaderLabels(headers);
//设定音乐文件目录,使用过滤器保留歌曲作者文件夹,详细用法见QDir帮助文档
QStringList folder;
QString dirPath = "C:/Users/jiage/Desktop/folder/QT/qtreewidget/music";
QDir dir(dirPath);
dir.setFilter(QDir::Dirs);
//foreach语句遍历dir中的文件夹名称列表,与此同时构建对应的目录树
foreach(QFileInfo fullDir, dir.entryInfoList())
{
if(fullDir.fileName() == "." || fullDir.fileName() == "..") continue;
folder.push_back(fullDir.fileName());
QStringList singer_name;
singer_name << fullDir.fileName();
QTreeWidgetItem *temp_singer = new QTreeWidgetItem( this, singer_name );
QDir temp_dir(fullDir.absoluteFilePath());
QStringList nameFilters;
nameFilters << "*.mp3";
QStringList files = temp_dir.entryList(nameFilters, QDir::Files|QDir::Readable, QDir::Name);
while( !files.isEmpty() ) {
QStringList song_name;
song_name << files[0];
QTreeWidgetItem *temp_song = new QTreeWidgetItem(temp_singer, song_name);
temp_singer->addChild(temp_song);
files.pop_front();
}
}
//当QTreeWidget中某个item被点击时,将这个被点击的item所对应的歌曲路径以字符串的形式发送出去
connect(this, &QTreeWidget::itemClicked, this, &MusicTreeList::musicChanged);
}
MusicTreeList::~MusicTreeList()
{
}
void MusicTreeList::musicChanged(QTreeWidgetItem *item, int column)
{
QString music_end_path;
QString pattern(".*.mp3");
QRegExp rx(pattern);
bool match = rx.exactMatch(item->text(column));
if ( !match )
{
return;
}
music_end_path = "/" + item->parent()->text(column) + "/" + item->text(column);
//qDebug() << music_end_path;
emit currentMusic( music_end_path );
}
总的来说,这个类的作用就是根据用户点击的歌曲,以字符串的形式输出该歌曲的路径。
控制面板模块
ControlPanel类继承自QWidget。
control_panel.h
#ifndef CONTRO_PANEL_H
#define CONTRO_PANEL_H
#include <QWidget>
class QPushButton;
class QSlider;
class QLabel;
class ControlPanel : public QWidget
{
Q_OBJECT
public:
explicit ControlPanel(QWidget *parent = nullptr);
~ControlPanel();
QPushButton * stop_btn;//停止
QPushButton * last_btn;//上一首
QPushButton * next_btn;//下一首
QPushButton * play_btn;//播放或者暂停
QPushButton * volume_btn;//音量启停
QPushButton * orderplay_btn;//顺序播放
QSlider * music_progress;//音乐进度滑杆
QSlider * volume_slider;//音量滑杆
QLabel * time_label;//时间标签
QString total_time;//总时间
QString current_time;//当前时间
public slots:
void changeTotalTime(qint64 duration);//歌曲切换时发生
void updateProgress(int64_t milli_sec);//歌曲进度
void volumeSliderChanged(int value);//音量改变时发生
void causePlayBtnClicked();//播放按钮按下,调用
void causeStopBtnClicked();//停止按钮按下,调用
void causeNextBtnClicked();//下一首按钮按下,调用
void causeLastBtnClicked();//上一首按钮按下,调用
signals:
void volumeChanged(int value);//发出音量改变信号
void playBtnClicked();//发出播放信号
void stopBtnClicked();//发出停止信号
void nextBtnClicked();//发出下一首信号
void lastBtnClicked();//发出上一首信号
private:
int64_t total_millisec;//当前歌曲总时间
};
#endif // CONTRO_PANEL_H
control_panel.cpp
#include "contro_panel.h"
#include "QSlider"
#include "QLabel"
#include "QPushButton"
#include "QHBoxLayout"
#include "QVBoxLayout"
#include "QGridLayout"
#include "QStyle"
ControlPanel::ControlPanel(QWidget *parent) : QWidget(parent)
{
total_time = "/00:00";
current_time = "00:00";
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
stop_btn = new QPushButton();
stop_btn->setIcon(this->style()->standardIcon(QStyle::SP_MediaStop));
last_btn = new QPushButton();
last_btn->setIcon(this->style()->standardIcon(QStyle::SP_MediaSkipBackward));
play_btn = new QPushButton();
play_btn->setIcon(this->style()->standardIcon(QStyle::SP_MediaPlay));
next_btn = new QPushButton();
next_btn->setIcon(this->style()->standardIcon(QStyle::SP_MediaSkipForward));
orderplay_btn = new QPushButton();
orderplay_btn->setIcon(this->style()->standardIcon(QStyle::SP_ArrowDown));
volume_btn = new QPushButton();
volume_btn->setIcon(this->style()->standardIcon(QStyle::SP_MediaVolume));
music_progress = new QSlider();
music_progress->setOrientation(Qt::Horizontal);
time_label = new QLabel(current_time + total_time);
time_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
QHBoxLayout * layout1 = new QHBoxLayout();
layout1->addWidget(music_progress);
layout1->addWidget(time_label);
volume_slider = new QSlider();
volume_slider->setOrientation(Qt::Horizontal);
QHBoxLayout * layout2 = new QHBoxLayout();
layout2->addStretch();
//layout2->addWidget(orderplay_btn);
layout2->addWidget(stop_btn);
layout2->addWidget(last_btn);
layout2->addWidget(play_btn);
layout2->addWidget(next_btn);
layout2->addWidget(volume_btn);
layout2->addWidget(volume_slider);
layout2->addStretch();
QVBoxLayout * layout = new QVBoxLayout();
layout->addLayout(layout1);
layout->addLayout(layout2);
this->setLayout(layout);
connect(volume_slider, &QSlider::valueChanged, this, &ControlPanel::volumeSliderChanged);
connect(play_btn, &QPushButton::clicked, this, &ControlPanel::causePlayBtnClicked);
connect(stop_btn, &QPushButton::clicked, this, &ControlPanel::causeStopBtnClicked);
connect(last_btn, &QPushButton::clicked, this, &ControlPanel::causeLastBtnClicked);
connect(next_btn, &QPushButton::clicked, this, &ControlPanel::causeNextBtnClicked);
}
void ControlPanel::updateProgress(int64_t milli_sec)
{
int64_t total_seconds;
int seconds;
int minutes;
QString sec;
QString min;
total_seconds = static_cast<int64_t>(milli_sec / 1000);
minutes = static_cast<int>(total_seconds / 60);
seconds = static_cast<int>(total_seconds % 60);
sec = QString::number(seconds);
min = QString::number(minutes);
current_time = min + ":" + sec;
time_label->setText(current_time + total_time);
int bar_percentage;
float percentage;
if(total_millisec == 0)
{
return;
} else {
percentage = static_cast<float>( milli_sec )/ total_millisec;
bar_percentage = static_cast<int>(percentage * 100);
}
music_progress->setValue(bar_percentage);
}
//设定当前歌曲总时长
void ControlPanel::changeTotalTime(qint64 duration)
{
total_millisec = duration;
int64_t total_seconds;
int seconds;
int minutes;
QString sec;
QString min;
total_seconds = static_cast<int64_t>(duration / 1000);
minutes = static_cast<int>(total_seconds / 60);
seconds = static_cast<int>(total_seconds % 60);
sec = QString::number(seconds);
min = QString::number(minutes);
total_time = "/" + min + ":" + sec;
time_label->setText(current_time + total_time);
}
void ControlPanel::causeNextBtnClicked()
{
emit nextBtnClicked();
}
void ControlPanel::causeLastBtnClicked()
{
emit lastBtnClicked();
}
void ControlPanel::volumeSliderChanged(int value)
{
emit volumeChanged(value);
}
void ControlPanel::causePlayBtnClicked()
{
emit playBtnClicked();
}
void ControlPanel::causeStopBtnClicked()
{
emit stopBtnClicked();
}
ControlPanel::~ControlPanel()
{
}
这个模块的作用总结来说是:发出五个信号,接受两个信息。五个信号指的是播放、停止、下一首、上一首、以及音量改变。两个信息指的是当前歌曲总时长、当前歌曲已经播放时长。
播放器模块
这里要谈的是QMediaPlayer的用法,这个类可以用来播放音频、视频、网络媒体。以本地音频上的具体用法为例,如下所示:
player = new QMediaPlayer;
//新建一个媒体播放器类
connect(player, SIGNAL(positionChanged(qint64)), this, SLOT(positionChanged(qint64)));
//当这个媒体播放器的进度改变时会发出一个信号指示当前进度,我们要写一个槽函数用来处理这个信号
player->setMedia(QUrl::fromLocalFile("/Users/me/Music/coolsong.mp3"));
//设置要播放的媒体
player->setVolume(50);
//设置音量
player->play();
//播放
还有更多用法以及更详细的说明,见官方帮助文档。
顶层模块
用来将各个子模块连接为一个整体
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
class QMediaPlayer;
class QMediaPlaylist;
class ControlPanel;
class QSplitter;
class MusicWord;
class QMediaContent;
class QLabel;
class MusicTreeList;
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
enum PlayState {
STOP = 1,
PLAYING = 2,
PAUSE = 3
};
public slots:
void playMusic();//用来接受控制面板发出的播放信号
void setMusic(QString music_name);//用来接受音乐树目录发来的歌曲信息,用来设置当前的媒体源
void setVolume(int i);//用来接受控制面板发出的改变音量信号
void stopPlayer();//用来接受控制面板发出的停止信号
void playNext();//接受控制面板发出的下一首信号
void playLast();//接受控制面板发出的上一首信号
private:
QMediaPlayer * music_player;
QMediaPlaylist * play_list;
ControlPanel * control_panel;
MusicTreeList * music_tree_list;
QLabel * music_title;//用来显示当前歌曲
QString path;//歌曲路径
QString project_path;//我的工程路径
int player_state;//播放器状态
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "contro_panel.h"
#include "music_word.h"
#include <QMediaPlayer>
#include <QPushButton>
#include "QFileDialog"
#include "QFileInfo"
#include "QHBoxLayout"
#include "QVBoxLayout"
#include "QGridLayout"
#include "QMediaPlaylist"
#include "QSplitter"
#include "QMediaContent"
#include "QLabel"
#include "QFont"
#include "music_tree_list.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
player_state = Widget::STOP;
project_path = "C:/Users/jiage/Desktop/folder/QT/music_player/music";
this->setWindowIcon( QIcon(":/picture/music.png") );
control_panel = new ControlPanel();
music_tree_list = new MusicTreeList;
music_title = new QLabel();
music_title->setAlignment(Qt::AlignHCenter);
music_title->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
QFont ff;
ff.setPointSize(20);
music_title->setFont(ff);
QSplitter * main_splitter = new QSplitter();
main_splitter->addWidget(music_tree_list);
main_splitter->addWidget(music_title);
main_splitter->setSizes(QList<int>{200, 600});
//播放器引擎对象
music_player = new QMediaPlayer();
music_player->setVolume(10);
control_panel->volume_slider->setValue(10);
play_list = new QMediaPlaylist;
QVBoxLayout *layout = new QVBoxLayout();
layout->addWidget(main_splitter);
layout->addWidget(control_panel);
this->setLayout(layout);
this->resize(550, 350);
connect(music_tree_list, &MusicTreeList::currentMusic, this, &Widget::setMusic);
connect(control_panel, &ControlPanel::playBtnClicked, this, &Widget::playMusic);
connect(control_panel, &ControlPanel::stopBtnClicked, this, &Widget::stopPlayer);
connect(control_panel, &ControlPanel::nextBtnClicked, this, &Widget::playNext);
connect(control_panel, &ControlPanel::lastBtnClicked, this, &Widget::playLast);
connect(control_panel, &ControlPanel::volumeChanged, this, &Widget::setVolume);
connect(music_player, &QMediaPlayer::durationChanged, control_panel, &ControlPanel::changeTotalTime);
connect(music_player, &QMediaPlayer::positionChanged, control_panel, &ControlPanel::updateProgress);
}
Widget::~Widget()
{
}
//播放音乐
void Widget::playMusic()
{
if( player_state == Widget::STOP || player_state == Widget::PAUSE ) {
music_player->play();
player_state = Widget::PLAYING;
control_panel->play_btn->setIcon(this->style()->standardIcon(QStyle::SP_MediaPause));
} else {
music_player->pause();
player_state = Widget::PAUSE;
control_panel->play_btn->setIcon(this->style()->standardIcon(QStyle::SP_MediaPlay));
}
}
//设定当前音乐
void Widget::setMusic(QString music_name)
{
control_panel->play_btn->setIcon(this->style()->standardIcon(QStyle::SP_MediaPlay));
player_state = Widget::STOP;
path = project_path + music_name;
music_player->setMedia(QUrl::fromLocalFile(path));
music_name.replace(QRegExp("(/(.*)/)"), "");
music_name.replace(QRegExp(".mp3"), "");
music_title->setText(music_name);
}
//设置音量
void Widget::setVolume(int i){
music_player->setVolume(i);
}
//停止播放
void Widget::stopPlayer()
{
player_state = Widget::STOP;
control_panel->play_btn->setIcon(this->style()->standardIcon(QStyle::SP_MediaPlay));
music_player->stop();
}
//播放下一首
void Widget::playNext()
{
//play_list->next();
qDebug() << "下一首";
}
//播放上一首
void Widget::playLast()
{
//play_list->previous();
qDebug() << "上一首";
}
来源:CSDN
作者:YinShiJiaX
链接:https://blog.csdn.net/YinShiJiaW/article/details/104524831