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

Last change on this file since 1728 was 1685, checked in by stoecker, 15 years ago

fixed audio handling a bit

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