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

歌词相关实现

歌词相关

  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. 保持当前行居中显示,提升用户体验

相关文章:

  • Vscode工具开发Vue+ts项目时vue文件ts语法报错-红波浪线等
  • 第n小的质数(信息学奥赛一本通-1099)
  • 【每日学点HarmonyOS Next知识】获取资源问题、软键盘弹起、swiper更新、C给图片设置位图、读取本地Json
  • 为AI聊天工具添加一个知识系统 之142 设计重审 之7 “我I”:mine/own/self
  • 程序化广告行业(9/89):定义、价值与发展脉络全解析
  • 版本控制器Git(3)
  • 微信小程序校园跑腿的设计与实现【lw+源码+部署+视频+讲解】
  • uni-app打包成H5使用相对路径
  • 串口通信函数汇总-ing
  • SpringMVC执行的流程
  • 子网掩码介绍
  • 批量ip反查域名工具
  • 第十八:go 并发 goroutine
  • VScode 运行LVGL
  • 前端监测窗口尺寸和元素尺寸变化的方法
  • 【git】【网络】【项目配置运行】HTTP 协议的微型简易 Web 服务器---tinyEasyMuduoWebServer
  • 机器人运动学与动力学
  • 自动驾驶与车路协同
  • 力扣:3305.元音辅音字符串计数
  • Guangzhaotest
  • 广西钦州:坚决拥护自治区党委对钟恒钦进行审查调查的决定
  • 会计江湖|年报披露关注什么:独董给出的“信号”
  • A股三大股指低收:银行股再度走强,两市成交11920亿元
  • 讲座预告|全球贸易不确定情况下企业创新生态构建
  • 保利42.41亿元竞得上海杨浦东外滩一地块,成交楼面单价超8万元
  • 人民日报评“组团退演出服”:市场经济诚信原则需全社会维护