Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/bbplayer-app/BBPlayer/llms.txt

Use this file to discover all available pages before exploring further.

SPL Format Specification

SPL (Salt Player Lyric) is an advanced lyric format based on LRC that extends it with word-level timing support, multiple translations, and enhanced timing control.

Format Overview

SPL is backward compatible with LRC while adding new features:
  • ✅ All valid LRC files are valid SPL files
  • ✅ Word-level timing using <mm:ss.SSS> tags
  • ✅ Multiple translations with repeated timestamps
  • ✅ Explicit end times for precise control
  • ✅ Supports both square brackets [...] and angle brackets <...> for timestamps

Basic Syntax

Standard LRC Format

SPL supports all standard LRC features:
[ti:Song Title]
[ar:Artist Name]
[al:Album Name]
[by:Creator]

[00:12.00]First line of lyrics
[00:17.20]Second line of lyrics
[00:21.10]Third line of lyrics

Metadata Tags

Metadata tags use the format [key:value]:
TagDescriptionExample
tiTitle[ti:Bohemian Rhapsody]
arArtist[ar:Queen]
alAlbum[al:A Night at the Opera]
byCreator[by:Freddie Mercury]
offsetTime offset (ms)[offset:+500]
Custom metadata keys are supported using any alphanumeric string.

Time Stamps

Time stamps use the format [mm:ss.SSS]:
  • Minutes: 1-3 digits (e.g., 00, 5, 120)
  • Seconds: 1-2 digits (e.g., 00, 5, 59)
  • Milliseconds: 1-6 digits (e.g., 0, 123, 123456)
[00:12.00]    Two decimal places (20ms precision)
[00:12.000]   Three decimal places (1ms precision)
[00:12.123456] Six decimal places (microsecond precision)
[5:30.5]      Flexible formatting (5min 30.5sec)
The parser automatically normalizes timestamps to milliseconds internally.

Advanced Features

Word-Level Timing (Dynamic Lyrics)

SPL’s most powerful feature is word-level timing using angle brackets <mm:ss.SSS>:
[00:12.00]Hello<00:12.50> World<00:13.00>
This creates three spans:
  1. “Hello”: 12.00s - 12.50s (500ms duration)
  2. ” World”: 12.50s - 13.00s (500ms duration)
  3. Line ends at 13.00s (explicit end time)

Parsing Rules

  1. Line start time is set by the leading [mm:ss.SSS] tag
  2. Word start times are set by inline <mm:ss.SSS> tags
  3. Word end times are inferred from the next timestamp
  4. Line end time is the last timestamp if followed by no text
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].spans)
// [
//   { text: "Hello", startTime: 12000, endTime: 12500, duration: 500 },
//   { text: " World", startTime: 12500, endTime: 13000, duration: 500 }
// ]

Implicit vs Explicit End Times

Implicit End Time

If a line has trailing text after the last timestamp, the end time is inferred:
[00:12.00]Hello<00:12.50> World
[00:15.00]Next line
  • Line 1 ends at 15.00s (start of next line)
  • If no next line exists, defaults to +10 seconds

Explicit End Time

If the last timestamp has no following text, it’s treated as an explicit end:
[00:12.00]Hello<00:12.50> World<00:13.00>
[00:15.00]Next line
  • Line 1 explicitly ends at 13.00s
  • Gap between 13.00s and 15.00s (instrumental break)

Multiple Translations

SPL supports multiple translation lines per timestamp:

Explicit Translation (Same Timestamp)

[00:12.00]Hello World
[00:12.00]你好世界
[00:12.00]こんにちは世界
All lines with the same timestamp are grouped:
{
  startTime: 12000,
  content: "Hello World",
  translations: ["你好世界", "こんにちは世界"]
}

Implicit Translation (No Timestamp)

Lines without timestamps are attached to the previous timestamped line:
[00:12.00]Hello World
你好世界
こんにちは世界
This produces the same result as explicit translation.

Repeated Lines

Multiple timestamps on one line create repeated entries:
[00:12.00][00:24.00]Chorus line
This creates two separate LyricLine objects:
[
  { startTime: 12000, content: "Chorus line", ... },
  { startTime: 24000, content: "Chorus line", ... }
]

Repeated Lines with Implicit Translation

[00:12.00][00:24.00]Chorus line
Translation
The translation applies to both instances:
[
  { startTime: 12000, content: "Chorus line", translations: ["Translation"] },
  { startTime: 24000, content: "Chorus line", translations: ["Translation"] }
]

Complete Example

[ti:Example Song]
[ar:Example Artist]
[al:Example Album]
[by:Lyric Creator]

[00:00.00]Instrumental intro<00:05.00>
[00:05.50]First<00:06.00> verse<00:06.50> begins<00:07.50>
第一段开始
[00:10.00]Second verse here
Second verse translation
[00:15.00][00:30.00]Repeated chorus line
Chorus translation
[00:20.00]Bridge section<00:25.00>
Parsing this produces:
{
  meta: {
    ti: "Example Song",
    ar: "Example Artist",
    al: "Example Album",
    by: "Lyric Creator"
  },
  lines: [
    {
      startTime: 0,
      endTime: 5000,
      content: "Instrumental intro",
      translations: [],
      isDynamic: true,
      spans: [{ text: "Instrumental intro", startTime: 0, endTime: 5000, duration: 5000 }]
    },
    {
      startTime: 5500,
      endTime: 7500,
      content: "First verse begins",
      translations: ["第一段开始"],
      isDynamic: true,
      spans: [
        { text: "First", startTime: 5500, endTime: 6000, duration: 500 },
        { text: " verse", startTime: 6000, endTime: 6500, duration: 500 },
        { text: " begins", startTime: 6500, endTime: 7500, duration: 1000 }
      ]
    },
    // ... more lines
  ]
}

Edge Cases

Negative Timestamps

Negative timestamps are clamped to 0:
[-1:-1.000]Pre-song content
Becomes:
{ startTime: 0, content: "Pre-song content", ... }

Backward Time Tags

Time tags that go backward are ignored with a warning:
[00:12.00]Hello<00:11.00>World
The <00:11.00> tag is ignored since it’s before the current time.

Orphaned Text

Text without any timestamp (and not following a timestamped line) throws an error:
Orphaned text without timestamp
Throws SplParseError: 第 1 行解析错误: 未找到时间戳,且无法关联到上一行

Empty Lines

Empty lines and whitespace-only lines are ignored:
[00:12.00]Line 1

    
[00:15.00]Line 2
Produces only two lyric lines.

Validation

Use the verify function to validate SPL content:
import { verify } from '@bbplayer/splash'

const result = verify(splContent)

if (result.isValid) {
  console.log('Valid SPL file')
} else {
  console.error(`Invalid SPL at line ${result.error.line}: ${result.error.message}`)
}

Best Practices

For Static Lyrics (LRC)

[ti:Song Title]
[ar:Artist]
[00:12.00]First line
[00:17.50]Second line

For Dynamic Lyrics (Word-by-Word)

[ti:Song Title]
[ar:Artist]
[00:12.00]First<00:12.30> line<00:12.80><00:13.00>
[00:17.50]Second<00:17.80> line<00:18.30><00:19.00>
Always include explicit end tags <mm:ss.SSS> for word-level lyrics to avoid relying on inferred timing.

For Translated Lyrics

[00:12.00]Original line
[00:12.00]Translation line
Or use implicit translation:
[00:12.00]Original line
Translation line

Resources

Build docs developers (and LLMs) love