简体   繁体   中英

Playing Audio with Swing - is my code thread safe?

Simplified version of what I'm actually doing (triggering a buzzer sound when a timer expires), but this demonstrates my design well enough.

Once playback is started, Swing never needs to touch the audio clip again. I have been able to confirm that this code does play back the sound and does not block the event dispatch thread, but I want to make sure there isn't some other thread safety concern that I am unknowingly violating. Thanks!

import java.io.IOException;
import java.net.URL;

import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class TriggeringSoundTest extends JFrame
{
    private static final long serialVersionUID = -4573437469199690850L;

    private boolean soundPlaying = false;

    public TriggeringSoundTest()
    {
        JButton button = new JButton("Play Sound");
        button.addActionListener(e -> new Thread(() -> playBuzzer()).start());
        getContentPane().add(button);
    }

    private void playBuzzer()
    {
        URL url = getClass().getResource("resources/buzzer.wav");
        try (AudioInputStream stream = AudioSystem.getAudioInputStream(url); 
                Clip clip = AudioSystem.getClip())
        {
            clip.open(stream);
            clip.addLineListener(e -> {
                if (e.getType() == LineEvent.Type.STOP)
                    soundPlaying = false;
            });
            soundPlaying = true;
            clip.start();
            while (soundPlaying)
            {
                try
                {
                    Thread.sleep(1000);
                }
                catch (InterruptedException exp)
                {
                    // TODO Auto-generated catch block
                    exp.printStackTrace();
                }
            }
        }
        catch (UnsupportedAudioFileException exp)
        {
            // TODO Auto-generated catch block
            exp.printStackTrace();
        }
        catch (IOException exp)
        {
            // TODO Auto-generated catch block
            exp.printStackTrace();
        }
        catch (LineUnavailableException exp)
        {
            // TODO Auto-generated catch block
            exp.printStackTrace();
        }
    }

    private static void createAndShowGUI()
    {
        JFrame frame = new TriggeringSoundTest();
        frame.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(() -> createAndShowGUI());
    }
}

I've gotten kind of removed from all the in's and out's of working with Clips , as they come with so many annoying aspects.

But yes, as mentioned in the comments, you should initialize (open) each Clip once and only once at the beginning of your program. Then, restart the Clip each time you need it. The API will show you the exact commands for resetting the Clip to its starting frame and replaying it.

Managing the threads is irksome. If I understand correctly, Clip launches a daemon thread for the actual audio out. Daemon threads die when the parent thread terminates. Hence your solution of putting in the Thread.sleep() command and the LineListener to keep the thread alive for the duration of the SFX, so its daemon doesn't get chomped when parent thread terminates.

I worry though, that if you use this approach, the calling thread (the code launched by the button press) won't be able to do anything else during your "sleep" period. If the button code is only executing the playback, then you should be fine. But what if you later decide to have anything else be triggered with the same button press (like a buzzer animation)? It might become necessary to add another layer of complication, such as wrapping the clip in yet another thread whose only responsibility is the clip playback. Then your button press could launch this wrapper and do other things without itself being subject to the Thread.sleep() solution.

Now, it should be possible to avoid all this! Let's say you made a Clip called buzzer and opened it early in your program, holding it in an instance variable (as suggested by Andrew). Then, let's say you call the buzzer.start() from within a thread that stays alive indefinitely, such as a classic game loop. I think in this the Clip start() will launch it's daemon thread and play as long as that game loop thread continues to live. In this way, you would be able to dispense with the sleep and line-listening. But I'm not 100% certain without actually writing an example and trying it myself.

(How does the button thread tell the game-loop thread to play the sound? Perhaps the button thread sets a flag, and the game-loop thread inspects that flag as part of its normal "update" cycle. Hmm. Maybe I really do need to try coding this before offering it as advice.)

As an alternative, feel free to examine the code, and to possibly make use of AudioCue . It is similar to a Clip in loading and playing, but underneath it makes use of SourceDataLine for the output instead of Clip . If you look at the code, you will see the SourceDataLine being maintained in its own thread (which stays alive as part of the AudioCue being opened). This eliminates a lot of complication.

A nice advantage that comes with this approach is that it makes it possible to provide for concurrent playbacks--for example if you want to start a second instance of a buzzer while the first is still playing. AudioCue also allows you to do things like play your buzzer at different speeds, which might be useful for leveraging your SFX into sounding like multiple cues. I did my best to provide documentation and examples. The license allows you to cut and paste the code as best works for your particular needs.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM