之前写过一个音乐播放器项目,今天再给它完善一下,加一个歌词同步滚动。

先看效果图:

 

要做歌词同步滚动,我们首先需要的文件资源就是音乐文件和与之匹配的歌词文件。现在歌词文件不太好找,没关系,我们可以自己做。先来看本项目的歌词示例,文件格式是.lrc

[00:20.5]
[00:20.5]I walked through the door with you
[00:23.63]曾与你一起穿过那扇门
[00:23.63]The air was cold
[00:25.83]空气充满寒意
[00:25.83]But something 'bout it felt like home somehow and I
[00:31.41]不知为何却让我觉得温暖得像家一般
[00:31.41]Left my scarf there at your sister's house
[00:35.22]我把自己的围巾落在了你姐姐家

内容中除了要有歌词文本之外,还要有每一行歌词出现的时间点,大家按照这种格式创建就可以,就是一行一行写比较麻烦。

准备好之后把音乐和歌词放入rawfile文件夹中,播放音乐就不说了,我们今天只说歌词部分。

首先读取一下本地文件:

this.context .resourceManager.getRawFileContent('AllTooWell.lrc')  .then((value: Uint8Array) => {
let textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true });
let stringData = textDecoder.decodeWithStream(value, { stream: false });
this.mLrcEntryList = parseLrcLyric(stringData);
})

然后后要把歌词和时间分离出来分别存储,方便进行滚动展示,这里使用正则表达式来提取时间部分:

const lrcLineRegex: RegExp = new RegExp('\\[\\d{2,}:\\d{2}((\\.|:)\\d{2,})\\]', 'g');
for (let i = 0; i < lyric.length; i++) {
let lineTime = lyric[i].match(lrcLineRegex);
let lineText = lyric[i].replace(lrcLineRegex, '');
if (lineTime && lineText) {
for (let j = 0; j < lineTime.length; j++) {
let min = Number(String(lineTime[j].match(lrcTimeRegex1)).slice(1));
let sec = Number.parseFloat(String(lineTime[j].match(lrcTimeRegex2)));
let timeInSeconds = (min * 60 + sec) * 1000;
lrc.push({ lineStartTime: timeInSeconds, lineDuration: 0, lineWords: lineText, words: [] });
}
}}

这样就得到了一个由时间和内容组成的数组。

接下来将歌词逐行展示,这里使用的是绘制画布的方式:

for (let i = 0; i < lyric.words.length; i++) {
let wordStartTime = lyric.lineStartTime + lyric.words[i].wordStartTime;
let wordEndTime = lyric.lineStartTime + lyric.words[i].wordStartTime + lyric.words[i].duration;
if (wordStartTime <= this.lyricMilliSecondsTime && wordEndTime >= this.lyricMilliSecondsTime) {
let wordProgress = (this.lyricMilliSecondsTime - wordStartTime) / lyric.words[i].duration / lyric.words.length; let wordPassedProgress = i / lyric.words.length; let progress = wordPassedProgress + wordProgress; this.context.fillStyle = this.progressGrad(startX, gradY, endX, gradY, progress);
this.context.font = this.fontWeight + ' ' + (this.mCurrentTextSize + this.TEXT_ADD_SIZE) + 'vp ' + this.fontFamily; this.context.fillText(lyric.words[i].text, wordX, this.lrcY + this.TEXT_ADD_SIZE / 2, this.lrcWidth);
} else { this.context.font = this.fontWeight + ' ' + this.mCurrentTextSize + 'vp ' + this.fontFamily;
this.context.fillText(lyric.words[i].text, wordX, this.lrcY, this.lrcWidth);
}
wordX += this.context.measureText(lyric.words[i].text).width;}

下面就是歌词同步滚动的问题,在播放音乐的时候肯定会有一个计时器,歌词滚动同样使用这个计时器,计时器计数改变时,找到对应的歌词,并计算该行歌词的行数和偏移量,进行画布滚动,就可以实现歌词的同步滚动了,我们还可以对歌词的透明度进行绘制,实现渐隐渐出的效果。

由于众所周知的原因,本项目就和大家分享一个大概的思路,有了思路这个功能就不是很难了,感谢您的阅读。

