import React, { useState, useEffect, useRef } from "react"; import API_BASE_URL from "../config.js"; import "./MusicPlayer.css"; import MusicComponent from "./MusicComponent"; export function MusicPlayer({ socket, shopId, user, isSpotifyNeedLogin }) { const [currentTime, setCurrentTime] = useState(0); const [trackLength, setTrackLength] = useState(0); const [viewing, setViewing] = useState(false); // State for expansion const [expanded, setExpanded] = useState(false); // State for expansion const [songName, setSongName] = useState(""); const [debouncedSongName, setDebouncedSongName] = useState(songName); const [currentSong, setCurrentSong] = useState([]); const [songs, setSongs] = useState([]); const [queue, setQueue] = useState([]); const [paused, setPaused] = useState([]); const [lyrics, setLyrics] = useState([]); const [currentLines, setCurrentLines] = useState({ past: [], present: [], future: [], }); const [lyric_progress_ms, setLyricProgressMs] = useState(0); const [subtitleColor, setSubtitleColor] = useState("black"); const [subtitleBG, setSubtitleBG] = useState("white"); const [backgroundImage, setBackgroundImage] = useState(""); useEffect(() => { const getDominantColor = async (imageSrc) => { return new Promise((resolve, reject) => { const img = new Image(); img.crossOrigin = "Anonymous"; img.src = imageSrc; img.onload = () => { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); canvas.width = img.width; canvas.height = img.height; ctx.drawImage(img, 0, 0); const imageData = ctx.getImageData( 0, 0, canvas.width, canvas.height, ).data; const length = imageData.length; let totalR = 0, totalG = 0, totalB = 0; for (let i = 0; i < length; i += 4) { totalR += imageData[i]; totalG += imageData[i + 1]; totalB += imageData[i + 2]; } const averageR = Math.round(totalR / (length / 4)); const averageG = Math.round(totalG / (length / 4)); const averageB = Math.round(totalB / (length / 4)); resolve({ r: averageR, g: averageG, b: averageB }); }; img.onerror = (error) => { reject(error); }; }); }; const fetchColor = async () => { if ( currentSong.item && currentSong.item.album && currentSong.item.album.images[0] ) { const imageUrl = currentSong.item.album.images[0].url; try { const dominantColor = await getDominantColor(imageUrl); // Calculate luminance (YIQ color space) to determine if subtitle should be black or white const luminance = (0.299 * dominantColor.r + 0.587 * dominantColor.g + 0.114 * dominantColor.b) / 255; if (luminance > 0.5) { setSubtitleColor("black"); setSubtitleBG("white"); } else { setSubtitleColor("white"); setSubtitleBG("black"); } setBackgroundImage(imageUrl); } catch (error) { console.error("Error fetching or processing image:", error); } } }; fetchColor(); }, [currentSong]); useEffect(() => { if (!socket) return; socket.on("searchResponse", (response) => { console.log(response); setSongs(response); }); socket.on("updateCurrentSong", (response) => { setCurrentSong(response); setCurrentTime(response.progress_ms / 1000); // Convert milliseconds to seconds setLyricProgressMs(response.progress_ms); setTrackLength(response.item.duration_ms / 1000); }); socket.on("updateQueue", (response) => { setQueue(response); console.log(response); }); socket.on("updatePlayer", (response) => { setPaused(response.decision); }); socket.on("updateLyrics", (response) => { setLyrics(response); console.log(response); setCurrentLines({ past: [], present: [], future: [], }); }); return () => { socket.off("searchResponse"); }; }, [socket]); useEffect(() => { // Simulate progress every 100ms const interval = setInterval(() => { setLyricProgressMs((prevProgress) => prevProgress + 100); }, 100); return () => clearInterval(interval); // Clean up interval on component unmount }, []); useEffect(() => { if (lyrics == null) return; const pastLines = lyrics.filter( (line) => line.startTimeMs < lyric_progress_ms, ); const presentLines = lyrics.filter( (line) => line.startTimeMs > lyric_progress_ms, ); const futureLines = lyrics.filter( (line) => line.startTimeMs > lyric_progress_ms, ); setCurrentLines({ past: pastLines.slice(-2, 1), // Get the last past line present: pastLines.slice(-1), future: futureLines.slice(0, 1), // Get the first future line }); }, [lyrics, lyric_progress_ms]); useEffect(() => { const handler = setTimeout(() => { setDebouncedSongName(songName); }, 300); // Cleanup function to clear the timeout if songName changes return () => { clearTimeout(handler); }; }, [songName]); useEffect(() => { if (socket != null && debouncedSongName) { socket.emit("searchRequest", { shopId, songName: debouncedSongName }); } }, [debouncedSongName, shopId, socket]); const handleInputChange = (event) => { setSongName(event.target.value); }; const onRequest = (trackId) => { const token = localStorage.getItem("auth"); if (socket != null && token) { socket.emit("songRequest", { token, shopId, trackId }); setSongName(""); } }; const onDecision = (trackId, vote) => { const token = localStorage.getItem("auth"); if (socket != null && token) socket.emit("songVote", { token, shopId, trackId, vote }); }; const handlePauseOrResume = (trackId, vote) => { const token = localStorage.getItem("auth"); if (socket != null && token) { socket.emit("playOrPause", { token, shopId, action: paused ? "pause" : "resume", }); console.log(paused); setPaused(!paused); } }; const handleSpotifyAuth = () => { const token = localStorage.getItem("auth"); let nextUrl = ""; // Use 'let' since the value will change if (isSpotifyNeedLogin) { nextUrl = API_BASE_URL + `/login?token=${token}&cafeId=${shopId}`; } else { nextUrl = API_BASE_URL + `/logout?token=${token}&cafeId=${shopId}`; } window.location.href = nextUrl; }; const handleLogin = () => { // navigate(`/login/${shopId}`); }; useEffect(() => { const interval = setInterval(() => { setCurrentTime((prevTime) => prevTime < trackLength ? prevTime + 1 : prevTime, ); }, 1000); return () => clearInterval(interval); }, [trackLength]); const formatTime = (timeInSeconds) => { const minutes = Math.floor(timeInSeconds / 60); const seconds = Math.floor(timeInSeconds % 60); // Ensure seconds and milliseconds are always displayed with two and three digits respectively const formattedSeconds = seconds < 10 ? `0${seconds}` : `${seconds}`; return `${minutes}:${formattedSeconds}`; }; const toggleView = () => { setViewing(!viewing); }; const toggleExpand = () => { setExpanded(!expanded); }; const expandableContainerRef = useRef(null); useEffect(() => { if (expanded && expandableContainerRef.current) { expandableContainerRef.current.scrollTo({ top: 0, behavior: "smooth" }); } }, [expanded]); const [text, setText] = useState("Awaiting the next hit"); const textIndex = useRef(0); const [messages, setMessages] = useState(["Awaiting the next hit", "Click to request your fav song"]); useEffect(() => { // Update the messages based on currentSong const newMessages = [ currentSong != null && currentSong.item != undefined ? `${currentSong.item.artists[0].name} - ${currentSong.item.name}` : "Awaiting the next hit", "Click to request your fav song" ]; setMessages(newMessages); setText(newMessages[0]); // Update the text state to the first message const element = document.querySelector('.animated-text'); // Check if the element exists before adding the event listener if (element) { const handleAnimationIteration = () => { // Toggle between the two text values based on the current index textIndex.current = (textIndex.current + 1) % messages.length; setText(messages[textIndex.current]); }; element.addEventListener('animationiteration', handleAnimationIteration); return () => { element.removeEventListener('animationiteration', handleAnimationIteration); }; } }, [currentSong]); // Run effect when currentSong changes return (
{currentLines.past.map((line, index) => (

{line.words}

))} {currentLines.present.map((line, index) => (

{line.words}

))} {currentLines.future.map((line, index) => (

{line.words}

))}
{currentSong.item && currentSong.item.name ? (viewing? currentSong.item.name:text) : viewing? messages[0]:text}
{viewing && <>
{currentSong.item && currentSong.item.album && currentSong.item.album.images[0] && currentSong.item.artists[0].name ? currentSong.item.artists[0].name : "Drop your hits below"}
{formatTime(currentTime)}
{formatTime(trackLength)}
}
{viewing && <>
{user.cafeId != null && user.cafeId == shopId && (
)}
{songName != "" && songs.map((song, index) => ( onRequest(song.trackId)} /> ))} {songName == "" && queue.length > 0 && queue.map((song, index) => ( onDecision(song.trackId, vote)} /> ))} {songName == "" && queue.length < 1 && (
No Beats Ahead - Drop Your Hits
)} {songName == "" && queue.length > 0 && queue.length < 3 && (
Drop Your Hits
)}
{expanded ? "︿" : "request your song"}
}
); }