Skip to content

Timing and Lifecycle

This page explains timing and lifecycle management for the lyric component.

The lyric component only handles the lyric view itself. It does not handle audio playback. Therefore, the host environment, which is your code, needs to manage audio playback and bridge the audio playback state to AMLL component state.

If you use the React or Vue bindings, the component manages part of the lifecycle for you. If you use the vanilla API directly, you need to manage the full flow yourself. This page mainly covers lifecycle management with the vanilla API and explains the state managed by the bindings.

During initialization, you need to:

  1. Create the lyric component and mount its element into a container with an explicit size.
  2. Optionally set custom lyric optimization options. The setOptimizeOptions method accepts OptimizeLyricOptions.
  3. Set lyric data. The setLyricLines method accepts LyricLine[]. After passing the objects in, do not modify them.
  4. Align the lyric position once with the current playback progress.

A typical vanilla sequence looks like this:

import { LyricPlayer } from "@applemusic-like-lyrics/core";
const player = new LyricPlayer();
host.appendChild(player.getElement());
const currentTime = Math.round(audio.currentTime * 1000);
player.setOptimizeOptions({}); // Optional
player.setLyricLines(lines, currentTime);
player.setCurrentTime(currentTime, true);
player.update(0);

Setting or changing lyric optimization options reprocesses the lyrics and automatically rebuilds the view. You can call setOptimizeOptions before setLyricLines, or call it at any time with existing lyrics.

If you need to update multiple settings at once (such as optimization options and obscene word mask settings), it is recommended to use the updateLyricProcessConfig method to batch update and avoid triggering multiple view rebuilds:

player.updateLyricProcessConfig({
optimizeOptions: { normalizeSpaces: true },
maskMode: "full-mask",
maskChar: "*",
});

Also note that currentTime is in milliseconds and should be an integer. audio.currentTime is in seconds, so multiply it by 1000.

pause() and resume() control the lyric component’s internal presentation state, including word-by-word animation, glow, and interlude dot animation. Call resume() when audio starts playing, and call pause() when audio pauses, ends, or is externally interrupted.

For example, when using <audio> for playback, drive this with its events:

const onPlay = () => {
player.resume();
};
const onPause = () => {
player.pause();
};
audio.addEventListener("play", onPlay);
audio.addEventListener("pause", onPause);

During playback, you need to update the lyric component’s time progress. All time values used by AMLL are in milliseconds.

Two time values are easy to confuse:

Time Type Accepted By Meaning
Current progress setCurrentTime(time) / currentTime prop Song playback progress
Frame delta update(delta) Time elapsed since the previous frame

In vanilla usage, setCurrentTime updates the lyric timeline, and update advances animation. They are not the same value.

let frameId = 0;
let lastFrameTime = -1;
function startFrameLoop() {
const onFrame = (frameTime: number) => {
const delta = lastFrameTime === -1 ? 0 : frameTime - lastFrameTime;
lastFrameTime = frameTime;
if (!audio.paused) {
player.setCurrentTime(Math.round(audio.currentTime * 1000));
}
player.update(delta);
frameId = requestAnimationFrame(onFrame);
};
frameId = requestAnimationFrame(onFrame);
}
function stopFrameLoop() {
cancelAnimationFrame(frameId);
frameId = 0;
lastFrameTime = -1;
}

Do not rely on the <audio> timeupdate event to sync lyrics. Browsers fire timeupdate at a low and unstable frequency, usually far below the animation frame rate. During playback, use requestAnimationFrame to sync current progress frame by frame.

Outside normal playback, playback progress may jump. For this kind of progress change, the lyric component switches to a different set of layout and animation behavior.

For more information, see Seeking and Progress Alignment.

When changing songs or lyric sources, set a new lyric line object array with setLyricLines. If loading fails, pass an empty array to clear the lyrics.

player.setLyricLines([]);
player.update(0);

The React and Vue bindings create and destroy the underlying Core component. They also automatically call update unless disabled. Therefore, when using bindings, you usually do not need to call the underlying update yourself.

You still need to provide these states:

State React / Vue Input Description
Lyric data lyricLines Parsed LyricLine[]
Current progress currentTime Synced from audio with requestAnimationFrame during playback
Playback state playing Pauses or resumes the lyric component’s internal presentation

The React binding additionally provides an isSeeking prop, which maps to the second parameter of setCurrentTime. The Vue binding is currently less complete and does not have a corresponding prop. Automatic derivation is enabled by default for both, so syncing currentTime is generally enough; see Seeking and Progress Alignment for details. We will continue improving the Vue binding functionality and usage experience in upcoming versions.

If disabled is set, the binding no longer manages frame-by-frame animation. In that case, you can access the underlying lyricPlayer through a component ref and call update yourself, just like with the vanilla API.

When the lyric player component is no longer needed, vanilla usage requires cleaning up every resource you created yourself:

// Clear the requestAnimationFrame loop you defined.
stopFrameLoop();
// Remove listeners you added.
audio.removeEventListener("play", onPlay);
audio.removeEventListener("pause", onPause);
audio.removeEventListener("seeked", onSeeked);
// Release component resources.
player.dispose();

dispose() removes the component element and releases internal listeners.

If you use the React or Vue bindings, the component automatically calls the underlying dispose() when unmounted. However, requestAnimationFrame, audio event listeners, ObjectURLs, and similar resources that you create yourself still need to be cleaned up when the component unmounts.

  • The container has an explicit size and has been mounted to the DOM.
  • Lyrics are passed through setLyricLines(lines, currentTime) or the lyricLines prop.
  • Playback progress is represented in milliseconds.
  • During playback, currentTime is synced with requestAnimationFrame.
  • In vanilla usage, update(delta) is called frame by frame.
  • Pause, resume, and playback end are synced to pause() / resume() or playing.
  • Seeks are recognized automatically by default; when you know a seek happened, you can additionally use the seek flag to mark it explicitly.
  • On unmount, cancel animation frames, remove event listeners, and dispose the component.