bold words
This commit is contained in:
@@ -77,6 +77,22 @@ const MusicDetails = () => {
|
|||||||
|
|
||||||
const player = useAudioPlayer(songUrl ? { uri: songUrl } : undefined);
|
const player = useAudioPlayer(songUrl ? { uri: songUrl } : undefined);
|
||||||
|
|
||||||
|
// Karaoke aligned words (from timestamps)
|
||||||
|
const alignedWords = useMemo(() => {
|
||||||
|
try {
|
||||||
|
const idx = Number(project?.songIndex) || 0;
|
||||||
|
const ts = project?.musicTimestamps?.[idx];
|
||||||
|
const arr = Array.isArray(ts?.alignedWords) ? ts.alignedWords : [];
|
||||||
|
return arr.map((w) => ({
|
||||||
|
word: String(w?.word ?? ""),
|
||||||
|
startS: Number(w?.startS ?? 0),
|
||||||
|
endS: Number(w?.endS ?? 0),
|
||||||
|
}));
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}, [project?.musicTimestamps, project?.songIndex]);
|
||||||
|
|
||||||
// Reset counters when the track changes
|
// Reset counters when the track changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
listenedMsRef.current = 0;
|
listenedMsRef.current = 0;
|
||||||
@@ -189,10 +205,65 @@ const MusicDetails = () => {
|
|||||||
}
|
}
|
||||||
const c = project?.lyrics?.couplet;
|
const c = project?.lyrics?.couplet;
|
||||||
const r = project?.lyrics?.refrain;
|
const r = project?.lyrics?.refrain;
|
||||||
const parts = [c ? `[Couplet]\n${c}` : null, r ? `[Refrain]\n${r}` : null].filter(Boolean);
|
const parts = [
|
||||||
|
c ? `[Couplet]\n${c}` : null,
|
||||||
|
r ? `[Refrain]\n${r}` : null,
|
||||||
|
].filter(Boolean);
|
||||||
return parts.length ? parts.join("\n\n") : "";
|
return parts.length ? parts.join("\n\n") : "";
|
||||||
}, [project]);
|
}, [project]);
|
||||||
|
|
||||||
|
// Current time in seconds for highlighting
|
||||||
|
const currentTimeS = useMemo(
|
||||||
|
() => Math.max(0, (progressInfo.pos || 0) / 1000),
|
||||||
|
[progressInfo.pos]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Render full lyrics with the currently sung word highlighted
|
||||||
|
const renderKaraokeFullLyrics = () => {
|
||||||
|
if (!alignedWords?.length) return null;
|
||||||
|
const spans = [];
|
||||||
|
for (let i = 0; i < alignedWords.length; i++) {
|
||||||
|
const w = alignedWords[i];
|
||||||
|
const text = String(w.word || "");
|
||||||
|
// Highlight all words that already started (and keep them lit)
|
||||||
|
const isHighlighted = currentTimeS >= (w.startS || 0);
|
||||||
|
|
||||||
|
// split by newline to preserve line breaks
|
||||||
|
const parts = text.split(/(\n+)/);
|
||||||
|
parts.forEach((p, j) => {
|
||||||
|
if (/^\n+$/.test(p)) {
|
||||||
|
spans.push(<Text key={`br-${i}-${j}`}>{"\n"}</Text>);
|
||||||
|
} else if (p.length > 0) {
|
||||||
|
spans.push(
|
||||||
|
<Text
|
||||||
|
key={`w-${i}-${j}`}
|
||||||
|
style={
|
||||||
|
isHighlighted ? styles.karaokeWordActive : styles.karaokeWord
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{p}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add space between words when no newline
|
||||||
|
if (!/\n/.test(text) && i < alignedWords.length - 1) {
|
||||||
|
spans.push(
|
||||||
|
<Text key={`sp-${i}`} style={styles.karaokeWord}>
|
||||||
|
{" "}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text style={styles.karaokeContainer} selectable>
|
||||||
|
{spans}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page
|
<Page
|
||||||
headerType="NAVIGATION"
|
headerType="NAVIGATION"
|
||||||
@@ -346,8 +417,10 @@ const MusicDetails = () => {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{description?.length > 0 && (
|
|
||||||
<View style={{ marginTop: 30, gap: 20 }}>
|
<View style={{ marginTop: 30, gap: 20 }}>
|
||||||
|
{alignedWords?.length ? (
|
||||||
|
renderKaraokeFullLyrics()
|
||||||
|
) : description?.length > 0 ? (
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
@@ -357,8 +430,8 @@ const MusicDetails = () => {
|
|||||||
>
|
>
|
||||||
{description}
|
{description}
|
||||||
</Text>
|
</Text>
|
||||||
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
)}
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
@@ -385,4 +458,20 @@ const styles = StyleSheet.create({
|
|||||||
color: Palette.white,
|
color: Palette.white,
|
||||||
fontFamily: FONT_FAMILY.HelveticaNeueRegular,
|
fontFamily: FONT_FAMILY.HelveticaNeueRegular,
|
||||||
},
|
},
|
||||||
|
karaokeContainer: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: Palette.white,
|
||||||
|
fontFamily: FONT_FAMILY.InterRegular,
|
||||||
|
lineHeight: 24,
|
||||||
|
},
|
||||||
|
karaokeWord: {
|
||||||
|
color: Palette.white,
|
||||||
|
fontFamily: FONT_FAMILY.InterRegular,
|
||||||
|
},
|
||||||
|
karaokeWordActive: {
|
||||||
|
color: Palette.white,
|
||||||
|
fontFamily: FONT_FAMILY.InterBold,
|
||||||
|
fontStyle: "italic",
|
||||||
|
fontWeight: "bold",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user