1 | // License: GPL. For details, see LICENSE file.
|
---|
2 | package org.openstreetmap.josm.io.audio;
|
---|
3 |
|
---|
4 | import java.io.File;
|
---|
5 | import java.io.FileNotFoundException;
|
---|
6 | import java.io.IOException;
|
---|
7 | import java.net.URISyntaxException;
|
---|
8 | import java.net.URL;
|
---|
9 | import java.util.concurrent.CountDownLatch;
|
---|
10 |
|
---|
11 | import org.openstreetmap.josm.io.audio.AudioPlayer.Execute;
|
---|
12 | import org.openstreetmap.josm.io.audio.AudioPlayer.State;
|
---|
13 | import org.openstreetmap.josm.tools.JosmRuntimeException;
|
---|
14 | import org.openstreetmap.josm.tools.ListenerList;
|
---|
15 |
|
---|
16 | import com.sun.javafx.application.PlatformImpl;
|
---|
17 |
|
---|
18 | import javafx.scene.media.Media;
|
---|
19 | import javafx.scene.media.MediaException;
|
---|
20 | import javafx.scene.media.MediaPlayer;
|
---|
21 | import javafx.scene.media.MediaPlayer.Status;
|
---|
22 | import javafx.util.Duration;
|
---|
23 |
|
---|
24 | /**
|
---|
25 | * Default sound player based on the Java FX Media API.
|
---|
26 | * Used on platforms where Java FX is available. It supports the following audio codecs:<ul>
|
---|
27 | * <li>MP3</li>
|
---|
28 | * <li>AIFF containing uncompressed PCM</li>
|
---|
29 | * <li>WAV containing uncompressed PCM</li>
|
---|
30 | * <li>MPEG-4 multimedia container with Advanced Audio Coding (AAC) audio</li>
|
---|
31 | * </ul>
|
---|
32 | * @since 12328
|
---|
33 | */
|
---|
34 | class JavaFxMediaPlayer implements SoundPlayer {
|
---|
35 |
|
---|
36 | private final ListenerList<AudioListener> listeners = ListenerList.create();
|
---|
37 |
|
---|
38 | private MediaPlayer mediaPlayer;
|
---|
39 |
|
---|
40 | JavaFxMediaPlayer() throws JosmRuntimeException {
|
---|
41 | try {
|
---|
42 | initFxPlatform();
|
---|
43 | } catch (InterruptedException e) {
|
---|
44 | throw new JosmRuntimeException(e);
|
---|
45 | }
|
---|
46 | }
|
---|
47 |
|
---|
48 | /**
|
---|
49 | * Initializes the JavaFX platform runtime.
|
---|
50 | * @throws InterruptedException if the current thread is interrupted while waiting
|
---|
51 | */
|
---|
52 | public static void initFxPlatform() throws InterruptedException {
|
---|
53 | final CountDownLatch startupLatch = new CountDownLatch(1);
|
---|
54 |
|
---|
55 | // Note, this method is called on the FX Application Thread
|
---|
56 | PlatformImpl.startup(startupLatch::countDown);
|
---|
57 |
|
---|
58 | // Wait for FX platform to start
|
---|
59 | startupLatch.await();
|
---|
60 | }
|
---|
61 |
|
---|
62 | @Override
|
---|
63 | public synchronized void play(Execute command, State stateChange, URL playingUrl) throws AudioException, IOException {
|
---|
64 | try {
|
---|
65 | final URL url = command.url();
|
---|
66 | if (playingUrl != url) {
|
---|
67 | if (mediaPlayer != null) {
|
---|
68 | mediaPlayer.stop();
|
---|
69 | }
|
---|
70 | // Fail fast in case of invalid local URI (JavaFX Media locator retries 5 times with a 1 second delay)
|
---|
71 | if ("file".equals(url.getProtocol()) && !new File(url.toURI()).exists()) {
|
---|
72 | throw new FileNotFoundException(url.toString());
|
---|
73 | }
|
---|
74 | mediaPlayer = new MediaPlayer(new Media(url.toString()));
|
---|
75 | mediaPlayer.setOnPlaying(() ->
|
---|
76 | listeners.fireEvent(l -> l.playing(url))
|
---|
77 | );
|
---|
78 | }
|
---|
79 | mediaPlayer.setRate(command.speed());
|
---|
80 | if (Status.PLAYING == mediaPlayer.getStatus()) {
|
---|
81 | Duration seekTime = Duration.seconds(command.offset());
|
---|
82 | if (!seekTime.equals(mediaPlayer.getCurrentTime())) {
|
---|
83 | mediaPlayer.seek(seekTime);
|
---|
84 | }
|
---|
85 | }
|
---|
86 | mediaPlayer.play();
|
---|
87 | } catch (MediaException | URISyntaxException e) {
|
---|
88 | throw new AudioException(e);
|
---|
89 | }
|
---|
90 | }
|
---|
91 |
|
---|
92 | @Override
|
---|
93 | public synchronized void pause(Execute command, State stateChange, URL playingUrl) throws AudioException, IOException {
|
---|
94 | if (mediaPlayer != null) {
|
---|
95 | try {
|
---|
96 | mediaPlayer.pause();
|
---|
97 | } catch (MediaException e) {
|
---|
98 | throw new AudioException(e);
|
---|
99 | }
|
---|
100 | }
|
---|
101 | }
|
---|
102 |
|
---|
103 | @Override
|
---|
104 | public boolean playing(Execute command) throws AudioException, IOException, InterruptedException {
|
---|
105 | // Not used: JavaFX handles the low-level audio playback
|
---|
106 | return false;
|
---|
107 | }
|
---|
108 |
|
---|
109 | @Override
|
---|
110 | public synchronized double position() {
|
---|
111 | return mediaPlayer != null ? mediaPlayer.getCurrentTime().toSeconds() : -1;
|
---|
112 | }
|
---|
113 |
|
---|
114 | @Override
|
---|
115 | public synchronized double speed() {
|
---|
116 | return mediaPlayer != null ? mediaPlayer.getCurrentRate() : -1;
|
---|
117 | }
|
---|
118 |
|
---|
119 | @Override
|
---|
120 | public void addAudioListener(AudioListener listener) {
|
---|
121 | listeners.addWeakListener(listener);
|
---|
122 | }
|
---|
123 | }
|
---|