当前位置: 首页 > news >正文

Lrc歌词分析

lrc歌词同步

播放歌曲时,当点击"词"按钮后窗口会慢慢弹出,当点击隐藏按钮后,窗⼝会慢慢隐藏,且没有标题栏。内部显示当前播放歌曲的歌词,以及歌曲名称和作者。当点击下拉按钮时,窗⼝会隐藏起来。

lrc歌词界面分析

lrcPage中元素种类比较少,具体分析如下:

①和②为QLabel,分别显示作者和歌曲名称;

③~⑨均为QLabel,用来显示歌词,⑥为当前正在播放歌词③④⑤为当前播放歌词的前三句,⑦⑧⑨为当前播放歌词的后三句。歌词会随着播放时间持续,从下往上移动。

⑩为按钮,点击之后窗口隐藏。

lrc歌词界面布局

在qt create中新创建一个qt 设计师界面,命名为LrcPage,geometry的宽高修改为:1020/680。

 

① 拖一个Widget到LrcPage中,objectName修改为bgStyle,选中LrcPage,然后点击垂直布局,并将LrcPage的margin和spacing修改为0;

① 拖两个Widget到bgStyle中,objectName从上往下分别修改为lrcTop和lrcContent,lrcTop的minimumSize和maximumSize的高修改为50;然后选中bgStyle点击垂直布局,并将bgStyle的margin和spacing修改为0;这里方便观察,我们给这俩填上背景颜色

② 拖一个Push Button按钮到lrcTop中,objectName修改为hideBtn,minimumSize和maximumSize的宽和高修改为:30/50;

拖一个Widget到lrcPage中,objectName修改为titleBox,然后选中lrcTop,点击水平布局,并将lrcTop的margin和spacing修改为0;

③ 拖两个QLabel到titleBox中,objectName从上往下修改为musicSinger和musicName,然后选中titleBox,点击垂直布局,并将titleBox的margin和spacing修改为0,并且把两个水平居中;

④ 拖六个QLabel到lrcContent中,从上往下将objectName依次修改为:line1、line2、line3、lineCenter、line4、line5、line6,将line1~line6的minimumSize的高度修改为50,font大小修改为15;

拖两个垂直弹簧,一个放在line1上,一个放在line6下,将所有的QLabel挤到中间,再将lineCenter的minimumSize高度修改为80,font的大小修改为25;

⑤ 选中lrcContent,然后点击垂直布局,将lrcContent的margin和spacing修改为0

下面给 bgStyle lineCenter  hideBtn进行QSS美化

bgStyle

#bgStyle{border-image: url(:/images/bg.png);
}
*{color : #FFFFFF;
}

 

注意这里不是 background-image而是 border-image,因为background-image是按照图片实际的大小的,会超出界面,所以要用 border-image,以边界为标准进行放置图片

lineCenter

#lineCenter
{color:#1ECE9A;
}

 hideBtn

#hideBtn
{border:none;
}

LrcPage显示

在LrcPage的构造函数中,将窗口的标题栏去除掉;并给hideBtn关联clicked信号,当按钮点击时将窗口隐藏。并且设置隐藏按钮的图标为下拉箭头

    // 设置窗口为无边框窗口,去除标题栏
    setWindowFlag(Qt::FramelessWindowHint);
    
    // 连接隐藏按钮的点击信号,点击时隐藏窗口
    connect(ui->hideBtn, &QPushButton::clicked, this, [=]{
        hide();  // 隐藏窗口
    });

// lrcPage.cpp 中添加
LyricsPage::LyricsPage(QWidget *parent) :QWidget(parent),ui(new Ui::LyricsPage)
{ui->setupUi(this);// 设置窗口为无边框窗口,去除标题栏setWindowFlag(Qt::FramelessWindowHint);// 连接隐藏按钮的点击信号,点击时隐藏窗口connect(ui->hideBtn, &QPushButton::clicked, this, [=]{hide();  // 隐藏窗口});// 设置隐藏按钮的图标为下拉箭头ui->hideBtn->setIcon(QIcon(":/images/xiala.png"));
}

现在我们需要点击界面的这个词的按钮时候,让它弹出来Lrc歌词。

所以我们要给这个按钮关联上一个鼠标点击的事件,这个事件的实现要在QQMusic中实现 

在qqmusic.h中添加 void onLrcWordClicked(); 

然后按住ALT+ENTER在qqmusic.cpp文件中生成其方法。此时不要着急,先把槽函数和信号关联起来,这个事件将来是由Lrc界面触发的,但是Lrc这个成员变量在qqmusic.h中没有,所以我们要先在qqmusic.h中添加

// qqmusic.h 中添加
#include "lrcpage.h"  // 包含歌词页面的头文件LrcPage* lrcPage;  // 声明歌词页面的指针,用于管理歌词窗口

