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

Qt QML实现Windows桌面歌词动态播放效果

前言

使用Qt5.15.2,QML实现简单的歌词动态播放效果。
效果图如下:
在这里插入图片描述
注:这里只是为了演示播放效果,并未真正加载音频进行播放。可以在此基础上进行扩展。

正文

关键代码
QML部分

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import LyricsPlayback 1.0Window {id: mainWindowwidth: 800height: 500visible: truetitle: "歌词播放器"// 设置窗口透明color: "transparent"flags: Qt.Window | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.WA_TranslucentBackground// 自定义播放位置属性property int customPosition: 0// 播放状态属性property bool isPlaying: falseItem{anchors.fill: parent// 允许拖动窗口MouseArea {anchors.fill: parentproperty point clickPos: "0,0"onPressed: {clickPos = Qt.point(mouse.x, mouse.y)}onPositionChanged: {if (pressed) {var delta = Qt.point(mouse.x - clickPos.x, mouse.y - clickPos.y)mainWindow.x += delta.xmainWindow.y += delta.y}}}// 歌词模型LyricsModel {id: lyricsModel}// 不再使用MediaPlayer组件// 主布局Rectangle {anchors.fill: parentcolor: "#80000000" // 半透明黑色背景radius: 10ColumnLayout {anchors.fill: parentanchors.margins: 10spacing: 10// 歌词显示区域ListView {id: lyricsViewLayout.fillWidth: trueLayout.fillHeight: truemodel: lyricsModelclip: truespacing: 5highlightFollowsCurrentItem: truehighlightMoveDuration: 200// 自动滚动到当前歌词onCountChanged: {if (lyricsModel.currentIndex >= 0) {positionViewAtIndex(lyricsModel.currentIndex, ListView.Center)}}Connections {target: lyricsModelfunction onCurrentIndexChanged() {if (lyricsModel.currentIndex >= 0) {lyricsView.positionViewAtIndex(lyricsModel.currentIndex, ListView.Center)}}}delegate: Item {width: lyricsView.widthheight: 40Text {id: lyricTextanchors.centerIn: parentwidth: parent.widthhorizontalAlignment: Text.AlignHCentertext: model.textfont.pixelSize: model.isCurrent ? 24 : 18font.bold: model.isCurrentcolor: model.isCurrent ? Qt.rgba(1, 1, 1, 1) : "#80FFFFFF" // 当前行为白色,其他为半透明白色elide: Text.ElideRight// 当前播放行的渐变效果Rectangle {visible: model.isCurrentanchors.left: parent.leftanchors.top: parent.topanchors.bottom: parent.bottomwidth: parent.width * lyricsModel.progresscolor: "transparent"clip: trueText {anchors.left: parent.leftanchors.top: parent.toptext: model.textfont.pixelSize: 24font.bold: truecolor: "#FF4081" // 高亮颜色width: lyricText.widthhorizontalAlignment: Text.AlignHCenterelide: Text.ElideRight}}}}}// 控制栏RowLayout {Layout.fillWidth: trueheight: 40spacing: 10Button {text: "加载示例歌词"onClicked: {lyricsModel.loadSampleLyrics()isPlaying = falseplayTimer.stop()customPosition = 0lyricsModel.position = 0}}Button {text: isPlaying ? "暂停" : "播放"onClicked: {if (isPlaying) {isPlaying = falseplayTimer.stop()} else {isPlaying = trueplayTimer.start()}}}Button {text: "退出"onClicked: {Qt.quit()}}Slider {Layout.fillWidth: truefrom: 0to: 40000 // 40秒value: customPositiononMoved: {customPosition = valuelyricsModel.position = value}}}}}// 由于没有实际的音频文件,使用定时器模拟播放进度Timer {id: playTimerinterval: 100repeat: truerunning: falseonTriggered: {if (customPosition < 40000) { // 40秒customPosition += 100// 更新歌词模型的位置lyricsModel.position = customPosition} else {stop()customPosition = 0lyricsModel.position = 0}}}Component.onCompleted: {lyricsModel.loadSampleLyrics()}}
}

通过设置歌词格式和时间来判定播放进度

void LyricsModel::setPosition(qint64 position)
{if (m_position != position) {m_position = position;emit positionChanged();// 更新当前歌词索引updateCurrentIndex();// 如果在当前歌词行内,更新进度if (m_currentIndex >= 0 && m_currentIndex < m_lyrics.size()) {emit progressChanged();}}
}QString LyricsModel::lyricsText() const
{return m_lyricsText;
}void LyricsModel::setLyricsText(const QString &text)
{if (m_lyricsText != text) {m_lyricsText = text;emit lyricsTextChanged();// 解析歌词beginResetModel();m_lyrics.clear();if (m_parser.parseLyrics(text)) {QMap<qint64, QString> parsedLyrics = m_parser.getLyrics();QMapIterator<qint64, QString> it(parsedLyrics);while (it.hasNext()) {it.next();LyricLine line;line.time = it.key();line.text = it.value();m_lyrics.append(line);}}endResetModel();// 重置当前索引setCurrentIndex(-1);updateCurrentIndex();}
}qint64 LyricsModel::currentStartTime() const
{if (m_currentIndex >= 0 && m_currentIndex < m_lyrics.size()) {return m_lyrics.at(m_currentIndex).time;}return 0;
}qint64 LyricsModel::currentEndTime() const
{if (m_currentIndex >= 0 && m_currentIndex < m_lyrics.size() - 1) {return m_lyrics.at(m_currentIndex + 1).time;}return 0;
}qreal LyricsModel::progress() const
{if (m_currentIndex >= 0 && m_currentIndex < m_lyrics.size()) {qint64 startTime = m_lyrics.at(m_currentIndex).time;qint64 endTime;if (m_currentIndex < m_lyrics.size() - 1) {// 非最后一行,使用下一行的时间作为结束时间endTime = m_lyrics.at(m_currentIndex + 1).time;} else {// 最后一行,使用开始时间加上固定时长(4秒)作为结束时间endTime = startTime + 4000;}if (endTime > startTime) {if (m_position >= startTime) {if (m_position <= endTime) {// 在时间范围内,正常计算进度return static_cast<qreal>(m_position - startTime) / (endTime - startTime);} else {// 超过结束时间,显示100%进度return 1.0;}}}}return 0.0;
}void LyricsModel::updateCurrentIndex()
{// 查找当前时间对应的歌词行int newIndex = -1;for (int i = 0; i < m_lyrics.size() - 1; ++i) {if (m_position >= m_lyrics.at(i).time && m_position < m_lyrics.at(i + 1).time) {newIndex = i;break;}}// 处理最后一行歌词if (newIndex == -1 && !m_lyrics.isEmpty() && m_position >= m_lyrics.last().time) {newIndex = m_lyrics.size() - 1;}setCurrentIndex(newIndex);
}void LyricsModel::loadSampleLyrics()
{QString sampleLyrics = "[00:00.00]示例歌词 - 测试\n""[00:02.00]作词:测试\n""[00:04.00]作曲:测试\n""[00:06.00]\n""[00:08.00]这是第一行歌词\n""[00:12.00]这是第二行歌词示例\n""[00:16.00]这是第三行歌词演示文本\n""[00:20.00]这是第四行歌词测试内容\n""[00:24.00]这是第五行歌词展示效果\n""[00:28.00]这是最后一行歌词\n""[00:32.00]\n";setLyricsText(sampleLyrics);
}

歌词解析部分:

bool LyricsParser::parseLyrics(const QString &lyricsText)
{// 清空之前的歌词clear();// 按行分割歌词文本QStringList lines = lyricsText.split('\n');// 正则表达式匹配时间标签 [mm:ss.xx]QRegularExpression timeRegex("\\[(\\d+):(\\d+)\\.(\\d+)\\]");for (const QString &line : lines) {// 跳过空行if (line.trimmed().isEmpty()) {continue;}QString remainingLine = line;QRegularExpressionMatch match;int position = 0;// 查找所有时间标签while ((match = timeRegex.match(remainingLine, position)).hasMatch()) {int minutes = match.captured(1).toInt();int seconds = match.captured(2).toInt();int milliseconds = match.captured(3).toInt();// 计算总毫秒数qint64 timeMs = minutes * 60 * 1000 + seconds * 1000 + milliseconds * 10; // 通常LRC中的时间戳精度为百分之一秒position = match.capturedEnd();// 提取歌词文本(去除所有时间标签后的内容)QString lyricText = remainingLine;lyricText.remove(timeRegex);lyricText = lyricText.trimmed();if (!lyricText.isEmpty()) {m_lyrics.insert(timeMs, lyricText);}}}return !m_lyrics.isEmpty();
}

注:这里只是为了演示播放效果,并未真正加载音频进行播放。可以在此基础上进行扩展。

完整Demo下载

http://www.xdnf.cn/news/2989.html

相关文章:

  • Win10如何一键切换IP地址教程
  • ubtuntu安装docker拉取iwebsec镜像
  • 关于STM32G030和G070未初始化看门狗,程序里面喂狗会导致擦除Flash失败或进入‘HardFault_Handler’
  • WebSocket 实现数据实时推送原理
  • GPT,Bert类模型对比
  • 4.17---实现商铺和缓存与数据库双写一致以及宕机处理
  • 2025.04.17【Dendrogram】生信数据可视化:Dendrogram图表详解
  • 网络编程 - 1
  • Genspark:重新定义AI搜索与代理的全能型工具
  • 私人笔记:动手学大模型应用开发llm-universe项目环境创建
  • 【前端vue生成二维码和条形码——MQ】
  • 【随身WIFI】随身WiFi Debian系统优化教程
  • AUTOSAR图解==>AUTOSAR_SWS_DefaultErrorTracer
  • 计算机网络 - UDP协议
  • 4.15BUUCTF Ez_bypass,HardSQL,AreUSerialz,BabyUpload,CheckIn
  • 【Docker】运行错误提示 unknown shorthand flag: ‘d‘ in -d ----详细解决方法
  • jQuery入门和选择器
  • JavaScript-立即执行函数(Immediately Invoked Function Expression,IIFE)
  • 【AI量化第24篇】KhQuant 策略框架深度解析:让策略开发回归本质——基于miniQMT的量化交易回测系统开发实记
  • dumpsys--音频服务状态信息
  • ASP.NET Core Web API 配置系统集成
  • 解决 VSCode 中 NVM 配置后无法识别 Node 和 NPM 的问题
  • 京东3D空间视频生成技术探索与应用
  • 游戏引擎学习第230天
  • [图论]Kruskal
  • Windows快速切换屏幕/桌面
  • 如何自学机器学习?零基础到实战的完整路径
  • 超详细VMware虚拟机扩容磁盘容量-无坑版
  • 探索关系型数据库 MySQL
  • 驱动-自旋锁