source: josm/trunk/src/org/openstreetmap/josm/io/audio/AudioPlayer.java@ 14095

Last change on this file since 14095 was 14095, checked in by Don-vip, 6 years ago

fix error_prone warnings

  • Property svn:eol-style set to native
File size: 11.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io.audio;
3
4import java.io.IOException;
5import java.net.URL;
6
7import org.openstreetmap.josm.spi.preferences.Config;
8import org.openstreetmap.josm.tools.JosmRuntimeException;
9import org.openstreetmap.josm.tools.Logging;
10
11/**
12 * Creates and controls a separate audio player thread.
13 *
14 * @author David Earl <david@frankieandshadow.com>
15 * @since 12326 (move to new package)
16 * @since 547
17 */
18public final class AudioPlayer extends Thread implements AudioListener {
19
20 private static volatile AudioPlayer audioPlayer;
21
22 /**
23 * Audio player state.
24 */
25 public enum State {
26 /** Initializing */
27 INITIALIZING,
28 /** Not playing */
29 NOTPLAYING,
30 /** Playing */
31 PLAYING,
32 /** Paused */
33 PAUSED,
34 /** Interrupted */
35 INTERRUPTED
36 }
37
38 /**
39 * Audio player command.
40 */
41 public enum Command { /** Audio play */ PLAY, /** Audio pause */ PAUSE }
42
43 /**
44 * Audio player result.
45 */
46 public enum Result { /** In progress */ WAITING, /** Success */ OK, /** Failure */ FAILED }
47
48 private State state;
49 private SoundPlayer soundPlayer;
50 private URL playingUrl;
51
52 /**
53 * Passes information from the control thread to the playing thread
54 */
55 public class Execute {
56 private Command command;
57 private Result result;
58 private Exception exception;
59 private URL url;
60 private double offset; // seconds
61 private double speed; // ratio
62
63 /*
64 * Called to execute the commands in the other thread
65 */
66 protected void play(URL url, double offset, double speed) throws InterruptedException, IOException {
67 this.url = url;
68 this.offset = offset;
69 this.speed = speed;
70 command = Command.PLAY;
71 result = Result.WAITING;
72 send();
73 }
74
75 protected void pause() throws InterruptedException, IOException {
76 command = Command.PAUSE;
77 send();
78 }
79
80 private void send() throws InterruptedException, IOException {
81 result = Result.WAITING;
82 interrupt();
83 while (result == Result.WAITING) {
84 sleep(10);
85 }
86 if (result == Result.FAILED)
87 throw new IOException(exception);
88 }
89
90 protected void possiblyInterrupt() throws InterruptedException {
91 if (interrupted() || result == Result.WAITING)
92 throw new InterruptedException();
93 }
94
95 protected void failed(Exception e) {
96 exception = e;
97 result = Result.FAILED;
98 state = State.NOTPLAYING;
99 }
100
101 protected void ok(State newState) {
102 result = Result.OK;
103 state = newState;
104 }
105
106 /**
107 * Returns the offset.
108 * @return the offset, in seconds
109 */
110 public double offset() {
111 return offset;
112 }
113
114 /**
115 * Returns the speed.
116 * @return the speed (ratio)
117 */
118 public double speed() {
119 return speed;
120 }
121
122 /**
123 * Returns the URL.
124 * @return The resource to play, which must be a WAV file or stream
125 */
126 public URL url() {
127 return url;
128 }
129
130 /**
131 * Returns the command.
132 * @return the command
133 */
134 public Command command() {
135 return command;
136 }
137 }
138
139 private final Execute command;
140
141 /**
142 * Plays a WAV audio file from the beginning. See also the variant which doesn't
143 * start at the beginning of the stream
144 * @param url The resource to play, which must be a WAV file or stream
145 * @throws InterruptedException thread interrupted
146 * @throws IOException audio fault exception, e.g. can't open stream, unhandleable audio format
147 */
148 public static void play(URL url) throws InterruptedException, IOException {
149 AudioPlayer instance = AudioPlayer.getInstance();
150 if (instance != null)
151 instance.command.play(url, 0.0, 1.0);
152 }
153
154 /**
155 * Plays a WAV audio file from a specified position.
156 * @param url The resource to play, which must be a WAV file or stream
157 * @param seconds The number of seconds into the audio to start playing
158 * @throws InterruptedException thread interrupted
159 * @throws IOException audio fault exception, e.g. can't open stream, unhandleable audio format
160 */
161 public static void play(URL url, double seconds) throws InterruptedException, IOException {
162 AudioPlayer instance = AudioPlayer.getInstance();
163 if (instance != null)
164 instance.command.play(url, seconds, 1.0);
165 }
166
167 /**
168 * Plays a WAV audio file from a specified position at variable speed.
169 * @param url The resource to play, which must be a WAV file or stream
170 * @param seconds The number of seconds into the audio to start playing
171 * @param speed Rate at which audio playes (1.0 = real time, > 1 is faster)
172 * @throws InterruptedException thread interrupted
173 * @throws IOException audio fault exception, e.g. can't open stream, unhandleable audio format
174 */
175 public static void play(URL url, double seconds, double speed) throws InterruptedException, IOException {
176 AudioPlayer instance = AudioPlayer.getInstance();
177 if (instance != null)
178 instance.command.play(url, seconds, speed);
179 }
180
181 /**
182 * Pauses the currently playing audio stream. Does nothing if nothing playing.
183 * @throws InterruptedException thread interrupted
184 * @throws IOException audio fault exception, e.g. can't open stream, unhandleable audio format
185 */
186 public static void pause() throws InterruptedException, IOException {
187 AudioPlayer instance = AudioPlayer.getInstance();
188 if (instance != null)
189 instance.command.pause();
190 }
191
192 /**
193 * To get the Url of the playing or recently played audio.
194 * @return url - could be null
195 */
196 public static URL url() {
197 AudioPlayer instance = AudioPlayer.getInstance();
198 return instance == null ? null : instance.playingUrl;
199 }
200
201 /**
202 * Whether or not we are paused.
203 * @return boolean whether or not paused
204 */
205 public static boolean paused() {
206 AudioPlayer instance = AudioPlayer.getInstance();
207 return instance != null && instance.state == State.PAUSED;
208 }
209
210 /**
211 * Whether or not we are playing.
212 * @return boolean whether or not playing
213 */
214 public static boolean playing() {
215 AudioPlayer instance = AudioPlayer.getInstance();
216 return instance != null && instance.state == State.PLAYING;
217 }
218
219 /**
220 * How far we are through playing, in seconds.
221 * @return double seconds
222 */
223 public static double position() {
224 AudioPlayer instance = AudioPlayer.getInstance();
225 return instance == null ? -1 : instance.soundPlayer.position();
226 }
227
228 /**
229 * Speed at which we will play.
230 * @return double, speed multiplier
231 */
232 public static double speed() {
233 AudioPlayer instance = AudioPlayer.getInstance();
234 return instance == null ? -1 : instance.soundPlayer.speed();
235 }
236
237 /**
238 * Returns the singleton object, and if this is the first time, creates it along with
239 * the thread to support audio
240 * @return the unique instance
241 */
242 private static AudioPlayer getInstance() {
243 if (audioPlayer != null)
244 return audioPlayer;
245 try {
246 audioPlayer = new AudioPlayer();
247 return audioPlayer;
248 } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException ex) {
249 Logging.error(ex);
250 return null;
251 }
252 }
253
254 /**
255 * Resets the audio player.
256 */
257 public static void reset() {
258 if (audioPlayer != null) {
259 try {
260 pause();
261 } catch (InterruptedException | IOException e) {
262 Logging.warn(e);
263 }
264 audioPlayer.playingUrl = null;
265 }
266 }
267
268 private AudioPlayer() {
269 state = State.INITIALIZING;
270 command = new Execute();
271 playingUrl = null;
272 double leadIn = Config.getPref().getDouble("audio.leadin", 1.0 /* default, seconds */);
273 double calibration = Config.getPref().getDouble("audio.calibration", 1.0 /* default, ratio */);
274 try {
275 soundPlayer = (SoundPlayer) Class.forName("org.openstreetmap.josm.io.audio.fx.JavaFxMediaPlayer")
276 .getDeclaredConstructor().newInstance();
277 } catch (ReflectiveOperationException | IllegalArgumentException | SecurityException e) {
278 Logging.debug(e);
279 Logging.warn("JOSM compiled without Java FX support. Falling back to Java Sound API");
280 } catch (NoClassDefFoundError | JosmRuntimeException e) {
281 Logging.debug(e);
282 Logging.warn("Java FX is unavailable. Falling back to Java Sound API");
283 }
284 if (soundPlayer == null) {
285 soundPlayer = new JavaSoundPlayer(leadIn, calibration);
286 }
287 soundPlayer.addAudioListener(this);
288 start();
289 while (state == State.INITIALIZING) {
290 yield();
291 }
292 }
293
294 /**
295 * Starts the thread to actually play the audio, per Thread interface
296 * Not to be used as public, though Thread interface doesn't allow it to be made private
297 */
298 @Override
299 public void run() {
300 /* code running in separate thread */
301
302 playingUrl = null;
303
304 for (;;) {
305 try {
306 switch (state) {
307 case INITIALIZING:
308 // we're ready to take interrupts
309 state = State.NOTPLAYING;
310 break;
311 case NOTPLAYING:
312 case PAUSED:
313 sleep(200);
314 break;
315 case PLAYING:
316 command.possiblyInterrupt();
317 if (soundPlayer.playing(command)) {
318 playingUrl = null;
319 state = State.NOTPLAYING;
320 }
321 command.possiblyInterrupt();
322 break;
323 default: // Do nothing
324 }
325 } catch (InterruptedException e) {
326 interrupted(); // just in case we get an interrupt
327 State stateChange = state;
328 state = State.INTERRUPTED;
329 try {
330 switch (command.command()) {
331 case PLAY:
332 soundPlayer.play(command, stateChange, playingUrl);
333 stateChange = State.PLAYING;
334 break;
335 case PAUSE:
336 soundPlayer.pause(command, stateChange, playingUrl);
337 stateChange = State.PAUSED;
338 break;
339 default: // Do nothing
340 }
341 command.ok(stateChange);
342 } catch (AudioException | IOException | SecurityException | IllegalArgumentException startPlayingException) {
343 Logging.error(startPlayingException);
344 command.failed(startPlayingException); // sets state
345 }
346 } catch (AudioException | IOException e) {
347 state = State.NOTPLAYING;
348 Logging.error(e);
349 }
350 }
351 }
352
353 @Override
354 public void playing(URL playingUrl) {
355 this.playingUrl = playingUrl;
356 }
357}
Note: See TracBrowser for help on using the repository browser.