This commit is contained in:
2025-09-03 11:32:56 +02:00
parent ade2d6ef9d
commit df880874e3
5 changed files with 297 additions and 60 deletions
+144
View File
@@ -0,0 +1,144 @@
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>
);
}