001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.io.audio.fx;
003
004import java.io.File;
005import java.io.FileNotFoundException;
006import java.io.IOException;
007import java.net.URISyntaxException;
008import java.net.URL;
009import java.util.concurrent.CountDownLatch;
010
011import org.openstreetmap.josm.io.audio.AudioException;
012import org.openstreetmap.josm.io.audio.AudioListener;
013import org.openstreetmap.josm.io.audio.AudioPlayer.Execute;
014import org.openstreetmap.josm.io.audio.AudioPlayer.State;
015import org.openstreetmap.josm.io.audio.SoundPlayer;
016import org.openstreetmap.josm.tools.JosmRuntimeException;
017import org.openstreetmap.josm.tools.ListenerList;
018
019import com.sun.javafx.application.PlatformImpl;
020
021import javafx.scene.media.Media;
022import javafx.scene.media.MediaException;
023import javafx.scene.media.MediaPlayer;
024import javafx.scene.media.MediaPlayer.Status;
025import javafx.util.Duration;
026
027/**
028 * Default sound player based on the Java FX Media API.
029 * Used on platforms where Java FX is available. It supports the following audio codecs:<ul>
030 * <li>MP3</li>
031 * <li>AIFF containing uncompressed PCM</li>
032 * <li>WAV containing uncompressed PCM</li>
033 * <li>MPEG-4 multimedia container with Advanced Audio Coding (AAC) audio</li>
034 * </ul>
035 * @since 12328
036 * @deprecated MP3 support moved to openjfx plugin as JavaFX is gone with Java 11.
037 */
038@Deprecated
039public class JavaFxMediaPlayer implements SoundPlayer {
040
041    private final ListenerList<AudioListener> listeners = ListenerList.create();
042
043    private MediaPlayer mediaPlayer;
044
045    JavaFxMediaPlayer() {
046        try {
047            initFxPlatform();
048        } catch (InterruptedException e) {
049            throw new JosmRuntimeException(e);
050        }
051    }
052
053    /**
054     * Initializes the JavaFX platform runtime.
055     * @throws InterruptedException if the current thread is interrupted while waiting
056     */
057    public static void initFxPlatform() throws InterruptedException {
058        final CountDownLatch startupLatch = new CountDownLatch(1);
059
060        // Note, this method is called on the FX Application Thread
061        PlatformImpl.startup(startupLatch::countDown);
062
063        // Wait for FX platform to start
064        startupLatch.await();
065    }
066
067    @Override
068    public synchronized void play(Execute command, State stateChange, URL playingUrl) throws AudioException, IOException {
069        try {
070            final URL url = command.url();
071            if (playingUrl != url) {
072                if (mediaPlayer != null) {
073                    mediaPlayer.stop();
074                }
075                // Fail fast in case of invalid local URI (JavaFX Media locator retries 5 times with a 1 second delay)
076                if ("file".equals(url.getProtocol()) && !new File(url.toURI()).exists()) {
077                    throw new FileNotFoundException(url.toString());
078                }
079                mediaPlayer = new MediaPlayer(new Media(url.toString()));
080                mediaPlayer.setOnPlaying(() ->
081                    listeners.fireEvent(l -> l.playing(url))
082                );
083            }
084            mediaPlayer.setRate(command.speed());
085            if (Status.PLAYING == mediaPlayer.getStatus()) {
086                Duration seekTime = Duration.seconds(command.offset());
087                if (!seekTime.equals(mediaPlayer.getCurrentTime())) {
088                    mediaPlayer.seek(seekTime);
089                }
090            }
091            mediaPlayer.play();
092        } catch (MediaException | URISyntaxException e) {
093            throw new AudioException(e);
094        }
095    }
096
097    @Override
098    public synchronized void pause(Execute command, State stateChange, URL playingUrl) throws AudioException, IOException {
099        if (mediaPlayer != null) {
100            try {
101                mediaPlayer.pause();
102            } catch (MediaException e) {
103                throw new AudioException(e);
104            }
105        }
106    }
107
108    @Override
109    public boolean playing(Execute command) throws AudioException, IOException, InterruptedException {
110        // Not used: JavaFX handles the low-level audio playback
111        return false;
112    }
113
114    @Override
115    public synchronized double position() {
116        return mediaPlayer != null ? mediaPlayer.getCurrentTime().toSeconds() : -1;
117    }
118
119    @Override
120    public synchronized double speed() {
121        return mediaPlayer != null ? mediaPlayer.getCurrentRate() : -1;
122    }
123
124    @Override
125    public void addAudioListener(AudioListener listener) {
126        listeners.addWeakListener(listener);
127    }
128}