This is a component that renders a single Wavesurfer wave:
import React, { useEffect, useRef, useState } from 'react';
import WaveSurfer from 'wavesurfer.js';
import { Box, Grid, Typography, useTheme } from '@mui/material';
import Image from 'next/image';
import { PiPauseFill, PiPlayFill } from 'react-icons/pi';
import { DesignTokens } from '@/theme/getDesignTokens';
import CustomSecondaryButton from '@/components/customButton/CustomSecondaryButton';
import { Song } from '../[artist_name]';
interface MusicPlayerProps {
song: Song;
}
const MusicPlayer: React.FC<MusicPlayerProps> = ({ song }) => {
const theme = useTheme<DesignTokens>();
const containerRef = useRef<HTMLDivElement>(null);
const waveSurferRef = useRef<any>();
const [currentTime, setCurrentTime] = useState<string>('0:00');
const [duration, setDuration] = useState('0:00');
const [isPlaying, setIsPlaying] = useState(false);
const formatTime = function (time: number) {
return [
Math.floor((time % 3600) / 60), // minutes
('00' + Math.floor(time % 60)).slice(-2), // seconds
].join(':');
};
useEffect(() => {
const waveSurfer = WaveSurfer.create({
container: containerRef.current as HTMLDivElement,
normalize: true,
height: 46,
waveColor: theme.palette.audio.barBackground,
progressColor: '#5b4aff',
cursorColor: '#ddd5e9',
cursorWidth: 2,
barWidth: 4,
barGap: 5,
barRadius: 30,
minPxPerSec: 1,
fillParent: true,
interact: true,
dragToSeek: true,
audioRate: 1,
autoScroll: true,
autoCenter: true,
sampleRate: 8000,
url: song.audioFileUrl,
});
waveSurfer.on('ready', () => {
waveSurferRef.current = waveSurfer;
});
waveSurfer.on('play', () => {
setIsPlaying(true);
});
waveSurfer.on('pause', () => {
setIsPlaying(false);
});
// Show current time
waveSurfer.on('audioprocess', function () {
setCurrentTime(formatTime(waveSurfer.getCurrentTime()));
console.log(formatTime(waveSurfer.getCurrentTime()));
});
waveSurfer.on('ready', function () {
setDuration(formatTime(waveSurfer.getDuration()));
});
console.log('WAVESURFER', containerRef.current);
return () => {
waveSurfer.destroy();
};
}, [song]);
return (
<Grid container p={4} alignItems="center">
<Grid
sx={{
pb: {
xs: 2,
sm: 0,
},
}}
display={'flex'}
minWidth={'300px'}
flexDirection={'row'}
>
<Image
style={{ borderRadius: '4px' }}
src={song.coverUrl}
priority
width={'64px'}
height={'64px'}
alt="album_pic"
/>
<Grid
sx={{
pl: {
xs: 2,
sm: 4,
},
}}
justifyContent={'space-between'}
display={'flex'}
flexDirection={'column'}
py={1}
>
<Typography sx={{ color: theme.palette.core.textPrimary }}>{song.title}</Typography>
<Typography sx={{ color: theme.palette.core.textSecondary }}>{song.year}</Typography>
</Grid>
</Grid>
<Box
pr={4}
pt={1}
sx={{ cursor: 'pointer' }}
onClick={() => waveSurferRef.current.playPause()}
>
{isPlaying ? <PiPauseFill size={24} /> : <PiPlayFill size={24} />}
</Box>
<Box pr={2}>
<Typography>{currentTime}</Typography>
</Box>
<Grid item xs>
<Box
sx={{
position: 'relative',
height: '50px',
}}
>
<div
ref={containerRef}
style={{
position: 'absolute',
top: 2,
bottom: 0,
left: 0,
right: 0,
}}
/>
</Box>
</Grid>
<Box
pl={1}
sx={{
pr: {
xs: 3,
sm: 6,
},
}}
>
<Typography>{duration}</Typography>
</Box>
<Box
sx={{
pl: {
xs: 0,
md: 3,
},
}}
>
<CustomSecondaryButton onClick={handleLicenseClick} text="License" />
</Box>
</Grid>
);
};
export default MusicPlayer;
In the parent component, I render the MusicPlayer in this way:
{songs.map((song, index) => (
<Grid key={index}>
<MusicPlayer song={song} />
</Grid>
))}
I am seriously struggling to find a way to stop a playing song if another song on the list is played, basically, I just want ONE song playing at the time, if the user plays a song which is not in a "play" state, I want to stop the currently played song. At the moment, each song is independent, therefore if I play 3 songs, the audio of these 3 songs will overlap.
Does anyone have a suggestion on how to do this? I have been trying several options but none of them work.
Thank you :D