mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 02:24:16 +00:00
Replace `TrackInstance` with direct `TrackManifest` usage in the player core. Introduce a `SyncRoom` class and related hooks (`useSyncRoom`, `useLyrics`, etc.) for real-time state synchronization and shared lyrics display. Enhance player indicators to show detailed audio format metadata (codec, sample rate, bit depth). Relocate the Indicators component and update the Lyrics page to utilize these new features and components.
166 lines
3.2 KiB
JavaScript
166 lines
3.2 KiB
JavaScript
import React from "react"
|
|
import classnames from "classnames"
|
|
import { motion, AnimatePresence } from "motion/react"
|
|
|
|
import { usePlayerStateContext } from "@contexts/WithPlayerContext"
|
|
|
|
// eslint-disable-next-line
|
|
const LyricsText = React.forwardRef((props, textRef) => {
|
|
const [playerState] = usePlayerStateContext()
|
|
|
|
const { lyrics } = props
|
|
|
|
const [syncInterval, setSyncInterval] = React.useState(null)
|
|
const [currentLineIndex, setCurrentLineIndex] = React.useState(0)
|
|
const [visible, setVisible] = React.useState(false)
|
|
|
|
function syncPlayback() {
|
|
const currentTrackTime = app.cores.player.controls.seek() * 1000
|
|
|
|
const lineIndex = lyrics.synced_lyrics.findIndex((line) => {
|
|
return (
|
|
currentTrackTime >= line.startTimeMs &&
|
|
currentTrackTime <= line.endTimeMs
|
|
)
|
|
})
|
|
|
|
if (lineIndex === -1) {
|
|
if (!visible) {
|
|
setVisible(false)
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
const line = lyrics.synced_lyrics[lineIndex]
|
|
|
|
setCurrentLineIndex(lineIndex)
|
|
|
|
if (line.break) {
|
|
return setVisible(false)
|
|
}
|
|
|
|
if (line.text) {
|
|
return setVisible(true)
|
|
}
|
|
}
|
|
|
|
function startSyncInterval() {
|
|
if (!lyrics || !lyrics.synced_lyrics) {
|
|
stopSyncInterval()
|
|
return false
|
|
}
|
|
|
|
if (playerState.playback_status !== "playing") {
|
|
stopSyncInterval()
|
|
return false
|
|
}
|
|
|
|
if (syncInterval) {
|
|
stopSyncInterval()
|
|
}
|
|
|
|
setSyncInterval(setInterval(syncPlayback, 100))
|
|
}
|
|
|
|
function stopSyncInterval() {
|
|
clearInterval(syncInterval)
|
|
setSyncInterval(null)
|
|
}
|
|
|
|
//* Handle when current line index change
|
|
React.useEffect(() => {
|
|
if (currentLineIndex === 0) {
|
|
setVisible(false)
|
|
} else {
|
|
setVisible(true)
|
|
|
|
if (textRef.current) {
|
|
// find line element by id
|
|
const lineElement = textRef.current.querySelector(
|
|
`#lyrics-line-${currentLineIndex}`,
|
|
)
|
|
|
|
// center scroll to current line
|
|
if (lineElement) {
|
|
lineElement.scrollIntoView({
|
|
behavior: "smooth",
|
|
block: "center",
|
|
})
|
|
} else {
|
|
// scroll to top
|
|
textRef.current.scrollTop = 0
|
|
}
|
|
}
|
|
}
|
|
}, [currentLineIndex])
|
|
|
|
//* Handle when playback status change
|
|
React.useEffect(() => {
|
|
startSyncInterval()
|
|
}, [playerState.playback_status])
|
|
|
|
//* Handle when manifest object change, reset...
|
|
React.useEffect(() => {
|
|
setVisible(false)
|
|
setCurrentLineIndex(0)
|
|
}, [playerState.track_manifest])
|
|
|
|
React.useEffect(() => {
|
|
startSyncInterval()
|
|
}, [lyrics])
|
|
|
|
React.useEffect(() => {
|
|
return () => {
|
|
clearInterval(syncInterval)
|
|
}
|
|
}, [])
|
|
|
|
if (!lyrics?.synced_lyrics) {
|
|
return null
|
|
}
|
|
|
|
return (
|
|
<div className="lyrics-text-wrapper">
|
|
<AnimatePresence>
|
|
{visible && (
|
|
<motion.div
|
|
ref={textRef}
|
|
className="lyrics-text"
|
|
animate={{
|
|
opacity: 1,
|
|
}}
|
|
initial={{
|
|
opacity: 0,
|
|
}}
|
|
exit={{
|
|
opacity: 0,
|
|
}}
|
|
transition={{
|
|
type: "spring",
|
|
stiffness: 100,
|
|
damping: 20,
|
|
}}
|
|
>
|
|
{lyrics.synced_lyrics.map((line, index) => {
|
|
return (
|
|
<p
|
|
key={index}
|
|
id={`lyrics-line-${index}`}
|
|
className={classnames("line", {
|
|
["current"]: currentLineIndex === index,
|
|
})}
|
|
>
|
|
{line.text}
|
|
</p>
|
|
)
|
|
})}
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
)
|
|
})
|
|
|
|
export default LyricsText
|