Skip to main content

API Reference

Complete API documentation for @bbplayer/splash including parsers, converters, utilities, and type definitions.

Parsers

parseSpl

Parse SPL (Salt Player Lyric) or LRC format lyrics into structured data.
function parseSpl(lrcContent: string): SplLyricData
lrcContent
string
required
SPL/LRC format lyric string to parse
return
SplLyricData
Parsed lyric data object containing metadata and lines
Throws: SplParseError when encountering unparseable content

Example: Parse Basic LRC

import { parseSpl } from '@bbplayer/splash'

const lrc = `
[ti:Song Title]
[ar:Artist Name]
[00:12.00]First line of lyrics
[00:17.20]Second line of lyrics
`

const result = parseSpl(lrc)

console.log(result.meta.ti)           // "Song Title"
console.log(result.meta.ar)           // "Artist Name"
console.log(result.lines.length)      // 2
console.log(result.lines[0].content)  // "First line of lyrics"
console.log(result.lines[0].startTime) // 12000 (milliseconds)

Example: Parse SPL with Word-Level Timing

import { parseSpl } from '@bbplayer/splash'

const spl = `[00:12.00]Hello<00:12.50> World<00:13.00>`

const result = parseSpl(spl)

console.log(result.lines[0].isDynamic)  // true
console.log(result.lines[0].spans)
// [
//   { text: "Hello", startTime: 12000, endTime: 12500, duration: 500 },
//   { text: " World", startTime: 12500, endTime: 13000, duration: 500 }
// ]

Example: Parse with Translations

import { parseSpl } from '@bbplayer/splash'

const spl = `
[00:12.00]Hello World
[00:12.00]你好世界
[00:12.00]こんにちは世界
`

const result = parseSpl(spl)

console.log(result.lines[0].content)      // "Hello World"
console.log(result.lines[0].translations) // ["你好世界", "こんにちは世界"]

Example: Error Handling

import { parseSpl, SplParseError } from '@bbplayer/splash'

try {
  const result = parseSpl('Invalid content without timestamp')
} catch (error) {
  if (error instanceof SplParseError) {
    console.error(`Parse error at line ${error.line}: ${error.message}`)
    // "Parse error at line 1: 第 1 行解析错误: 未找到时间戳,且无法关联到上一行"
  }
}
parseSpl is the main entry point for parsing. It handles both standard LRC and extended SPL formats.

verify

Validate SPL/LRC lyric content without throwing exceptions.
function verify(
  lrcContent: string
): { isValid: true } | { isValid: false; error: SplParseError }
lrcContent
string
required
SPL/LRC content to validate
return
{ isValid: boolean; error?: SplParseError }
Validation result object

Example: Validate Lyrics

import { verify } from '@bbplayer/splash'

const result = verify('[00:12.00]Valid lyrics')

if (result.isValid) {
  console.log('Lyrics are valid!')
} else {
  console.error(`Invalid at line ${result.error.line}: ${result.error.message}`)
}

Example: Validation in Form

import { verify } from '@bbplayer/splash'

function validateLyricsInput(input: string): string | null {
  const result = verify(input)
  
  if (result.isValid) {
    return null // No error
  } else {
    return `Line ${result.error.line}: ${result.error.message}`
  }
}

// Usage in a form
const error = validateLyricsInput(userInput)
if (error) {
  showError(error)
}

Converters

parseYrc

Convert Netease Cloud Music YRC format to SPL format.
function parseYrc(yrcContent: string): string
yrcContent
string
required
YRC format lyrics from Netease Cloud Music API
return
string
SPL format lyrics with word-level timing preserved

YRC Format

Netease Cloud Music uses two YRC formats: 1. JSON format (metadata):
{"t":0,"c":[{"tx":"作词: "},{"tx":"DECO*27"}]}
2. YRC format (word-level lyrics):
[lineStartTime,lineDuration](wordStartTime,wordDuration,0)word1(wordStartTime,wordDuration,0)word2

Example: Convert JSON Metadata

import { parseYrc } from '@bbplayer/splash'

const yrc = `
{"t":0,"c":[{"tx":"作词: "},{"tx":"DECO*27"}]}
{"t":1000,"c":[{"tx":"作曲: "},{"tx":"DECO*27"}]}
`

const spl = parseYrc(yrc)
console.log(spl)
// [00:00.000]作词: DECO*27
// [00:01.000]作曲: DECO*27

Example: Convert Word-Level Timing

import { parseYrc } from '@bbplayer/splash'

// YRC: [lineStart,lineDuration](wordStart,wordDur,0)text
const yrc = '[1000,2000](1000,500,0)Hello(1500,500,0)World'

