1、创建一个qmainwindow项目
2、导入素材包
3、实现ui界面
设置按钮的宽高
4、实现打开文件
void MainWindow::readSongFlies(bool)
{
files.clear(); // 先清空原有链表
songList->clear(); // 先清空原有链表
files = QFileDialog::getOpenFileNames(
this,
"选择文件",
"/home",
"Audio Files (*.wav *.mp3 *.ogg *.flac)");
// 添加新数据
for(int i=0;i<files.size();i++)
{
QString songfile = files.at(i); // 处理文件路径
QStringList songfileSplite = songfile.split("/");
QString name = songfileSplite.at(songfileSplite.size()-1);
songList->appendRow(new QStandardItem(name));
}
ui->songList->setModel(songList);
}
5、双击歌曲名获取歌词并播放
5.1添加设计师类继承qwidget
5.2监听双击鼠标事件
#include "mylistview.h"
#include "ui_mylistview.h"
#include<QDebug>
MyListView::MyListView(QWidget *parent) :
QWidget(parent),
ui(new Ui::MyListView)
{
ui->setupUi(this);
// 设置不可编译
ui->listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
// 连接QListView的双击信号到槽函数
connect(ui->listView, &QListView::doubleClicked, this, &MyListView::getIndexData);
}
MyListView::~MyListView()
{
delete ui;
}
void MyListView::setSongList(QStandardItemModel *&songList)
{
ui->listView->setModel(songList);
}
void MyListView::getIndexData(const QModelIndex &index)
{
// 获取当前双击的项目数据
QString songName = index.data().toString();
// 处理获取到的当前内容
emit selectSongName(songName);
}
阶段效果
6、实现歌词展示
6.1读取歌词
添加一个c++类处理歌词文件解析,返回对象
6.2解析歌曲头信息
int MyLrcHandle::getLrcHead(QString data)
{
// 切割歌词
QStringList lrcData = data.split("\n");
int i=0;
for(;i<lrcData.size();i++) // 歌曲头部信息
{
char key[10] = "";
char value[128] = "";
sscanf(lrcData.at(i).toStdString().c_str(), "[%[^:]:%[^]]",key, value);
if (strcmp(key, "ti") == 0) // 歌名
{
lrcdata->ti = value;
}
else if (strcmp(key, "ar") == 0) // 歌手
{
lrcdata->ar = value;
}
else if (strcmp(key, "al") == 0) // 专辑
{
lrcdata->al = value;
}
else if (strcmp(key, "by") == 0) // 制作
{
lrcdata->by = value;
}
else if (strcmp(key, "offset") == 0) // 延时
{
continue;
}
else if (isdigit(key[0])) // 识别到数字
{
break;
}
else
{
continue;
}
}
// 歌词信息
return 0;
}
6.3展示歌词
void MyLrcHandle::analyzeLrc(QString data)
{
// 切割歌词
QStringList lrcData = data.split("\n");
int i=0;
for(;i<lrcData.size();i++)
{
char key[10] = "";
char value[128] = "";
sscanf(lrcData.at(i).toStdString().c_str(), "[%[^:]:%[^]]",key, value);
if (strcmp(key, "ti") == 0) // 歌名
{
lrcdata->ti = value;
}
else if (strcmp(key, "ar") == 0) // 歌手
{
lrcdata->ar = value;
}
else if (strcmp(key, "al") == 0) // 专辑
{
lrcdata->al = value;
}
else if (strcmp(key, "by") == 0) // 制作
{
lrcdata->by = value;
}
else if (strcmp(key, "offset") == 0) // 延时
{
continue;
}
else if (isdigit(key[0])) // 识别到数字
{
break;
}
else
{
continue;
}
}
// 歌词信息
for(;i<lrcData.size();i++)
{
QString str = lrcData.at(i);
if(str.isEmpty() || str.size()< 10) // 处理空行
{
continue;
}
// [03:34.64][02:34.71][01:05.83]我想就这样牵着你的手不放开(使用splite更方便)
char *tmp = const_cast<char*>(str.toUtf8().constData());
char *lrc = tmp;
while(*lrc == '[') // 如果第一个字符是 [ 跳过固定长度的字符
lrc += 10;
while(*tmp == '[') // 获取时间
{
int min=0,sec=0;
sscanf(tmp, "[%d:%d", &min, &sec);
// 存入qmap中qmap以秒数为key,歌词为value
lrcdata->lrc->push_back(MyLrc(min * 60 + sec, lrc));
tmp+=10;
}
}
// 对歌词排序
lrcdata->lrc->sort(CompareLrc());
}
处理歌词最开始和结束的情况
std::list<MyLrc>::iterator MainWindow::findPointFirst()
{
std::list<MyLrc>::iterator tmpBegin = mLrc->getLrc()->begin();
std::list<MyLrc>::iterator tmpBegin2 = std::next(tmpBegin);
std::list<MyLrc>::iterator tmpEnd = mLrc->getLrc()->end();
std::list<MyLrc>::iterator tmpEnd2 = std::prev(tmpEnd); // 最后一行
std::list<MyLrc>::iterator tmpEnd3 = std::prev(tmpEnd2); // 倒数第二行
std::list<MyLrc>::iterator currentLrcPrev2 = std::prev(currentLrc);
std::list<MyLrc>::iterator currentLrcPrev3 = std::prev(currentLrcPrev2);
if(currentLrc == tmpBegin) // 如果是一行歌词
return tmpBegin;
else if(currentLrc == tmpBegin2) // 当前是第二行歌词
return tmpBegin;
else if(currentLrc == tmpEnd2) // 最后一行
{
tmpEnd2--;
tmpEnd2--;
tmpEnd2--;
tmpEnd2--;
return tmpEnd2;
}else if(currentLrc == tmpEnd3) // 倒数第二行
{
tmpEnd3--;
tmpEnd3--;
tmpEnd3--;
return tmpEnd3;
}
else
{
return currentLrcPrev3; // 其他情况显示当前歌词的前两行
}
}
双击歌曲进行播放 歌词部分初步完成。
播放音乐部分
1、使用有名管道对mplayer进行操作 定时对mplayer进行歌曲时间获取 (此处使用pthread处理)
void *send_cmd(void *arg) // 定时发送获取时间操作
{
DATA *data = (DATA *)arg;
MainWindow *w = data->w;
int fi_fd = w->fi_fd;
usleep(500000); // 等待0.5秒
while(1)
{
usleep(500000);
if(w->flag)
{
if(write(fi_fd,"get_time_pos\n", strlen("get_time_pos\n"))==-1)
{
perror("write");
}
}
}
}
2、使用无名管道,将标准输出重定向到无名管道的输入
void *recv_msg(void* arg)
{
DATA* data = (DATA*)arg;
MainWindow* w = data->w;
int fd0 = w->fd0;
while(1)
{
char buf[64]="";
if(read(fd0, buf, sizeof(buf)) == -1)
{
perror("fail to read");
}
if(strncmp(buf,"ANS_LENGTH", strlen("ANS_LENGTH")) == 0) // 获取歌曲总时长
{
float stime = 0.0f;
sscanf(buf,"ANS_LENGTH=%f", &stime);
int totalTime =(int)stime;
if(totalTime > 0)
{
w->totalTime = totalTime;
int min = w->totalTime / 60;
int sec = w->totalTime % 60;
w->currentSongTime = QString("%1:%2").arg(min, 2, 10, QLatin1Char('0'))
.arg(sec, 2, 10, QLatin1Char('0'));
}
}
if(strncmp(buf,"ANS_TIME_POSITION", strlen("ANS_TIME_POSITION")) == 0) // 获取当前运行时间
{
float time = 0.0f;
sscanf(buf,"ANS_TIME_POSITION=%f", &time);
// qDebug() << time;
w->runTime=(int)time;
}
}
}
总时长 * 拖拽到的百分比 - 当前运行时间 = 快进或快退(正数是快进,负数是快退)
void MainWindow::dragProgress(int value)
{
qDebug() << "dragProgress " << value;
// 重置当前歌词和时间
int tmpTime = (int)(value * 0.01 * totalTime); // 计算获取拖拽到的时间
QString cmd = QString("seek %1\n").arg(tmpTime - runTime); // 计算是进还是退
qDebug() << cmd;
if(write(fi_fd,cmd.toStdString().c_str(),strlen(cmd.toStdString().c_str())) == -1) // 控制mplayer快进秒数 如果是负数就快退
{
perror("write");
}
runTime = tmpTime;
// 找到大于该时间的第一个歌词
std::list<MyLrc>::iterator it = mLrc->getLrc()->begin();
for(;it != mLrc->getLrc()->end(); it++)
{
if((*it).second > runTime)
break;
}
currentLrc = it;
// 重置时间
minutes = runTime / 60;
seconds = runTime % 60;
}
可以关闭mplayer标准异常文件描述符
美化程度请自行设置