diff options
| author | Pinapelz <yukais@pinapelz.com> | 2023-11-16 21:49:11 -0800 |
|---|---|---|
| committer | Pinapelz <yukais@pinapelz.com> | 2023-11-16 21:49:11 -0800 |
| commit | 1caebf3d8a8ba9deb7fb77fcc17b02c46652f9f1 (patch) | |
| tree | 4e58da6feea914c074334f883182724c98e1691c /src | |
| parent | 63950ac21a046c582cbaa9aad86304a136e27310 (diff) | |
feat: add supplementary audio upload option
Diffstat (limited to 'src')
| -rw-r--r-- | src/app/page.tsx | 240 |
1 files changed, 190 insertions, 50 deletions
diff --git a/src/app/page.tsx b/src/app/page.tsx index 4be028c..457610d 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,4 +1,4 @@ -"use client" +"use client"; import React, { useEffect, useRef, useState } from "react"; import styled from "styled-components"; import KaraokePlayer from "./components/KaraokePlayer"; @@ -35,6 +35,7 @@ const FileInput = styled.input` padding: 10px 15px; border-radius: 5px; border: 1px solid #ddd; + justify-content: center; cursor: pointer; display: none; font-family: Arial; @@ -57,29 +58,46 @@ const FileInputLabel = styled.label` } `; +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; + } +`; + 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 [isPlaying, setIsPlaying] = useState(false); - const [showVolume, setShowVolume] = useState(false); - const [scrubValue, setScrubValue] = useState(0); - const [showFileInputs, setShowFileInputs] = useState(true); + const [lrcContent, setLrcContent] = useState<string>(""); + const [videoUrl, setVideoUrl] = useState<string>(""); + const [supplementAudioUrl, setSupplementAudioUrl] = useState<string>(""); + const [isPlaying, setIsPlaying] = useState<boolean>(false); + const [showVolume, setShowVolume] = useState<boolean>(false); + const [scrubValue, setScrubValue] = useState<number>(0); + const [showFileInputs, setShowFileInputs] = useState<boolean>(true); const videoRef = useRef<HTMLVideoElement>(null); - const [captionsText, setCaptionsText] = useState(""); - const [offset, setOffset] = useState("0"); - const [dragOver, setDragOver] = useState(false); - const [statusText, setStatusText] = useState("No video selected"); + const supplementAudioRef = useRef<HTMLAudioElement>(null); + const [captionsText, setCaptionsText] = useState<string>(""); + const [offset, setOffset] = useState<string>("0"); + const [dragOver, setDragOver] = useState<boolean>(false); + const [statusText, setStatusText] = useState<string>("No video selected"); + const [balance, setBalance] = useState<number>(0); + const [supplementAudioOffset, setSupplementAudioOffset] = useState<string>("0"); // Functions for handling file input changes const handleLrcFileChange = (event: React.ChangeEvent<HTMLInputElement>) => { @@ -118,7 +136,26 @@ function KaraokePage() { }; reader.readAsText(file); toast.success("SRV file loaded successfully", { autoClose: 2000 }); - } + } + }; + + const handleSupplementAudioFileChange = ( + event: React.ChangeEvent<HTMLInputElement> + ) => { + 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, + }); + } }; // Side effects for keyboard shortcuts @@ -161,6 +198,26 @@ function KaraokePage() { }; }); + 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 === "" || supplementAudioOffset == null) return; + audio.currentTime = video.currentTime + parseInt(supplementAudioOffset)/1000; + },[supplementAudioOffset]); // General video control functionality @@ -174,13 +231,16 @@ function KaraokePage() { 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!"); @@ -193,19 +253,47 @@ function KaraokePage() { } }, [videoUrl, lrcContent]); + // Video Control Bar functionality const handleScrub = (event: React.ChangeEvent<HTMLInputElement>) => { const time = (parseFloat(event.target.value) / 100) * videoRef.current!.duration; videoRef.current!.currentTime = time; + if (supplementAudioOffset === "" || supplementAudioOffset == null){ + supplementAudioRef.current!.currentTime = time; + } + else { + supplementAudioRef.current!.currentTime = time + parseInt(supplementAudioOffset)/1000; + } setScrubValue(parseFloat(event.target.value)); }; const handleVolumeChange = (event: React.ChangeEvent<HTMLInputElement>) => { + const volume = Number(event.target.value) / 100; const video = videoRef.current; - if (!video) return; - video.volume = Number(event.target.value) / 100; + const audio = supplementAudioRef.current; + if (!video || !audio) return; + + if (balance < 0) { + video.volume = volume * (1 + balance); + audio.volume = volume; + } else { + video.volume = volume; + audio.volume = volume * (1 - balance); + } + }; + + const handleVideoEnded = () => { + setIsPlaying(false); + supplementAudioRef.current?.pause(); }; + const syncSupplementAudioWithVideo = () => { + const video = videoRef.current; + const audio = supplementAudioRef.current; + if (!video || !audio) return; + if (supplementAudioOffset === "" || supplementAudioOffset == null) return; + audio.currentTime = video.currentTime + parseInt(supplementAudioOffset)/1000; + } // Handling drag and drop files const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => { @@ -227,7 +315,7 @@ function KaraokePage() { event.preventDefault(); setDragOver(false); const file = event.dataTransfer.files?.[0]; - if(file.name.endsWith(".lrc")) { + if (file.name.endsWith(".lrc")) { const reader = new FileReader(); reader.onload = (e) => { setLrcContent(e.target?.result as string); @@ -235,44 +323,44 @@ function KaraokePage() { }; reader.readAsText(file); toast.success("LRC file loaded successfully", { autoClose: 2000 }); - } - else if(file.name.endsWith(".srv3")) { + } 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") ) { + } 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.success("Video/Audio file loaded successfully", { + autoClose: 2000, + }); + } else { toast.error("Unsupported file type", { autoClose: 2000 }); } - } - - + }; return ( <Root> <ToastContainer /> - {/*LRC viewer*/ } + {/*LRC viewer*/} <div style={{ display: "flex", width: "100%", height: "100vh" }}> <KaraokePlayer lrc={lrcContent} currentMillisecond={currentMillisecond} /> - {/* Ternary operation for if videoUrl has been set */} <div - style={{ flex: 1, position: "relative", backgroundColor: dragOver ? 'lightblue' : 'white', }} + style={{ + flex: 1, + position: "relative", + backgroundColor: dragOver ? "lightblue" : "white", + }} onMouseEnter={() => setShowFileInputs(true)} onMouseLeave={() => setShowFileInputs(false)} onDragOver={handleDragOver} @@ -286,17 +374,23 @@ function KaraokePage() { ref={videoRef} src={videoUrl} style={{ position: "absolute", width: "100%", height: "100%" }} + onEnded={handleVideoEnded} /> - <div - style={{ width: '90%', height: '90%', margin: 'auto' }} - onClick={() => handlePlayPause()} + <audio + ref={supplementAudioRef} + src={supplementAudioUrl} + style={{ display: "none" }} + /> + <div + style={{ width: "90%", height: "90%", margin: "auto" }} + onClick={() => handlePlayPause()} > <CaptionsRenderer srv3={captionsText} currentTime={currentMillisecond / 1000} /> </div> - { /*Video control bar*/ } + {/*Video control bar*/} <div style={{ position: "absolute", @@ -382,13 +476,19 @@ function KaraokePage() { display: "flex", justifyContent: "center", alignItems: "center", - flexDirection: "column" + flexDirection: "column", }} > - <h1 style={{ fontFamily: "Arial", fontWeight: "bold", fontSize: "32px" }}> + <h1 + style={{ + fontFamily: "Arial", + fontWeight: "bold", + fontSize: "32px", + }} + > {statusText} </h1> - {/* Show a placeholder while no video selected */} + {/* Show a placeholder while no video selected */} <p style={{ @@ -397,10 +497,9 @@ function KaraokePage() { fontFamily: "Arial", }} > - Please select the video and lrc (lyrics) file <br /> (Drag and Drop them here, or use the menus below!) - <br/> + <br /> <StyledLink href="/about>">About</StyledLink> </p> </div> @@ -432,13 +531,28 @@ function KaraokePage() { accept="video/*" onChange={handleVideoFileChange} /> - <FileInputLabel - htmlFor="srvUpload" + <FileInputLabel htmlFor="srvUpload" style={{ cursor: "pointer" }}> + SRV + </FileInputLabel> + <FileInput + id="srvUpload" + type="file" + accept=".srv3" + onChange={handleSrvFileChange} + /> + <FileInputLabel + htmlFor="supplementAudioUpload" style={{ cursor: "pointer" }} - > - SRV + > + Audio #2 </FileInputLabel> - <FileInput id="srvUpload" type="file" accept=".srv3" onChange={handleSrvFileChange}/> + <FileInput + id="supplementAudioUpload" + type="file" + accept="audio/*" + onChange={handleSupplementAudioFileChange} + /> + <ControlBarButton onClick={syncSupplementAudioWithVideo}>Sync Audio</ControlBarButton> <div style={{ display: "flex", @@ -446,6 +560,15 @@ function KaraokePage() { fontFamily: "Arial", }} > + <label>Audio/Video Balance</label> + <input + type="range" + min="-1" + max="1" + step="0.01" + value={balance} + onChange={(e) => setBalance(Number(e.target.value))} + /> <label>Offset (±ms) </label> <input type="number" @@ -456,6 +579,23 @@ function KaraokePage() { step="100" /> </div> + <div + style={{ + display: "flex", + flexDirection: "column", + fontFamily: "Arial", + }} + > + <label>Audio 2 Offset (±ms) </label> + <input + type="number" + style={{ fontSize: "14px" }} + id="numberInput" + value={supplementAudioOffset} + onChange={(e) => setSupplementAudioOffset(e.target.value)} + step="25" + /> + </div> </FileInputContainer> )} </div> |
