mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 10:34:17 +00:00
Fix nested optional chaining in lossless tag & improve video sync
This commit is contained in:
parent
6e80fc67fa
commit
6100feb608
@ -177,7 +177,7 @@ const PlayerController = React.forwardRef((props, ref) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="lyrics-player-controller-tags">
|
<div className="lyrics-player-controller-tags">
|
||||||
{playerState.track_manifest?.metadata.lossless && (
|
{playerState.track_manifest?.metadata?.lossless && (
|
||||||
<Tag
|
<Tag
|
||||||
icon={
|
icon={
|
||||||
<Icons.Lossless
|
<Icons.Lossless
|
||||||
|
@ -7,178 +7,197 @@ import { usePlayerStateContext } from "@contexts/WithPlayerContext"
|
|||||||
const maxLatencyInMs = 55
|
const maxLatencyInMs = 55
|
||||||
|
|
||||||
const LyricsVideo = React.forwardRef((props, videoRef) => {
|
const LyricsVideo = React.forwardRef((props, videoRef) => {
|
||||||
const [playerState] = usePlayerStateContext()
|
const [playerState] = usePlayerStateContext()
|
||||||
|
|
||||||
const { lyrics } = props
|
const { lyrics } = props
|
||||||
|
|
||||||
const [initialLoading, setInitialLoading] = React.useState(true)
|
const [initialLoading, setInitialLoading] = React.useState(true)
|
||||||
const [syncInterval, setSyncInterval] = React.useState(null)
|
const [syncInterval, setSyncInterval] = React.useState(null)
|
||||||
const [syncingVideo, setSyncingVideo] = React.useState(false)
|
const [syncingVideo, setSyncingVideo] = React.useState(false)
|
||||||
const [currentVideoLatency, setCurrentVideoLatency] = React.useState(0)
|
const [currentVideoLatency, setCurrentVideoLatency] = React.useState(0)
|
||||||
const hls = React.useRef(new HLS())
|
const hls = React.useRef(new HLS())
|
||||||
|
|
||||||
async function seekVideoToSyncAudio() {
|
async function seekVideoToSyncAudio() {
|
||||||
if (!lyrics) {
|
if (!lyrics) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!lyrics.video_source || typeof lyrics.sync_audio_at_ms === "undefined") {
|
if (
|
||||||
return null
|
!lyrics.video_source ||
|
||||||
}
|
typeof lyrics.sync_audio_at_ms === "undefined"
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
const currentTrackTime = app.cores.player.controls.seek()
|
const currentTrackTime = app.cores.player.controls.seek()
|
||||||
|
|
||||||
setSyncingVideo(true)
|
setSyncingVideo(true)
|
||||||
|
|
||||||
let newTime = currentTrackTime + (lyrics.sync_audio_at_ms / 1000) + app.cores.player.gradualFadeMs / 1000
|
let newTime =
|
||||||
|
currentTrackTime + lyrics.sync_audio_at_ms / 1000 + 150 / 1000
|
||||||
|
|
||||||
// dec some ms to ensure the video seeks correctly
|
// dec some ms to ensure the video seeks correctly
|
||||||
newTime -= 5 / 1000
|
newTime -= 5 / 1000
|
||||||
|
|
||||||
videoRef.current.currentTime = newTime
|
videoRef.current.currentTime = newTime
|
||||||
}
|
}
|
||||||
|
|
||||||
async function syncPlayback() {
|
async function syncPlayback() {
|
||||||
// if something is wrong, stop syncing
|
// if something is wrong, stop syncing
|
||||||
if (videoRef.current === null || !lyrics || !lyrics.video_source || typeof lyrics.sync_audio_at_ms === "undefined" || playerState.playback_status !== "playing") {
|
if (
|
||||||
return stopSyncInterval()
|
videoRef.current === null ||
|
||||||
}
|
!lyrics ||
|
||||||
|
!lyrics.video_source ||
|
||||||
|
typeof lyrics.sync_audio_at_ms === "undefined" ||
|
||||||
|
playerState.playback_status !== "playing"
|
||||||
|
) {
|
||||||
|
return stopSyncInterval()
|
||||||
|
}
|
||||||
|
|
||||||
const currentTrackTime = app.cores.player.controls.seek()
|
const currentTrackTime = app.cores.player.controls.seek()
|
||||||
const currentVideoTime = videoRef.current.currentTime - (lyrics.sync_audio_at_ms / 1000)
|
const currentVideoTime =
|
||||||
|
videoRef.current.currentTime - lyrics.sync_audio_at_ms / 1000
|
||||||
|
|
||||||
//console.log(`Current track time: ${currentTrackTime}, current video time: ${currentVideoTime}`)
|
//console.log(`Current track time: ${currentTrackTime}, current video time: ${currentVideoTime}`)
|
||||||
|
|
||||||
const maxOffset = maxLatencyInMs / 1000
|
const maxOffset = maxLatencyInMs / 1000
|
||||||
const currentVideoTimeDiff = Math.abs(currentVideoTime - currentTrackTime)
|
const currentVideoTimeDiff = Math.abs(
|
||||||
|
currentVideoTime - currentTrackTime,
|
||||||
|
)
|
||||||
|
|
||||||
setCurrentVideoLatency(currentVideoTimeDiff)
|
setCurrentVideoLatency(currentVideoTimeDiff)
|
||||||
|
|
||||||
if (syncingVideo === true) {
|
if (syncingVideo === true) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentVideoTimeDiff > maxOffset) {
|
if (currentVideoTimeDiff > maxOffset) {
|
||||||
seekVideoToSyncAudio()
|
seekVideoToSyncAudio()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function startSyncInterval() {
|
function startSyncInterval() {
|
||||||
setSyncInterval(setInterval(syncPlayback, 300))
|
setSyncInterval(setInterval(syncPlayback, 300))
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopSyncInterval() {
|
function stopSyncInterval() {
|
||||||
setSyncingVideo(false)
|
setSyncingVideo(false)
|
||||||
setSyncInterval(null)
|
setSyncInterval(null)
|
||||||
clearInterval(syncInterval)
|
clearInterval(syncInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
//* handle when player is loading
|
//* handle when player is loading
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (lyrics?.video_source && playerState.loading === true && playerState.playback_status === "playing") {
|
if (
|
||||||
videoRef.current.pause()
|
lyrics?.video_source &&
|
||||||
}
|
playerState.loading === true &&
|
||||||
|
playerState.playback_status === "playing"
|
||||||
|
) {
|
||||||
|
videoRef.current.pause()
|
||||||
|
}
|
||||||
|
|
||||||
if (lyrics?.video_source && playerState.loading === false && playerState.playback_status === "playing") {
|
if (
|
||||||
videoRef.current.play()
|
lyrics?.video_source &&
|
||||||
}
|
playerState.loading === false &&
|
||||||
}, [playerState.loading])
|
playerState.playback_status === "playing"
|
||||||
|
) {
|
||||||
|
videoRef.current.play()
|
||||||
|
}
|
||||||
|
}, [playerState.loading])
|
||||||
|
|
||||||
//* Handle when playback status change
|
//* Handle when playback status change
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (initialLoading === false) {
|
if (initialLoading === false) {
|
||||||
console.log(`VIDEO:: Playback status changed to ${playerState.playback_status}`)
|
console.log(
|
||||||
|
`VIDEO:: Playback status changed to ${playerState.playback_status}`,
|
||||||
|
)
|
||||||
|
|
||||||
if (lyrics && lyrics.video_source) {
|
if (lyrics && lyrics.video_source) {
|
||||||
if (playerState.playback_status === "playing") {
|
if (playerState.playback_status === "playing") {
|
||||||
videoRef.current.play()
|
videoRef.current.play()
|
||||||
startSyncInterval()
|
startSyncInterval()
|
||||||
} else {
|
} else {
|
||||||
videoRef.current.pause()
|
videoRef.current.pause()
|
||||||
stopSyncInterval()
|
stopSyncInterval()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [playerState.playback_status])
|
}, [playerState.playback_status])
|
||||||
|
|
||||||
//* Handle when lyrics object change
|
//* Handle when lyrics object change
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setCurrentVideoLatency(0)
|
setCurrentVideoLatency(0)
|
||||||
stopSyncInterval()
|
stopSyncInterval()
|
||||||
|
|
||||||
if (lyrics) {
|
if (lyrics) {
|
||||||
if (lyrics.video_source) {
|
if (lyrics.video_source) {
|
||||||
console.log("Loading video source >", lyrics.video_source)
|
console.log("Loading video source >", lyrics.video_source)
|
||||||
|
|
||||||
if (lyrics.video_source.endsWith(".mp4")) {
|
if (lyrics.video_source.endsWith(".mp4")) {
|
||||||
videoRef.current.src = lyrics.video_source
|
videoRef.current.src = lyrics.video_source
|
||||||
} else {
|
} else {
|
||||||
hls.current.loadSource(lyrics.video_source)
|
hls.current.loadSource(lyrics.video_source)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof lyrics.sync_audio_at_ms !== "undefined") {
|
if (typeof lyrics.sync_audio_at_ms !== "undefined") {
|
||||||
videoRef.current.loop = false
|
videoRef.current.loop = false
|
||||||
videoRef.current.currentTime = lyrics.sync_audio_at_ms / 1000
|
videoRef.current.currentTime =
|
||||||
|
lyrics.sync_audio_at_ms / 1000
|
||||||
|
|
||||||
startSyncInterval()
|
startSyncInterval()
|
||||||
} else {
|
} else {
|
||||||
videoRef.current.loop = true
|
videoRef.current.loop = true
|
||||||
videoRef.current.currentTime = 0
|
videoRef.current.currentTime = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playerState.playback_status === "playing") {
|
if (playerState.playback_status === "playing") {
|
||||||
videoRef.current.play()
|
videoRef.current.play()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setInitialLoading(false)
|
setInitialLoading(false)
|
||||||
}, [lyrics])
|
}, [lyrics])
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
videoRef.current.addEventListener("seeked", (event) => {
|
videoRef.current.addEventListener("seeked", (event) => {
|
||||||
setSyncingVideo(false)
|
setSyncingVideo(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
hls.current.attachMedia(videoRef.current)
|
hls.current.attachMedia(videoRef.current)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
stopSyncInterval()
|
stopSyncInterval()
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return <>
|
return (
|
||||||
{
|
<>
|
||||||
props.lyrics?.sync_audio_at && <div
|
{props.lyrics?.sync_audio_at && (
|
||||||
className={classnames(
|
<div className={classnames("videoDebugOverlay")}>
|
||||||
"videoDebugOverlay",
|
<div>
|
||||||
)}
|
<p>Maximun latency</p>
|
||||||
>
|
<p>{maxLatencyInMs}ms</p>
|
||||||
<div>
|
</div>
|
||||||
<p>Maximun latency</p>
|
<div>
|
||||||
<p>{maxLatencyInMs}ms</p>
|
<p>Video Latency</p>
|
||||||
</div>
|
<p>{(currentVideoLatency * 1000).toFixed(2)}ms</p>
|
||||||
<div>
|
</div>
|
||||||
<p>Video Latency</p>
|
{syncingVideo ? <p>Syncing video...</p> : null}
|
||||||
<p>{(currentVideoLatency * 1000).toFixed(2)}ms</p>
|
</div>
|
||||||
</div>
|
)}
|
||||||
{syncingVideo ? <p>Syncing video...</p> : null}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<video
|
<video
|
||||||
className={classnames(
|
className={classnames("lyrics-video", {
|
||||||
"lyrics-video",
|
["hidden"]: !lyrics || !lyrics?.video_source,
|
||||||
{
|
})}
|
||||||
["hidden"]: !lyrics || !lyrics?.video_source
|
ref={videoRef}
|
||||||
}
|
controls={false}
|
||||||
)}
|
muted
|
||||||
ref={videoRef}
|
preload="auto"
|
||||||
controls={false}
|
/>
|
||||||
muted
|
</>
|
||||||
preload="auto"
|
)
|
||||||
/>
|
|
||||||
</>
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export default LyricsVideo
|
export default LyricsVideo
|
||||||
|
@ -89,7 +89,8 @@ const EnhancedLyricsPage = () => {
|
|||||||
|
|
||||||
// Track manifest comparison
|
// Track manifest comparison
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newManifest = playerState.track_manifest?.toSeriableObject()
|
const newManifest = playerState.track_manifest
|
||||||
|
|
||||||
if (JSON.stringify(newManifest) !== JSON.stringify(trackManifest)) {
|
if (JSON.stringify(newManifest) !== JSON.stringify(trackManifest)) {
|
||||||
setTrackManifest(newManifest)
|
setTrackManifest(newManifest)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user