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, 5 weeks 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
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;
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    enum State { INITIALIZING, NOTPLAYING, PLAYING, PAUSED, INTERRUPTED }
23
24    enum Command { PLAY, PAUSE }
25
26    enum Result { WAITING, OK, FAILED }
27
28    private State state;
29    private SoundPlayer soundPlayer;
30    private URL playingUrl;
31
32    /**
33     * Passes information from the control thread to the playing thread
34     */
35    class Execute {
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
42
43        /*
44         * Called to execute the commands in the other thread
45         */
46        protected void play(URL url, double offset, double speed) throws InterruptedException, IOException {
47            this.url = url;
48            this.offset = offset;
49            this.speed = speed;
50            command = Command.PLAY;
51            result = Result.WAITING;
52            send();
53        }
54
55        protected void pause() throws InterruptedException, IOException {
56            command = Command.PAUSE;
57            send();
58        }
59
60        private void send() throws InterruptedException, IOException {
61            result = Result.WAITING;
62            interrupt();
63            while (result == Result.WAITING) {
64                sleep(10);
65            }
66            if (result == Result.FAILED)
67                throw new IOException(exception);
68        }
69
70        protected void possiblyInterrupt() throws InterruptedException {
71            if (interrupted() || result == Result.WAITING)
72                throw new InterruptedException();
73        }
74
75        protected void failed(Exception e) {
76            exception = e;
77            result = Result.FAILED;
78            state = State.NOTPLAYING;
79        }
80
81        protected void ok(State newState) {
82            result = Result.OK;
83            state = newState;
84        }
85
86        protected double offset() {
87            return offset;
88        }
89
90        protected double speed() {
91            return speed;
92        }
93
94        protected URL url() {
95            return url;
96        }
97
98        protected Command command() {
99            return command;
100        }
101    }
102
103    private final Execute command;
104
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
109     * @throws InterruptedException thread interrupted
110     * @throws IOException audio fault exception, e.g. can't open stream, unhandleable audio format
111     */
112    public static void play(URL url) throws InterruptedException, IOException {
113        AudioPlayer instance = AudioPlayer.getInstance();
114        if (instance != null)
115            instance.command.play(url, 0.0, 1.0);
116    }
117
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
122     * @throws InterruptedException thread interrupted
123     * @throws IOException audio fault exception, e.g. can't open stream, unhandleable audio format
124     */
125    public static void play(URL url, double seconds) throws InterruptedException, IOException {
126        AudioPlayer instance = AudioPlayer.getInstance();
127        if (instance != null)
128            instance.command.play(url, seconds, 1.0);
129    }
130
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
135     * @param speed Rate at which audio playes (1.0 = real time, > 1 is faster)
136     * @throws InterruptedException thread interrupted
137     * @throws IOException audio fault exception, e.g. can't open stream,  unhandleable audio format
138     */
139    public static void play(URL url, double seconds, double speed) throws InterruptedException, IOException {
140        AudioPlayer instance = AudioPlayer.getInstance();
141        if (instance != null)
142            instance.command.play(url, seconds, speed);
143    }
144
145    /**
146     * Pauses the currently playing audio stream. Does nothing if nothing playing.
147     * @throws InterruptedException thread interrupted
148     * @throws IOException audio fault exception, e.g. can't open stream,  unhandleable audio format
149     */
150    public static void pause() throws InterruptedException, IOException {
151        AudioPlayer instance = AudioPlayer.getInstance();
152        if (instance != null)
153            instance.command.pause();
154    }
155
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() {
161        AudioPlayer instance = AudioPlayer.getInstance();
162        return instance == null ? null : instance.playingUrl;
163    }
164
165    /**
166     * Whether or not we are paused.
167     * @return boolean whether or not paused
168     */
169    public static boolean paused() {
170        AudioPlayer instance = AudioPlayer.getInstance();
171        return instance != null && instance.state == State.PAUSED;
172    }
173
174    /**
175     * Whether or not we are playing.
176     * @return boolean whether or not playing
177     */
178    public static boolean playing() {
179        AudioPlayer instance = AudioPlayer.getInstance();
180        return instance != null && instance.state == State.PLAYING;
181    }
182
183    /**
184     * How far we are through playing, in seconds.
185     * @return double seconds
186     */
187    public static double position() {
188        AudioPlayer instance = AudioPlayer.getInstance();
189        return instance == null ? -1 : instance.soundPlayer.position();
190    }
191
192    /**
193     * Speed at which we will play.
194     * @return double, speed multiplier
195     */
196    public static double speed() {
197        AudioPlayer instance = AudioPlayer.getInstance();
198        return instance == null ? -1 : instance.soundPlayer.speed();
199    }
200
201    /**
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
205     */
206    private static AudioPlayer getInstance() {
207        if (audioPlayer != null)
208            return audioPlayer;
209        try {
210            audioPlayer = new AudioPlayer();
211            return audioPlayer;
212        } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException ex) {
213            Logging.error(ex);
214            return null;
215        }
216    }
217
218    /**
219     * Resets the audio player.
220     */
221    public static void reset() {
222        if (audioPlayer != null) {
223            try {
224                pause();
225            } catch (InterruptedException | IOException e) {
226                Logging.warn(e);
227            }
228            audioPlayer.playingUrl = null;
229        }
230    }
231
232    private AudioPlayer() {
233        state = State.INITIALIZING;
234        command = new Execute();
235        playingUrl = null;
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) {
241            Logging.debug(e);
242            Logging.warn("Java FX is unavailable. Falling back to Java Sound API");
243            soundPlayer = new JavaSoundPlayer(leadIn, calibration);
244        }
245        soundPlayer.addAudioListener(this);
246        start();
247        while (state == State.INITIALIZING) {
248            yield();
249        }
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     */
256    @Override
257    public void run() {
258        /* code running in separate thread */
259
260        playingUrl = null;
261
262        for (;;) {
263            try {
264                switch (state) {
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:
274                        command.possiblyInterrupt();
275                        if (soundPlayer.playing(command)) {
276                            playingUrl = null;
277                            state = State.NOTPLAYING;
278                        }
279                        command.possiblyInterrupt();
280                        break;
281                    default: // Do nothing
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()) {
289                        case PLAY:
290                            soundPlayer.play(command, stateChange, playingUrl);
291                            stateChange = State.PLAYING;
292                            break;
293                        case PAUSE:
294                            soundPlayer.pause(command, stateChange, playingUrl);
295                            stateChange = State.PAUSED;
296                            break;
297                        default: // Do nothing
298                    }
299                    command.ok(stateChange);
300                } catch (AudioException | IOException | SecurityException | IllegalArgumentException startPlayingException) {
301                    Logging.error(startPlayingException);
302                    command.failed(startPlayingException); // sets state
303                }
304            } catch (AudioException | IOException e) {
305                state = State.NOTPLAYING;
306                Logging.error(e);
307            }
308        }
309    }
310
311    @Override
312    public void playing(URL playingURL) {
313        this.playingUrl = playingURL;
314    }
315}
Note: See TracBrowser for help on using the repository browser.