歌词相关实现

news2025/3/13 11:46:11

歌词相关

  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 = 0
    
    func 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 = lyricCurrentWordIndex
                    cell.lineView.wordPlayedTime = wordPlayedTime
                    cell.lineView.setNeedsDisplay()
                }
            }
        }
    }
}
  1. 歌词行视图
// LyricLineView.swift
class LyricLineView: UIView {
    var data: LyricLine?
    var accurate: Bool = false
    var lineSelected = false
    
    override 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] = lyricSelectedTextColor
                    wordStringNSString.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 + value
            if 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?.lrc
            if 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.coloradmin.cn/o/2314276.html

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

相关文章

51单片机Proteus仿真速成教程——P1-软件与配置+Proteus绘制51单片机最小系统+新建程序模版

前言&#xff1a;本文主要围绕 51 单片机最小系统的绘制及程序模板创建展开。首先介绍了使用 Proteus 绘制 51 单片机最小系统的详细步骤&#xff0c;包括软件安装获取途径、工程创建、器件添加&#xff08;如单片机 AT89C51、晶振、电容、电阻、按键等&#xff09;、外围电路&…

使用 pytesseract 进行 OCR 识别:以固定区域经纬度提取为例

引言 在智能交通、地图定位等应用场景中&#xff0c;经常会遇到需要从图像中提取经纬度信息的需求。本篇文章将介绍如何利用 Python 的 pytesseract 库结合 PIL 对图像进行预处理&#xff0c;通过固定区域裁剪&#xff0c;来有效地识别出图像上显示的经纬度信息。 1. OCR 与 …

【51单片机】程序实验15.DS18B20温度传感器

主要参考学习资料&#xff1a;B站【普中官方】51单片机手把手教学视频 开发资料下载链接&#xff1a;http://www.prechin.cn/gongsixinwen/208.html 单片机套装&#xff1a;普中STC51单片机开发板A4标准版套餐7 目录 DS18B20介绍主要特性内部结构控制时序初始化时序写时序读时序…

Vue项目上传到GitHub,vscode拉取vue项目更新后推送到GitHub上

1、新建Vue项目 2、在GitHub新建仓库 3、留意建立好仓库后提示的命令 4、进入vue项目目录&#xff0c;在空白处点击鼠标右键选择git bash here 5、输入命令 git init git add . git commit -m "注释内容" 输入之前创建GitHub仓库后记下的代码的第一句 git remote…

InternVL:论文阅读 -- 多模态大模型(视觉语言模型)

更多内容&#xff1a;XiaoJ的知识星球 文章目录 InternVL: 扩展视觉基础模型与通用视觉语言任务对齐1.概述2.InternVL整体架构1&#xff09;大型视觉编码器&#xff1a;InternViT-6B2&#xff09;语言中间件&#xff1a;QLLaMA。3&#xff09;训练策略&#xff08;1&#xff09…

【BUG】类文件具有错误的版本 61.0, 应为 52.0,请删除该文件或确保该文件位于正确的类路径子目录中。

报错&#xff1a; [ERROR] 类文件具有错误的版本 61.0, 应为 52.0 [ERROR] 请删除该文件或确保该文件位于正确的类路径子目录中。 报错截图&#xff1a; 原因&#xff1a;Java 版本和 Spring 不兼容&#xff0c;显示 Spring 版本过高 解决方法 1. 使用更高版本的 J…

康谋应用 | 基于多传感器融合的海洋数据采集系统

在海洋监测领域&#xff0c;基于无人艇能够实现高效、实时、自动化的海洋数据采集&#xff0c;从而为海洋环境保护、资源开发等提供有力支持。其中&#xff0c;无人艇的控制算法训练往往需要大量高质量的数据支持。然而&#xff0c;海洋数据采集也面临数据噪声和误差、数据融合…

双周报Vol.67: 模式匹配支持守卫、LLVM 后端发布、支持 Attribute 语法...多项核心技术更新!

2025-03-10 语言更新 模式匹配支持守卫&#xff08;Pattern Guard&#xff09; 模式守卫可以通过在模式后追加 if ... 的语法结构来指定。有模式守卫的分支只有在被模式匹配的值满足对应模式&#xff0c;并且模式守卫为真的情况下才会执行。如果模式守卫为假&#xff0c;则会…

MCP-代码解读TypeScript版本