const spl = parseYrc(yrc)
console.log(spl)
// [00:01.000]Hello<00:01.500>World<00:02.000>

Example: Handle Gaps (Instrumental)

import { parseYrc } from '@bbplayer/splash'

// Word 1 ends at 1500, Word 2 starts at 2000 (500ms gap)
const yrc = '[1000,2000](1000,500,0)Before(2000,500,0)After'

const spl = parseYrc(yrc)
console.log(spl)
// [00:01.000]Before<00:01.500><00:02.000>After<00:02.500>
//                   ^^^^^^^^^^^ explicit gap markers

Example: Mixed Content

import { parseYrc } from '@bbplayer/splash'

const yrc = `
{"t":0,"c":[{"tx":"作词: "},{"tx":"DECO*27"}]}
[00:20.848]特別な君と 特別な日を
[57500,3500](57500,520,0)流(58020,180,0)れ
`

const spl = parseYrc(yrc)
console.log(spl)
// [00:00.000]作词: DECO*27
// [00:20.848]特別な君と 特別な日を
// [00:57.500]流<00:58.020>れ<00:58.200>

Example: Complete Workflow

import { parseYrc, parseSpl } from '@bbplayer/splash'

// Step 1: Fetch YRC from Netease API
const response = await fetch('https://music.163.com/api/song/lyric?id=687506')
const data = await response.json()

// Step 2: Convert YRC to SPL
const splContent = parseYrc(data.yrc.lyric)

// Step 3: Parse SPL to structured data
const lyrics = parseSpl(splContent)

// Step 4: Use in your app
lyrics.lines.forEach(line => {
  console.log(`[${line.startTime}ms] ${line.content}`)
  if (line.isDynamic) {
    line.spans.forEach(span => {
      console.log(`  ${span.text}: ${span.startTime}ms - ${span.endTime}ms`)
    })
  }
})

formatSplTime

Format milliseconds to SPL time format.
function formatSplTime(ms: number): string
ms
number
required
Time in milliseconds (negative values are clamped to 0)
return
string
Formatted time string in mm:ss.SSS format

Example: Format Time

import { formatSplTime } from '@bbplayer/splash'

console.log(formatSplTime(0))       // "00:00.000"
console.log(formatSplTime(1000))    // "00:01.000"
console.log(formatSplTime(60000))   // "01:00.000"
console.log(formatSplTime(61234))   // "01:01.234"
console.log(formatSplTime(3661234)) // "61:01.234"
console.log(formatSplTime(-100))    // "00:00.000" (clamped)

Utilities

parseTimeTag

Parse SPL/LRC time tags to milliseconds.
function parseTimeTag(timeStr: string): number
timeStr
string
required
Time tag string in various formats:
  • [mm:ss.SS] (standard LRC)
  • <mm:ss.SS> (SPL word-level)
  • mm:ss.SS (no brackets)
  • Supports 1-6 digit milliseconds
return
number
Absolute time in milliseconds (negative values are clamped to 0)

Example: Parse Various Formats

import { parseTimeTag } from '@bbplayer/splash'

console.log(parseTimeTag('[00:12.00]'))    // 12000
console.log(parseTimeTag('<00:12.50>'))    // 12500
console.log(parseTimeTag('00:12.123'))     // 12123
console.log(parseTimeTag('[5:30.5]'))      // 330500
console.log(parseTimeTag('[0:0.1]'))       // 100
console.log(parseTimeTag('[0:0.02]'))      // 20
console.log(parseTimeTag('[0:0.123456]'))  // 123

Types

SplLyricData

The root data structure for all parsed lyrics.
interface SplLyricData {
  meta: Record<string, string>
  lines: LyricLine[]
}
meta
Record<string, string>
Key-value pairs of metadata from tags like [ti:Title], [ar:Artist], etc.
lines
LyricLine[]
Sorted array of lyric lines, ordered by startTime

Example

const data: SplLyricData = {
  meta: {
    ti: "Song Title",
    ar: "Artist Name",
    al: "Album Name"
  },
  lines: [
    {
      startTime: 12000,
      endTime: 17000,
      content: "First line",
      translations: [],
      isDynamic: false,
      spans: []
    }
  ]
}

LyricLine

Represents a single line of lyrics with timing and content.
interface LyricLine {
  startTime: number
  endTime: number
  content: string
  translations: string[]
  isDynamic: boolean
  spans: LyricSpan[]
}
startTime
number
Line start time in milliseconds
endTime
number
Line end time in milliseconds (inferred or explicit)
content
string
Main lyric content (text only, without timestamps)
translations
string[]
Array of translation strings for this line
isDynamic
boolean
true if line contains word-level timing (spans), false otherwise
spans
LyricSpan[]
Array of word-level timing data (empty if isDynamic is false)

