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