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

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

added link to presets, fix #1675

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