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.

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.

npm i @braccato/core @braccato/provider-blyrics
Provider chain docs
  1. Import
    element, styles, providers
    import "@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";
  2. Build the chain
    first match wins
    const 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(),
    });
  3. Fetch and render
    hand the result to the element
    const 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);
});

Next steps

  • Parsers: parse the TTML, Kugou and QQ responses
  • Renderer: properties, events, scrolling and theming
  • Provider: fetch from several sources with fallback