I’m building a live chat application where users can send and receive voice messages. Each voice message is visualized using WaveSurfer.js. My issue is that when a user plays a voice message, it works fine, but I want to make sure that only one message plays at a time. If one voice message is already playing and the user starts playing another one, the previous message should automatically stop.
Here’s the my code:
import { useEffect, useRef, useState } from "react";
import MessageStatus from "../common/MessageStatus";
import WaveSurfer from "wavesurfer.js";
import { FaPlay, FaPause } from "react-icons/fa";
const VoiceMessage = ({ user, message }) => {
const [audioMessage, setAudioMessage] = useState(null);
const [totalDuration, setTotalDuration] = useState(0);
const [currentPlaybackTime, setCurrentPlaybackTime] = useState(0);
const [playingAudio, setPlayingAudio] = useState(false);
const [waveform, setWaveform] = useState(null);
const [audioRate, setAudioRate] = useState(1);
const waveFormRef = useRef(null);
useEffect(() => {
const waveSurfer = WaveSurfer.create({
container: waveFormRef.current,
waveColor: "#040404",
progressColor: "#040404",
barWidth: 2,
height: 30,
barGap: 2,
minPxPerSec: 1,
fillParent: true,
dragToSeek: true,
audioRate: audioRate,
});
setWaveform(waveSurfer);
waveSurfer.on("finish", () => {
setPlayingAudio(false);
});
return () => {
if (waveSurfer) {
waveSurfer.destroy();
}
};
}, []);
useEffect(() => {
if (waveform) {
const audio = new Audio(message.message);
setAudioMessage(audio);
waveform.load(message.message);
waveform.on("ready", () => {
setTotalDuration(waveform.getDuration());
});
return () => {
if (waveform) {
waveform.un("ready");
}
};
}
}, [waveform, message.message]);
const handlePlayingAudio = () => {
if (audioMessage) {
waveform.play();
setPlayingAudio(true);
}
};
const handleStopAudio = () => {
if (audioMessage) {
waveform.stop();
setPlayingAudio(false);
}
};
const handleChangeAudioRate = () => {
if (audioRate === 1) {
setAudioRate(1.5);
waveform.setPlaybackRate(1.5);
} else if (audioRate === 1.5) {
setAudioRate(2);
waveform.setPlaybackRate(2);
} else {
setAudioRate(1);
waveform.setPlaybackRate(1);
}
};
const formatTime = (time) => {
if (isNaN(time)) return "00:00";
const minutes = Math.floor(time / 60);
const seconds = Math.floor(time % 60);
return `${minutes.toString().padStart(2, "0")} : ${seconds
.toString()
.padStart(2, "0")}`;
};
useEffect(() => {
if (waveform) {
const updatePlaybackTime = () => {
setCurrentPlaybackTime(waveform.getCurrentTime());
};
waveform.on("audioprocess", updatePlaybackTime);
waveform.on("finish", () => {
setCurrentPlaybackTime(0);
});
return () => {
waveform.un("audioprocess", updatePlaybackTime);
waveform.un("finish");
};
}
}, [waveform]);
return (
<div
className={`flex ${
message.sender !== user.userInfo._id ? "justify-start" : "justify-end"
} mb-2`}
>
<div className="relative max-w-md p-5">
<div
className={`p-1 rounded-xl shadow-lg break-words ${
message.sender !== user.userInfo._id
? "bg-secondary text-secondary-foreground rounded-bl-none"
: "bg-primary text-primary-foreground rounded-br-none"
}`}
>
<div className="p-2 rounded-xl">
<div className="flex items-center gap-4">
{audioMessage &&
(!playingAudio ? (
<div
className="cursor-pointer w-8"
onClick={handlePlayingAudio}
>
<FaPlay className="text-xl" />
</div>
) : (
<div className="cursor-pointer w-8" onClick={handleStopAudio}>
<FaPause className="text-xl" />
</div>
))}
<div className="flex flex-col w-52">
<div className="flex w-52 gap-2">
<div
className="w-40"
ref={waveFormRef}
id="waveformContainer"
></div>
<div className="flex justify-center w-10 rounded-full px-2 bg-slate-500/30">
<button className="text-sm" onClick={handleChangeAudioRate}>
{audioRate}x
</button>
</div>
</div>
<span className="text-xs">
{!playingAudio
? formatTime(totalDuration)
: formatTime(currentPlaybackTime)}
</span>
</div>
</div>
</div>
<div className="flex justify-between px-1 gap-8">
{message.sender === user.userInfo._id && (
<MessageStatus recipients={message.recipientStatuses} />
)}
<div
className={`text-xs text-gray-500 ${
message.sender !== user.userInfo._id ? "text-start" : "text-end"
}`}
>
{calculateTime(message.createdAt)}
</div>
</div>
</div>
</div>
</div>
);
};
export default VoiceMessage;
I’ve tried a few things like using refs and state to keep track of the playing instance, but I can’t seem to get the behavior right where the previous message stops when a new one is played. Could anyone guide me on how to implement this?