项目地址
自己用C With Class C++做的一个小项目,GUI界面使用的是Qt5.12,播放模块一开始使用Qt自带的multimedia模块,后改为VLC-qt以支持更多格式。
本项目主要用于学习目的,需要的可以借鉴,也欢迎提出意见和建议。
功能特点与展望:
支持打开文件夹并自动搜索合法音频文件(暂不支持递归搜索),还可以拖拽文件到界面上。
支持多种格式。
支持简单的播放列表。可以保存、加载播放列表。也支持下次启动时自动装入上次退出时的播放列表。后期或将加入多个播放列表的支持。
目前界面比较丑,后期可能会美化界面,还可能加入网络音乐播放功能之类的功能。
音乐信息模块:实现存放音乐信息,并具有对文件合法性的简单判断功能。
类定义:
class Music {
private:
QString formattedTime;
QUrl url;
//歌曲名
QString title,description,album;
QUrl albumImage;
int length;
static bool isMPEG(QFile *media,QDataStream &reader);
static bool isWav(QFile *media,QDataStream &reader);
static bool isWma(QFile *media,QDataStream &reader);
static bool isAiff(QFile *media,QDataStream &reader);
static bool isFlac(QFile *media,QDataStream &reader);
static bool isAAC(QFile *media,QDataStream &reader);
static bool isM4A(QFile *media,QDataStream &reader);
static bool isAPE(QFile *media,QDataStream &reader);
static bool isVorbis(QFile *media,QDataStream &reader);
static bool isAU(QFile *media,QDataStream &reader);
public:
/**
* 根据指定的URL建立音乐对象
* @param uri 给定的URL
* @note 本构造函数**不会**判断URL是否合法
*/
Music(const QUrl &uri = QUrl());
Music(const Music &a) = default;
///获取包含标题、时间、唱片集、描述的音乐信息
QString toString();
/**
* 比较两个Music是否相等
* @param a 另一个Music
* @return 当且仅当满足以下条件之一时返回true:
* 1.url相同
* 2.长度,标题均相同
*/
bool equals(const Music &a) const;
friend bool operator==(const Music &a,const Music &b) {
return a.equals(b);
}
friend bool operator!=(const Music &a,const Music &b) {
return !a.equals(b);
}
//hash相同,可能不同,但若hash不同,则认为一定不同
friend uint qHash(const Music &key,uint seed = 0) {
return qHash(key.title,seed) ^ qHash(key.length,seed);
}
//getters
///获取Url
const QUrl &getUrl() const;
/// 获取标题
const QString &getTitle() const;
/// 获取介绍
const QString &getDcrp() const;
/// 获取专辑图片 注意:可能不准
const QUrl &getAlbumImage() const;
///获取经格式化后的时间
QString formatTime();
/**
* @brief 检查文件是否合法
* @param media 被检查的文件名。
* @return 当文件>1KB且符合任意一种支持的格式时,返回true,否则返回false
*/
static bool isLegal(QString media);
};
#endif // MUSIC_H
UI模块,包括主窗口模块和自定义控件模块。
主窗口(Playerwindow)模块就是整个播放器的主窗口。模块负责用户与播放器的交互。继承于QMainWindow。
自定义控件模块,包括播放列表(PlayListView),播放器控件(PlayerButton),进度条(PlayerSlider)。分别继承于QListView,QLabel,QSlider,为适应功能需求进行了扩展。
播放器核心模块(PlayerCore)继承于VlcMediaPlayer,其实就是对VlcMediaPlayer的一些封装与扩展。内部有一个静态VlcInstance对象,不同player之间共享。
类定义:
class PlayerCore : public VlcMediaPlayer{
Q_OBJECT
private:
static VlcInstance ins;
VlcEqualizer *equ;
VlcMedia *curMedia;
QSet<Music> medias;
QList<QUrl> list;
int current = -1;
int startLoc = 0;
void connectSlots();
void setMedia(const QString &media);
public:
static constexpr int MAX_MEDIA_COUNT = 10000;
static constexpr int MODE_COUNT = 4,FORMAT_COUNT = 16;
static const QString Formats[FORMAT_COUNT];
/// 各模式的提示文字
static const QString MODE_TIPS[MODE_COUNT];
/// 播放模式
enum PlayMode{SIGNLE = 0,SEQUENTIAL,SIGNLE_LOOP,LIST_LOOP};
PlayMode mode = SIGNLE;
explicit PlayerCore(QObject *parent = nullptr);
///获取当前媒体url
QUrl getMedia();
///获取指定编号的媒体目录url
const QUrl &getMedia(int i);
///获取媒体详细信息
Music getMediaDetail(int i);
Music getMediaDetail();
///获取以秒为单位的时间
int getPosInSecond();
int getCurrentMediaIndex();
///设置时间,以秒为单位
void setPos(int pos);
///设置播放列表位置
void setCurrentMediaIndex(int i);
///添加到播放列表
bool addToList(const QString &media,bool local = true);
/**
* 从播放列表中移除特定媒体
* 若移除的是当前活动媒体,则自动将当前活动媒体设为上一个,如果同时是第一个则设为删除后的第一个(即原来的第二个)
*/
bool removeFromList(int loc);
///清空播放列表
void clear();
bool isLocal(int i);
~PlayerCore();
public slots:
void play();
void goNext();
void goPrevious();
/**
* @brief moveUp 将指定媒体上移
* @param i 要移动哪个媒体
* @param k 移动多少个单位
* @return 当i位置合法且移动后的位置仍合法,返回true,否则返回false
* @note moveDown类似
*/
bool moveUp(int i,int k = 1);
bool moveDown(int i, int k = 1);
void setSoundEffect(uint index);
signals:
void finished();
//void mediaSourceChanged(qint64 newTime);
};
网络模块:支持网络音乐搜索、播放。实现搜索的核心采用python爬虫脚本实现,用pyinstaller编译成可执行文件后供C++调用,通过参数传递搜索关键词
爬虫的核心代码如下
host = "https://www.xzmp3.com"
pool = ThreadPool(20)
fp = open("links.tmp","w",encoding="utf-8")
# 第一步:获取下载页面的URLs
def get_downpage_list(name:str):
search_page = host + "/xiazai/"
uri = search_page + parse.quote(name) + ".htm"
response = request.urlopen(request.Request(uri,headers=head,method="GET"))
page_text = response.read().decode("utf-8")
tree = etree.HTML(page_text)
# html/body/div/div[2]/div/div[2](截止到这里是"body")/div[1]/div/div[3]/div[1]/div[i]/a
result = tree.xpath("//body/div/div[2]/div/div[2]//div[@class=\"list_row\"]")
#print(result)
#开始查找urls
url_list = []
for each in result:
location = each.xpath("./div[1]/a/@href")
title = each.xpath("./div[1]/a/text()")
singer = each.xpath("./div[2]/a/text()")
if len(location)>0:
url_list.append(title[0] + ';' + singer[0] + ';' + host+location[0])
for i in range(0,len(url_list),10):
pool.map(get_downlink,url_list[i,min(len(url_list),i+10)]) # 分成10个1组
pool.close()
pool.join()
# 第二步:从获取到的URLs中提取实际下载地址,保存在文件中
def get_downlink(info:str):
lst = info.split(';')
response = request.urlopen(request.Request(url=lst[2],headers=head,method="GET"))
page_text = response.read().decode("utf-8")
tree = etree.HTML(page_text)
result_addr = tree.xpath("//body/div/div[2]/div[1]/div[2]/div[1]/div/div[2]/div[1]/a")
# fp.write(result_addr[0].xpath("./@href")[0])
response = request.urlopen(request.Request(url=result_addr[0].xpath("./@href")[0],headers=head,method="GET"))
true_url = response.geturl()
if(true_url.find(host) < 0 and (not true_url.endswith("/404"))):
fp.write(lst[0] + ';' + lst[1] + ';' + true_url + '\n')
if __name__ == "__main__":
if len(sys.argv) < 2:
fp.close()
raise Exception("错误:未指定关键词")
# st = time.time()
get_downpage_list(sys.argv[1])
# print(time.time() - st)
fp.close()
实现效果:
(左下角为专辑图片)
拖拽
加载播放列表
8.20更新:支持网络音乐搜索和打开外部文件,效果如下:
9.20更新:更新代码
10.4更新:更新部分截图