Files
kedaimaster/src/components/MusicPlayer.js
zadit 73de85e295 ok
2025-01-09 10:40:39 +07:00

595 lines
19 KiB
JavaScript

import React, { useState, useEffect, useRef } from "react";
import "./MusicPlayer.css";
import MusicComponent from "./MusicComponent";
import Switch from "react-switch";
export function MusicPlayer({ socket, shopId, user, shopOwnerId, isSpotifyNeedLogin, queue }) {
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 [paused, setPaused] = useState([]);
const [getRecommendedMusic, setGetRecommendedMusic] = useState(false);
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("");
const [clicked, setClicked] = useState(false);
const [canvaz, setCanvaz] = useState('');
const [videoSrc, setVideoSrc] = useState('');
const videoRef = useRef(null);
const inputRef = useRef(null); // Create a ref to the input field
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 &&
currentSong[0]?.image
) {
const imageUrl = currentSong[0]?.trackId != 'kCGs5_oCtBE' && currentSong[0]?.trackId != 'O8eYd7oAZtA' ? currentSong[0].image : '';
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]);
const convertToMilliseconds = (timeStr) => {
const [minutes, seconds] = timeStr.split(':').map(Number);
return (minutes * 60 + seconds) * 1000;
};
useEffect(() => {
if (!socket) return;
socket.on("searchResponse", (response) => {
console.log(response);
setSongs(response);
});
socket.on("updateCurrentSong", (response) => {
console.log(response)
setCurrentSong(response);
// setCurrentTime(response.progress_ms / 1000); // Convert milliseconds to seconds
// setLyricProgressMs(response.progress_ms);
// setTrackLength(convertToMilliseconds(response.item.length));
});
socket.on("updatePlayer", (response) => {
setPaused(response.decision);
});
socket.on("updateLyrics", (response) => {
setLyrics(response);
console.log(response);
setCurrentLines({
past: [],
present: [],
future: [],
});
});
socket.on("updateQueue", ({ getRecommendedMusic }) => {
if (getRecommendedMusic == undefined) return;
setGetRecommendedMusic(getRecommendedMusic); // Only set the queue if it's a valid non-empty array
console.log("Updated config:", getRecommendedMusic); // Log the valid queue
});
return () => {
socket.off("searchResponse");
};
}, [socket]);
// useEffect for setting up the socket listener
useEffect(() => {
const handleUpdateCanvas = (response) => {
if (response && response !== canvaz) {
console.log(response);
console.log(canvaz);
setCanvaz(response);
fetch(response)
.then((response) => response.blob())
.then((blob) => {
const blobUrl = URL.createObjectURL(blob);
setVideoSrc(blobUrl);
if (videoRef.current) {
videoRef.current.load(); // Reload the video element
}
})
.catch((error) => console.error('Error loading video:', error));
} else if (!response) {
// Clear the video source if response is empty
setVideoSrc('');
if (videoRef.current) {
videoRef.current.load(); // Reload the video element
}
}
};
// Listen for the "updateCanvas" event
socket.on("updateCanvas", handleUpdateCanvas);
socket.on("claimPlayerRes", (response) => {
if (response.error) {
console.log('Error:', response.error);
// Handle error
} else {
window.open(response.url);
}
});
socket.on("unClaimPlayerRes", (response) => {
if (response.error) {
console.log('Error:', response.error);
// Handle error
} else {
console.log('Player token:', response.token);
// Handle success and use the player token
}
});
// Clean up the socket listener when the component is unmounted
return () => {
socket.off("updateCanvas", handleUpdateCanvas);
};
}, [socket, canvaz]);
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]);
const changeIsGetRecommendedMusic = () => {
const isGetRecommendedMusic = !getRecommendedMusic;
setGetRecommendedMusic(isGetRecommendedMusic)
const token = localStorage.getItem("auth");
if (socket != null && token) {
socket.emit("configPlayer", { token, shopId, getRecommendedMusic: isGetRecommendedMusic });
}
}
useEffect(() => {
if (socket != null && debouncedSongName) {
socket.emit("searchRequest", { shopId, songName: debouncedSongName });
}
}, [debouncedSongName, shopId, socket]);
const handleInputChange = (event) => {
setSongName(event.target.value);
};
const onRequest = (track) => {
const token = localStorage.getItem("auth");
if (socket != null && token) {
socket.emit("songRequest", { token, shopId, track });
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 handleSetPlayer = () => {
const token = localStorage.getItem("auth");
socket.emit("claimPlayer", {
token,
shopId,
});
if (isSpotifyNeedLogin) {
} else {
// socket.emit("unClaimPlayer", {
// token,
// 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("Menunggu musik favoritmu");
const textIndex = useRef(0);
const [messages, setMessages] = useState(["Menunggu musik favoritmu", "Klik untuk putar musik favoritmu"]);
useEffect(() => {
// Update the messages based on currentSong
const newMessages = [
currentSong != null && currentSong[0]?.trackId != 'kCGs5_oCtBE' && currentSong[0]?.trackId != 'O8eYd7oAZtA' && currentSong[0]?.name != undefined
? `${currentSong[0]?.name} - ${currentSong[0]?.artist}`
: "Menunggu musik favoritmu",
"Klik untuk putar musik favoritmu"
];
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
const handleButtonClick = () => {
setClicked(true);
if (inputRef.current) {
inputRef.current.focus(); // Focus the input when the button is clicked
}
// After 1 second, remove the "clicked" class to let the color gradually return to the original
setTimeout(() => {
setClicked(false);
}, 1000); // 1 second timeout (same as the CSS transition duration)
};
function modifyUrl(url) {
// Use a regular expression to find the part of the URL that starts with '='
return url.replace(/=(w\d+)-(h\d+)-/, '=w255-h255-');
}
return (
<div className={`music-player`} style={{ marginBottom: `${viewing ? '-10px' : ''}` }}>
<div
onClick={toggleView}
className="current-bgr"
style={{ backgroundImage: `url(${modifyUrl(backgroundImage)})` }}
// style={{ backgroundImage: `url(${videoSrc != "" ? '' : backgroundImage})` }}
>
{/* <video
ref={videoRef}
autoPlay
loop
playsInline
muted
style={{ height: '100%', width: '100%', objectFit: 'cover', position: 'absolute', top: 0, right: 0, zIndex: -1 }}
>
{videoSrc && <source src={videoSrc} type="video/mp4" />}
</video> */}
{currentLines.present.map((line, index) => (
<div className="present" style={{
color: subtitleColor,
textShadow: `2px 2px 2px ${subtitleBG}`,
}} key={index}>
<p>{line.words}</p>
</div>
))}
{currentLines.future.map((line, index) => (
<div className="future" style={{
color: subtitleColor,
textShadow: `2px 2px 2px ${subtitleBG}`,
}} key={index}>
<p>{line.words}</p>
</div>
))}
</div>
<div className="current-info" >
<div
className={`current-name ${viewing ? '' : 'animated-text'}`} style={{ margin: `${viewing ? '35px 30px' : '13px 30px'}` }}>
{currentSong && currentSong[0]?.name
? (viewing && currentSong[0]?.trackId != 'kCGs5_oCtBE' && currentSong[0]?.trackId != 'O8eYd7oAZtA' ? currentSong[0]?.name : text)
:
viewing ? messages[0] : text}
</div>
{viewing && <>
<div className="current-artist">
{
currentSong &&
currentSong[0]?.artist && currentSong[0]?.trackId != 'kCGs5_oCtBE' && currentSong[0]?.trackId != 'O8eYd7oAZtA'
? currentSong[0]?.artist
: "Pilih hits terbaikmu dibawah!"}
</div>
</>
}
</div>
{viewing &&
<>
<div
className={`expandable-container ${expanded ? "expanded" : ""}`}
ref={expandableContainerRef}
>
{user.cafeId == shopId || user.userId == shopOwnerId && (
<>
<div className="auth-box">
<div
onClick={handleSetPlayer}
>{isSpotifyNeedLogin ? "Jadikan perangkat sebagai pemutar musik" : "Unset as music player"}</div>
</div>
<div className="config-box">
<div
>
Dapatkan rekomendasi musik &nbsp;
<div><Switch
onChange={() => changeIsGetRecommendedMusic()}
checked={getRecommendedMusic}
/></div>
</div>
</div>
</>
)}
<div className="search-box">
<input
ref={inputRef}
type="text"
placeholder="Cari musik..."
value={songName}
onChange={handleInputChange}
className={clicked ? 'clicked' : ''}
/>
</div>
<div
className="rectangle"
style={{
justifyContent: songName === "" && queue && queue.length > 0 && queue.length < 3 || songName != '' ? 'flex-start' : 'center'
}}
>
{(songName == "" && queue && queue.length < 2 || (queue[0] != undefined && queue.length < 1 && queue[0][5]) == true) && (
<div className="middle-text">
<span className="bold-text">Antrian kosong</span><br />
<span className="normal-text">Pilih musikmu</span>
<button
className={`search-button ${clicked ? 'clicked' : ''}`}
onClick={handleButtonClick}
>
Cari musik
</button>
</div>
)}
{songName != "" &&
songs.map((song, index) => (
<MusicComponent
key={index}
song={song}
min={0}
max={100}
onDecision={(e) => onRequest(song)}
/>
))}
{
songName === "" &&
queue &&
Array.isArray(queue) &&
queue.length > 0 && (
queue
.filter(song => song[5] !== true) // Filter out songs where song[5] is true or undefined
.map((song, index) => (
<MusicComponent
key={index}
song={song}
min={song[3] ? 0 : -100}
max={song[3] ? 0 : 100}
onDecision={(vote) => onDecision(song[0].trackId, vote)}
/>
))
)
}
{
songName === "" &&
queue &&
Array.isArray(queue) &&
queue.length > 0 && (
queue
.filter(song => song[5] === true) // Filter out songs where song[5] is true or undefined
.map((song, index) => (
<MusicComponent
key={index}
song={song}
min={song[3] ? 0 : -100}
max={song[3] ? 0 : 100}
onDecision={(vote) => onDecision(song[0].trackId, vote)}
/>
))
)
}
{/* {songName == "" && queue && queue.length > 0 && queue.length < 3 && (
<div className="middle-text">
<span className="normal-text">Tambahkan musikmu</span>
<button
className={`search-button ${clicked ? 'clicked' : ''}`}
onClick={handleButtonClick}
>
Cari musik
</button>
</div>
)} */}
</div>
</div>
<div className={`expand-button ${expanded ? "expanded" : ""}`} onClick={toggleExpand}>
<h5>
{expanded ? '⋀' : 'Lihat antrian musik'}
</h5>
</div></>
}
</div >
);
}