This commit is contained in:
zadit
2024-11-16 23:44:48 +07:00
parent bea0ff63d7
commit b8c1d30d14
26 changed files with 1841 additions and 799 deletions

View File

@@ -20,7 +20,7 @@ const Title = styled.h2`
font-family: "Poppins", sans-serif;
font-weight: 500;
font-style: normal;
font-size: 6vw;
font-size:${(props) => (props.HeaderSize)};
color: rgba(88, 55, 50, 1);
`;
@@ -214,6 +214,7 @@ const Child = styled.div`
const Header = ({
HeaderText,
HeaderSize='6vw',
shopId,
shopName,
shopImage,
@@ -300,7 +301,7 @@ const Header = ({
};
return (
<HeaderBar>
<Title>
<Title HeaderSize={HeaderSize}>
{shopName == null
? HeaderText == null
? "kedaimaster"
@@ -309,9 +310,9 @@ const Header = ({
</Title>
<div style={{ visibility: showProfile ? "visible" : "hidden" }}>
<ProfileImage
src={shopImage || "https://static-00.iconduck.com/assets.00/profile-major-icon-1024x1024-9rtgyx30.png"}
src={user.username == undefined? shopImage : "https://static-00.iconduck.com/assets.00/profile-major-icon-1024x1024-9rtgyx30.png"}
alt="Profile"
onClick={handleImageClick}
onClick={user.username !== undefined?handleImageClick: null}
animate={showRectangle && animate}
/>
<ProfileName animate={showRectangle && animate}>
@@ -325,9 +326,6 @@ const Header = ({
this is the guest side of {guestSideOfClerk.clerkUsername}
</Child>
)}
{user.username === undefined && !guestSideOfClerk && (
<Child onClick={goToLogin}>Click to login</Child>
)}
{user.username !== undefined && (
<Child onClick={() => setModal("edit_account")}>
Ubah profil
@@ -340,22 +338,17 @@ const Header = ({
user.username !== undefined &&
user.roleId === 1 && (
<>
{/* <Child onClick={() => setModal("update_stock")}>
update stock
</Child> */}
<Child hasChildren>
{shopName}
<div class="toggle-switch">
<label class="toggle-switch-label" for="toggleSwitch">
Mode edit
</label>
<Child>
Mode edit
<Switch
borderRadius={0}
checked={isEditMode}
onChange={() => setIsEditMode(!isEditMode)}
/>
</div>
</Child>
<Child onClick={() => setModal("welcome_config")}>
Halaman sambutan
@@ -401,15 +394,14 @@ const Header = ({
<Child hasChildren>
{shopName}
<div class="toggle-switch">
<label class="toggle-switch-label" for="toggleSwitch">
Mode edit
</label>
<Switch
checked={isEditMode}
onChange={() => setIsEditMode(!isEditMode)}
/>
</div>
<Child>
Mode edit&nbsp;
<Switch
borderRadius={0}
checked={isEditMode}
onChange={() => setIsEditMode(!isEditMode)}
/>
</Child>
<Child onClick={() => setModal("add_material")}>
stok
</Child>

View File

@@ -18,6 +18,9 @@ import NotificationRequest from "../pages/NotificationRequest.js";
import NotificationBlocked from "../pages/NotificationBlocked.js";
import WelcomePageEditor from "../pages/WelcomePageEditor.js";
import GuidePage from "../pages/GuidePage";
import Join from "../pages/Join";
import Login from "../pages/Login";
import ResetPassword from "../pages/ResetPassword";
const Modal = ({ shop, isOpen, onClose, modalContent, setModal }) => {
if (!isOpen) return null;
@@ -35,6 +38,8 @@ const Modal = ({ shop, isOpen, onClose, modalContent, setModal }) => {
return (
<div onClick={handleOverlayClick} className={styles.modalOverlay}>
<div className={styles.modalContent} onClick={handleContentClick}>
{modalContent === "join" && <Join />}
{modalContent === "reset-password" && <ResetPassword />}
{modalContent === "req_notification" && <NotificationRequest setModal={setModal} />}
{modalContent === "blocked_notification" && <NotificationBlocked />}
{modalContent === "create_clerk" && <CreateClerk shopId={shop.cafeId} />}

View File

@@ -134,7 +134,7 @@ const MusicComponent = ({ song, min, max, onDecision }) => {
<p className="artist-name">{song.artist}</p>
{min < 0 && <p className="artist-name">&lt;--- {song.disagree} no - {song.agree} yes ---&gt;</p>}
</div>
<p className="song-duration">{formatDuration(song.duration_ms)}</p>
<p className="song-duration">{song.length}</p>
</div>
);
};

View File

@@ -3,7 +3,7 @@ import API_BASE_URL from "../config.js";
import "./MusicPlayer.css";
import MusicComponent from "./MusicComponent";
export function MusicPlayer({ socket, shopId, user, isSpotifyNeedLogin }) {
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
@@ -13,7 +13,6 @@ export function MusicPlayer({ socket, shopId, user, isSpotifyNeedLogin }) {
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([]);
@@ -111,6 +110,11 @@ export function MusicPlayer({ socket, shopId, user, isSpotifyNeedLogin }) {
fetchColor();
}, [currentSong]);
const convertToMilliseconds = (timeStr) => {
const [minutes, seconds] = timeStr.split(':').map(Number);
return (minutes * 60 + seconds) * 1000;
};
useEffect(() => {
if (!socket) return;
@@ -123,13 +127,9 @@ export function MusicPlayer({ socket, shopId, user, isSpotifyNeedLogin }) {
setCurrentSong(response);
setCurrentTime(response.progress_ms / 1000); // Convert milliseconds to seconds
setLyricProgressMs(response.progress_ms);
setTrackLength(response.item.duration_ms / 1000);
setTrackLength(convertToMilliseconds(response.item.length));
});
socket.on("updateQueue", (response) => {
setQueue(response);
console.log(response);
});
socket.on("updatePlayer", (response) => {
setPaused(response.decision);
@@ -149,42 +149,61 @@ export function MusicPlayer({ socket, shopId, user, isSpotifyNeedLogin }) {
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
// 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);
// Listen for the "updateCanvas" event
socket.on("updateCanvas", handleUpdateCanvas);
// Clean up the socket listener when the component is unmounted
return () => {
socket.off("updateCanvas", handleUpdateCanvas);
};
}, [socket, canvaz]);
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
@@ -235,10 +254,10 @@ useEffect(() => {
setSongName(event.target.value);
};
const onRequest = (trackId) => {
const onRequest = (track) => {
const token = localStorage.getItem("auth");
if (socket != null && token) {
socket.emit("songRequest", { token, shopId, trackId });
socket.emit("songRequest", { token, shopId, track });
setSongName("");
}
};
@@ -262,19 +281,20 @@ useEffect(() => {
}
};
const handleSpotifyAuth = () => {
const handleSetPlayer = () => {
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}`);
if (isSpotifyNeedLogin) {
socket.emit("claimPlayer", {
token,
shopId,
});
} else {
socket.emit("unClaimPlayer", {
token,
shopId,
});
}
};
useEffect(() => {
@@ -354,7 +374,7 @@ useEffect(() => {
<div
onClick={toggleView}
className="current-bgr"
style={{ backgroundImage: `url(${videoSrc != ""? '':backgroundImage})` }}
style={{ backgroundImage: `url(${videoSrc != "" ? '' : backgroundImage})` }}
>
<video
ref={videoRef}
@@ -362,7 +382,7 @@ useEffect(() => {
loop
playsInline
muted
style={{ height: '100%', width: '100%', objectFit: 'cover', position:'absolute', top:0,right:0,zIndex:-1}}
style={{ height: '100%', width: '100%', objectFit: 'cover', position: 'absolute', top: 0, right: 0, zIndex: -1 }}
>
{videoSrc && <source src={videoSrc} type="video/mp4" />}
</video>
@@ -432,14 +452,14 @@ useEffect(() => {
className={`expandable-container ${expanded ? "expanded" : ""}`}
ref={expandableContainerRef}
>
{user.cafeId != null && user.cafeId == shopId && (
{user.cafeId == shopId || user.userId == shopOwnerId && (
<div className="auth-box">
<input
type="text"
placeholder={
isSpotifyNeedLogin ? "Login Spotify" : "Logout Spotify"
isSpotifyNeedLogin ? "Set as music player" : "Unset as music player"
}
onClick={handleSpotifyAuth}
onClick={handleSetPlayer}
/>
</div>
)}
@@ -466,20 +486,25 @@ useEffect(() => {
song={song}
min={0}
max={100}
onDecision={(e) => onRequest(song.trackId)}
/>
))}
{songName == "" &&
queue.length > 0 &&
queue.map((song, index) => (
<MusicComponent
key={index}
song={song}
min={-100}
max={100}
onDecision={(vote) => onDecision(song.trackId, vote)}
onDecision={(e) => onRequest(song)}
/>
))}
{
songName === "" &&
queue &&
Array.isArray(queue) &&
queue.length > 0 && (
queue.map((song, index) => (
<MusicComponent
key={index}
song={song}
min={-100}
max={100}
onDecision={(vote) => onDecision(song.trackId, vote)}
/>
))
)
}
{songName == "" && queue.length < 1 && (
<div className="rectangle">
<div className="diagonal-text">No Beats Ahead - Drop Your Hits</div>

View File

@@ -1,94 +0,0 @@
/* src/RouletteWheel.css */
/* html,
body,
img {
overflow: hidden;
} */
.roulette-wheel-container {
position: fixed;
left: 50%;
/* Center the container horizontally */
top: 30%;
transform: translate(-40%, 20%);
scale: 3;
max-width: 100vw;
/* Limit container width to viewport width */
max-height: 100vh;
/* Limit container height to viewport height */
overflow: hidden;
/* Hide overflowing content */
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
-webkit-user-select: none;
/* Safari */
-ms-user-select: none;
/* IE 10 and IE 11 */
user-select: none;
/* Standard syntax */
}
.roulette-wheel {
width: 300px;
height: 300px;
display: flex;
align-items: center;
justify-content: center;
pointer-events: auto;
border-radius: 50%;
transition: transform 0.2s ease-out;
}
.roulette-image {
width: 200px;
height: 200px;
user-select: none;
/* Prevents the image from being selected */
pointer-events: none;
}
.roulette-input {
position: absolute;
width: 30%;
/* Increase size for better visibility */
height: auto;
/* Increase size for better visibility */
border: none;
/* Remove border for simplicity */
border-radius: 5px;
/* Add border radius for rounded corners */
color: rgb(2, 2, 2);
/* Text color */
font-size: 16px;
/* Font size */
border: 2px solid #ccc;
}
.roulette-button {
z-index: 100;
position: absolute;
width: 30%;
height: auto;
border: none;
border-radius: 5px;
color: rgb(2, 2, 2);
font-size: 16px;
border: 2px solid #ccc;
background-color: #fff;
}
.roulette-button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.roulette-button.error {
background-color: red; /* Change button color on error */
transition: background-color 0.5s ease-in-out;
}
.hidden {
display: none;
}

View File

@@ -1,257 +0,0 @@
import React, { useState, useRef, useEffect } from "react";
import "./RouletteWheel.css";
import coffeeImage from "./coffee.png"; // Update the path to your image
import { ThreeDots } from "react-loader-spinner";
const RouletteWheel = ({ isForRegister, onSignIn, onSignUp }) => {
const [rotation, setRotation] = useState(0);
const [isDragging, setIsDragging] = useState(false);
const startAngleRef = useRef(0);
const startRotationRef = useRef(0);
const wheelRef = useRef(null);
const [email, setEmail] = useState("");
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const emailInputRef = useRef(null);
const usernameInputRef = useRef(null);
const passwordInputRef = useRef(null);
const handleSignIn = () => {
setError(false); // Reset error state before login attempt
onSignIn(email, username, password, setLoading, setError);
};
const handleSignUp = () => {
setError(false); // Reset error state before login attempt
onSignUp(email, username, password, setLoading, setError);
};
const handleStart = (x, y) => {
setIsDragging(true);
startAngleRef.current = getAngle(x, y);
startRotationRef.current = rotation;
};
useEffect(() => {
setError(false);
}, [error]);
const handleMove = (x, y) => {
if (isDragging) {
const angle = getAngle(x, y);
const deltaAngle = angle - startAngleRef.current;
setRotation(startRotationRef.current + deltaAngle);
if (isForRegister) {
if (rotation + deltaAngle > 30 || rotation + deltaAngle < -210)
handleEnd();
} else {
if (rotation + deltaAngle > 30 || rotation + deltaAngle < -120)
handleEnd();
}
}
};
const handleEnd = () => {
setIsDragging(false);
setRotation((prevRotation) => {
const snappedRotation = Math.round(prevRotation / 90) * 90;
return snappedRotation;
});
};
const handleMouseDown = (e) => {
handleStart(e.clientX, e.clientY);
};
const handleMouseMove = (e) => {
handleMove(e.clientX, e.clientY);
};
const handleMouseUp = () => {
handleEnd();
};
const handleTouchStart = (e) => {
const touch = e.touches[0];
handleStart(touch.clientX, touch.clientY);
};
const handleTouchMove = (e) => {
if (!isDragging) return;
e.preventDefault();
const touch = e.touches[0];
handleMove(touch.clientX, touch.clientY);
};
const handleTouchEnd = (e) => {
handleEnd();
};
const handleChildMouseDown = (e) => {
e.stopPropagation();
};
const handleChildTouchStart = (e) => {
e.stopPropagation();
};
const getAngle = (x, y) => {
const rect = wheelRef.current.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const dx = x - centerX;
const dy = y - centerY;
return Math.atan2(dy, dx) * (180 / Math.PI);
};
useEffect(() => {
if (isDragging) {
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
document.addEventListener("touchmove", handleTouchMove, {
passive: false,
});
document.addEventListener("touchend", handleTouchEnd, { passive: false });
} else {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
document.removeEventListener("touchmove", handleTouchMove);
document.removeEventListener("touchend", handleTouchEnd);
}
return () => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
document.removeEventListener("touchmove", handleTouchMove);
document.removeEventListener("touchend", handleTouchEnd);
};
}, [isDragging]);
const inputPositions = [-90, 0, 90, 180]; // Positions for the inputs
const isVisible = (angle) => {
const modAngle = ((angle % 360) + 360) % 360;
return modAngle % 90 === 0;
};
useEffect(() => {
if (isForRegister) {
if (isVisible(rotation % 360 !== -0)) {
emailInputRef.current.focus();
} else if (isVisible(rotation % 360 !== -90)) {
usernameInputRef.current.focus();
} else if (isVisible(rotation % 360 !== -180)) {
passwordInputRef.current.focus();
}
} else {
if (isVisible(rotation % 360 !== -0)) {
usernameInputRef.current.focus();
} else if (isVisible(rotation % 360 !== -90)) {
passwordInputRef.current.focus();
}
}
}, [rotation]);
return (
<div className="roulette-wheel-container">
<div
className="roulette-wheel"
ref={wheelRef}
onMouseDown={handleMouseDown}
onTouchStart={handleTouchStart}
style={{ transform: `rotate(${rotation}deg)` }}
>
<input
className={`roulette-input ${
isVisible(rotation % 360 !== -0) ? "" : "hidden"
}`}
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
ref={usernameInputRef}
style={{ transform: "translate(60%, -220%) rotate(0deg)" }}
/>
<input
className={`roulette-input ${
isVisible(rotation % 360 !== -0) ? "" : "hidden"
}`}
placeholder="Password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
ref={passwordInputRef}
style={{ transform: "translate(90%, -90%) rotate(0deg)" }}
/>
<button
className={`roulette-button ${error ? "error" : ""} ${
isVisible(rotation % 360 !== -0) ? "" : "hidden"
}`}
disabled={loading}
onClick={handleSignIn}
onMouseDown={handleChildMouseDown}
onTouchStart={handleChildTouchStart}
style={{ transform: "translate(110%, -10%) rotate(0deg)" }}
>
{loading ? (
<ThreeDots color="black" height={15} width={40} /> // Show loader when loading
) : (
"Sign in"
)}
</button>
<input
className={`roulette-input ${
isVisible(rotation % 360 !== -90) ? "" : "hidden"
}`}
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
ref={emailInputRef}
style={{ transform: "translate(30%, 320%) rotate(90deg)" }}
/>
<input
className={`roulette-input ${
isVisible(rotation % 360 !== -90) ? "" : "hidden"
}`}
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
ref={usernameInputRef}
style={{ transform: "translate(10%, 320%) rotate(90deg)" }}
/>
<input
className={`roulette-input ${
isVisible(rotation % 360 !== -90) ? "" : "hidden"
}`}
placeholder="Password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
ref={passwordInputRef}
style={{ transform: "translate(-10%, 320%) rotate(90deg)" }}
/>
<button
className={`roulette-button ${error ? "error" : ""} ${
isVisible(rotation % 360 !== -90) ? "" : "hidden"
} `}
onClick={handleSignUp}
onMouseDown={handleChildMouseDown}
onTouchStart={handleChildTouchStart}
style={{ transform: "translate(-60%, 320%) rotate(90deg)" }}
>
{loading ? (
<ThreeDots color="black" height={15} width={40} /> // Show loader when loading
) : (
"Sign Up"
)}
</button>
<img src={coffeeImage} className="roulette-image" alt="Coffee" />
</div>
</div>
);
};
export default RouletteWheel;

View File

@@ -1,108 +1,39 @@
import React, { useState, useEffect, useRef } from 'react';
import React, { useState } from 'react';
import YouTube from 'react-youtube';
export function TrackPlayer({ next }) {
// State to store the progress in milliseconds, video duration, and the next video ID
const [progress, setProgress] = useState(0);
const [duration, setDuration] = useState(0);
const [currentTrack, setCurrentTrack] = useState(null); // Initial video ID
const [nextTrack, setNextTrack] = useState(null); // Initial video ID
const [isNearEnd, setIsNearEnd] = useState(false); // Flag for 20 seconds left
const playerRef = useRef(null);
export function TrackPlayer({ next, queueList, setQueueList }) {
// Initial list of video IDs (YouTube video IDs)
const videos = [
'dQw4w9WgXcQ', // Example Video 1
'tgbNymZ7vqY', // Example Video 2
'oHg5SJYRHA0', // Example Video 3
];
useEffect(() => {
if (next == null) return;
if (currentTrack == null) setCurrentTrack(next);
setNextTrack(next);
}, [next]);
// State to track the current video index
const [currentVideoIndex, setCurrentVideoIndex] = useState(0);
const handlePlayerStateChange = (event) => {
if (event.data === window.YT.PlayerState.PLAYING) {
// Start tracking progress once the video starts playing
const interval = setInterval(() => {
if (playerRef.current) {
const currentTime = playerRef.current.getCurrentTime(); // Get current time in seconds
setProgress(currentTime * 1000); // Convert to milliseconds
// Check if the video is 20 seconds from ending
if (currentTime >= duration / 1000 - 20 && !isNearEnd) {
setIsNearEnd(true);
} else if (currentTime < duration / 1000 - 20 && isNearEnd) {
setIsNearEnd(false);
}
}
}, 100); // Update every 100 ms
// Clean up when the video is paused or finished
event.target.addEventListener('onStateChange', (e) => {
if (e.data === window.YT.PlayerState.PAUSED || e.data === window.YT.PlayerState.ENDED) {
clearInterval(interval);
// When the video ends, set the next track
if (e.data === window.YT.PlayerState.ENDED) {
// Logic to set the next video ID (for now, just updating to another static video)
setCurrentTrack(nextTrack); // Replace 'newVideoId' with the ID of the next video you want
}
}
});
}
// Function to handle video end event
const handleVideoEnd = () => {
// Update to next video or loop to the first video if at the end
setCurrentVideoIndex((prevIndex) => (prevIndex + 1) % videos.length);
};
const handlePlayerReady = (event) => {
playerRef.current = event.target;
const durationInSeconds = playerRef.current.getDuration(); // Get video duration in seconds
setDuration(durationInSeconds * 1000); // Set the duration in milliseconds
// Set the video quality to the lowest available (typically 360p or smaller)
playerRef.current.setPlaybackQuality('small');
// YouTube video player options
const opts = {
height: '390',
width: '640',
playerVars: {
autoplay: 1, // Automatically play the video
},
};
useEffect(() => {
// Make sure to load the YouTube iframe API script when the component mounts
const script = document.createElement('script');
script.src = 'https://www.youtube.com/iframe_api';
document.body.appendChild(script);
return () => {
// Cleanup if needed (for example, removing the script)
document.body.removeChild(script);
};
}, []);
useEffect(() => {
// When the currentTrack changes, reset progress and duration
setProgress(0);
setDuration(0);
setIsNearEnd(false);
}, [currentTrack]);
return (
<div className="App" style={{visibility: 'hidden', position: 'fixed'}}>
{currentTrack != null && (
<div>
<YouTube
videoId={currentTrack.videoId} // Dynamically change video based on currentTrack
opts={{
height: '315',
width: '560',
playerVars: {
autoplay: 1,
controls: 1,
mute: 0,
loop: 0, // Do not loop; handle next video manually
quality: 'small', // Request small quality (360p or lower)
},
}}
onReady={handlePlayerReady} // Get duration and set quality on ready
onStateChange={handlePlayerStateChange}
/>
</div>
)}
<div>Progress: {progress} ms</div>
<div>Video Duration: {duration} ms</div>
<div>
{isNearEnd && <div>Video is near the end (20 seconds left)</div>}
</div>
<div>
<YouTube
videoId={videos[currentVideoIndex]} // Use the current video ID
opts={opts}
onEnd={handleVideoEnd} // Call handleVideoEnd when the video ends
/>
</div>
);
}
};