@uimaxbai/am-lyrics

repository·main·Indexed 18 days ago

https://github.com/binimum/am-lyrics

A web component for displaying synchronized, animated lyrics that supports multiple providers including LyricsPlus and Apple Music. It features automatic scrolling, word-by-word interpolation animations, and can be used as a standalone component or integrated into React applications. The component supports song identification via query, music-id, ISRC, or metadata, and allows for local lyrics playback using TTML strings.

Tokens
6.5K
Snippets
15
Records
27
Agent score
61%

What's inside @uimaxbai/am-lyrics

  1. How lyrics providers work

    main

    The component primarily uses the LyricsPlus (KPoe) API.

    1. LyricsPlus: Triggered by providing song-title and song-artist (plus optional song-album/song-duration) or a standalone query (e.g., "Bad Habit - Steve Lacy").
    2. Apple Music (Fallback): If LyricsPlus fails or metadata is missing, the component falls back to the legacy Apple Music endpoint using query, music-id, or isrc.

    Note: Requests relying solely on music-id are handled exclusively by the Apple Music backup service.

  2. Use @uimaxbai/am-lyrics in React

    main

    To use the component in React, you must install react and @lit/react. Import the AmLyrics component from @uimaxbai/am-lyrics/react.

    Important: You must include the 'use client'; directive at the top of your file if using Next.js App Router.

    To sync with an audio element, use requestAnimationFrame or the timeupdate event to update the currentTime prop (in milliseconds).

    'use client'; // VERY IMPORTANT!!!
    
    import React, { useState, useCallback, useRef, useEffect } from 'react';
    import { AmLyrics } from '@uimaxbai/am-lyrics/react';
    
    export default function App() {
      const [currentTime, setCurrentTime] = useState(0);
      const audioRef = useRef<HTMLAudioElement>(null);
    
      // Sync audio player time with the component
      useEffect(() => {
        const audio = audioRef.current;
        if (!audio) return;
    
        let animationFrameId: number;
    
        const updateCurrentTime = () => {
          setCurrentTime(audio.currentTime * 1000);
          animationFrameId = requestAnimationFrame(updateCurrentTime);
        };
    
        const handlePlay = () => {
          animationFrameId = requestAnimationFrame(updateCurrentTime);
        };
    
        const handlePause = () => {
          cancelAnimationFrame(animationFrameId);
        };
    
        const handleTimeUpdate = () => {
          setCurrentTime(audio.currentTime * 1000);
        };
    
        audio.addEventListener('play', handlePlay);
        audio.addEventListener('pause', handlePause);
        audio.addEventListener('timeupdate', handleTimeUpdate);
    
        return () => {
          cancelAnimationFrame(animationFrameId);
          audio.removeEventListener('play', handlePlay);
          audio.removeEventListener('pause', handlePause);
          audio.removeEventListener('timeupdate', handleTimeUpdate);
        };
      }, []);
    
      const handleLineClick = useCallback((event: Event) => {
        const customEvent = event as CustomEvent<{ timestamp: number }>;
        const audio = audioRef.current;
        if (audio) {
          audio.currentTime = customEvent.detail.timestamp / 1000;
          audio.play();
        }
      }, []);
    
      return (
        <div>
          <audio ref={audioRef} src="/uptown_funk.flac" controls />
          <AmLyrics
            songTitle="Uptown Funk"
            songArtist="Mark Ronson"
            query="Uptown Funk Mark Ronson"
            currentTime={currentTime}
            onLineClick={handleLineClick}
            autoScroll
            highlightColor='#fff'
          />
        </div>
      );
    }
  3. Install @uimaxbai/am-lyrics

    main

    You can install the package via npm for React users or local development, or use the CDN for a quick setup.

    NPM Installation:

    npm install @uimaxbai/am-lyrics

    CDN Usage: Import the component directly in your HTML using a module script:

    <script type="module">
      import 'https://cdn.jsdelivr.net/npm/@uimaxbai/am-lyrics/dist/src/am-lyrics.min.js';
    </script>
  4. Synchronize <am-lyrics> with an <audio> element

    main

    To sync lyrics with a standard HTML <audio> element, listen to the timeupdate event on the audio player and update the currentTime property of the <am-lyrics> component (converting seconds to milliseconds). You can also listen for line-click on the component to seek the audio player.

    <audio id="audio-player" src="path/to/your/song.mp3" controls></audio>
    <am-lyrics
      song-title="Uptown Funk"
      song-artist="Mark Ronson"
      query="Uptown Funk Mark Ronson"
    ></am-lyrics>
    
    <script>
      document.addEventListener('DOMContentLoaded', () => {
        const amLyrics = document.querySelector('am-lyrics');
        const audioPlayer = document.querySelector('#audio-player');
    
        if (amLyrics && audioPlayer) {
          // Update lyrics time when audio time updates
          audioPlayer.addEventListener('timeupdate', () => {
            // The component expects time in milliseconds
            amLyrics.currentTime = audioPlayer.currentTime * 1000;
          });
    
          // Seek audio when a lyric line is clicked
          amLyrics.addEventListener('line-click', e => {
            // The event detail contains the timestamp in milliseconds
            audioPlayer.currentTime = e.detail.timestamp / 1000;
            audioPlayer.play();
          });
        }
      });
    </script>
  5. Generate TTML format from lyrics data

    main
    The component can generate a TTML (Timed Text Markup Language) XML structure from its lyrics data, compatible with standard web video/audio captioning requirements. It includes the necessary XML namespaces for tt and itunes.
  6. Configure syllable and word animation behaviors

    main

    The component supports advanced word-level animations. The visual style of syllables depends on their state:

    • Wipe Highlight: Uses a linear gradient to 'wipe' color across syllables as they are sung.
    • Char Rise/Drag: Certain words can be configured with .char-rise or .char-drag classes to trigger specific character-level transformations.
    • Line-Synced Lyrics: Syllables with the .line-synced class use a faster fade-in-line animation instead of the standard wipe effect, making them suitable for less granular timing data.
  7. Generate LRC format from lyrics data

    main

    The component can internally generate an LRC (Lyric) string from its current lyrics state. The generated string includes metadata tags if available and timestamped lines.

    Supported metadata tags:

    • [ti:...] for Song Title
    • [ar:...] for Artist
    • [al:...] for Album
    • [re:...] for Lyrics Source

    Each line follows the format: [mm:ss.xx]Line text.

  8. Understand the internal syllable and word animation logic

    main

    The AmLyrics component manages complex lyric animations using a multi-pass system. It handles both syllable-level animations (like wipes) and word-level animations (like growable, char-rise, or char-drag effects).

    Key animation behaviors include:

    • Wipe Pass: Uses char-wipe or char-start-wipe animations on individual character spans (span.char) to create a progressive highlight effect.
    • Pre-Wipe/Pre-Highlight: To ensure smooth transitions between words, the component can 'arm' a pre-wipe on the next word before the current word finishes, using the pre-highlight class and CSS variables --pre-wipe-duration and --pre-wipe-delay.
    • Word Styles: Words can be configured with specific animation styles via CSS classes: growable, char-rise, or char-drag.
    • State Management: Syllables transition through several states: pre-highlight $\rightarrow$ highlight $\rightarrow$ finished $\rightarrow$ cleanup.
  9. Understand the visual states of lyric lines

    main

    The <am-lyrics> component uses several CSS classes to manage the lifecycle and visual state of lyric lines. Understanding these helps when building custom wrappers or debugging styles:

    • .active: The currently playing lyric line. It has full opacity and uses the primary highlight color.
    • .pre-active: A line that is about to become active. It has full opacity but is not yet the primary focus.
    • .scroll-exiting: A line that has just finished playing and is transitioning out.
    • .lyrics-gap: Represents an instrumental break. It displays pulsing dots instead of text.
    • .far-line: A virtualization class applied to offscreen lines to strip expensive filters and animations for performance.
    • .persist-highlight: A state used to keep a line highlighted even if it is no longer the primary active line.
  10. Supported lyric formats and parsing

    main

    The component internally handles several lyric data formats to normalize them into a consistent LyricsLine[] structure:

    • TTML (Timed Text Markup Language): Parses complex XML structures including ttm:agent for singer identification, translation for multilingual support, and transliteration for romanized text (e.g., for CJK languages).
    • LRC (Lyric Subtitles): Parses the standard [mm:ss.xx] text format. It calculates endtime for each line based on the start of the subsequent line.
    • KPoe/YouLyPlus JSON: Converts specialized payload formats (containing syllabus, words, or element properties) into synchronized lines and syllables.
    • Plain Text: For unsynced lyrics, it creates lines with timestamp: 0 and endtime: 0.
  11. Toggle Romanization and Translation displays

    main

    The component provides built-in support for displaying transliterated (romanized) text and translations. These can be toggled via the component's internal state, which is exposed through the UI controls.

    • Romanization: Displays romanizedText for syllables. It is hidden if the romanized text is identical to the original text.
    • Translation: Displays line.translation. It is hidden if the translation matches the original line text to avoid redundancy.
  12. Customize appearance with CSS Custom Properties

    main

    You can override component properties using CSS variables. CSS variables take precedence over the set properties.

    am-lyrics {
      /* Highlight color for active lyrics */
      --am-lyrics-highlight-color: #007aff;
    
      /* Alternative highlight color (fallback) */
      --highlight-color: #000;
    }