Initial Commit
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { activeLyricIndex, parseLrc } from "../lrc-parser/lrc-parser.js";
|
||||
import type { Lyric } from "../lrc-parser/lrc-parser.types.js";
|
||||
import type { UseLyricsOptions, UseLyricsResult } from "./use-lyrics.types.js";
|
||||
|
||||
const DEFAULT_INTERVAL_MS = 100;
|
||||
|
||||
/**
|
||||
* Engine-agnostic karaoke hook. Accepts a pre-parsed `lyrics` array
|
||||
* (preferred) or a raw `lrc` string, plus a `currentSec` value, and
|
||||
* returns the active line. Sampling cadence is decoupled from the
|
||||
* caller's render rate: a 100 ms interval (configurable) forces a
|
||||
* re-render even when neither `lyrics` nor `currentSec` mutate.
|
||||
*
|
||||
* The hook is deliberately decoupled from `useTransportTime` so it
|
||||
* can be driven from any clock — `<LyricsDisplay>` wires the
|
||||
* transport in for you when used inside `<AudioProvider>`, but a
|
||||
* preview panel running under a deterministic test clock can call
|
||||
* this hook directly with a hand-fed `currentSec`.
|
||||
*/
|
||||
export function useLyrics(opts: UseLyricsOptions): UseLyricsResult {
|
||||
const lyrics: readonly Lyric[] = useMemo(() => {
|
||||
if (opts.lyrics) return opts.lyrics;
|
||||
if (typeof opts.lrc === "string") return parseLrc(opts.lrc);
|
||||
return [];
|
||||
}, [opts.lyrics, opts.lrc]);
|
||||
|
||||
const intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS;
|
||||
|
||||
// A heartbeat counter forces a re-render every `intervalMs` so
|
||||
// the memo below recomputes even when `currentSec` did not
|
||||
// change between commits — relevant when the consumer's clock
|
||||
// mutates a ref or external store the hook cannot subscribe to.
|
||||
const [, setTick] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(
|
||||
() => {
|
||||
setTick((t) => (t + 1) % 1_000_000);
|
||||
},
|
||||
Math.max(1, intervalMs)
|
||||
);
|
||||
return () => {
|
||||
clearInterval(id);
|
||||
};
|
||||
}, [intervalMs]);
|
||||
|
||||
const activeIndex = useMemo(
|
||||
() => activeLyricIndex(lyrics, opts.currentSec),
|
||||
[lyrics, opts.currentSec]
|
||||
);
|
||||
|
||||
return {
|
||||
lyrics,
|
||||
activeIndex,
|
||||
activeLyric: activeIndex >= 0 ? lyrics[activeIndex] : undefined,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user