Skip to content

[Bug] broken audio playback — multiple songs play simultaneously + no actual pause #17

@OmanshiRaj

Description

@OmanshiRaj

Type of Change

  • Bug fix (non-breaking change which fixes an issue)

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

  1. Load songs and click play on song A — confirm it plays
  2. While A is playing, click song B — confirm A stops and B starts (no overlap)
  3. Click the pause button on the active song — confirm audio actually stops
  4. Let a song play to its end — confirm the button resets to the play icon
  5. Navigate away while a song is playing — confirm audio stops

Checklist

  • Only one song plays at a time
  • Pause actually pauses playback via audioRef.current.pause()
  • Song end is handled — UI resets automatically
  • Audio stops on component unmount
  • Key changed from index to song._id where available (avoids React reorder bugs)
  • No new dependencies

PLEASE ASSIGN THIS ISSUE TO ME UNDER SSOC'26.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions