source: josm/trunk/src/org/openstreetmap/josm/tools/AudioPlayer.java@ 1865

Last change on this file since 1865 was 1865, checked in by Gubaer, 15 years ago

replaced JOptionPane.show* by OptionPaneUtil.show*
ExtendeDialog now always on top, too

  • Property svn:eol-style set to native
File size: 13.2 KB
Line 
1// License: GPL. Copyright 2008 by David Earl and others
2package org.openstreetmap.josm.tools;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.IOException;
7import java.net.URL;
8
9import javax.sound.sampled.AudioFormat;
10import javax.sound.sampled.AudioInputStream;
11import javax.sound.sampled.AudioSystem;
12import javax.sound.sampled.DataLine;
13import javax.sound.sampled.SourceDataLine;
14import javax.swing.JOptionPane;
15
16import org.openstreetmap.josm.Main;
17import org.openstreetmap.josm.gui.OptionPaneUtil;
18
19/**
20 * Creates and controls a separate audio player thread.
21 *
22 * @author David Earl <david@frankieandshadow.com>
23 *
24 */
25public class AudioPlayer extends Thread {
26
27 private static AudioPlayer audioPlayer = null;
28
29 private enum State { INITIALIZING, NOTPLAYING, PLAYING, PAUSED, INTERRUPTED }
30 private State state;
31 private enum Command { PLAY, PAUSE }
32 private enum Result { WAITING, OK, FAILED }
33 private URL playingUrl;
34 private double leadIn; // seconds
35 private double calibration; // ratio of purported duration of samples to true duration
36 private double position; // seconds
37 private double bytesPerSecond;
38 private static long chunk = 4000; /* bytes */
39 private double speed = 1.0;
40
41 /**
42 * Passes information from the control thread to the playing thread
43 */
44 private class Execute {
45 private Command command;
46 private Result result;
47 private Exception exception;
48 private URL url;
49 private double offset; // seconds
50 private double speed; // ratio
51
52 /*
53 * Called to execute the commands in the other thread
54 */
55 protected void play(URL url, double offset, double speed) throws Exception {
56 this.url = url;
57 this.offset = offset;
58 this.speed = speed;
59 command = Command.PLAY;
60 result = Result.WAITING;
61 send();
62 }
63 protected void pause() throws Exception {
64 command = Command.PAUSE;
65 send();
66 }
67 private void send() throws Exception {
68 result = Result.WAITING;
69 interrupt();
70 while (result == Result.WAITING) { sleep(10); /* yield(); */ }
71 if (result == Result.FAILED)
72 throw exception;
73 }
74 private void possiblyInterrupt() throws InterruptedException {
75 if (interrupted() || result == Result.WAITING)
76 throw new InterruptedException();
77 }
78 protected void failed (Exception e) {
79 exception = e;
80 result = Result.FAILED;
81 state = State.NOTPLAYING;
82 }
83 protected void ok (State newState) {
84 result = Result.OK;
85 state = newState;
86 }
87 protected double offset() {
88 return offset;
89 }
90 protected double speed() {
91 return speed;
92 }
93 protected URL url() {
94 return url;
95 }
96 protected Command command() {
97 return command;
98 }
99 }
100
101 private Execute command;
102
103 /**
104 * Plays a WAV audio file from the beginning. See also the variant which doesn't
105 * start at the beginning of the stream
106 * @param url The resource to play, which must be a WAV file or stream
107 * @throws audio fault exception, e.g. can't open stream, unhandleable audio format
108 */
109 public static void play(URL url) throws Exception {
110 AudioPlayer.get().command.play(url, 0.0, 1.0);
111 }
112
113 /**
114 * Plays a WAV audio file from a specified position.
115 * @param url The resource to play, which must be a WAV file or stream
116 * @param seconds The number of seconds into the audio to start playing
117 * @throws audio fault exception, e.g. can't open stream, unhandleable audio format
118 */
119 public static void play(URL url, double seconds) throws Exception {
120 AudioPlayer.get().command.play(url, seconds, 1.0);
121 }
122
123 /**
124 * Plays a WAV audio file from a specified position at variable speed.
125 * @param url The resource to play, which must be a WAV file or stream
126 * @param seconds The number of seconds into the audio to start playing
127 * @param speed Rate at which audio playes (1.0 = real time, > 1 is faster)
128 * @throws audio fault exception, e.g. can't open stream, unhandleable audio format
129 */
130 public static void play(URL url, double seconds, double speed) throws Exception {
131 AudioPlayer.get().command.play(url, seconds, speed);
132 }
133
134 /**
135 * Pauses the currently playing audio stream. Does nothing if nothing playing.
136 * @throws audio fault exception, e.g. can't open stream, unhandleable audio format
137 */
138 public static void pause() throws Exception {
139 AudioPlayer.get().command.pause();
140 }
141
142 /**
143 * To get the Url of the playing or recently played audio.
144 * @return url - could be null
145 */
146 public static URL url() {
147 return AudioPlayer.get().playingUrl;
148 }
149
150 /**
151 * Whether or not we are paused.
152 * @return boolean whether or not paused
153 */
154 public static boolean paused() {
155 return AudioPlayer.get().state == State.PAUSED;
156 }
157
158 /**
159 * Whether or not we are playing.
160 * @return boolean whether or not playing
161 */
162 public static boolean playing() {
163 return AudioPlayer.get().state == State.PLAYING;
164 }
165
166 /**
167 * How far we are through playing, in seconds.
168 * @return double seconds
169 */
170 public static double position() {
171 return AudioPlayer.get().position;
172 }
173
174 /**
175 * Speed at which we will play.
176 * @return double, speed multiplier
177 */
178 public static double speed() {
179 return AudioPlayer.get().speed;
180 }
181
182 /**
183 * gets the singleton object, and if this is the first time, creates it along with
184 * the thread to support audio
185 */
186 private static AudioPlayer get() {
187 if (audioPlayer != null)
188 return audioPlayer;
189 try {
190 audioPlayer = new AudioPlayer();
191 return audioPlayer;
192 } catch (Exception ex) {
193 return null;
194 }
195 }
196
197 public static void reset() {
198 if(audioPlayer != null)
199 {
200 try {
201 pause();
202 } catch(Exception e) {}
203 audioPlayer.playingUrl = null;
204 }
205 }
206
207 private AudioPlayer() {
208 state = State.INITIALIZING;
209 command = new Execute();
210 playingUrl = null;
211 leadIn = Main.pref.getDouble("audio.leadin", "1.0" /* default, seconds */);
212 calibration = Main.pref.getDouble("audio.calibration", "1.0" /* default, ratio */);
213 start();
214 while (state == State.INITIALIZING) { yield(); }
215 }
216
217 /**
218 * Starts the thread to actually play the audio, per Thread interface
219 * Not to be used as public, though Thread interface doesn't allow it to be made private
220 */
221 @Override public void run() {
222 /* code running in separate thread */
223
224 playingUrl = null;
225 AudioInputStream audioInputStream = null;
226 SourceDataLine audioOutputLine = null;
227 AudioFormat audioFormat = null;
228 byte[] abData = new byte[(int)chunk];
229
230 for (;;) {
231 try {
232 switch (state) {
233 case INITIALIZING:
234 // we're ready to take interrupts
235 state = State.NOTPLAYING;
236 break;
237 case NOTPLAYING:
238 case PAUSED:
239 sleep(200);
240 break;
241 case PLAYING:
242 command.possiblyInterrupt();
243 for(;;) {
244 int nBytesRead = 0;
245 nBytesRead = audioInputStream.read(abData, 0, abData.length);
246 position += nBytesRead / bytesPerSecond;
247 command.possiblyInterrupt();
248 if (nBytesRead < 0) { break; }
249 audioOutputLine.write(abData, 0, nBytesRead); // => int nBytesWritten
250 command.possiblyInterrupt();
251 }
252 // end of audio, clean up
253 audioOutputLine.drain();
254 audioOutputLine.close();
255 audioOutputLine = null;
256 audioInputStream.close();
257 audioInputStream = null;
258 playingUrl = null;
259 state = State.NOTPLAYING;
260 command.possiblyInterrupt();
261 break;
262 }
263 } catch (InterruptedException e) {
264 interrupted(); // just in case we get an interrupt
265 State stateChange = state;
266 state = State.INTERRUPTED;
267 try {
268 switch (command.command()) {
269 case PLAY:
270 double offset = command.offset();
271 speed = command.speed();
272 if (playingUrl != command.url() ||
273 stateChange != State.PAUSED ||
274 offset != 0.0)
275 {
276 if (audioInputStream != null) {
277 audioInputStream.close();
278 audioInputStream = null;
279 }
280 playingUrl = command.url();
281 audioInputStream = AudioSystem.getAudioInputStream(playingUrl);
282 audioFormat = audioInputStream.getFormat();
283 long nBytesRead = 0;
284 position = 0.0;
285 offset -= leadIn;
286 double calibratedOffset = offset * calibration;
287 bytesPerSecond = audioFormat.getFrameRate() /* frames per second */
288 * audioFormat.getFrameSize() /* bytes per frame */;
289 if (speed * bytesPerSecond > 256000.0) {
290 speed = 256000 / bytesPerSecond;
291 }
292 if (calibratedOffset > 0.0) {
293 long bytesToSkip = (long)(
294 calibratedOffset /* seconds (double) */ * bytesPerSecond);
295 /* skip doesn't seem to want to skip big chunks, so
296 * reduce it to smaller ones
297 */
298 // audioInputStream.skip(bytesToSkip);
299 while (bytesToSkip > chunk) {
300 nBytesRead = audioInputStream.skip(chunk);
301 if (nBytesRead <= 0)
302 throw new IOException(tr("This is after the end of the recording"));
303 bytesToSkip -= nBytesRead;
304 }
305 if (bytesToSkip > 0) {
306 audioInputStream.skip(bytesToSkip);
307 }
308 position = offset;
309 }
310 if (audioOutputLine != null) {
311 audioOutputLine.close();
312 }
313 audioFormat = new AudioFormat(audioFormat.getEncoding(),
314 audioFormat.getSampleRate() * (float) (speed * calibration),
315 audioFormat.getSampleSizeInBits(),
316 audioFormat.getChannels(),
317 audioFormat.getFrameSize(),
318 audioFormat.getFrameRate() * (float) (speed * calibration),
319 audioFormat.isBigEndian());
320 DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
321 audioOutputLine = (SourceDataLine) AudioSystem.getLine(info);
322 audioOutputLine.open(audioFormat);
323 audioOutputLine.start();
324 }
325 stateChange = State.PLAYING;
326 break;
327 case PAUSE:
328 stateChange = State.PAUSED;
329 break;
330 }
331 command.ok(stateChange);
332 } catch (Exception startPlayingException) {
333 command.failed(startPlayingException); // sets state
334 }
335 } catch (Exception e) {
336 state = State.NOTPLAYING;
337 }
338 }
339 }
340
341 public static void audioMalfunction(Exception ex) {
342 OptionPaneUtil.showMessageDialog(Main.parent,
343 "<html><p>" + tr(ex.getMessage()) + "</p></html>",
344 tr("Error playing sound"), JOptionPane.ERROR_MESSAGE);
345 }
346}
Note: See TracBrowser for help on using the repository browser.