Overview
Braccato is the Better Lyrics rendering engine and its lyric parsers, published as npm packages. Use it to parse any response from this API into one shape, then render it with syllable-level highlighting and autoscroll.
It is the same code that runs inside the Better Lyrics extension, packaged so you can use it in your own player, site, or app.
- Demo and docs: braccato.boidu.dev
- Source: github.com/better-lyrics/braccato (MIT)
Packages
| Package | Version | What it does |
|---|---|---|
@braccato/core | 1.16.14 | The <braccato-lyrics> custom element that renders synced lyrics |
@braccato/parsers | 0.4.0 | TTML, LRC, SRT, QRC and plain text parsers, with format detection |
@braccato/provider-blyrics | 0.2.3 | A provider chain that fetches from several lyrics sources in priority order |
@braccato/types | 1.0.0 | The shared Lyric and LyricPart types. Types only, and re-exported by core and parsers |
@braccato/rics | 0.1.3 | Compiles RICS theme source to plain CSS |
Most projects need @braccato/core and @braccato/provider-blyrics. Add
@braccato/parsers when you call this API yourself.
Quickstart
The minimal setup
Register the providers once, ask for a song, and hand the result to
<braccato-lyrics>. If a provider has nothing for the song, the chain tries the next one.
Import
element, styles, providersimport "@braccato/core/element"; import "@braccato/core/styles/variables.css"; import "@braccato/core/styles/lyrics.css"; import "@braccato/core/styles/instrumental.css"; import { ProviderChain, createBLyricsProvider, createPortatoProvider, createLegatoProvider, } from "@braccato/provider-blyrics";Build the chain
first match winsconst chain = new ProviderChain(); chain.register({ key: "ttml", displayName: "TTML", syncType: "syllable", fetch: createBLyricsProvider(), }); chain.register({ key: "qq", displayName: "QQ", syncType: "word", fetch: createPortatoProvider(), }); chain.register({ key: "kugou", displayName: "Kugou", syncType: "line", fetch: createLegatoProvider(), });Fetch and render
hand the result to the elementconst result = await chain.fetchLyrics({ song: "APT.", artist: "ROSÉ, Bruno Mars", album: "APT.", duration: 170, signal: new AbortController().signal, }); // <audio id="player" src="song.mp3"></audio> // <braccato-lyrics source="#player"></braccato-lyrics> if (result) { document.querySelector("braccato-lyrics").lyrics = result.lyrics; }
Add the element
Put the media element before the lyrics element, so the source selector resolves
when the element connects. Give the element a height and let it scroll.
<audio id="player" src="song.mp3" controls></audio>
<braccato-lyrics source="#player"></braccato-lyrics>
<style>
braccato-lyrics {
display: block;
height: 60vh;
overflow-y: auto;
}
</style>
With source set, the element reads the playback position from the
<audio> element on its own, and a click on a line seeks it.
Drive the clock yourself
If your player is not a media element (an embedded player, a native bridge, a remote device),
leave out source and write the position on every frame. currentTime
is in seconds.
function tick() {
view.currentTime = player.getCurrentTime();
view.playing = player.isPlaying();
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
view.addEventListener("braccato:line-click", (event) => {
player.seekTo(event.detail.timeS);
});