MCP-代码解读TypeScript版本 文章目录 MCP-代码解读TypeScript版本1-参考网址2-TypeScript代码3-代码解读1-[非重点]定义函数2-[非重点]定义工具说明3-[重点]运行MCP服务 1-参考网址 B站视频参考 2-TypeScript代码 import { McpServer } from "modelcontextprotocol/sd…

写了一个二叉树构造函数和画图函数,方便debug

代码 class TreeNode(object):def __init__(self, val, leftNone, rightNone):self.val valself.left leftself.right rightdef construct_tree(nodes):if not nodes:return Noneroot TreeNode(nodes[0])queue [root]index 1while index < len(nodes):node queue.p…

docker 小记

一、卸载 查看当前版本 docker -v2. 如果有&#xff0c;先停止docker systemctl stop docker如果是yum安装&#xff0c;卸载方式为 #已防版本冲突&#xff0c;直接卸载 yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-lat…

G-Star 公益行起航,挥动开源技术点亮公益!

公益组织&#xff0c;一直是社会温暖的传递者&#xff0c;但在数字化浪潮中&#xff0c;也面临着诸多比大众想象中复杂的挑战&#xff1a;项目管理如何更高效&#xff1f;志愿者管理又该如何创新&#xff1f;宣传推广怎么才能更有影响力&#xff1f;内部管理和技术支持又该如何…

k9s入门及实战

概述 k9s&#xff0c;GitHub&#xff0c;是用于管理k8s集群的CLI&#xff0c;提供一个终端UI来与k8s集群进行交互。通过封装kubectl功能&#xff0c;k9s会以特定时间间隔监控k8s的变化&#xff0c;默认为2秒&#xff0c;并提供后续命令来与k8s资源进行交互&#xff0c;k9s可让…

redis增加ip白名单

Redis增加IP白名单 随着互联网的快速发展&#xff0c;网络安全问题也日益凸显。为了保护服务器安全&#xff0c;我们常常需要对访问服务器的IP地址进行限制。而Redis作为一种高性能的缓存数据库&#xff0c;可以用来实现IP白名单功能。本文将介绍如何使用Redis来增加IP白名单&…

git commit messege 模板设置 (规范化管理git)

配置方法 git config --global core.editor vim &#xff08;设置 Git 的默认编辑器为 Vim&#xff09;在用户根目录下&#xff08;~&#xff09;&#xff0c;创建一个.git_commit_msg文件&#xff0c;然后把下面的内容拷贝到文件中并保存。 [version][模块][类型]{解决xxx问题…

Franka机器人ROS 2 发布:赋能机器人研究和行业应用

Franka机器人 ROS 2 发布&#xff1a;赋能机器人研究和行业应用 Franka ROS 2 发布&#xff1a;赋能机器人研究和行业应用 由zlem Odeh 于Franka Robotics 发布 在机器人操作系统 (ROS) 等技术和生态系统的推动下&#xff0c;机器人世界正以前所未有的速度发展。ROS 2 是广受…

docker部署jenkins,安装使用一条龙教程

Jenkins k8s 实现CI/CD 一、简介 1. JenKins是什么? Jenkins是一个开源软件项目&#xff0c;是基于Java开发的一种持续集成工具&#xff0c;用于监控持续重复的工作&#xff0c;旨在提供一个开放易用的软件平台&#xff0c;使软件项目可以进行持续集成。 2. 什么是CI / CD…

短视频下载去水印,用什么工具好?

去除视频和图片水印是许多用户的需求&#xff0c;尤其是在分享或保存内容时。以下是6款超好用的工具&#xff0c;帮助你轻松去除水印&#xff0c;享受纯净的视觉体验&#xff1a; 1. 易下载去水印小程序 特点&#xff1a; 操作简单&#xff0c;支持抖音、快手、小红书、哔哩哔哩…

推理大模型时代,TextIn ParseX助力出版业知识资产重构

在大语言模型&#xff08;LLM&#xff09;与推理能力快速进化的技术浪潮下&#xff0c;出版机构沉淀数十年的非结构化数据资产&#xff0c;包括书籍、期刊、手稿及历史档案&#xff0c;正在焕发新的机遇。基于文档解析、NLP与大模型的推理生成能力&#xff0c;我们在图书馆层层…

Redis 2025/3/9

Redis主从集群 搭建主从集群 Redis并发能力非常强&#xff0c;单节点能够达到数万的并发。 不过对一些用户规模大&#xff0c;并发量比较高的应用来讲&#xff0c;数万并发不太够。这时候就用到redis的集群了。因为Redis中的数据通常读多写少&#xff0c;所以最常用的集群方…