3.8 KiB
karaoke/lrc-parser
(no submodules)
Purpose
parseLrc(source, opts?) parses LRC
karaoke source into a timestamp-sorted array of Lyric entries
suitable for the package's <LyricsDisplay> and any consumer-built
karaoke surface. The parser is universal — it has no React, DOM, or
audio-context dependency — so it is safe to call in workers, on the
server during SSR, or inside other Suaveplan tools that need to
preview lyric timing.
The companion activeLyricIndex(lyrics, currentSec) performs a
binary search for the active line and is the bridge between the
transport's seconds and the <LyricsDisplay> highlight state.
Both functions are pure; the parser throws structured
ReactAudioPlayerError for the two invalid-input cases.
Features
- Recognises
[mm:ss],[mm:ss.xx],[mm:ss.xxx], and[h:mm:ss(.xxx)]timestamp forms. - Expands multi-timestamp lines (
[00:10][00:20]hello) by default; togglable viaexpandMultiTimestamp. - Drops the LRC metadata tags
[ar:],[ti:],[al:],[length:],[by:],[offset:],[re:],[ve:]— no extraction in v1. - Skips empty lines, malformed lines, and lines with no leading timestamp.
- Output is sorted ascending by
timestampSecand frozen. - Throws
REACT_AUDIO_PLAYER.LRC_PARSE_FAILEDfor non-string input or input exceedingmaxLength(default 1 MiB). activeLyricIndexis O(log n) and allocation-free.
Usage: Basic
import { parseLrc } from "@suaveplan/react-audio-player/karaoke/lrc-parser";
const lyrics = parseLrc(`
[ar:Test Artist]
[00:10.50]First line
[00:25.00]Second line
`);
// → [{ timestampSec: 10.5, text: "First line" },
// { timestampSec: 25.0, text: "Second line" }]
Usage: Advanced
import {
parseLrc,
activeLyricIndex,
} from "@suaveplan/react-audio-player/karaoke/lrc-parser";
import { useTransportTime } from "@suaveplan/react-audio";
import { useMemo } from "react";
function LyricLine({ source }: { source: string }) {
const lyrics = useMemo(
() => parseLrc(source, { expandMultiTimestamp: false }),
[source]
);
const seconds = useTransportTime();
const idx = activeLyricIndex(lyrics, seconds);
return (
<span aria-live="polite">
{idx >= 0 ? lyrics[idx].text : "…"}
</span>
);
}
API Reference
parseLrc(source: unknown, opts?: ParseLrcOptions): readonly Lyric[]activeLyricIndex(lyrics: readonly Lyric[], currentSec: number): numberLyric—{ timestampSec: number; text: string }.ParseLrcOptions—maxLength?: number(default 1 MiB),expandMultiTimestamp?: boolean(defaulttrue).
Implementation Notes
The timestamp regex /\[(?:(\d{1,2}):)?(\d{1,3}):(\d{1,2})(?:[.:](\d{1,3}))?\]/g
captures optional hours, minutes, seconds, and an optional
fractional component. The fractional value is padded right with
zeros to three digits then divided by 1000, so .5 parses to 0.500
seconds, .50 to 0.500, and .500 to 0.500 — all equivalent. Only . is accepted as the
fractional separator; the : form would conflict with the
[h:mm:ss] extended timestamp grammar.
Multi-timestamp expansion walks the regex's global state, but only
honours timestamps that form a contiguous prefix of the line. A
bracketed sequence later in the line is treated as text — many
karaoke services emit [00:10]hello [00:11]world to mean two
separate lyric lines and this parser handles both layouts
correctly.
activeLyricIndex uses the upper-bound binary search idiom: pick
the greatest index whose timestamp is ≤ current. When current is
between two entries, the earlier entry wins; when current
precedes the first entry, -1 is returned (so the consumer can
render an empty active line until the first cue).