我在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选项卡时,计时器将按预期显示0:00:00。
答案 0 :(得分:0)
问题是代码断断续续地起作用,加上代码中缺乏线程安全性,这使我相信您处于竞争状态。基本上,正在发生的事情是,播放声音的线程不是更改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
之类的字段的线程安全性,以便一个线程的更改可被其他线程观察到。 / p>
由于一个线程创建的clip
和stream
实例可能无法被其他线程观察到,因此也可能引起线程安全问题。为了解决这个问题,我将这些字段设为final
,以确保它们具有单个引用。 Java的Optional
类还用于将clip
和stream
设置为比null
更安全的值。
希望有帮助。如有任何疑问,请发表评论。