因为这个lrcPage是个指针,那么将来肯定要进行赋值的,我们把这个赋值放在qqmusic.cpp的init初始化中

// qqmusic.cpp 中添加 
void QQMusic::initUI()
{// ...// 创建lrc歌词窗⼝ lrcPage = new LrcPage(this);lrcPage->hide();
}

此时我们再关联上槽函数,并且实现它的方法

void QQMusic::onLrcWordClicked()
{lrcPage->show();  // 显示歌词窗口
}void QQMusic::connectSignalAndSlot()
{// ...(其他信号槽连接)// 连接歌词按钮的点击信号到对应的槽函数connect(ui->lrcWord, &QPushButton::clicked, this, &QQMusic::onLrcWordClicked);  // 点击歌词按钮时显示歌词窗口
}

现在我们运行下,看看效果有没有实现

可以发现已经能够实现效果了,接下来我们就是要实现显示和消失以动画的方式展示出来 

在解决这个问题前我们先把这个歌词的位置和原界面对应好,因为当时我们再设置主界面的背景的时候,为了保持美观,没有把间隔都设置为0,所以现在要再qqmusic.cpp中再初始化歌词界面的时候,给它传入默认的x,y不让它从0,0开始

lrcPage->setGeometry(10,10,lrcPage->width(),lrcPage->height());

 这下就完全对应上了

LrcPage添加动画效果

当点击QQMusic中"歌词"按钮时,lrcPage窗口是以动画效果显示出来的,当点击lrcPage上"下拉"按钮时,窗口先以动画的方式下移,动画结束后窗口隐藏。

窗口上移动画应该是在QQMusic类中实现

窗口下移动画 + 隐藏应该是在LrcPage中实现

我们之前动画的效果已经在btform中实现过了,就是那个跳动的音符,也包括推荐页面的内容,当鼠标放上去会有上移的效果,也是相同的方法实现的

这个歌词页面打开的动画思路大致如下

下移动画刚好跟他们反过来

窗口显示和上移动画

① 声明一个QPropertyAnimation类型的私有成员指针变量,这个指针将用于存储和控制歌词窗口的动画效果,通过这个指针,可以设置动画的起始值、结束值、持续时间等参数

// qqmusic.h  中新增
#include <QPropertyAnimation>// 歌词按钮槽函数
void onLrcWordClicked();private:QPropertyAnimation* lrcPageAnimation;

② QQMusic的initUI函数中,创建lrcPage对象并将窗口隐藏;给lrcPage窗口添加上移动画,动画暂不开启

// 实例化LrcWord对象
lrcPage = new LrcPage(this);
lrcPage->hide();// lrcPage添加动画效果
lrcPageAnimation = new QPropertyAnimation(lrcPage, "geometry", this);
lrcPageAnimation->setDuration(250);
lrcPageAnimation->setStartValue(QRect(10, 10+lrcPage->height(), lrcPage->width(), lrcPage->height()));
lrcPageAnimation->setEndValue(QRect(10, 10, lrcPage->width(), lrcPage->height()));
//这里注意不要直接启动动画效果
}

这里注意不要直接启动动画效果,动画的启动应该放在歌词按钮点击的时候,而不是程序启动的时候 ,所以在onLrcWordClicked方法中,添加启动动画效果

// 显示窗口 并 开启动画
void QQMusic::onLrcWordClicked()
{lrcPage->show();lrcPageAnimation->start();
}

接下来我们运行看看,发现动画是有了,但是有点bug 

// qqmusic.cpp 中新增 
void QQMusic::initUi()
{// ...// 此处需要将圆角半径不能太大,否则动画效果有问题,可以设置为10shadowEffect->setBlurRadius(20);
}

这个bug是因为我们自己给窗口设计过阴影,但是这个阴影在动画移动的过程也会进行刷新,重绘等等,会增加cpu负担,所以我们要把这个阴影效果给它调的小一点

③QQMusic中给"歌词"按钮添加槽函数,当按钮点击时,显示窗口,开启动画

void QQMusic::connectSignalAndSlots()
{// ...// 歌词按钮点击信号和槽函数connect(ui->lrcWord, &QPushButton::clicked, this, &QQMusic::onLrcWordClicked);// ...
}

此时效果就完美实现了

 接下来我们实现下拉动画的效果,这个效果我们需要在LrcPage中实现

// lrcpage.h 中新增
#include <QPropertyAnimation>private:QPropertyAnimation* lrcAnimation;

那这个动画应该是在什么时候启动呢,那肯定是要在初始化的时候进行启动动画了 

// lrcpage.cpp 中新增
LrcPage::LrcPage(QWidget *parent) :QWidget(parent),ui(new Ui::LrcPage)
{ui->setupUi(this);// ...lrcAnimation = new QPropertyAnimation(this, "geometry", this);lrcAnimation->setDuration(250);lrcAnimation->setStartValue(QRect(10, 10, width(), height()));lrcAnimation->setEndValue(QRect(10, 10+height(), width(), height()));// 点击设置下拉按钮时开启动画connect(ui->hideBtn, &QPushButton::clicked, this, [=]{lrcAnimation->start();});// 动画结束时,将窗口隐藏connect(lrcAnimation, &QPropertyAnimation::finished, this, [=]{hide();});
}

运行下就完美实现了 

lrc歌词解析和同步

什么时LRC歌词

lrc是英文lyric(歌词)的缩写,被用作歌词文件的扩展名。该文件将歌词和歌词出现的时间编辑到一起,当歌曲播放的时候,按照歌词文件中的时间依次将歌词显示出来。

这样的好处:当播放歌曲时,要能方便找到对应的歌词,一般歌词文件和歌曲文件名字都是一样的,只是后缀不同

标准格式:[分:秒.毫秒] 歌词

其他格式:① [分:秒] 歌词 ② [分:秒:毫秒] 歌词

// lrcpage.h 中新增
struct LrcWordLine
{qint64 time;   // 时间QString text;  // 歌词内容LrcWordLine(qint64 qtime, QString qtext): time(qtime), text(qtext){}
};// LrcPage类中添加成员变量
QVector<LrcLine> lrcLines;    // 按照时间的先后次序保存每行歌词

也就是说当播放位置发生改变的时候,我们需要从磁盘找到歌曲文件中解析改变后的歌曲的内容,在歌词界面同步显示歌词。 那该怎么解析呢,我们之前也在onMetaDataAvabileChanged之前也处理过,在最后的位置我们再添加个解析歌曲的LRC歌词,这个工作是由lrcpage来完成的,lrcpage把歌词更新之后,内部在实现一个显示歌词的方法show

通过歌曲名找LRC文件 

一般情况下,播放器在设计之初就会设计好歌曲文件和歌词文件的存放位置,以及对应关系,通常歌曲文件和lrc歌词文件名字相同,后缀不同。在磁盘存放的时候,可以将歌曲文件和lrc文件分两个文件夹存储,也可以存储到一个文件夹下。

本文为了方便处理,存储在一个文件夹下,因此可以通过Music对象快速找到lrc歌词文件。

// music.h 中新增
QString getLrcFilePath() const;// music.cpp 中新增
QString Music::getLrcFilePath() const
{// 音频文件和LRC文件在一个文件夹下// 直接将音频文件的后缀替换为.lrcQString lrcPath = musicUrl.toLocalFile();lrcPath.replace(".mp3", ".lrc");lrcPath.replace(".flac", ".lrc");lrcPath.replace(".mpga", ".lrc");return lrcPath;
}

LRC歌词解析

找到lrc歌词文件后,由lrcPage类完成对歌词的解析。解析的大概步骤:

①打开歌词文件

② 以行为单位,读取歌词文件中的每一行

③ 按照lrc歌词文件格式,从每行文本中解析出时间和歌词

  • [00:17.94]那些失眠的人啊 你们还好吗
  • [0:58.600.00]你像一只飞来飞去的蝴蝶

]之前为事件,]之后为歌词的文本,通过]就可以把歌词和歌词时间给分离开

④ 用<时间,行歌词>构建一个LrcLine对象存储到lrcLines中。

// lrcpage.h 中新增
bool parseLrc(const QString& lrcPath);// lrcpage.cpp 中新增
bool LrcPage::parseLrc(const QString& lrcPath)
{lrcLines.clear();// 打开歌词文件QFile lrcFile(lrcPath);if(!lrcFile.open(QFile::ReadOnly)){qDebug()<<"打开文件:"<<lrcPath;return false;}while(!lrcFile.atEnd()){QString lrcWord = lrcFile.readLine(1024);// [00:17.94]那些失眠的人啊 你们还好吗// [0:58.600.00]你像一只飞来飞去的蝴蝶int left = lrcWord.indexOf('[');int right = lrcWord.indexOf(']');// 解析时间qint64 lineTime = 0;int start = 0;int end = 0;QString time = lrcWord.mid(left+1, right-left-1);// 解析分钟start = 1;end = time.indexOf(':');lineTime += lrcWord.mid(start, end-start).toInt()*60*1000;// 解析秒start = end + 1;end = time.indexOf('.', start);lineTime += lrcWord.mid(start, end-start).toInt()*1000;// 解析毫秒start = end+1;end = time.indexOf('.', start);lineTime += lrcWord.mid(start, end-start).toInt();// 解析歌词QString word = lrcWord.mid(right+1).trimmed();lrcLines.push_back(LrcLine(lineTime, word.trimmed()));}// 测试验证for(auto word : lrcLines){qDebug()<<"word.time="<<word.time;}return true;
}

根据歌曲播放位置获取歌词并显示

当歌曲播放进度改变时候,QMediaPlayer的positionChanged信号会触发,该信号同步播放时间的时候已经在QQMusic类中处理过了,在其槽函数中就能拿到当前歌曲的播放时间,通过播放时间,就能在LrcPage中找到对应行的歌词。

// lrcpage.h 中新增
// lrcpage.cpp 中新增
int LrcPage::getLineLrcWordIndex(qint64 pos)
{// 如果歌词是空的,返回-1if(lrcLines.isEmpty()){return -1;}if(lrcLines[0].time > pos){return 0;}// 通过时间比较,查取下标for(int i = 1; i < lrcLines.size(); ++i){if(pos > lrcLines[i-1].time && pos <= lrcLines[i].time){return i-1;}}// 如果没有找到,返回最后一行return lrcLines.size()-1;
}QString LrcPage::getLineLrcWord(qint64 index)
{if(index < 0 || index >= lrcLines.size()){return "";}return lrcLines[index].text;
}void LrcPage::showLrcWord(int time)
{// 先要获取歌词--根据歌词的时间进行获取int index = getLineLrcWordIndex(time);if(-1 == index){ui->line1->setText("");ui->line2->setText("");ui->line3->setText("");ui->lineCenter->setText("当前歌曲无歌词");ui->line4->setText("");ui->line5->setText("");ui->line6->setText("");}else{ui->line1->setText(getLineLrcWord(index-3));ui->line2->setText(getLineLrcWord(index-2));ui->line3->setText(getLineLrcWord(index-1));ui->lineCenter->setText(getLineLrcWord(index));ui->line4->setText(getLineLrcWord(index+1));ui->line5->setText(getLineLrcWord(index+2));ui->line6->setText(getLineLrcWord(index+3));}
}

lrc歌词同步播放进度

当歌曲发生切换时,需要完成lrc歌词文件的解析;

当歌曲播放进度发生改变时,根据歌曲的当前播放时间,通过lrcPage找到对应行歌词并显示出来。

// qqmusic.cpp 添加
void QQMusic::onMetaDataAvailableChanged(bool available)
{// 歌曲名称、歌曲作者直接到Musci对象中获取// 此时需要知道媒体源在播放列表中的索引QString musicId = currentPage->getMusicIdByIndex(currentIndex);auto it = musicList.findMusicByMusicId(musicId);// ...// 加载lrc歌词并解析if(it != musicList.end()){lrcPage->parseLrc(it->getLrcFilePath());}
}void QQMusic::onPositionChanged(qint64 position)
{// 1. 更新当前播放时间ui->currentTime->setText(QString("%1:%2").arg(position/1000/60, 2, 10, QChar('0')).arg(position/1000%60, 2, 10, QChar('0')));// 2. 更新进度条的位置ui->progressBar->setStep(position/(float)totalTime);// 3. 同步lrc歌词if(playList->currentIndex() >= 0){lrcPage->showLrcWord(position);}
}

相关文章:

  • 简单了解一下Hugging Face(抱抱脸)
  • C++中的右值引用与移动语义的理解
  • @Transactional注解失效的原因有哪些?
  • 如何对Video视频进行SEO优化?
  • OLED(SSD306)移植全解-基于IIC
  • Semaphore - 信号量
  • CPP基础
  • 西门子 S7-1200 PLC 海外远程运维技术方案
  • DAX权威指南8:DAX引擎与存储优化
  • 第七章:未名湖畔的樱花网关
  • 书籍推荐 --- 《筚路维艰:中国经济社会主义路径的五次选择》
  • 【信息系统项目管理师-案例真题】2025上半年(第二批)案例分析答案和详解(回忆版)
  • ​​Java 异常处理​​ 的详细说明及示例,涵盖 try-catch-finally、自定义异常、throws 与 throw 的核心概念和使用场景
  • 在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)
  • Benchmarking Potential Based Rewards for Learning Humanoid Locomotion
  • 关于锁策略的简单介绍
  • 固态继电器与驱动隔离器:电力系统的守护者
  • C++.OpenGL (6/64)坐标系统(Coordinate Systems)
  • 为什么要对邮件列表清洗?
  • C++ --- vector
  • 茂名 网站建设/网上推广产品怎么做
  • 在excel中怎么做邮箱网站/潍坊网站开发公司
  • 怎么做自己的网站/百度浏览器官网
  • 国外做鞋子的网站/指数分布的期望和方差
  • 亦庄网站开发公司/东莞seo托管
  • 广州市专业做商城网站/10条重大新闻