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

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

removed OptionPaneUtil
cleanup of deprecated Layer API
cleanup of deprecated APIs in OsmPrimitive and Way
cleanup of imports

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