我用 Swing 编写了一个相当简单的时钟和计时器程序。计时器似乎工作正常。它以适当的速率倒计时秒、分钟和可能的小时(尽管我还没有测试过),并且当时间到期时,它会播放一个 *.wav 文件来让您知道。问题是,如果程序在后台运行,则并不总是能听到该声音。我刚才做了一些测试,如果我只是在 Firefox 中阅读网页,我可以正常听到声音,但是当我输入一些内容(例如 URL)时,声音似乎根本不播放。
这是我的 Java 代码的问题吗——我这个周末才开始使用 Java 处理声音——还是操作系统的问题?我使用的是 OpenJDK 11 和 Ubuntu 18.04。
这是(我希望是)相关的代码。
class ClipPlayer implements LineListener {
boolean playable;
boolean done;
AudioInputStream stream;
Clip clip;
public ClipPlayer(String fileName) {
playable = false;
done = false;
stream = null;
clip = null;
File source = new File(fileName);
boolean proceed = source.exists();
if (proceed) {
try {
stream = AudioSystem.getAudioInputStream(source);
} catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
proceed = false;
}
}
if (proceed) {
AudioFormat format = stream.getFormat();
DataLine.Info info = new DataLine.Info(Clip.class,format);
try {
clip = (Clip)AudioSystem.getLine(info);
clip.addLineListener(this);
} catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
proceed = false;
}
}
playable = proceed;
}
public void close() {
try {
clip.close();
stream.close();
} catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
System.exit(1);
}
}
public void open() {
if (playable) {
try {
clip.open(stream);
} catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
System.exit(1);
}
} else {
System.out.println("The file cannot be played.");
}
}
public void play() {
if (playable && !done) {
try {
clip.start();
while (!done) {
try {
Thread.sleep(1000);
} catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
System.exit(1);
}
}
} catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
System.exit(1);
}
} else if (!playable) {
System.out.println("The file cannot be played.");
} else if (done) {
System.out.println("The player must be reset before it can be played again.");
}
}
public void repeat(int n) {
for (int i = 0; i < n; i++) {
play();
reset();
}
}
public void reset() {
done = false;
clip.setFramePosition(0);
}
@Override
public void update(LineEvent event) {
if (event.getType() == LineEvent.Type.STOP) {
done = true;
}
}
}
class Timer extends JPanel implements ActionListener {
private static final int MILLISECONDS_PER_SECOND = 1000;
private static final int MILLISECONDS_PER_MINUTE = 60*MILLISECONDS_PER_SECOND;
private static final int MILLISECONDS_PER_HOUR = 60*MILLISECONDS_PER_MINUTE;
private static final int HOURS = 0;
private static final int MINUTES = 1;
private static final int SECONDS = 2;
private static final int RESET = 0;
private static final int PAUSE = 1;
long timeRemaining;
boolean finished;
boolean paused;
JSpinner[] spinners;
JButton[] buttons;
JLabel remainderLabel;
ClipPlayer player;
public Timer() {
timeRemaining = 0;
finished = true;
paused = true;
player = new ClipPlayer("/example/path/file.wav");
spinners = new JSpinner[3];
spinners[HOURS] = new JSpinner(new SpinnerNumberModel(0,0,99,1));
spinners[MINUTES] = new JSpinner(new SpinnerNumberModel(0,0,60,1));
spinners[SECONDS] = new JSpinner(new SpinnerNumberModel(0,0,60,1));
buttons = new JButton[2];
buttons[RESET] = new JButton("Begin");
buttons[PAUSE] = new JButton("Pause");
buttons[PAUSE].setEnabled(false);
for (int i = 0; i < 2; i++) {
buttons[i].addActionListener(this);
buttons[i].setActionCommand("Button_" + i);
}
remainderLabel = new JLabel();
// Layout code omitted for brevity
}
// actionPerformed method omitted for brevity
public void initialize() {
Object h = spinners[HOURS].getValue();
Object m = spinners[MINUTES].getValue();
Object s = spinners[SECONDS].getValue();
timeRemaining = ((Integer)h)*MILLISECONDS_PER_HOUR;
timeRemaining += ((Integer)m)*MILLISECONDS_PER_MINUTE;
timeRemaining += ((Integer)s)*MILLISECONDS_PER_SECOND;
if (timeRemaining > 0) {
finished = false;
paused = false;
buttons[RESET].setText("Reset");
buttons[PAUSE].setEnabled(true);
} else {
finished = true;
paused = false;
}
}
public void update(long dt) {
if (!finished && !paused) {
timeRemaining -= dt;
if (timeRemaining > 0) {
long hours = timeRemaining/MILLISECONDS_PER_HOUR;
long r = timeRemaining - hours*MILLISECONDS_PER_HOUR;
long minutes = r/MILLISECONDS_PER_MINUTE;
r -= minutes*MILLISECONDS_PER_MINUTE;
long seconds = r/MILLISECONDS_PER_SECOND;
StringBuilder sb = new StringBuilder();
sb.append(hours);
sb.append(":");
if (minutes < 10) sb.append("0");
sb.append(minutes);
sb.append(":");
if (seconds < 10) sb.append("0");
sb.append(seconds);
remainderLabel.setText(sb.toString());
} else {
finished = true;
buttons[RESET].setText("Begin");
buttons[PAUSE].setEnabled(false);
remainderLabel.setText("0:00:00");
System.out.println("Time's up!");
player.open();
player.repeat(3);
player.close();
}
}
}
}
public class Time implements Runnable {
JFrame frame;
Clock clock;
Timer timer;
// Constructor and main method omitted for brevity
public void run() {
long t0 = System.currentTimeMillis();
boolean running = true;
while (running) {
long t1 = System.currentTimeMillis();
clock.update(t1);
timer.update(t1-t0);
t0 = t1;
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
System.out.println("Interrupted Exception: " + ie.getMessage());
System.exit(1);
}
}
System.exit(0);
}
}
正如我所说,该程序在大多数情况下都可以正常运行。问题是当程序在后台运行时并不总是能听到声音。我应该注意到,即使在后台运行,计时器也会正确倒计时。当我意识到我错过了返回 Java 程序的声音和 alt-tab 时,计时器显示 0:00:00,正如预期的那样。
最佳答案
代码间歇性地工作,加上问题中的代码缺乏线程安全性,这一事实使我相信您存在竞争条件。基本上,发生的情况是播放声音的线程不是更改诸如 playable
和 done
等字段的线程。因此,一个线程对这些字段所做的更改将不会被另一个线程观察到。因此,我重构了上面的代码,使其变得线程安全。
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
import javax.swing.*;
import java.awt.event.ActionListener;
import java.io.File;
import java.time.Clock;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
class ClipPlayer implements LineListener {
private final AtomicBoolean playable;
private final AtomicBoolean done;
private final Optional<AudioInputStream> stream;
private final Optional<Clip> clip;
public ClipPlayer(String fileName) {
playable = new AtomicBoolean(false);
done = new AtomicBoolean(false);
File source = new File(fileName);
boolean proceed = source.exists();
stream = createAudioInputStream(source, proceed);
if (stream == Optional.<AudioInputStream>empty()) {
proceed = false;
}
clip = createClip(stream.get(), proceed);
if (clip == Optional.<Clip>empty()) {
proceed = false;
}
playable.set(proceed);
}
private Optional<Clip> createClip(AudioInputStream stream, boolean proceed) {
if (!proceed) {
return Optional.empty();
}
AudioFormat format = stream.getFormat();
DataLine.Info info = new DataLine.Info(Clip.class,format);
try {
Clip clip = (Clip)AudioSystem.getLine(info);
clip.addLineListener(this);
return Optional.of(clip);
} catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
return Optional.empty();
}
}
private static Optional<AudioInputStream> createAudioInputStream(File source, boolean proceed) {
if (!proceed) {
return Optional.empty();
}
try {
AudioInputStream stream = AudioSystem.getAudioInputStream(source);
Optional<AudioInputStream> optionalStream = Optional.ofNullable(stream);
return optionalStream;
} catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
return Optional.empty();
}
}
public void close() {
try {
if (clip.isPresent()) {
clip.get().close();
}
if (stream.isPresent()) {
stream.get().close();
}
} catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
System.exit(1);
}
}
public void open() {
if (playable.get()) {
try {
clip.get().open(stream.get());
} catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
System.exit(1);
}
} else {
System.out.println("The file cannot be played.");
}
}
public void play() {
if (playable.get() && !done.get()) {
try {
clip.start();
while (!done.get()) {
try {
Thread.sleep(1000);
} catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
System.exit(1);
}
}
} catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
System.exit(1);
}
} else if (!playable.get()) {
System.out.println("The file cannot be played.");
} else if (done.get()) {
System.out.println("The player must be reset before it can be played again.");
}
}
public void repeat(int n) {
for (int i = 0; i < n; i++) {
play();
reset();
}
}
public void reset() {
done.set(false);
if (clip.isPresent()) {
clip.get().setFramePosition(0);
}
}
@Override
public void update(LineEvent event) {
if (event.getType() == LineEvent.Type.STOP) {
done.set(true);
}
}
}
class Timer extends JPanel implements ActionListener {
private static final int MILLISECONDS_PER_SECOND = 1000;
private static final int MILLISECONDS_PER_MINUTE = 60*MILLISECONDS_PER_SECOND;
private static final int MILLISECONDS_PER_HOUR = 60*MILLISECONDS_PER_MINUTE;
private static final int HOURS = 0;
private static final int MINUTES = 1;
private static final int SECONDS = 2;
private static final int RESET = 0;
private static final int PAUSE = 1;
private final AtomicLong timeRemaining;
private final AtomicBoolean finished;
private final AtomicBoolean paused;
private final JSpinner[] spinners;
private final JButton[] buttons;
private final JLabel remainderLabel;
private final ClipPlayer player;
public Timer() {
this.timeRemaining = new AtomicLong(0L);
this.finished = new AtomicBoolean(true);
paused = new AtomicBoolean(true);
player = new ClipPlayer("/example/path/file.wav");
spinners = new JSpinner[3];
spinners[HOURS] = new JSpinner(new SpinnerNumberModel(0,0,99,1));
spinners[MINUTES] = new JSpinner(new SpinnerNumberModel(0,0,60,1));
spinners[SECONDS] = new JSpinner(new SpinnerNumberModel(0,0,60,1));
buttons = new JButton[2];
buttons[RESET] = new JButton("Begin");
buttons[PAUSE] = new JButton("Pause");
buttons[PAUSE].setEnabled(false);
for (int i = 0; i < 2; i++) {
buttons[i].addActionListener(this);
buttons[i].setActionCommand("Button_" + i);
}
remainderLabel = new JLabel();
// Layout code omitted for brevity
}
// actionPerformed method omitted for brevity
public void initialize() {
Object h = spinners[HOURS].getValue();
Object m = spinners[MINUTES].getValue();
Object s = spinners[SECONDS].getValue();
timeRemaining.set(((Integer)h)*MILLISECONDS_PER_HOUR);
timeRemaining.addAndGet(((Integer)m)*MILLISECONDS_PER_MINUTE);
timeRemaining.addAndGet(((Integer)s)*MILLISECONDS_PER_SECOND);
if (timeRemaining.get() > 0) {
this.finished.set(false);
paused.set(false);
buttons[RESET].setText("Reset");
buttons[PAUSE].setEnabled(true);
} else {
this.finished.set(true);
paused.set(false);
}
}
public void update(long dt) {
if (!finished.get() && !paused.get()) {
timeRemaining.addAndGet(-1 * dt);
if (timeRemaining.get() > 0) {
long hours = timeRemaining.get()/MILLISECONDS_PER_HOUR;
long r = timeRemaining.get() - hours*MILLISECONDS_PER_HOUR;
long minutes = r/MILLISECONDS_PER_MINUTE;
r -= minutes*MILLISECONDS_PER_MINUTE;
long seconds = r/MILLISECONDS_PER_SECOND;
StringBuilder sb = new StringBuilder();
sb.append(hours);
sb.append(":");
if (minutes < 10) sb.append("0");
sb.append(minutes);
sb.append(":");
if (seconds < 10) sb.append("0");
sb.append(seconds);
remainderLabel.setText(sb.toString());
} else {
finished.set(true);
buttons[RESET].setText("Begin");
buttons[PAUSE].setEnabled(false);
remainderLabel.setText("0:00:00");
System.out.println("Time's up!");
player.open();
player.repeat(3);
player.close();
}
}
}
}
public class Time implements Runnable {
private final JFrame frame;
private final Clock clock;
private final Timer timer;
// Constructor and main method omitted for brevity
public void run() {
long t0 = System.currentTimeMillis();
boolean running = true;
while (running) {
long t1 = System.currentTimeMillis();
clock.update(t1);
timer.update(t1-t0);
t0 = t1;
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
System.out.println("Interrupted Exception: " + ie.getMessage());
System.exit(1);
}
}
System.exit(0);
}
}
请注意,我使用了类似 AtomicBoolean
的类确保 playable
和 done
等字段的线程安全,以便一个线程的更改可以被其他线程观察到。
还可能出现线程安全问题,因为由一个线程创建的 clip
和 stream
实例可能无法被其他线程观察到。为了解决这个问题,我将这些字段设为final
,以便保证它们具有单个引用。 Java 的 Optional
类还用于将 clip
和 stream
设置为比 null
更安全的值。
希望有帮助。如果您有任何疑问,请评论。
关于java - 当我在另一个应用程序中键入内容时,我的 Java 程序不会发出声音,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57041701/