javaswingaudiojbuttonjavasound

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();

    }
  }
}


Solution

  • 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);
            }
        }
    
    }