鸿蒙NEXT实战教程—实现音乐歌词同步滚动的更多相关文章

  1. Android VLC播放器二次开发3——音乐播放(歌曲列表+歌词同步滚动)

    今天讲一下对VLC播放器音频播放功能进行二次开发,讲解如何改造音乐播放相关功能.最近一直在忙着优化视频解码部分代码,因为我的视频播放器需要在一台主频比较低的机器上跑(800M主频),所以视频解码能力受 ...

  2. 我的Android进阶之旅------>Android自定义View来实现解析lrc歌词并同步滚动、上下拖动、缩放歌词的功能

    前言 一LRC歌词文件简介 1什么是LRC歌词文件 2LRC歌词文件的格式 LRC歌词文件的标签类型 1标识标签 2时间标签 二解析LRC歌词 1读取出歌词文件 2解析得到的歌词内容 1表示每行歌词内 ...

  3. 基于jplayer实现歌词同步的JS音乐播放器效果

    分享一款基于jplayer实现歌词同步的JS音乐播放器效果.这是一款基于jQuery实现的音乐播放器功能代码.效果图如下: 在线预览   源码下载 实现的代码. html代码: <textare ...

  4. 《ElasticSearch6.x实战教程》之实战ELK日志分析系统、多数据源同步

    第十章-实战:ELK日志分析系统 ElasticSearch.Logstash.Kibana简称ELK系统,主要用于日志的收集与分析. 一个完整的大型分布式系统,会有很多与业务不相关的系统,其中日志系 ...

  5. TextView实现歌词同步

    利用TextView实现歌词同步显示,这是一个简单的利用TextView实现滚动实时显示歌词的. 里面的内容都已经写上了详细的注释.里面播放音乐的时候歌词同步展示. 做媒体这块的朋友可以学习一下,练练 ...

  6. NDK-JNI实战教程(二) JNI官方中文资料

    声明 设计概述 JNI接口函数和指针 加载和链接本地方法 解析本地方法名 本地方法的参数 引用Java对象 全局和局部引用 实现局部引用 访问Java对象 访问基本类型数组 访问域和方法 报告编程错误 ...

  7. 用grunt搭建自动化的web前端开发环境实战教程(详细步骤)

    用grunt搭建自动化的web前端开发环境实战教程(详细步骤) jQuery在使用grunt,bootstrap在使用grunt,百度UEditor在使用grunt,你没有理由不学.不用!前端自动化, ...

  8. wpf 仿QQ音乐歌词卡拉OK

    最近用WPF做了个音乐播放器,读取歌词.歌词同步都已经实现了.卡拉OK逐字变色 也实现了,但是逐字变色时不能根据歌手唱的快慢来逐字显示.请问各位大神,这个如何解决,有何思路?(附上我做的界面) 感谢各 ...

  9. Node+Express+MongoDB + Socket.io搭建实时聊天应用实战教程(二)--node解析与环境搭建

    前言 本来开始写博客的时候只是想写一下关于MongoDB的使用总结的,后来觉得还不如干脆写一个node项目实战教程实战.写教程一方面在自己写的过程中需要考虑更多的东西,另一方面希望能对node入门者有 ...

  10. Node+Express+MongoDB+Socket.io搭建实时聊天应用实战教程(一)--MongoDB入门

    前言 本文并不是网上流传的多少天学会MongoDB那种全面的教程,而意在总结这几天使用MongoDB的心得,给出一个完整的Node+Express+MongoDB+Socket.io搭建实时聊天应用实 ...

随机推荐

  1. 并发编程 - 线程同步(八)之自旋锁SpinLock

    前面对互斥锁Monitor进行了详细学习,今天我们将继续学习,一种更轻量级的锁--自旋锁SpinLock. 在 C# 中,SpinLock是一个高效的自旋锁实现,用于提供一种轻量级的锁机制.SpinL ...

  2. ffmpeg-5.0-essentials_build 下载

    ffmpeg-5.0-essentials_build下载放到蓝奏里了 https://wwz.lanzoub.com/if9xq02pttkb密码:ee8i

  3. MybatisPlus - [08] RestFul

    编号 接口 请求方式 请求路径 请求参数 返回值 1 新增用户 POST /users 用户表单实体 无 2 删除用户 DELETE /users/{id} 用户id 无 3 根据id查询用户 GET ...

  4. 学习高可靠Redis分布式锁实现思路

    一.分布式锁的必要性 在单体应用时代,我们使用ReentrantLock或synchronized就能解决线程安全问题.但当系统拆分为分布式架构后(目前大多数公司应该不会只是单体应用了),跨进程的共享 ...

  5. 探秘Transformer系列之(18)--- FlashAttention

    探秘Transformer系列之(18)--- FlashAttention 目录 0x00 概述 0.1 问题 0.2 其它解决方案 0.3 Flash Attention 0x01 背景知识 1. ...

  6. vue3-webseek网页版AI问答|Vite6+DeepSeek+Arco流式ai聊天打字效果

    2025 AI实战vue3+deepseek+arcoDesign仿DeepSeek/豆包网页版AI聊天助手. vue3-web-deepseek 实战网页PC版智能AI对话,基于vite6+vue3 ...

  7. 方法重写-java se 进阶-day01

    1.方法重写的介绍 当子父类中,某方法存在相同的定义(方法名.参数.返回值)时,子类的方法会将父类的方法进行重写操作(覆盖) 2.方法重写与方法重载的区别 1.方法重载:又称Overload,在同一个 ...

  8. 【Unity】改变游戏运行时Window的窗口标题

    [Unity]改变游戏运行时Window的窗口标题 零.需求 Unity打包好的Windows程序,启动后如何更改窗口标题?因为看着英文的感觉不太好,故有此想法.什么?你说为啥不改项目产品名?产品名会 ...

  9. DateTime.ToString(String) 格式化方法

    小故事: 群里有位问了个问题:"dateime.now 怎么取20170610 这样格式的数据啊?"...然后等了好久,没人帮忙回答下(这里肯定少不了歧视和异议). 虽然问题很简单 ...

  10. leetcode每日一题:酿造药水需要的最少总时间

    引言 ​ 今天的每日一题原题是2255. 统计是给定字符串前缀的字符串数目,直接模拟,逐个匹配words中的字符串是否是s的前缀即可.更换成前几天遇到的更有意思的一题来写这个每日一题. 题目 给你两个 ...