From d05ec56a64bba63dbb9bf0e8b9657736ee40adf1 Mon Sep 17 00:00:00 2001 From: Pinapelz Date: Sun, 10 Nov 2024 20:07:42 -0800 Subject: add line animations in as an option --- src/app/about/page.tsx | 155 ++++-- src/app/components/LRCPlayer.tsx | 101 ++-- src/app/page.tsx | 1122 ++++++++++++++++++++------------------ 3 files changed, 781 insertions(+), 597 deletions(-) diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx index dd5f35b..881e5a2 100644 --- a/src/app/about/page.tsx +++ b/src/app/about/page.tsx @@ -1,6 +1,6 @@ "use client"; -import React from 'react'; -import styled, { createGlobalStyle } from 'styled-components'; +import React from "react"; +import styled, { createGlobalStyle } from "styled-components"; const GlobalStyle = createGlobalStyle` body { @@ -81,69 +81,146 @@ const AboutPage: React.FC = () => { About What is this player? - This player is capable of simultaneously playing back a lyric file (LRC), a main video/audio file, a SRV3 YouTube Timed Text, and a backing audio file. -
The idea is that this helps with not only karaoke but also checking how well a LRC or SRV3 file syncs with the main video/audio. + This player is capable of simultaneously playing back a + lyric file (LRC), a main video/audio file, a SRV3 YouTube + Timed Text, and a backing audio file. +
+ The idea is that this helps with not only karaoke but also + checking how well a LRC or SRV3 file syncs with the main + video/audio.
How to use this player? - You'll need to prepare a few files for the media you want to play back first. -
Theoretically you can mix/match any of the files below since the main video/audio is all that's mandatory for playback. -
In this guide I'll assume that you're after a karaoke experience, and want the works. -

To add any files to the player simply drag it onto the right part of the player page. -
EVERYTHING IS RAN LOCALLY, NO FILES ARE EVER UPLOADED TO ANY SERVERS. + You'll need to prepare a few files for the media you + want to play back first. +
+ Theoretically you can mix/match any of the files below since + the main video/audio is all that's mandatory for + playback. +
+ In this guide I'll assume that you're after a + karaoke experience, and want the works. +
+
+ To add any files to the player simply drag it onto the right + part of the player page. +
+ EVERYTHING IS RAN LOCALLY, NO FILES ARE EVER UPLOADED TO ANY + SERVERS.
1. Main video/audio file - Note: I've renamed the second button seen in the demos from Video to Media to avoid confusion since it can support audio/video files + Note: I've renamed the second button seen in the demos + from Video to Media to avoid confusion since it can support + audio/video files - This is the file that you want to play back. It can be a video or an audio file. -
Supported formats: mp4, webm, ogg, mp3, wav, flac, and more. -
If you choose to use an audio file here, the right part of the player will not show a video preview. -

A good way would be to download some video from YouTube. You may need to make adjustments to the offset later depending on how well the LRC file syncs with the video. -
How you do that will be up to you, but I recommend using yt-dlp to download the video. + This is the file that you want to play back. It can be a + video or an audio file. +
+ Supported formats: mp4, webm, ogg, mp3, wav, flac, and more. +
+ If you choose to use an audio file here, the right part of + the player will not show a video preview. +

A good way would be to download some video from + YouTube. You may need to make adjustments to the offset + later depending on how well the LRC file syncs with the + video. +
How you do that will be up to you, but I recommend + using + yt-dlp + {" "} + to download the video.
2. Lyric File (LRC) - This is the file that contains the lyrics of the song you want to sing. -
An example LRC file is shown below... + This is the file that contains the lyrics of the song you + want to sing. +
+ An example LRC file is shown below...
- ') }} /> + "), + }} + /> - The player will highlight the current line of the lyrics as the main media progresses. + The player will highlight the current line of the lyrics as + the main media progresses.
- If you need a LRC file, a good way is to rip it from Spotify using Syrics. + If you need a LRC file, a good way is to rip it from Spotify + using{" "} + + Syrics + + .
-
-
+
+
- At this point you should already be able to play back the main media and have the lyrics highlighted as the media progresses. -
Depending on how well the LRC file syncs with the main media, you may need to adjust the main offset labelled as "Offset (±ms)" + At this point you should already be able to play back the + main media and have the lyrics highlighted as the media + progresses. +
+ Depending on how well the LRC file syncs with the main + media, you may need to adjust the main offset labelled as + "Offset (±ms)"
3. Instrumental/Vocals (Audio 2) - If you only wanted one or the other, simply add that as the main media then you're done -
There are a ton of tools online to remove this but you'll want to make sure you get the instrumental track in an audio format (mp3, wav, etc.) -
Then hover over the right side of the player, click the "Audio #2" button, and find your instrumental track. -

(TIP!) I suggest going back and setting the main media in step 1 (Media button) in the previous step to a vocal only video/audio. -
This will make it significantly easier to offset the 2 tracks. You can always mux a video file on top of that if you want visuals too! -
Ultimately it doesn't matter which "slot" the instrumental or "vocals" go into, it's just better to have them separated! -

Now adjust the offset using the numerical inputs, the "Sync" button will adjust Audio 2 relative to the main media. -
I suggest positioning the playhead at 00:00 and then adding the secondary audio; this will make adjustments much easier. -

- Now you should be able to control the balance between both of these files (which one is louder) by using the slider! + If you only wanted one or the other, simply add that as the + main media then you're done +
+ There are a ton of tools online to remove this but + you'll want to make sure you get the instrumental track + in an audio format (mp3, wav, etc.) +
+ Then hover over the right side of the player, click the + "Audio #2" button, and find your instrumental + track. +
+
+ (TIP!) I suggest going back and setting the main media in + step 1 (Media button) in the previous step to a vocal only + video/audio. +
This will make it significantly easier to offset the + 2 tracks. You can always mux a video file on top of that if + you want visuals too! +
+ Ultimately it doesn't matter which "slot" the + instrumental or "vocals" go into, it's just + better to have them separated! +
+
+ Now adjust the offset using the numerical inputs, the + "Sync" button will adjust Audio 2 relative to the + main media. +
I suggest positioning the playhead at 00:00 and then + adding the secondary audio; this will make adjustments much + easier. +
+
+ Now you should be able to control the balance between both + of these files (which one is louder) by using the slider!
4. YouTube Timed Text - If the YouTube video you downloaded has subtitles (sometimes they look really cool and fancy), you can download that using yt-dlp - for use in the player as well. -

Unfortunately there is no way to adjust the offset for this, it'll play according to the main media. + If the YouTube video you downloaded has subtitles (sometimes + they look really cool and fancy), you can download that + using yt-dlp for use in the player as well. +
+
+ Unfortunately there is no way to adjust the offset for this, + it'll play according to the main media.
Back to player diff --git a/src/app/components/LRCPlayer.tsx b/src/app/components/LRCPlayer.tsx index e691957..e6d9551 100644 --- a/src/app/components/LRCPlayer.tsx +++ b/src/app/components/LRCPlayer.tsx @@ -1,8 +1,14 @@ -import React, { CSSProperties, useCallback } from 'react'; -import styled, { css } from 'styled-components'; -import { Lrc, LrcLine } from 'react-lrc'; +import React, { CSSProperties, useCallback } from "react"; +import styled, { css } from "styled-components"; +import { Lrc, LrcLine } from "react-lrc"; -const Line = styled.div<{ $active: boolean; $next: boolean }>` +interface LineProps { + $active: boolean; + $next: boolean; + $animate: boolean; +} + +const Line = styled.div` min-height: 10px; padding: 14px 30px; @@ -10,48 +16,71 @@ const Line = styled.div<{ $active: boolean; $next: boolean }>` font-family: "Roboto", sans-serif; font-weight: 500; text-align: center; - color: rgb(72,72,72); + color: rgb(72, 72, 72); - background: linear-gradient(to right, rgba(0,0,0,0) 50%, rgb(200, 190, 190) 50%); + background: linear-gradient( + to right, + rgba(0, 0, 0, 0) 50%, + rgb(200, 190, 190) 50% + ); background-size: 200% 100%; background-position: right bottom; - ${({ $active }) => $active && css` - color: black; - font-weight: 700; - background-position: left bottom; - color: rgb(50, 50, 50); - `} + ${({ $animate }) => + $animate && + css` + transition: + color 0.3s ease, + background-position 0.5s ease; + `} + + ${({ $active }) => + $active && + css` + color: rgb(50, 50, 50); + font-weight: 700; + background-position: left bottom; + `} `; + const lrcStyle: CSSProperties = { - flex: 1, - minHeight: 0, - overflow: 'hidden !important' + flex: 1, + minHeight: 0, + overflow: "hidden !important", }; interface LrcPlayerProps { - currentMillisecond: number; - lrc: string; + currentMillisecond: number; + lrc: string; + animate: boolean; } -const LrcPlayer: React.FC = ({ currentMillisecond, lrc }) => { - const lineRenderer = useCallback( - ({ active, line: { content } }: { active: boolean; line: LrcLine }) => { - const next = active && content === ''; - return {content}; - }, - [] - ); - - return ( - - ); +const LrcPlayer: React.FC = ({ + currentMillisecond, + lrc, + animate, +}) => { + const lineRenderer = useCallback( + ({ active, line: { content } }: { active: boolean; line: LrcLine }) => { + const next = active && content === ""; + return ( + + {content} + + ); + }, + [animate], + ); + + return ( + + ); }; -export default LrcPlayer; \ No newline at end of file +export default LrcPlayer; diff --git a/src/app/page.tsx b/src/app/page.tsx index 3b23eca..3e8dfb6 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -10,559 +10,637 @@ import { Button } from "react-bootstrap"; // Srtyled components const Root = styled.div` - position: absolute; - width: 100%; - height: 100%; - top: 0; - left: 0; - display: flex; - flex-direction: column; - align-items: center; - background-color: #f5f5f5; + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + display: flex; + flex-direction: column; + align-items: center; + background-color: #f5f5f5; `; const FileInputContainer = styled.div` - margin-bottom: 20px; - display: flex; - justify-content: center; - gap: 20px; - padding: 10px; - border-radius: 5px; - background-color: #ffffff; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + margin-bottom: 20px; + display: flex; + justify-content: center; + gap: 20px; + padding: 10px; + border-radius: 5px; + background-color: #ffffff; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); `; const FileInput = styled.input` - padding: 10px 15px; - border-radius: 5px; - border: 1px solid #ddd; - justify-content: center; - cursor: pointer; - display: none; - font-family: Arial; - &:hover, - &:focus { - background-color: #eaeaea; - outline: none; - } + padding: 10px 15px; + border-radius: 5px; + border: 1px solid #ddd; + justify-content: center; + cursor: pointer; + display: none; + font-family: Arial; + &:hover, + &:focus { + background-color: #eaeaea; + outline: none; + } `; const FileInputLabel = styled.label` - padding: 10px 15px; - border-radius: 5px; - border: 1px solid #ddd; - cursor: pointer; - &:hover, - &:focus { - background-color: #eaeaea; - outline: none; - } + padding: 10px 15px; + border-radius: 5px; + border: 1px solid #ddd; + cursor: pointer; + &:hover, + &:focus { + background-color: #eaeaea; + outline: none; + } `; const ControlBarButton = styled.button` - padding: 10px 15px; - border-radius: 5px; - border: 1px solid #ddd; - align-items: center; - cursor: pointer; - &:hover, - &:focus { - background-color: #eaeaea; - outline: none; - } + padding: 10px 15px; + border-radius: 5px; + border: 1px solid #ddd; + align-items: center; + cursor: pointer; + &:hover, + &:focus { + background-color: #eaeaea; + outline: none; + } `; const StyledLink = styled.a` - font-size: 20px; - font-family: Arial; - text-decoration: none; - text-color: black; - &:hover { - text-decoration: underline; - } + font-size: 20px; + font-family: Arial; + text-decoration: none; + text-color: black; + &:hover { + text-decoration: underline; + } `; function KaraokePage() { - const [currentMillisecond, setCurrentMillisecond] = useState(0); - const [lrcContent, setLrcContent] = useState(""); - const [videoUrl, setVideoUrl] = useState(""); - const [supplementAudioUrl, setSupplementAudioUrl] = useState(""); - const [isPlaying, setIsPlaying] = useState(false); - const [showVolume, setShowVolume] = useState(false); - const [scrubValue, setScrubValue] = useState(0); - const [showFileInputs, setShowFileInputs] = useState(true); - const videoRef = useRef(null); - const supplementAudioRef = useRef(null); - const [captionsText, setCaptionsText] = useState(""); - const [offset, setOffset] = useState(0); - const [dragOver, setDragOver] = useState(false); - const [statusText, setStatusText] = useState("No video selected"); - const [balance, setBalance] = useState(0); - const [supplementAudioOffset, setSupplementAudioOffset] = useState(0); - - // Functions for handling file input changes - const handleLrcFileChange = (event: React.ChangeEvent) => { - const file = event.target.files?.[0]; - if (file) { - const reader = new FileReader(); - reader.onload = (e) => { - setLrcContent(e.target?.result as string); - if (videoUrl) setShowFileInputs(false); - }; - reader.readAsText(file); - toast.success("LRC file loaded successfully", { autoClose: 2000 }); - } - }; - - const handleVideoFileChange = ( - event: React.ChangeEvent - ) => { - const file = event.target.files?.[0]; - if (file) { - const url = URL.createObjectURL(file); - setVideoUrl(url); - setCurrentMillisecond(0); - setScrubValue(0); - setIsPlaying(false); - toast.success("Video file loaded successfully", { autoClose: 2000 }); - } - }; - - const handleSrvFileChange = (event: React.ChangeEvent) => { - const file = event.target.files?.[0]; - if (file) { - const reader = new FileReader(); - reader.onload = (e) => { - setCaptionsText(e.target?.result as string); - }; - reader.readAsText(file); - toast.success("SRV file loaded successfully", { autoClose: 2000 }); - } - }; - - const handleSupplementAudioFileChange = ( - event: React.ChangeEvent - ) => { - const file = event.target.files?.[0]; - const video = videoRef.current; - if (file) { - const url = URL.createObjectURL(file); - setSupplementAudioUrl(url); - setCurrentMillisecond(0); - setScrubValue(0); - setIsPlaying(false); - if (video) - video.pause(); - toast.success("Supplemental Audio file loaded successfully", { - autoClose: 2000, - }); - } - }; - - const handleOnClickDemoButton = (event: React.MouseEvent) => { - event.preventDefault(); - setOffset(-1550); - fetch("https://utfs.io/f/e2e18ea7-9841-437b-9ca3-5723355bd41a-rlck46.lrc") - .then(function (response) { - response.text().then(function (responseString) { - setLrcContent(responseString) + const [currentMillisecond, setCurrentMillisecond] = useState(0); + const [lrcContent, setLrcContent] = useState(""); + const [videoUrl, setVideoUrl] = useState(""); + const [supplementAudioUrl, setSupplementAudioUrl] = useState(""); + const [isPlaying, setIsPlaying] = useState(false); + const [showVolume, setShowVolume] = useState(false); + const [scrubValue, setScrubValue] = useState(0); + const [showFileInputs, setShowFileInputs] = useState(true); + const videoRef = useRef(null); + const supplementAudioRef = useRef(null); + const [captionsText, setCaptionsText] = useState(""); + const [offset, setOffset] = useState(0); + const [dragOver, setDragOver] = useState(false); + const [statusText, setStatusText] = useState("No video selected"); + const [balance, setBalance] = useState(0); + const [animate, setAnimate] = useState(true); + const [supplementAudioOffset, setSupplementAudioOffset] = + useState(0); + + // Functions for handling file input changes + const handleLrcFileChange = ( + event: React.ChangeEvent, + ) => { + const file = event.target.files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + setLrcContent(e.target?.result as string); + if (videoUrl) setShowFileInputs(false); + }; + reader.readAsText(file); + toast.success("LRC file loaded successfully", { autoClose: 2000 }); + } + }; + + const handleVideoFileChange = ( + event: React.ChangeEvent, + ) => { + const file = event.target.files?.[0]; + if (file) { + const url = URL.createObjectURL(file); + setVideoUrl(url); + setCurrentMillisecond(0); + setScrubValue(0); + setIsPlaying(false); + toast.success("Video file loaded successfully", { + autoClose: 2000, + }); + } + }; + + const handleSrvFileChange = ( + event: React.ChangeEvent, + ) => { + const file = event.target.files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + setCaptionsText(e.target?.result as string); + }; + reader.readAsText(file); + toast.success("SRV file loaded successfully", { autoClose: 2000 }); + } + }; + + const handleSupplementAudioFileChange = ( + event: React.ChangeEvent, + ) => { + const file = event.target.files?.[0]; + const video = videoRef.current; + if (file) { + const url = URL.createObjectURL(file); + setSupplementAudioUrl(url); + setCurrentMillisecond(0); + setScrubValue(0); + setIsPlaying(false); + if (video) video.pause(); + toast.success("Supplemental Audio file loaded successfully", { + autoClose: 2000, + }); + } + }; + + const handleOnClickDemoButton = ( + event: React.MouseEvent, + ) => { + event.preventDefault(); + setOffset(-1550); + fetch( + "https://utfs.io/f/e2e18ea7-9841-437b-9ca3-5723355bd41a-rlck46.lrc", + ).then(function (response) { + response.text().then(function (responseString) { + setLrcContent(responseString); + }); }); - }) - setVideoUrl("https://utfs.io/f/84f5dfa6-821d-407f-a16d-a685b09c11d9-7xx2h4.webm") - toast.success("Loading Demo: Mr.Raindrop - Amplified") - toast.success("Applied offset of -1550ms") - - } - - // Side effects for keyboard shortcuts - useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - if (e.code === "Space") { - handlePlayPause(); - } - if (e.code === "ArrowRight") { - if (document.activeElement?.tagName === "INPUT") return; + setVideoUrl( + "https://utfs.io/f/84f5dfa6-821d-407f-a16d-a685b09c11d9-7xx2h4.webm", + ); + toast.success("Loading Demo: Mr.Raindrop - Amplified"); + toast.success("Applied offset of -1550ms"); + }; + + // Side effects for keyboard shortcuts + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.code === "Space") { + handlePlayPause(); + } + if (e.code === "ArrowRight") { + if (document.activeElement?.tagName === "INPUT") return; + const video = videoRef.current; + if (!video) return; + video.currentTime += 5; + } + if (e.code === "ArrowLeft") { + if (document.activeElement?.tagName === "INPUT") return; + const video = videoRef.current; + if (!video) return; + video.currentTime -= 5; + } + }; + document.addEventListener("keydown", handleKeyDown); + return () => { + document.removeEventListener("keydown", handleKeyDown); + }; + }); + + // Side effects for the video itself + useEffect(() => { const video = videoRef.current; if (!video) return; - video.currentTime += 5; - } - if (e.code === "ArrowLeft") { - if (document.activeElement?.tagName === "INPUT") return; + const syncLrcWithVideo = () => { + setCurrentMillisecond(video.currentTime * 1000 + offset); // updates lrc position + setScrubValue((video.currentTime / video.duration) * 100); // update playhead position + }; + video.addEventListener("timeupdate", syncLrcWithVideo); + + return () => { + video.removeEventListener("timeupdate", syncLrcWithVideo); + }; + }); + + useEffect(() => { + const video = videoRef.current; + const audio = supplementAudioRef.current; + if (!video || !audio) return; + + if (balance < 0) { + video.volume = 1 + balance; + } else { + video.volume = 1; + audio.volume = 1 - balance; + } + }, [balance]); + + useEffect(() => { + const video = videoRef.current; + const audio = supplementAudioRef.current; + if (!video || !audio) return; + if (supplementAudioOffset === null || supplementAudioOffset == null) + return; + audio.currentTime = video.currentTime + supplementAudioOffset / 1000; + }, [supplementAudioOffset]); + + // General video control functionality + + const handleVolumeToggle = () => { + setShowVolume(!showVolume); + }; + + const handlePlayPause = () => { const video = videoRef.current; if (!video) return; - video.currentTime -= 5; - } + + if (video.paused) { + video.play(); + if (supplementAudioUrl) supplementAudioRef.current?.play(); + setIsPlaying(true); + } else { + video.pause(); + if (supplementAudioUrl) supplementAudioRef.current?.pause(); + setIsPlaying(false); + } }; - document.addEventListener("keydown", handleKeyDown); - return () => { - document.removeEventListener("keydown", handleKeyDown); + + // Status text styling depending on whats loaded. Not all visible + useEffect(() => { + if (videoUrl && lrcContent) { + setStatusText("Ready to play!"); + } else if (videoUrl) { + setStatusText("No lyrics file selected"); + } else if (lrcContent) { + setStatusText("No video file selected"); + } else { + setStatusText("No video or lyrics file selected"); + } + }, [videoUrl, lrcContent]); + + // Video Control Bar functionality + const handleScrub = (event: React.ChangeEvent) => { + const time = + (parseFloat(event.target.value) / 100) * videoRef.current!.duration; + videoRef.current!.currentTime = time; + if (supplementAudioOffset === null || supplementAudioOffset == null) { + supplementAudioRef.current!.currentTime = time; + } else { + supplementAudioRef.current!.currentTime = + time + supplementAudioOffset / 1000; + } + setScrubValue(parseFloat(event.target.value)); }; - }); - - // Side effects for the video itself - useEffect(() => { - const video = videoRef.current; - if (!video) return; - const syncLrcWithVideo = () => { - setCurrentMillisecond(video.currentTime * 1000 + offset); // updates lrc position - setScrubValue((video.currentTime / video.duration) * 100); // update playhead position + + const handleVideoEnded = () => { + setIsPlaying(false); + supplementAudioRef.current?.pause(); }; - video.addEventListener("timeupdate", syncLrcWithVideo); - return () => { - video.removeEventListener("timeupdate", syncLrcWithVideo); + const syncSupplementAudioWithVideo = () => { + const video = videoRef.current; + const audio = supplementAudioRef.current; + if (!video || !audio) return; + if (supplementAudioOffset === null || supplementAudioOffset == null) + return; + audio.currentTime = video.currentTime + supplementAudioOffset / 1000; }; - }); - - useEffect(() => { - const video = videoRef.current; - const audio = supplementAudioRef.current; - if (!video || !audio) return; - - if (balance < 0) { - video.volume = (1 + balance); - } else { - video.volume = 1; - audio.volume = (1 - balance); - } - }, [balance]); - - useEffect(() => { - const video = videoRef.current; - const audio = supplementAudioRef.current; - if (!video || !audio) return; - if (supplementAudioOffset === null || supplementAudioOffset == null) return; - audio.currentTime = video.currentTime + supplementAudioOffset / 1000; - }, [supplementAudioOffset]); - - // General video control functionality - - const handleVolumeToggle = () => { - setShowVolume(!showVolume); - }; - - const handlePlayPause = () => { - const video = videoRef.current; - if (!video) return; - - if (video.paused) { - video.play(); - if (supplementAudioUrl) supplementAudioRef.current?.play(); - setIsPlaying(true); - } else { - video.pause(); - if (supplementAudioUrl) supplementAudioRef.current?.pause(); - setIsPlaying(false); - } - }; - - // Status text styling depending on whats loaded. Not all visible - useEffect(() => { - if (videoUrl && lrcContent) { - setStatusText("Ready to play!"); - } else if (videoUrl) { - setStatusText("No lyrics file selected"); - } else if (lrcContent) { - setStatusText("No video file selected"); - } else { - setStatusText("No video or lyrics file selected"); - } - }, [videoUrl, lrcContent]); - - // Video Control Bar functionality - const handleScrub = (event: React.ChangeEvent) => { - const time = - (parseFloat(event.target.value) / 100) * videoRef.current!.duration; - videoRef.current!.currentTime = time; - if (supplementAudioOffset === null || supplementAudioOffset == null) { - supplementAudioRef.current!.currentTime = time; - } - else { - supplementAudioRef.current!.currentTime = time + supplementAudioOffset / 1000; - } - setScrubValue(parseFloat(event.target.value)); - }; - - - const handleVideoEnded = () => { - setIsPlaying(false); - supplementAudioRef.current?.pause(); - }; - - const syncSupplementAudioWithVideo = () => { - const video = videoRef.current; - const audio = supplementAudioRef.current; - if (!video || !audio) return; - if (supplementAudioOffset === null || supplementAudioOffset == null) return; - audio.currentTime = video.currentTime + supplementAudioOffset / 1000; - } - - // Handling drag and drop files - const handleDragOver = (event: React.DragEvent) => { - setDragOver(true); - event.preventDefault(); - }; - - const handleDragEnter = (event: React.DragEvent) => { - setDragOver(true); - event.preventDefault(); - }; - - const handleDragLeave = (event: React.DragEvent) => { - setDragOver(false); - event.preventDefault(); - }; - - const handleDrop = (event: React.DragEvent) => { - event.preventDefault(); - setDragOver(false); - const file = event.dataTransfer.files?.[0]; - if (file.name.endsWith(".lrc")) { - const reader = new FileReader(); - reader.onload = (e) => { - setLrcContent(e.target?.result as string); - if (videoUrl) setShowFileInputs(false); - }; - reader.readAsText(file); - toast.success("LRC file loaded successfully", { autoClose: 2000 }); - } else if (file.name.endsWith(".srv3")) { - const reader = new FileReader(); - reader.onload = (e) => { - setCaptionsText(e.target?.result as string); - }; - reader.readAsText(file); - toast.success("SRV file loaded successfully", { autoClose: 2000 }); - } else if (file.type.startsWith("video") || file.type.startsWith("audio")) { - const url = URL.createObjectURL(file); - setVideoUrl(url); - setCurrentMillisecond(0); - setScrubValue(0); - setIsPlaying(false); - toast.success("Video/Audio file loaded successfully", { - autoClose: 2000, - }); - } else { - toast.error("Unsupported file type", { autoClose: 2000 }); - } - }; - - return ( - - - {/*LRC viewer*/} -
- - - {/* Ternary operation for if videoUrl has been set */} -
setShowFileInputs(true)} - onMouseLeave={() => setShowFileInputs(false)} - onDragOver={handleDragOver} - onDragEnter={handleDragEnter} - onDragLeave={handleDragLeave} - onDrop={handleDrop} - > - {videoUrl ? ( - <> -
-
-
- ); + + ); } export default KaraokePage; -- cgit v1.2.3