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

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

fix #15110 - Disable audio actions when no audio is present (patch by bafonins, modified)

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