Skip to content
Advertisement

Java Stop Sound Button Not Working Correctly

So I have been making a stop button recently and have been wondering how can I stop a button sound instantly when already playing.

The problem : When you click the stop sound button, it only stops the next button you press.

What Im Trying to Achieve : When you click the stop sound button, it stops all playing sounds.

Here is the main button class sound :

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;

//JButtons Class
public class Buttons extends JButton implements ActionListener{
  private int locX = 0;
  private int locY = 0;

  //Sets the basic features of the buttons and adds an action listener
  public Buttons(String title){
    super(title);
    setBounds(locX,locY,100,100);
    setOpaque(true);
    setBorderPainted(false);
    setBorder(BorderFactory.createLineBorder(Color.WHITE));
    addActionListener(this);
  }

  //Sets the dimentions of the buttons
  public void setDimentions(int x, int y){
    this.locX = x;
    this.locY = y;
    setBounds(locX,locY,100,100);
  }

  //Maps button colors to sting values
  static Map<String, Color> colorMap = Map.ofEntries(Map.entry("WHITE", Color.WHITE), Map.entry("GRAY", Color.GRAY), Map.entry( "BLACK", Color.BLACK), Map.entry( "RED", Color.RED), Map.entry( "ORANGE", new Color(255,121,0)), Map.entry( "YELLOW", Color.YELLOW), Map.entry( "GREEN", Color.GREEN), Map.entry( "BLUE", Color.BLUE), Map.entry( "MAGENTA", Color.MAGENTA), Map.entry( "PINK", Color.PINK), Map.entry( "CYAN", Color.CYAN));

  //Gets the color from the map and returns it
  static Color getColor(String col){
    return colorMap.get(col.toUpperCase());
  }

  //Sets the color of the button and repaints it
  public void setColors(String colorBack, String colorFront){
    setBackground(getColor(colorBack));
    setForeground(getColor(colorFront));
    repaint();
  }

  public String[] listFilesForFolder(final File folder) {
    String[] f = new String[25];
    int count = 0;
    for(int i = 0; i < 25; i++){
      f[i] = "";
    }
    for (final File fileEntry : folder.listFiles()) {
        if (fileEntry.isDirectory()) {
            listFilesForFolder(fileEntry);
        } else {
            if(fileEntry.getName().equals(".DS_Store")){

            }else{
              f[count] = fileEntry.getName();
              count++;
            }
        }
    }
    return f;
  }

  public void playSound(String url, boolean loop, boolean stop){
    try{
      AudioInputStream audioIn = AudioSystem.getAudioInputStream(Launchpad.class.getResource("soundFiles/" + url));
      Clip clip = AudioSystem.getClip();
      clip.open(audioIn);
      clip.start();
      if(loop == true){
        clip.loop(Clip.LOOP_CONTINUOUSLY);
      }
      if(stop == true){
        stopSound(clip);
      }
    }
    catch(Exception e){
      System.out.println("Error");
    }
  }

  public void stopSound(Clip clip){
    if(clip.isActive()){
      clip.stop();
      clip.flush();
      clip.close();
    }
  }

  //Event Handler / Action Listener
  @Override
  public void actionPerformed(ActionEvent e){
    if(e.getSource() == this){
      String sNum = this.getText();
      int num = Integer.parseInt(sNum);
      final File folder = new File("/Users/ethanbowles/Desktop/idk/programing/java/Launchpad/soundFiles");
      String[] names =listFilesForFolder(folder);
      System.out.println(names[num - 1]);
      System.out.println(num);
      boolean fullStop = StopButton.stop;
      playSound(names[num - 1], LoopButton.loop, fullStop);
      StopButton.stop = false;
      LoopButton.loop = false;
    }
  }
}

Here is the main sound stop button :

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;

public class StopButton extends JButton implements ActionListener{
  public static boolean stop = false;
  public StopButton(){
    super("Stop");
    setBounds(10,10,100,50);
    addActionListener(this);
  }

  @Override
  public void actionPerformed(ActionEvent e){
    if(e.getSource() == this){
      if(stop == true){
        stop = false;
      }else{
        stop = true;
      }
      super.repaint();

    }
  }
}

Advertisement

Answer

The problem is due to the logic with the .stop field in one JButton modified by the other JButton. Also reusing playSound() to play or stop is not a good design.

Here is a solution with a much cleaner design with 2 buttons, one for play one for stop.

The MusicController is independent from the UI:

class MusicController
{
    // A property for the state of the controller
    public final static String PROP_STATE = "StateProperty";

    enum State
    {
        NOT_READY, STOPPED, PLAYING
    };
    State state = State.NOT_READY;
    boolean loop;
    
    // Manage property change listeners
    private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); 

    public void loadSound()
    {
        // Initialize music data, load clip from file etc.
        ...
        State oldState = state;        
        state = State.STOPPED;  // We can actually play a sound only from the STOPPED state
        pcs.firePropertyChange(PROP_STATE, oldState, state);
    }

    public State getState()
    {
        return state;
    }

    public void play()
    {
        switch (state)
        {
            case NOT_READY:
                // Error "Not ready"
                ...
                break;
            case STOPPED:
                // Start playback (looped if loop is true)
                ...
                State oldState = state;                
                state = State.PLAYING;
                pcs.firePropertyChange(PROP_STATE, oldState, state);   // Notify listeners
                break;
            case PLAYING:
                // Already playing, do nothing
                break;
            default:
                throw new AssertionError(state.name());
        }
    }

    public void stop()
    {
        // Same code structure than play(), but adapted to stop playback if current state is PLAYING.
        ...
    }

    public void addPropertyChangeListener(PropertyChangeListener listener)
    {
        pcs.addPropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener)
    {
        pcs.removePropertyChangeListener(listener);
    }

}

The View manages the UI and just listens to the MusicController state.

class View implements PropertyChangeListener
{

    JButton playButton, stopButton;
    MusicController controller = new MusicController();

    public View()
    {
        // Listen to music controller state changes
        controller.addPropertyChangeListener(this);

        // Create UI
        Action playAction = new AbstractAction("Play")
        {
            public void actionPerformed(ActionEvent ae)
            {
                controller.play();
            }
        };
        playButton = new JButton(playAction);
        
        // Same for stopButton with controller.stop();
        ...
                
        // Add buttons to UI etc.
        ...

        updateUI(controller.getState());
    }

    /**
     * Update the user interface depending on the music controller state.
     *
     * @param state
     */
    private void updateUI(State state)
    {
        switch (state)
        {
            case NOT_READY:
                playButton.setEnabled(false);
                stopButton.setEnabled(false);
                break;
            case STOPPED:
                playButton.setEnabled(true);
                stopButton.setEnabled(false);
                break;
            case PLAYING:
                playButton.setEnabled(false);
                stopButton.setEnabled(true);
                break;
            default:
                throw new AssertionError(state.name());
        }
    }

    /**
     * Called when a MusicController property has changed.
     */
    public void propertyChange(PropertyChangeEvent evt)
    {
        if (evt.getSource() == controller && MusicController.PROP_STATE.equals(evt.getPropertyName()))
        {
            // State has changed, update UI accordingly
            State state = (State) evt.getNewValue();
            updateUI(state);
        }
    }

}
Advertisement