Java 使用Swing播放音频-我的代码线程安全吗?

Java 使用Swing播放音频-我的代码线程安全吗?,java,swing,audio,thread-safety,javasound,Java,Swing,Audio,Thread Safety,Javasound,当计时器过期时,我实际上正在做的事情的简化版本会触发蜂鸣器声音,但这充分说明了我的设计 播放开始后,Swing再也不需要触摸音频剪辑。我已经能够确认这段代码确实播放了声音,并且没有阻止事件调度线程,但是我想确保没有其他一些我在不知不觉中违反的线程安全问题。谢谢 import java.io.IOException; import java.net.URL; import javax.sound.sampled.AudioInputStream; import javax.sound.sampl

当计时器过期时,我实际上正在做的事情的简化版本会触发蜂鸣器声音,但这充分说明了我的设计

播放开始后,Swing再也不需要触摸音频剪辑。我已经能够确认这段代码确实播放了声音,并且没有阻止事件调度线程,但是我想确保没有其他一些我在不知不觉中违反的线程安全问题。谢谢

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

我已经从处理剪辑的所有进进出出中解脱出来,因为它们有很多令人讨厌的方面

但是,是的,正如评论中提到的,您应该初始化打开每个剪辑一次,并且在程序开始时只打开一次。然后,在每次需要时重新启动剪辑。API将向您显示用于将剪辑重置为起始帧并重放它的确切命令

管理线程是令人厌烦的。如果我理解正确,Clip将为实际的音频输出启动一个守护进程线程。守护进程线程在父线程终止时死亡。因此,您的解决方案是加入Thread.sleep命令和LineListener,以使线程在SFX期间保持活动状态,这样当父线程终止时,它的守护进程就不会被选中

但是我担心,如果你使用这种方法,在你的睡眠期间,通过按键启动的代码调用线程将不能做任何其他事情。如果按钮代码只是执行回放,那么您应该没事。但是,如果你以后决定用相同的按钮来触发其他任何东西,比如蜂鸣器动画呢?可能需要添加另一层复杂的内容,例如用另一个线程包装剪辑,该线程的唯一职责是播放剪辑。然后按下按钮可以启动此包装器并执行其他操作,而不受Thread.sleep解决方案的约束

现在,应该可以避免这一切了!假设您制作了一个名为蜂鸣器的剪辑,并在程序的早期将其打开,将其保存在Andrew建议的实例变量中。然后,让我们假设您调用蜂鸣器。从一个无限期保持活动的线程开始,例如一个经典的游戏循环。我认为在这个剪辑开始将启动它的守护进程线程和发挥,只要游戏循环线程继续存在。这样,你们就可以省去睡眠和在线收听。但是如果我没有写一个例子并亲自尝试的话,我不是100%肯定

按钮线程如何告诉游戏循环线程播放声音?也许按钮线程设置了一个标志,游戏循环线程在其正常更新周期中检查该标志。嗯。也许我真的需要在提供建议之前尝试编码这个

作为一种选择,您可以随意检查代码,并尽可能利用它。它类似于加载和播放中的剪辑,但在其下面使用SourceDataLine作为输出,而不是剪辑。如果您查看代码,您将看到SourceDataLine被维护在它自己的线程中,该线程作为正在打开的AudioCue的一部分保持活动状态。这就消除了许多复杂问题


这种方法的一个很好的优点是,它可以提供并发回放,例如,如果您想在第一个蜂鸣器仍在播放时启动第二个蜂鸣器实例。AudioCue还允许您以不同的速度播放蜂鸣器,这可能有助于利用您的SFX使其听起来像多个提示。我尽力提供文档和示例。许可证允许您剪切和粘贴代码,以满足您的特殊需要。

w.r.t.线程看起来不错。你最好先预装一段视频,这样它会播放得更快一些。而且,等待声音播放变为假似乎完全没有必要。那是干什么用的?如果您想在声音播放后执行某些操作,请将其放置在匿名LineListener中。@hendrik您最好以某种方式预先加载该剪辑,例如,将其声明为类的属性,并在构造时读取一次。我曾想过尝试更早地加载该剪辑,但我不知道如果我这样做,如何确保它正确关闭,因为Java不允许析构函数。另外,剪辑已经被一条线包裹起来,它唯一的责任就是剪辑的播放。更新:关机钩子看到了,我丢失的那一块。这允许我将剪辑和音频输入流作为静态字段加载,并在关机挂钩中关闭它们。这也消除了对包装线程和thread.sleep业务的需求,正如Phil所述-守护进程线程中播放的音频不会阻止事件调度线程,并且不需要睡眠,因为线程不会死亡。FWIW,使用JavaFX GUI,存在stage.setOnCloseRequest以实现干净关机。如果我有什么东西
sts就像一个为音频提供线程的执行者,我在那个时候关闭它。但我不清楚当你说“适当接近”时,你的意图是什么。如果让GC在Clip变量超出范围时处理它是不够的,则可以始终将Clip变量设置为null或重新初始化它。AudioInputStream数据源是一个潜在的内存泄漏,应该关闭,但我认为一旦成功打开剪辑,就不再需要ais了。我可能错了,只是在我的测试程序中检查了这个。AIS可以在剪辑打开后立即关闭,它实际上可以位于我打开剪辑的静态初始值设定项块的本地。Re:正确地关闭,我想既然Clip有一个close方法,我应该确保调用它,即使对于您来说,这可能不是严格必要的。另外,我的GUI是Swing而不是JavaFX。你知道有没有类似于你提到的JavaFX命令的Swing,或者如果我使用Swing,我仍然在寻找一个关机钩子吗?谢谢你对AIS的验证!我花了一些时间在几年前用sound编写的Swing应用程序中查找,但我找不到您要查找的内容。我为这个程序提供的唯一关机功能是右上角的关闭按钮,但我找不到任何与此相关的功能。所以现在,我很困惑。幸运的是,在这个程序中,一切都会正常关闭。我再四处看看。我记得在Java-gaming.org论坛上有一个话题。