QT 音乐播放器【二】 歌词同步+滚动+特效

打印 上一主题 下一主题

主题 975|帖子 975|积分 2925

效果图



概述



  • 先整体阐明一下这个效果的实现,你所看到的歌词都是QGraphicsObject,在QGraphicsView上绘制(paint)出来的。也就是说每一句歌词都是一个图元(item)。

  • 为什么用QGraphicsView框架?

    • 在做歌词滚动效果时,我看网上实现这一效果基本上都是用QLabel,这样或许简单许多,但是效果单一,且不够灵活。使用图形视图这套,图形项可以自由地被移动、缩放、旋转和编辑。当然主要照旧为了提拔自己,可以更熟悉这套框架。

  • 怎样剖析歌词?

    • 这里剖析的是lrc文件为一样平常的歌词文件,格式如下:格式是固定的,那么就可以通过正则表达式来剖析。然后存放在一个QMap中,key是时间,value是歌词。
    1. [02:08.496]乌蒙山连着山外山
    2. [02:11.138]月光洒下了响水滩
    3. [02:13.993]有没有人能告诉我
    4. [02:16.487]可是苍天对你在呼唤
    复制代码

  • 怎样同步歌词?

    • QMediaPlayer中有一个信号positionChanged,播放音乐时会时候刻触发,可以获取当前播放时间。然后和前面我们存放在QMap中的时间进行对比,所以QMap存放的时间格式要按positionChanged发出的时间格式来剖析。但我试验过许多次俩者的时间都是无法精确相称的。这里采取的方案是遍历QMap,找到第一个时间大于即是positionChanged发出的时间,然后获取这个时间对应的歌词,这便是当前的歌词。然后通过当前的key在获取前后几句的歌词。

  • 歌词滚动以及那些特效怎样实现的?

    • 同步歌词的时间会获取当前歌词以及前后几句歌词,提前存好对应歌词的特效,如下:这里面存了一个QMap,里面存放了每一句歌词的属性,包括字体巨细,位置,透明度等等。
    1. m_textMapInfolst << QMap<QString, QString>{
    2. {"index", "1"},
    3. {"font", "12"},
    4. {"y", "-100"},
    5. {"x", "300"},
    6. {"opacity", "0.2"}};
    复制代码
    我这里有七句歌词,那么就存七个QMap在一个QList中,当歌词刷新的时间就去遍历,根据QMap中的属性来设置item歌词,这里的图元要自己实现,重写paint函数。


代码

剖析歌词



  • 剖析的时间把格式设置GB 2312,不然会是乱码。按行已经QMediaPlayer的时间格式读取数据,并全部存放到listLyricsMap中。
  1. bool Lyrics::readLyricsFile(QString lyricsPath)
  2. {
  3.     listLyricsMap.clear();
  4.     QFile file(lyricsPath);
  5.     if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
  6.     {
  7.         listLyricsMap.clear();
  8.         return false;
  9.     }
  10.     QTextStream in(&file);
  11.     in.setCodec("GB 2312");
  12.     QString line;
  13.     while (!in.atEnd())
  14.     {
  15.         line = in.readLine();
  16.         analysisLyricsFile(line);
  17.     }
  18.     return true;
  19. }
  20. bool Lyrics::analysisLyricsFile(QString line)
  21. {
  22.     if (line == NULL || line.isEmpty())
  23.     {
  24.         return false;
  25.     }
  26.     QRegExp timeRegExp("\\[(\\d+):(\\d+\\.\\d+)\\]");
  27.     if (timeRegExp.indexIn(line) != -1)
  28.     {
  29.         qint64 totalTime = timeRegExp.cap(1).toInt() * 60000 + // 分钟
  30.                            timeRegExp.cap(2).toFloat() * 1000; // 秒
  31.         QString lyricText = line.mid(timeRegExp.matchedLength());
  32.         listLyricsMap.insert(totalTime, lyricText);
  33.     }
  34.     return true;
  35. }
