我正在做的简化版本(当计时器到期时触发蜂鸣器声音),但这足以证明我的设计。
开始播放后,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());
}
}
答案 0 :(得分:1)
我已经从使用Clips
的所有内容中删除了一些内容,因为它们带来了许多恼人的方面。
但是,正如评论中所提到的那样,您应该在程序开始时一次初始化(打开)每个Clip
一次。然后,每次需要时重新启动Clip
。 API将显示将Clip
重置为其起始帧并重播的确切命令。
管理线程是令人厌烦的。如果我理解正确,Clip
会启动一个守护程序线程来实现音频输出。父线程终止时,守护程序线程会死亡。因此,您可以通过放入 Thread.sleep()命令和LineListener
来保持线程在SFX的持续时间内保持活动状态,因此它的守护进程不会被扼杀父线程终止。
我担心,如果你使用这种方法,调用线程(按下按钮启动的代码)在你的" sleep"期间无法做任何其他事情。期。如果按钮代码只是执行播放,那么你应该没问题。但是如果你以后决定用相同的按钮按下(比如蜂鸣器动画)还有什么东西可以触发怎么办?可能有必要添加另一层复杂功能,例如将剪辑包装到另一个线程中,该线程的唯一责任是剪辑播放。然后按下按钮就可以启动这个包装器并执行其他操作,而不受Thread.sleep()解决方案的限制。
现在,应该可以避免这一切!我们假设你做了一个Clip
名为的蜂鸣器,并在程序的早期打开它,将其保存在一个实例变量中(如Andrew所建议)。然后,让我们说你从一个无限期保持活着的线程中调用 buzzer.start(),例如经典的游戏循环。我想在这里,只要游戏循环线程继续存在,Clip start()就会启动它的守护程序线程并进行游戏。通过这种方式,您可以省去睡眠和听线。但是,如果没有实际编写示例并亲自尝试,我并不是百分之百确定。
(按钮线程如何告诉游戏循环线程播放声音?也许按钮线程设置一个标志,游戏循环线程检查该标志作为其正常"更新"的一部分。嗯。也许我真的需要在提供它之前尝试编码。)
作为替代方案,请随意检查代码,并可能使用AudioCue。它与加载和播放中的Clip
类似,但在其下方使用SourceDataLine
代替Clip
。如果您查看代码,您将看到SourceDataLine
在其自己的线程中维护(它作为正在打开的AudioCue
的一部分保持活动状态)。这消除了很多复杂情况。
这种方法带来的一个很好的优势是它可以提供并发播放 - 例如,如果你想在第一个蜂鸣器仍在播放时启动第二个蜂鸣器实例。 AudioCue
还允许您执行诸如以不同速度播放蜂鸣器之类的操作,这可能有助于将SFX充当听起来像多个提示。我尽力提供文档和示例。该许可证允许您剪切和粘贴代码,以满足您的特定需求。