Type of Change
Problem
MoodSongs.jsx has a fundamental broken audio architecture with two symptoms
that together make the audio player non-functional:
Bug 1 — Multiple songs can play at the same time, silently
isPlaying tracks a single index, but the audio elements are conditionally
mounted — they appear in the DOM when isPlaying === index and disappear
otherwise. Switching from song A to song B unmounts A's <audio> element and
mounts B's. Because A's element is removed from the DOM, its playback never
stops — the browser keeps streaming and playing it until the garbage collector
eventually cleans it up. A user clicking through songs quickly can stack several
playing at once.
Bug 2 — The "pause" button does not actually pause
When the user clicks again on the active song, setIsPlaying(null) is called.
This unmounts the <audio> element entirely rather than calling
audioElement.pause(). The audio continues playing even though the UI now
shows the play icon again. There is no real pause — only "hide and forget".
Root Cause
// Current — conditional mount instead of imperative control
{isPlaying === index && (
<audio
src={song.audio}
className="hidden"
autoPlay={isPlaying === index}
></audio>
)}
<button onClick={() => handlePlayPause(index)}>
{isPlaying === index ? <i className="ri-pause-line" /> : <i className="ri-play-circle-fill" />}
</button>
Mounting/unmounting <audio> for play/pause is the wrong model. The correct
approach is a persistent <audio> element controlled imperatively via
audioRef.current.play() / audioRef.current.pause().
Fix
Replace the per-song conditional <audio> mount with a single persistent
<audio> ref that is controlled imperatively. The ref is stable across
renders so pause() actually works.
import { useState, useRef, useEffect } from 'react';
const MoodSongs = ({ Songs }) => {
const [playingIndex, setPlayingIndex] = useState(null);
const audioRef = useRef(new Audio());
// When the playing index changes, swap the src and play/pause accordingly
useEffect(() => {
const audio = audioRef.current;
if (playingIndex === null) {
audio.pause();
return;
}
const song = Songs[playingIndex];
if (!song) return;
// Only reload src if it's actually a different song
if (audio.src !== song.audio) {
audio.src = song.audio;
audio.load();
}
audio.play().catch((err) => {
console.error('Playback failed:', err);
setPlayingIndex(null);
});
// When the song ends naturally, reset the UI
const handleEnded = () => setPlayingIndex(null);
audio.addEventListener('ended', handleEnded);
return () => audio.removeEventListener('ended', handleEnded);
}, [playingIndex, Songs]);
// Stop audio when component unmounts
useEffect(() => {
const audio = audioRef.current;
return () => {
audio.pause();
audio.src = '';
};
}, []);
const handlePlayPause = (index) => {
setPlayingIndex((prev) => (prev === index ? null : index));
};
return (
<div className="flex justify-center flex-col items-center mood-songs p-5 w-full pt-0 text-amber-200 font-mono text-lg">
<h2 className="mb-4 text-3xl">Recommended Songs</h2>
{Songs.map((song, index) => (
<div className="flex w-full justify-center mx-0 my-1.5" key={song._id ?? index}>
<div className="title">
<h3>{song.title}</h3>
<p>{song.artist}</p>
</div>
<div className="play-pause-button">
<button onClick={() => handlePlayPause(index)}>
{playingIndex === index ? (
<i className="ri-pause-line"></i>
) : (
<i className="ri-play-circle-fill"></i>
)}
</button>
</div>
</div>
))}
</div>
);
};
export default MoodSongs;
File changed: frontend/src/components/MoodSongs.jsx
Behaviour after this fix
| Action |
Before |
After |
| Click play on Song A |
Plays |
Plays |
| Click play on Song B while A is playing |
Both play simultaneously |
A stops, B starts |
| Click pause on active song |
Song keeps playing, icon flips |
Song actually pauses |
| Song reaches end |
UI stays on pause icon forever |
UI resets to play icon |
| Component unmounts |
Audio keeps playing |
Audio stops cleanly |
How to Test
- Load songs and click play on song A — confirm it plays
- While A is playing, click song B — confirm A stops and B starts (no overlap)
- Click the pause button on the active song — confirm audio actually stops
- Let a song play to its end — confirm the button resets to the play icon
- Navigate away while a song is playing — confirm audio stops
Checklist
PLEASE ASSIGN THIS ISSUE TO ME UNDER SSOC'26.
Type of Change
Problem
MoodSongs.jsxhas a fundamental broken audio architecture with two symptomsthat together make the audio player non-functional:
Bug 1 — Multiple songs can play at the same time, silently
isPlayingtracks a single index, but the audio elements are conditionallymounted — they appear in the DOM when
isPlaying === indexand disappearotherwise. Switching from song A to song B unmounts A's
<audio>element andmounts B's. Because A's element is removed from the DOM, its playback never
stops — the browser keeps streaming and playing it until the garbage collector
eventually cleans it up. A user clicking through songs quickly can stack several
playing at once.
Bug 2 — The "pause" button does not actually pause
When the user clicks again on the active song,
setIsPlaying(null)is called.This unmounts the
<audio>element entirely rather than callingaudioElement.pause(). The audio continues playing even though the UI nowshows the play icon again. There is no real pause — only "hide and forget".
Root Cause
Mounting/unmounting
<audio>for play/pause is the wrong model. The correctapproach is a persistent
<audio>element controlled imperatively viaaudioRef.current.play()/audioRef.current.pause().Fix
Replace the per-song conditional
<audio>mount with a single persistent<audio>ref that is controlled imperatively. The ref is stable acrossrenders so
pause()actually works.File changed:
frontend/src/components/MoodSongs.jsxBehaviour after this fix
How to Test
Checklist
audioRef.current.pause()indextosong._idwhere available (avoids React reorder bugs)PLEASE ASSIGN THIS ISSUE TO ME UNDER SSOC'26.