LyricSpan

Represents a single word or phrase with precise timing.
interface LyricSpan {
  text: string
  startTime: number
  endTime: number
  duration: number
}
text
string
The text content of this span (word or phrase)
startTime
number
Absolute start time in milliseconds
endTime
number
Absolute end time in milliseconds
duration
number
Pre-calculated duration in milliseconds (endTime - startTime)

Example

const span: LyricSpan = {
  text: "Hello",
  startTime: 12000,
  endTime: 12500,
  duration: 500
}

SplParseError

Error class for parsing failures.
class SplParseError extends Error {
  constructor(line: number, message: string)
  line: number
  message: string
  name: 'SplParseError'
}
line
number
Line number where the error occurred (1-based)
message
string
Error message in format: 第 ${line} 行解析错误: ${detail}

Example

import { parseSpl, SplParseError } from '@bbplayer/splash'

try {
  parseSpl('Invalid content')
} catch (error) {
  if (error instanceof SplParseError) {
    console.log(error.line)    // 1
    console.log(error.message) // "第 1 行解析错误: 未找到时间戳..."
    console.log(error.name)    // "SplParseError"
  }
}

YrcLine

Type definition for Netease Cloud Music JSON lyric format.
interface YrcLine {
  t: number                    // Start time in milliseconds
  c: { tx: string }[]          // Array of text components
}
t
number
Start time in milliseconds
c
{ tx: string }[]
Array of text components that make up the line

Example

const yrcLine: YrcLine = {
  t: 1000,
  c: [
    { tx: "作词: " },
    { tx: "DECO*27" }
  ]
}
// Converts to: [00:01.000]作词: DECO*27

RawLine

Internal type used during parsing (exported for advanced use cases).
interface RawLine {
  lineNumber: number
  timestamps: number[]
  content: string
}
lineNumber
number
Source line number in the input string
timestamps
number[]
All timestamps extracted from this line
content
string
Content after removing timestamp tags
RawLine is primarily for internal use. Most users don’t need to interact with this type.

Complete Example

Building a Karaoke Player

import { parseSpl, parseYrc } from '@bbplayer/splash'

// Fetch lyrics from Netease Cloud Music
async function fetchLyrics(songId: string) {
  const response = await fetch(`https://music.163.com/api/song/lyric?id=${songId}`)
  const data = await response.json()
  
  // Convert YRC to SPL
  const splContent = parseYrc(data.yrc?.lyric || data.lrc?.lyric)
  
  // Parse to structured data
  return parseSpl(splContent)
}

// Display lyrics with karaoke effect
function displayKaraoke(lyrics: SplLyricData, currentTime: number) {
  // Find current line
  const currentLine = lyrics.lines.find(
    line => currentTime >= line.startTime && currentTime < line.endTime
  )
  
  if (!currentLine) return
  
  // Display line with progress
  if (currentLine.isDynamic) {
    // Word-by-word display
    currentLine.spans.forEach(span => {
      const isActive = currentTime >= span.startTime && currentTime < span.endTime
      const progress = isActive 
        ? (currentTime - span.startTime) / span.duration
        : currentTime >= span.endTime ? 1 : 0
      
      renderWord(span.text, progress)
    })
  } else {
    // Line-by-line display
    const progress = (currentTime - currentLine.startTime) / 
                     (currentLine.endTime - currentLine.startTime)
    renderLine(currentLine.content, progress)
  }
  
  // Display translations
  currentLine.translations.forEach(trans => {
    renderTranslation(trans)
  })
}

// Usage
const lyrics = await fetchLyrics('687506')
setInterval(() => {
  displayKaraoke(lyrics, audio.currentTime * 1000)
}, 16) // 60fps

Converting and Validating User Input

import { parseSpl, verify, formatSplTime } from '@bbplayer/splash'

function processUserLyrics(input: string) {
  // Validate first
  const validation = verify(input)
  
  if (!validation.isValid) {
    return {
      success: false,
      error: `Line ${validation.error.line}: ${validation.error.message}`
    }
  }
  
  // Parse lyrics
  const lyrics = parseSpl(input)
  
  // Generate statistics
  const stats = {
    totalLines: lyrics.lines.length,
    dynamicLines: lyrics.lines.filter(l => l.isDynamic).length,
    translations: lyrics.lines.filter(l => l.translations.length > 0).length,
    duration: formatSplTime(
      lyrics.lines[lyrics.lines.length - 1]?.endTime || 0
    ),
    metadata: lyrics.meta
  }
  
  return {
    success: true,
    lyrics,
    stats
  }
}

SPL Format

Learn about the SPL format specification

Overview

Package overview and getting started

Build docs developers (and LLMs) love