145 lines
3.7 KiB
JavaScript
145 lines
3.7 KiB
JavaScript
import React, { useMemo } from "react";
|
|
import { Text, View } from "react-native";
|
|
import { Palette } from "../styles";
|
|
import { FONT_FAMILY } from "../styles/Fonts";
|
|
|
|
export function groupAlignedWordsToLines(alignedWords = [], { removeTags = true } = {}) {
|
|
const out = [];
|
|
let buf = [];
|
|
let start = null;
|
|
const clean = (txt) =>
|
|
String(txt || "")
|
|
.replace(/\s+/g, " ")
|
|
.trim();
|
|
const isSentenceEnd = (txt) => /[.!?]$/.test((txt || "").trim());
|
|
const isSectionTag = (txt) => /^\s*\[[^\]]+\]\s*$/i.test((txt || "").trim());
|
|
|
|
for (let i = 0; i < alignedWords.length; i++) {
|
|
const w = alignedWords[i] || {};
|
|
const original = String(w.word || "");
|
|
const textNoNewline = original.replace(/\n/g, " ");
|
|
const textNoTag = removeTags
|
|
? textNoNewline.replace(/^\s*\[[^\]]+\]\s*/i, "")
|
|
: textNoNewline;
|
|
const next = alignedWords[i + 1] || null;
|
|
const gapToNext = next
|
|
? Math.max(0, Number(next.startS || 0) - Number(w.endS || 0))
|
|
: 0;
|
|
|
|
if (buf.length === 0) start = Number(w.startS || 0);
|
|
|
|
if (isSectionTag(textNoNewline)) {
|
|
if (!removeTags) {
|
|
const joined = clean(textNoNewline);
|
|
if (joined)
|
|
out.push({
|
|
text: joined,
|
|
startS: start ?? Number(w.startS || 0),
|
|
endS: Number(w.endS || 0),
|
|
});
|
|
}
|
|
buf = [];
|
|
start = null;
|
|
continue;
|
|
}
|
|
|
|
if (!clean(textNoTag)) {
|
|
continue;
|
|
}
|
|
|
|
buf.push(textNoTag);
|
|
|
|
const eolByNewline = /\n/.test(original);
|
|
const eolByPause = gapToNext >= 0.6; // threshold for a logical break
|
|
const eolByPunct = isSentenceEnd(textNoTag);
|
|
const isLast = i === alignedWords.length - 1;
|
|
|
|
if (eolByNewline || eolByPause || eolByPunct || isLast) {
|
|
const joined = clean(buf.join(" "));
|
|
if (joined)
|
|
out.push({
|
|
text: joined,
|
|
startS: start ?? Number(w.startS || 0),
|
|
endS: Number(w.endS || 0),
|
|
});
|
|
buf = [];
|
|
start = null;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
export default function KaraokeLyrics({
|
|
alignedWords = [],
|
|
currentTimeS = 0,
|
|
showContext = true,
|
|
removeTags = true,
|
|
}) {
|
|
const lines = useMemo(
|
|
() => groupAlignedWordsToLines(alignedWords, { removeTags }),
|
|
[alignedWords, removeTags]
|
|
);
|
|
|
|
const currentLineIdx = useMemo(() => {
|
|
if (!lines || lines.length === 0) return -1;
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const L = lines[i];
|
|
if (currentTimeS >= (L.startS || 0) && currentTimeS <= (L.endS || 0))
|
|
return i;
|
|
}
|
|
if (currentTimeS > (lines[lines.length - 1]?.endS || 0))
|
|
return lines.length - 1;
|
|
return -1;
|
|
}, [lines, currentTimeS]);
|
|
|
|
if (!lines.length) return null;
|
|
|
|
const prev =
|
|
showContext && currentLineIdx > 0 ? lines[currentLineIdx - 1]?.text : "";
|
|
const curr =
|
|
currentLineIdx >= 0 ? lines[currentLineIdx]?.text : lines[0]?.text;
|
|
const next =
|
|
showContext && currentLineIdx + 1 < lines.length
|
|
? lines[currentLineIdx + 1]?.text
|
|
: "";
|
|
|
|
return (
|
|
<View style={{ gap: 4 }}>
|
|
{/* {prev ? (
|
|
<Text
|
|
style={{
|
|
color: "#FFFFFF99",
|
|
fontSize: 14,
|
|
textAlign: "center",
|
|
fontFamily: FONT_FAMILY.InterMedium,
|
|
}}
|
|
>
|
|
{prev}
|
|
</Text>
|
|
) : null} */}
|
|
<Text
|
|
style={{
|
|
color: Palette.white,
|
|
fontSize: 18,
|
|
textAlign: "center",
|
|
fontFamily: FONT_FAMILY.InterSemiBold,
|
|
}}
|
|
>
|
|
{curr}
|
|
</Text>
|
|
{next ? (
|
|
<Text
|
|
style={{
|
|
color: "#FFFFFF99",
|
|
fontSize: 14,
|
|
textAlign: "center",
|
|
fontFamily: FONT_FAMILY.InterMedium,
|
|
}}
|
|
>
|
|
{next}
|
|
</Text>
|
|
) : null}
|
|
</View>
|
|
);
|
|
}
|