复制代码
歌词同步



  • 绑定信号
  1.     connect(player, SIGNAL(positionChanged(qint64)),
  2.             this, SLOT(updateTextTime(qint64)));
复制代码


  • 读取对应listLyricsMap中的歌词
  1. void MainWindow::updateTextTime(qint64 position)
  2. {
  3.     auto lrcMap = lyric->getListLyricsMap();
  4.     qint64 previousTime = 0;
  5.     qint64 currentLyricTime = 0;
  6.     QMapIterator<qint64, QString> i(lrcMap);
  7.     while (i.hasNext())
  8.     {
  9.         i.next();
  10.         if (position < i.key())
  11.         {
  12.             QString currentLyric = lrcMap.value(previousTime);
  13.             currentLyricTime = previousTime;
  14.             break;
  15.         }
  16.         previousTime = i.key();
  17.     }
  18.     QStringList displayLyrics; // 存储将要显示的歌词列表。
  19.     // 获取将要显示的歌词
  20.     QMap<qint64, QString>::iterator it = lrcMap.find(currentLyricTime);
  21.     // 显示前三句,如果it不是开头,就向前移动迭代器
  22.     for (int i = 0; i < 3 && it != lrcMap.begin(); i++)
  23.     {
  24.         --it;
  25.         displayLyrics.prepend(it.value());
  26.     }
  27.     // 重置迭代器
  28.     it = lrcMap.find(currentLyricTime);
  29.     QString currntStr = QString();
  30.     // 显示当前句
  31.     if (it != lrcMap.end())
  32.     {
  33.         currntStr = QString("<font color='red'>" + it.value() + "</font>");
  34.         displayLyrics.append(it.value());
  35.     }
  36.     // 显示后三句
  37.     for (int i = 0; i < 3 && it != lrcMap.end(); i++)
  38.     {
  39.         ++it;
  40.         if (it != lrcMap.end())
  41.         {
  42.             displayLyrics.append(it.value());
  43.         }
  44.     }
  45.     //更新显示
  46.     imageViewWindow->textChanged(displayLyrics);
  47. }
复制代码
歌词特效



  • 同步于上述歌词的改动,清空场景遍历特效m_textMapInfolst,重新进行图元绘制
  1. void ImageViewWindow::textChanged(QStringList &lsit)
  2. {
  3. m_scene->clear();
  4. for (int index = 0; index < m_textMapInfolst.size(); index++)
  5. {
  6.   const auto textInfoMap = m_textMapInfolst[index];
  7.   GraphicsText *item = new GraphicsText();
  8.   item->setStr(lsit[index]);
  9.   item->setStrFont(textInfoMap["font"].toInt());
  10.   item->setItemOffset(QPointF(textInfoMap["x"].toInt() + image_xoffset, textInfoMap["y"].toInt() + image_yoffset));
  11.   item->setZValue(textInfoMap["index"].toInt());
  12.   item->setOpacity(textInfoMap["opacity"].toFloat());
  13.   m_items << item;
  14.   m_scene->addItem(item);
  15. }
  16. }
复制代码


  • 自绘图元
  1. void GraphicsText::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
  2. {
  3. painter->setFont(m_font);
  4. if (m_font.pointSize() > 20)
  5. {
  6.   painter->setPen(QPen(Qt::red));
  7. }
  8. else
  9. {
  10.   painter->setPen(QPen(Qt::blue));
  11. }
  12. painter->drawText(offset, str);
  13. }
复制代码

总结



  • 实现这个功能遇到的题目挺多的,比如绘制文本的时间只有一根线显示,是要view设置setViewportUpdateMode(QGraphicsView::FullViewportUpdate),雷同的题目挺多,还不好找。
  • 歌词特效这块还可以再扩展,字体,入场效果等都可以设置。
  • 当然这个功能还有许多可以优化的地方,BUG或许也不少,实现标题的功能的逻辑就是如上,可以作为参考。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

天津储鑫盛钢材现货供应商

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表