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

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

see #15182 - deprecate all Main logging methods and introduce suitable replacements in Logging for most of them

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