Skip to content

Commit 04e0fe0

Browse files
committed
8356049: Need a simple way to play back a sound clip
Reviewed-by: serb, aivanov, kizune
1 parent 83a2804 commit 04e0fe0

File tree

9 files changed

+496
-11
lines changed

9 files changed

+496
-11
lines changed

src/java.desktop/share/classes/com/sun/media/sound/DataPusher.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2002, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2002, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -59,6 +59,7 @@ public final class DataPusher implements Runnable {
5959
private boolean looping;
6060

6161
private Thread pushThread = null;
62+
boolean daemonThread = false;
6263
private int wantedState;
6364
private int threadState;
6465

@@ -70,28 +71,39 @@ public final class DataPusher implements Runnable {
7071
private final int BUFFER_SIZE = 16384;
7172

7273
public DataPusher(SourceDataLine sourceLine, AudioFormat format, byte[] audioData, int byteLength) {
73-
this(sourceLine, format, null, audioData, byteLength);
74+
this(sourceLine, format, null, audioData, byteLength, false);
75+
}
76+
77+
public DataPusher(SourceDataLine sourceLine, AudioFormat format,
78+
byte[] audioData, int byteLength, boolean daemon) {
79+
this(sourceLine, format, null, audioData, byteLength, daemon);
7480
}
7581

7682
public DataPusher(SourceDataLine sourceLine, AudioInputStream ais) {
77-
this(sourceLine, ais.getFormat(), ais, null, 0);
83+
this(sourceLine, ais.getFormat(), ais, null, 0, false);
7884
}
7985

8086
private DataPusher(final SourceDataLine source, final AudioFormat format,
8187
final AudioInputStream ais, final byte[] audioData,
82-
final int audioDataByteLength) {
88+
final int audioDataByteLength,
89+
final boolean daemon) {
8390
this.source = source;
8491
this.format = format;
8592
this.ais = ais;
8693
this.audioDataByteLength = audioDataByteLength;
8794
this.audioData = audioData == null ? null : Arrays.copyOf(audioData,
8895
audioData.length);
96+
this.daemonThread = daemon;
8997
}
9098

9199
public synchronized void start() {
92100
start(false);
93101
}
94102

103+
public synchronized boolean isPlaying() {
104+
return threadState == STATE_PLAYING;
105+
}
106+
95107
public synchronized void start(boolean loop) {
96108
try {
97109
if (threadState == STATE_STOPPING) {
@@ -109,7 +121,7 @@ public synchronized void start(boolean loop) {
109121
if (pushThread == null) {
110122
pushThread = JSSecurityManager.createThread(this,
111123
"DataPusher", // name
112-
false, // daemon
124+
daemonThread, // daemon
113125
-1, // priority
114126
true); // doStart
115127
}

src/java.desktop/share/classes/com/sun/media/sound/JavaSoundAudioClip.java

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -28,6 +28,8 @@
2828
import java.applet.AudioClip;
2929
import java.io.BufferedInputStream;
3030
import java.io.ByteArrayOutputStream;
31+
import java.io.File;
32+
import java.io.FileInputStream;
3133
import java.io.IOException;
3234
import java.io.InputStream;
3335
import java.net.URL;
@@ -69,8 +71,10 @@ public final class JavaSoundAudioClip implements AudioClip, MetaEventListener, L
6971

7072
private AutoClosingClip clip = null;
7173
private boolean clipLooping = false;
74+
private boolean clipPlaying = false;
7275

7376
private DataPusher datapusher = null;
77+
private boolean daemonThread = false;
7478

7579
private Sequencer sequencer = null;
7680
private Sequence sequence = null;
@@ -90,12 +94,21 @@ public final class JavaSoundAudioClip implements AudioClip, MetaEventListener, L
9094
private static final long CLIP_THRESHOLD = 1048576;
9195
private static final int STREAM_BUFFER_SIZE = 1024;
9296

97+
public static JavaSoundAudioClip create(final File file) throws IOException {
98+
JavaSoundAudioClip clip = new JavaSoundAudioClip();
99+
clip.daemonThread = true; // used only by javax.sound.SoundClip
100+
try (FileInputStream stream = new FileInputStream(file)) {
101+
clip.init(stream);
102+
}
103+
return clip;
104+
}
105+
93106
public static JavaSoundAudioClip create(final URLConnection uc) {
94107
JavaSoundAudioClip clip = new JavaSoundAudioClip();
95108
try {
96109
clip.init(uc.getInputStream());
97110
} catch (final Exception ignored) {
98-
// AudioClip will be no-op if some exception will occurred
111+
// Playing the clip will be a no-op if an exception occured in inititialization.
99112
}
100113
return clip;
101114
}
@@ -105,7 +118,7 @@ public static JavaSoundAudioClip create(final URL url) {
105118
try {
106119
clip.init(url.openStream());
107120
} catch (final Exception ignored) {
108-
// AudioClip will be no-op if some exception will occurred
121+
// Playing the clip will be a no-op if an exception occurred in inititialization.
109122
}
110123
return clip;
111124
}
@@ -128,7 +141,6 @@ private void init(InputStream in) throws IOException {
128141
}
129142
}
130143
} catch (UnsupportedAudioFileException e) {
131-
// not an audio file
132144
try {
133145
MidiFileFormat mff = MidiSystem.getMidiFileFormat(bis);
134146
success = createSequencer(bis);
@@ -138,6 +150,23 @@ private void init(InputStream in) throws IOException {
138150
}
139151
}
140152

153+
public synchronized boolean canPlay() {
154+
return success;
155+
}
156+
157+
public synchronized boolean isPlaying() {
158+
if (!canPlay()) {
159+
return false;
160+
} else if (clip != null) {
161+
return clipPlaying;
162+
} else if (datapusher != null) {
163+
return datapusher.isPlaying();
164+
} else if (sequencer != null) {
165+
return sequencer.isRunning();
166+
}
167+
return false;
168+
}
169+
141170
@Override
142171
public synchronized void play() {
143172
if (!success) {
@@ -258,6 +287,16 @@ public synchronized void stop() {
258287

259288
@Override
260289
public synchronized void update(LineEvent event) {
290+
if (clip != null) {
291+
if (clip == event.getSource()) {
292+
if (event.getType() == LineEvent.Type.START) {
293+
clipPlaying = true;
294+
} else if ((event.getType() == LineEvent.Type.STOP) ||
295+
(event.getType() == LineEvent.Type.CLOSE)) {
296+
clipPlaying = false;
297+
}
298+
}
299+
}
261300
}
262301

263302
// handle MIDI track end meta events for looping
@@ -381,6 +420,7 @@ private boolean createClip() {
381420
}
382421
clip = (AutoClosingClip) line;
383422
clip.setAutoClosing(true);
423+
clip.addLineListener(this);
384424
} catch (Exception e) {
385425
if (Printer.err) e.printStackTrace();
386426
// fail silently
@@ -403,7 +443,7 @@ private boolean createSourceDataLine() {
403443
return false;
404444
}
405445
SourceDataLine source = (SourceDataLine) AudioSystem.getLine(info);
406-
datapusher = new DataPusher(source, loadedAudioFormat, loadedAudio, loadedAudioByteLength);
446+
datapusher = new DataPusher(source, loadedAudioFormat, loadedAudio, loadedAudioByteLength, daemonThread);
407447
} catch (Exception e) {
408448
if (Printer.err) e.printStackTrace();
409449
// fail silently
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package javax.sound;
27+
28+
import java.io.File;
29+
import java.io.IOException;
30+
31+
import com.sun.media.sound.JavaSoundAudioClip;
32+
33+
/**
34+
* The {@code SoundClip} class is a simple abstraction for playing a sound clip.
35+
* It will play any format that is recognized by the {@code javax.sound} API,
36+
* and for which it has support. This includes midi data.
37+
* <p>
38+
* This class is intended for easy playback of short clips or snippets of sound.
39+
* Examples of when this might be used is to play an audible alert or effect in a UI app,
40+
* or to make a short announcement or to provide audible feedback such announcing as the
41+
* function of a button or control.
42+
* The application will typically let such clips play once to completion.
43+
* <p>
44+
* Applications needing more precise control or advanced
45+
* features should look into other parts of the {@code javax.sound} API.
46+
* Playing sound requires that the environment grants access to audio devices.
47+
* Typically this means running the application in a desktop environment.
48+
* <p>
49+
* Multiple {@code SoundClip} items can be playing at the same time, and
50+
* the resulting sound is mixed together to produce a composite.
51+
*
52+
* @since 25
53+
*/
54+
public final class SoundClip {
55+
56+
private final JavaSoundAudioClip clip;
57+
58+
/**
59+
* Creates a {@code SoundClip} instance which will play a clip from the supplied file.
60+
* <p>
61+
* The file contents will be fully read before this method returns.
62+
* If the file does not contain recognizable and supported sound data, or
63+
* if the implementation does not find a suitable output device for the data,
64+
* playing the clip will be a no-op.
65+
*
66+
* @param file the file from which to obtain the sound data
67+
* @return a {@code SoundClip}
68+
* @throws NullPointerException if {@code file} is {@code null}
69+
* @throws IOException if there is an error reading from {@code file}
70+
*/
71+
public static SoundClip createSoundClip(File file) throws IOException {
72+
if (file == null) {
73+
throw new NullPointerException("file must not be null");
74+
}
75+
return new SoundClip(file);
76+
}
77+
78+
private SoundClip(File file) throws IOException {
79+
this.clip = JavaSoundAudioClip.create(file);
80+
}
81+
82+
/**
83+
* {@return whether this is a playable sound clip}
84+
* <p>
85+
* A value of {@code false} means that calling any of the other methods
86+
* of this class is a no-op.
87+
*/
88+
public boolean canPlay() {
89+
return clip.canPlay();
90+
}
91+
92+
/**
93+
* {@return whether sound is currently playing}
94+
*/
95+
public boolean isPlaying() {
96+
return clip.isPlaying();
97+
}
98+
99+
/**
100+
* Starts playing this sound clip.
101+
* Each time this method is called, the clip is restarted from the beginning.
102+
* This method will return immediately whether or not sound is played,
103+
* and possibly before the sound has started playing.
104+
* <p>
105+
* Threading notes : Most applications will not need to do anything except call {@code play()}.
106+
* The following is therefore something most applications need not be concerned about.
107+
* Play back is managed in a background thread, which is usually a daemon thread.
108+
* Running daemon threads do not prevent the VM from exiting.
109+
* So at least one thread must be alive to prevent the VM from terminating.
110+
* A UI application with any window displayed automatically satisfies this requirement.
111+
* Conversely, if the application wants to guarantee VM exit before the play() has completed,
112+
* it should call the {@code stop()} method.
113+
*/
114+
public void play() {
115+
clip.play();
116+
}
117+
118+
/**
119+
* Starts playing this sound clip in a loop.
120+
* Each time this method is called, the clip is restarted from the beginning.
121+
* This method will return immediately whether or not sound is played,
122+
* and possibly before the sound has started playing.
123+
* <p>
124+
* Threading notes : Most applications will not need to do anything except call {@code loop()}.
125+
* The following is therefore something most applications need not be concerned about.
126+
* Play back is managed in a background thread, which is ususally a daemon thread.
127+
* Running daemon threads do not prevent the VM from exiting.
128+
* So at least one thread must be alive to prevent the VM from terminating.
129+
* A UI application with any window displayed automatically satisfies this requirement.
130+
* Conversely, if the application wants to guarantee VM exit before the play() has completed,
131+
* it should call the {@code stop()} method.
132+
*/
133+
public void loop() {
134+
clip.loop();
135+
}
136+
137+
/**
138+
* Stops playing this sound clip.
139+
* Call this if the clip is playing and the application needs to stop
140+
* it early, for example so that the application can ensure the clip
141+
* playing does not block exit. It is also required to stop a {@code loop()}.
142+
*/
143+
public void stop() {
144+
clip.stop();
145+
}
146+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
/**
27+
* Provides interfaces and classes for the Java Sound API.
28+
* The API is divided into sub-packages.
29+
* <ul>
30+
* <li>Capture, processing and playback of sampled audio data is under {@link javax.sound.sampled}.
31+
* <li>Sequencing, and synthesis of MIDI (Musical Instrument Digital Interface) data is under {@link javax.sound.midi}.
32+
* </ul>
33+
*
34+
* <h2>Related Documentation</h2>
35+
* For more information on using Java Sound see:
36+
* <ul>
37+
* <li><a href="https://docs.oracle.com/javase/tutorial/sound/">
38+
* The Java Sound Tutorial</a>
39+
* </ul>
40+
*
41+
* @since 25
42+
*/
43+
package javax.sound;

src/java.desktop/share/classes/module-info.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -89,6 +89,7 @@
8989
exports javax.print.attribute;
9090
exports javax.print.attribute.standard;
9191
exports javax.print.event;
92+
exports javax.sound;
9293
exports javax.sound.midi;
9394
exports javax.sound.midi.spi;
9495
exports javax.sound.sampled;

0 commit comments

Comments
 (0)