歌词相关实现

news/2025/3/13 11:12:43/

歌词相关

  1. 歌词数据模型
swift">// 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. 歌词解析
swift">// 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. 歌词显示视图
swift">// 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. 歌词行视图
swift">// 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. 时间计算工具
swift">// 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. 播放器集成
swift">// 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. 时间同步机制
swift">// 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. 时间计算
swift">// 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. 滚动实现
swift">// 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. 播放器集成
swift">// MusicPlayerManager.swift
class MusicPlayerManager {// 播放进度更新时调用func updateProgress(_ progress: Float) {// 更新歌词显示lyricView?.setProgress(progress)}
}

同步流程:

  1. 准备阶段

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

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

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

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

关键点:

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

http://www.ppmy.cn/news/1578779.html

相关文章

centos8.0系统部署zabbix6.0监控

centos8.0系统部署zabbix6.0监控 一、部署过程1、确认系统版本2、主机基础环境设置3、安装MySQL 8.0数据库3.1 安装MySQL 8.0仓库3.2 安装软件3.3 设置root用户密码3.4 创建zabbix数据库&#xff0c;授权用户 4、配置zabbix6.0仓库5、安装zabbix服务端软件6、导入zabbix数据表7…

基于MATLAB的冰块变化仿真

如图1所示&#xff0c;边长为5cm的冰块&#xff0c;初始温度为-2℃&#xff0c;放在25℃的环境中自然冷却&#xff0c;对流换热系数为10W/mK&#xff0c;本文将通过matlab编程求解冰块融化的过程&#xff0c;计算其温度场。 图1 案例示意图 02 温度场计算 本文通过matlab分别…

力扣HOT100之双指针:11. 盛最多水的容器

这道题用双指针法很快就做出来了&#xff0c;但是为什么我的双指针法在时间和空间上都不占优啊&#xff1f; 用两个指针分别指向数组的首元素和尾元素&#xff0c;然后取其中的较小值两个位置之间的间隔就得到了这两根垂直线之间所能容纳的水量&#xff0c;例如&#xff0c;对于…

如何检查电脑的硬盘健康状况?

检查硬盘健康状况可以使用多种工具和方法。以下是一些常用的工具和步骤&#xff1a; Windows系统&#xff1a; 使用Windows内置工具&#xff1a; 磁盘检查&#xff1a;可以通过命令提示符&#xff08;cmd&#xff09;使用chkdsk命令来检查硬盘错误。例如&#xff0c;输入chkd…

Windows 上安装配置 Apache Tomcat 及Tomcat 与 JDK 版本对应

Apache Tomcat 是一种广泛使用的 Web 服务器和 Java 容器&#xff0c;对于部署和运行 Java Web 应用程序至关重要。它的可靠性和强大的功能使其成为全球开发人员和组织的首选。 在这篇文章中&#xff0c;我们将介绍在 Windows 机器上安装 Apache Tomcat 的过程&#xff0c;以确…

SQL Server的连接时发生了与网络相关或特定于实例的错误。未找到服务器或无法访问服务器

项目场景&#xff1a; 今天在服务器配置数据库&#xff0c;如果在外网使用IP登录数据库一直连接不上&#xff0c;然后在服务器上面装的数据库使用IP连接还是连接不上&#xff0c;这让我确认不是防火墙的入站规则原因&#xff0c;然后各种配置也看了&#xff0c;还是不好使&…

Python Cookbook-3.15 检查信用卡校验

任务 检查信用卡校验。 解决方案 Luhn mod 10是信用卡业检验和的标准。它不是 Python 内建的算法&#xff0c;不过我们可以很容易地实现这个算法: def cardluhnChecksumIsValid(card_number): 通过 lunn mod-10 校验和算法检查信用卡号sum 0num_digits len(card_number)o…

初识云计算

1.传统IT的劣势 讯速整升的互联风普及率给企业带来了大最的流量&#xff0c;用户以及数据&#xff0c;为了能够匹配企业高速发展的进度&#xff0c;就需要不断地买购传统IT设备&#xff0c;时间一长&#xff0c;传统IT设备的弊端就逐渐显示出来&#xff1a; ① 采购周期…