歌词相关实现

歌词相关

  1. 歌词数据模型
// Lyric.swift
class Lyric: BaseModel {/// 是否是精确到字的歌词var isAccurate:Bool = false/// 所有的歌词var datum:Array<LyricLine>!
}// LyricLine.swift
class LyricLine: BaseModel {/// 整行歌词var data:String!/// 开始时间(毫秒)var startTime:Int!/// 每个字(KSC格式)var words:Array<String>!/// 每个字的持续时间(KSC格式)var wordDurations:Array<Int>!/// 结束时间var endTime:Int = 0
}
  1. 歌词解析
// LRCLyricParser.swift - LRC格式解析
static func parse(_ data:String) -> Lyric {let result = Lyric()result.isAccurate = false  // LRC格式不精确到字// 按行分割let strings = data.components(separatedBy: "\n")for line in strings {if line.starts(with: "[0") {// 解析时间戳和歌词内容// 例如:[00:00.300]爱的代价let lyricLine = LyricLine()// 解析时间戳lyricLine.startTime = DateUtil.parseToInt(commands[0])// 解析歌词内容lyricLine.data = commands[1]result.datum.append(lyricLine)}}return result
}// KSCLyricParser.swift - KSC格式解析
static func parse(_ data:String) -> Lyric {let result = Lyric()result.isAccurate = true  // KSC格式精确到字// 解析每行歌词// 例如:karaoke.add('00:27.487', '00:32.068', '一时失志不免怨叹', '347,373,1077,320,344,386,638,1096')// 包含每个字的持续时间
}
  1. 歌词显示视图
// LyricListView.swift
class LyricListView: BaseRelativeLayout {var data: Lyric?var tableView: UITableView!var datum: [Any] = []/// 当前显示的歌词行号var lyricLineNumber: Int = 0/// 歌词上下填充的占位行数var lyricPlaceholderSize = 0func setProgress(_ progress: Float) {// 1. 计算当前应该显示哪一行let newLineNumber = LyricUtil.getLineNumber(data!, progress) + lyricPlaceholderSize// 2. 如果行号变化,滚动到新位置if newLineNumber != lyricLineNumber {scrollPosition(newLineNumber)lyricLineNumber = newLineNumber}// 3. 如果是精确到字的歌词,更新当前字的位置if data!.isAccurate {if let object = datum[lyricLineNumber] as? LyricLine {// 计算当前是第几个字let lyricCurrentWordIndex = LyricUtil.getWordIndex(object, progress)// 计算当前字已经播放的时间let wordPlayedTime = LyricUtil.getWordPlayedTime(object, progress)// 更新显示if let cell = getCell(lyricLineNumber) {cell.lineView.lyricCurrentWordIndex = lyricCurrentWordIndexcell.lineView.wordPlayedTime = wordPlayedTimecell.lineView.setNeedsDisplay()}}}}
}
  1. 歌词行视图
// LyricLineView.swift
class LyricLineView: UIView {var data: LyricLine?var accurate: Bool = falsevar lineSelected = falseoverride func draw(_ rect: CGRect) {if let data = self.data {if accurate {// 精确到字的歌词绘制// 1. 绘制整行歌词(灰色)wordStringNSString.draw(at: point, withAttributes: attributes)if lineSelected {// 2. 计算高亮部分的宽度let lineLyricPlayedWidth = calculatePlayedWidth()// 3. 绘制高亮部分(红色)let selectedRect = CGRect(x: point.x, y: point.y, width: lineLyricPlayedWidth, height: size.height)context.clip(to: selectedRect)attributes[.foregroundColor] = lyricSelectedTextColorwordStringNSString.draw(at: point, withAttributes: attributes)}} else {// 普通歌词绘制if lineSelected {attributes[.foregroundColor] = lyricSelectedTextColor}wordStringNSString.draw(at: point, withAttributes: attributes)}}}
}
  1. 时间计算工具
// LyricUtil.swift
class LyricUtil {/// 计算当前时间对应的歌词行static func getLineNumber(_ lyric: Lyric, _ progress: Float) -> Int {let progress = progress * 1000  // 转为毫秒// 倒序遍历找到第一个开始时间小于等于当前时间的行for (index, value) in lyric.datum.enumerated().reversed() {if progress >= Float(value.startTime) {return index}}return 0}/// 计算当前时间对应的字(KSC格式)static func getWordIndex(_ line: LyricLine, _ progress: Float) -> Int {let newTime = Int(progress * 1000)var startTime = line.startTime!// 累加每个字的持续时间,找到当前字for (index, value) in line.wordDurations!.enumerated() {startTime = startTime + valueif newTime < startTime {return index}}return -1}
}
  1. 播放器集成
// MusicPlayerManager.swift
class MusicPlayerManager {func prepareLyric() {// 1. 检查是否有歌词if data!.parsedLyric != nil {onLyricReady()} else if SuperStringUtil.isNotBlank(data!.lyric) {// 2. 解析本地歌词parseLyric()} else {// 3. 从网络获取歌词let urlString = data?.lrcif let url = URL(string: urlString ?? "") {// 下载并解析歌词}}}// 播放进度更新时调用func updateProgress(_ progress: Float) {// 更新歌词显示lyricView?.setProgress(progress)}
}

这个实现的主要特点:

  1. 支持多种格式

    • LRC:简单的时间戳+歌词格式
    • KSC:支持精确到字的歌词显示
  2. 精确的时间控制

    • 毫秒级的时间计算
    • 支持精确到字的歌词显示
    • 平滑的滚动效果
  3. 良好的用户体验

    • 歌词居中显示
    • 支持拖拽交互
    • 显示拖拽位置的时间
    • 点击可以跳转到对应位置
  4. 性能优化

    • 使用占位行实现居中效果
    • 按需更新显示
    • 避免不必要的重绘

歌词同步机制:

  1. 时间同步机制
// LyricListView.swift
func setProgress(_ progress: Float) {if datum.count > 0 {// 1. 根据当前播放时间,计算应该显示哪一行歌词let newLineNumber = LyricUtil.getLineNumber(data!, progress) + lyricPlaceholderSize//所以为什么不二分// 2. 如果行号发生变化,滚动到新位置if newLineNumber != lyricLineNumber {scrollPosition(newLineNumber)lyricLineNumber = newLineNumber}}
}
  1. 时间计算
// LyricUtil.swift
static func getLineNumber(_ lyric: Lyric, _ progress: Float) -> Int {// 将播放时间转换为毫秒let progress = progress * 1000// 倒序遍历歌词行,找到第一个开始时间小于等于当前时间的行for (index, value) in lyric.datum.enumerated().reversed() {if progress >= Float(value.startTime) {return index}}return 0
}
  1. 滚动实现
// LyricListView.swift
func scrollPosition(_ lineNumber: Int) {let indexPaht = IndexPath(item: lineNumber, section: 0)if tableView.visibleCells.count > 0 {// 使用动画滚动到当前行,并保持居中tableView.selectRow(at: indexPaht, animated: true, scrollPosition: .middle)}
}
  1. 播放器集成
// MusicPlayerManager.swift
class MusicPlayerManager {// 播放进度更新时调用func updateProgress(_ progress: Float) {// 更新歌词显示lyricView?.setProgress(progress)}
}

同步流程:

  1. 准备阶段

    • 解析歌词文件,获取每行歌词的开始时间
    • 将歌词数据存储在 parsedLyric
  2. 播放阶段

    • 播放器实时提供播放进度(秒)
    • 调用 setProgress 方法更新歌词显示
  3. 同步计算

    • 将播放时间转换为毫秒
    • 遍历歌词行,找到当前时间对应的行
    • 如果行号变化,滚动到新位置
  4. 显示更新

    • 使用动画滚动到当前歌词行
    • 保持当前行在屏幕中央
    • 高亮显示当前行

关键点:

  1. 使用毫秒级的时间计算,保证同步精度
  2. 倒序遍历歌词行,提高查找效率
  3. 使用动画滚动,提供流畅的视觉效果
  4. 保持当前行居中显示,提升用户体验

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/71988.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

纺织服装制造行业现状 内检实验室系统在纺织服装制造行业的应用

在纺织服装制造行业&#xff0c;内检实验室LIMS系统&#xff08;实验室信息管理系统&#xff09;已成为提升检测效率、优化质量控制和满足行业合规性要求的关键工具。随着行业竞争的加剧和消费者对产品质量要求的提高&#xff0c;纺织服装制造企业需要更加高效、准确的检测流程…

K8s 1.27.1 实战系列(十一)ConfigMap

ConfigMap 是 Kubernetes 中管理非敏感配置的核心资源,通过解耦应用与配置实现灵活性和可维护性。 一、ConfigMap 的核心功能及优势 ​1、配置解耦 将配置文件(如数据库地址、日志级别)与容器镜像分离,支持动态更新而无需重建镜像。 ​2、多形式注入 ​环境变量:将键值…

3分钟复现 Manus 超强开源项目 OpenManus

文章目录 前言什么是 OpenManus构建方式环境准备克隆代码仓库安装依赖配置 LLM API运行 OpenManus 效果演示总结个人简介 前言 近期人工智能领域迎来了一位备受瞩目的新星——Manus。Manus 能够独立执行复杂的现实任务&#xff0c;无需人工干预。由于限制原因大部分人无法体验…

从零开始学机器学习——构建一个推荐web应用

首先给大家介绍一个很好用的学习地址:https://cloudstudio.net/columns 今天,我们终于将分类器这一章节学习完活了,和回归一样,最后一章节用来构建web应用程序,我们会回顾之前所学的知识点,并新增一个web应用用来让模型和用户交互。所以今天的主题是美食推荐。 美食推荐…

【最后203篇系列】014 AI机器人-1

说明 终于开张了&#xff0c;我觉得AI机器人是一件真正正确&#xff0c;具有商业价值的事。 把AI机器人当成一笔生意&#xff0c;我如何做好这笔生意&#xff1f;一端是业务价值&#xff0c;另一端是技术支撑。如何构造高质量的内容和服务&#xff0c;如何确保技术的广度和深度…

【大模型统一集成项目】如何封装多个大模型 API 调用

&#x1f31f; 在这系列文章中&#xff0c;我们将一起探索如何搭建一个支持大模型集成项目 NexLM 的开发过程&#xff0c;从 架构设计 到 代码实战&#xff0c;逐步搭建一个支持 多种大模型&#xff08;GPT-4、DeepSeek 等&#xff09; 的 一站式大模型集成与管理平台&#xff…

AI4CODE】3 Trae 锤一个贪吃蛇的小游戏

【AI4CODE】目录 【AI4CODE】1 Trae CN 锥安装配置与迁移 【AI4CODE】2 Trae 锤一个 To-Do-List 这次还是采用 HTML/CSS/JAVASCRIPT 技术栈 Trae 锤一个贪吃蛇的小游戏。 1 环境准备 创建一个 Snake 的子文件夹&#xff0c;清除以前的会话记录。 2 开始构建 2.1 输入会…

【简答题002】Java变量简答题

博主会经常补充完善这里面问题的答案。希望可以得到大家的一键三连支持&#xff0c;你的鼓励是我坚持下去的最大动力&#xff01;谢谢&#xff01; 001 什么是Java变量&#xff1f; Java变量是用来存储数据并在程序中引用的命名空间。 002 Java变量有哪些类型&#xff1f; J…

从零开发Chrome广告拦截插件:开发、打包到发布全攻略

从零开发Chrome广告拦截插件&#xff1a;开发、打包到发布全攻略 想打造一个属于自己的Chrome插件&#xff0c;既能拦截烦人的广告&#xff0c;又能优雅地发布到Chrome Web Store&#xff1f;别担心&#xff0c;这篇教程将带你从零开始&#xff0c;动手开发一个功能强大且美观…

基于腾讯云高性能HAI-CPU的跨境电商客服助手全链路解析

跨境电商的背景以及痛点 根据Statista数据&#xff0c;2025年全球跨境电商市场规模预计达6.57万亿美元&#xff0c;年增长率保持在12.5% 。随着平台规则趋严&#xff08;如亚马逊封店潮&#xff09;&#xff0c;更多卖家选择自建独立站&#xff0c;2024年独立站占比已达35%。A…

maven的项目构建

常用构建命令 命令说明mvn clean清理编译结果&#xff08;删掉target目录&#xff09;mvn compile编译核心代码&#xff0c;生成target目录mvn test-compile编译测试代码&#xff0c;生成target目录mvn test执行测试方法mvn package打包&#xff0c;生成jar或war文件mvn insta…

定时任务和分布式任务框架

文章目录 一 Spring Task1.@Scheduled注解介绍2 基本用法(1)使用@EnableScheduling修饰启动类(2)创建定时任务的类(3)fixedDelay(4)fixedRate(5)cron3 执行多个任务4 设置异步执行5 @Async使用自定义线程池6 缺点二 xxl-job介绍架构图与其他任务调度平台的比较运行调…

git安装,配置SSH公钥(查看版本、安装路径,更新版本)git常用指令

目录 一、git下载安装 1、下载git 2、安装Git‌&#xff1a; 二、配置SSH公钥 三、查看安装路径、查看版本、更新版本 四、git常用指令 1、仓库初始化与管理 2、配置 3、工作区与暂存区管理 4、提交 5、分支管理 6、远程仓库管理 7、版本控制 8、其他高级操作 一…

[Web]ServletContext域(Application)

简介 Web应用的Application域的实现是通过ServletContext对象实现的。整个Web应用程序的所有资源共享这个域。生命周期与Web应用程序相同&#xff0c;即当前Web应用程序启动时&#xff08;以服务器视角而非访客视角&#xff09;出生&#xff0c;Web应用服务程序关闭时停止。 通…

qt c++ 进程和线程

在Qt C开发中&#xff0c;进程&#xff08;Process&#xff09;和线程&#xff08;Thread&#xff09;是两种不同的并发模型&#xff0c;各有适用场景和实现方式。以下是详细对比和实际开发中的用法总结&#xff1a; 一、进程&#xff08;Process&#xff09; 进程是操作系统资…

【鸿蒙开发】OpenHarmony调测工具hdc使用教程(设备开发者)

00. 目录 文章目录 00. 目录01. OpenHarmony概述02. hdc简介03. hdc获取04. option相关的命令05. 查询设备列表的命令06. 服务进程相关命令07. 网络相关的命令08. 文件相关的命令09. 应用相关的命令10. 调试相关的命令11. 常见问题12. 附录 01. OpenHarmony概述 OpenHarmony是…

手写简易Tomcat核心实现:深入理解Servlet容器原理

目录 一、Tomcat概况 1. tomcat全局图 2.项目结构概览 二、实现步骤详解 2.1 基础工具包&#xff08;com.qcby.util&#xff09; 2.1.1 ResponseUtil&#xff1a;HTTP响应生成工具 2.1.2 SearchClassUtil&#xff1a;类扫描工具 2.1.3 WebServlet&#xff1a;自定义注解…

【Java开发指南 | 第三十四篇】IDEA没有Java Enterprise——解决方法

读者可订阅专栏&#xff1a;Java开发指南 |【CSDN秋说】 文章目录 1、新建Java项目2、单击项目名&#xff0c;并连续按两次shift键3、在搜索栏搜索"添加框架支持"4、勾选Web应用程序5、最终界面6、添加Tomcat 1、新建Java项目 2、单击项目名&#xff0c;并连续按两次…

在MATLAB中实现PID控制仿真

在MATLAB中实现PID控制仿真可以通过代码编程或Simulink图形化建模两种方式完成。以下是两种方法的详细操作步骤和示例&#xff1a; 方法1&#xff1a;使用MATLAB脚本编程&#xff08;基于控制系统工具箱&#xff09; 步骤1&#xff1a;定义被控对象的数学模型 假设被控对象是…

Conda常用命令汇总

Conda 是一个流行的包管理器和环境管理工具&#xff0c;广泛应用于数据科学、机器学习等领域。它可以帮助我们管理 Python 包以及不同版本的环境&#xff0c;避免包冲突&#xff0c;提升项目的可复现性。以下是一些常用的 Conda 命令&#xff0c;涵盖环境创建、管理、包安装等常…