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

Last change on this file since 1023 was 891, checked in by stoecker, 16 years ago

minor I18N fix

  • Property svn:eol-style set to native
File size: 10.0 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 private AudioPlayer() {
196 state = State.INITIALIZING;
197 command = new Execute();
198 playingUrl = null;
199 try {
200 leadIn = Double.parseDouble(Main.pref.get("audio.leadin", "1.0" /* default, seconds */));
201 } catch (NumberFormatException e) {
202 leadIn = 1.0; // failed to parse
203 }
204 try {
205 calibration = Double.parseDouble(Main.pref.get("audio.calibration", "1.0" /* default, ratio */));
206 } catch (NumberFormatException e) {
207 calibration = 1.0; // failed to parse
208 }
209 start();
210 while (state == State.INITIALIZING) { yield(); }
211 }
212
213 /**
214 * Starts the thread to actually play the audio, per Thread interface
215 * Not to be used as public, though Thread interface doesn't allow it to be made private
216 */
217 @Override public void run() {
218 /* code running in separate thread */
219
220 playingUrl = null;
221 AudioInputStream audioInputStream = null;
222 SourceDataLine audioOutputLine = null;
223 AudioFormat audioFormat = null;
224 byte[] abData = new byte[(int)chunk];
225
226 for (;;) {
227 try {
228 switch (state) {
229 case INITIALIZING:
230 // we're ready to take interrupts
231 state = State.NOTPLAYING;
232 break;
233 case NOTPLAYING:
234 case PAUSED:
235 sleep(200);
236 break;
237 case PLAYING:
238 command.possiblyInterrupt();
239 for(;;) {
240 int nBytesRead = 0;
241 nBytesRead = audioInputStream.read(abData, 0, abData.length);
242 position += nBytesRead / bytesPerSecond;
243 command.possiblyInterrupt();
244 if (nBytesRead < 0) { break; }
245 audioOutputLine.write(abData, 0, nBytesRead); // => int nBytesWritten
246 command.possiblyInterrupt();
247 }
248 // end of audio, clean up
249 audioOutputLine.drain();
250 audioOutputLine.close();
251 audioOutputLine = null;
252 audioInputStream.close();
253 audioInputStream = null;
254 playingUrl = null;
255 state = State.NOTPLAYING;
256 command.possiblyInterrupt();
257 break;
258 }
259 } catch (InterruptedException e) {
260 interrupted(); // just in case we get an interrupt
261 State stateChange = state;
262 state = State.INTERRUPTED;
263 try {
264 switch (command.command()) {
265 case PLAY:
266 double offset = command.offset();
267 speed = command.speed();
268 if (playingUrl != command.url() ||
269 stateChange != State.PAUSED ||
270 offset != 0.0)
271 {
272 if (audioInputStream != null) {
273 audioInputStream.close();
274 audioInputStream = null;
275 }
276 playingUrl = command.url();
277 audioInputStream = AudioSystem.getAudioInputStream(playingUrl);
278 audioFormat = audioInputStream.getFormat();
279 long nBytesRead = 0;
280 position = 0.0;
281 offset -= leadIn;
282 double calibratedOffset = offset * calibration;
283 bytesPerSecond = audioFormat.getFrameRate() /* frames per second */
284 * audioFormat.getFrameSize() /* bytes per frame */;
285 if (speed * bytesPerSecond > 256000.0)
286 speed = 256000 / bytesPerSecond;
287 if (calibratedOffset > 0.0) {
288 long bytesToSkip = (long)(
289 calibratedOffset /* seconds (double) */ * bytesPerSecond);
290 /* skip doesn't seem to want to skip big chunks, so
291 * reduce it to smaller ones
292 */
293 // audioInputStream.skip(bytesToSkip);
294 while (bytesToSkip > chunk) {
295 nBytesRead = audioInputStream.skip(chunk);
296 if (nBytesRead <= 0)
297 throw new IOException(tr("This is after the end of the recording"));
298 bytesToSkip -= nBytesRead;
299 }
300 if (bytesToSkip > 0)
301 audioInputStream.skip(bytesToSkip);
302 position = offset;
303 }
304 if (audioOutputLine != null)
305 audioOutputLine.close();
306 audioFormat = new AudioFormat(audioFormat.getEncoding(),
307 audioFormat.getSampleRate() * (float) (speed * calibration),
308 audioFormat.getSampleSizeInBits(),
309 audioFormat.getChannels(),
310 audioFormat.getFrameSize(),
311 audioFormat.getFrameRate() * (float) (speed * calibration),
312 audioFormat.isBigEndian());
313 DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
314 audioOutputLine = (SourceDataLine) AudioSystem.getLine(info);
315 audioOutputLine.open(audioFormat);
316 audioOutputLine.start();
317 }
318 stateChange = State.PLAYING;
319 break;
320 case PAUSE:
321 stateChange = State.PAUSED;
322 break;
323 }
324 command.ok(stateChange);
325 } catch (Exception startPlayingException) {
326 command.failed(startPlayingException); // sets state
327 }
328 } catch (Exception e) {
329 state = State.NOTPLAYING;
330 }
331 }
332 }
333
334 public static void audioMalfunction(Exception ex) {
335 JOptionPane.showMessageDialog(Main.parent,
336 "<html><p>" + tr(ex.getMessage()) + "</p></html>",
337 tr("Error playing sound"), JOptionPane.ERROR_MESSAGE);
338 }
339}
Note: See TracBrowser for help on using the repository browser.