当我在另一个应用程序中输入时,我的 Java 程序没有发出声音
My Java program doesn't produce an audible sound when I'm typing in another application
我用 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。
代码间歇性工作的事实加上问题代码中缺少 thread-safety 让我相信你有竞争条件。基本上,发生的事情是播放声音的线程不是更改 playable
和 done
等字段的线程。因此,一个线程对这些字段所做的更改不会被另一个线程观察到。因此,我将上面的代码重构为 thread-safe.
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);
}
}
请注意,我已使用 class 等 AtomicBoolean
来确保 thread-safety 字段 playable
和 done
以便更改一个线程对其他线程是可见的。
Thread-safety 问题也可能出现,因为由一个线程创建的 clip
和 stream
的实例可能无法被其他线程观察到。为了解决这个问题,我将这些字段设置为 final
以保证它们只有一个引用。 Java 的 Optional
class 也用于将 clip
和 stream
设置为比 null
.
更安全的值
希望对您有所帮助。有问题可以评论。
我用 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。
代码间歇性工作的事实加上问题代码中缺少 thread-safety 让我相信你有竞争条件。基本上,发生的事情是播放声音的线程不是更改 playable
和 done
等字段的线程。因此,一个线程对这些字段所做的更改不会被另一个线程观察到。因此,我将上面的代码重构为 thread-safe.
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);
}
}
请注意,我已使用 class 等 AtomicBoolean
来确保 thread-safety 字段 playable
和 done
以便更改一个线程对其他线程是可见的。
Thread-safety 问题也可能出现,因为由一个线程创建的 clip
和 stream
的实例可能无法被其他线程观察到。为了解决这个问题,我将这些字段设置为 final
以保证它们只有一个引用。 Java 的 Optional
class 也用于将 clip
和 stream
设置为比 null
.
希望对您有所帮助。有问题可以评论。