[12328] | 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
|
---|
[12414] | 51 | PlatformImpl.startup(startupLatch::countDown);
|
---|
[12328] | 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 {
|
---|
[12414] | 60 | final URL url = command.url();
|
---|
[12328] | 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 | }
|
---|
[12413] | 69 | mediaPlayer = new MediaPlayer(new Media(url.toString()));
|
---|
[12414] | 70 | mediaPlayer.setOnPlaying(() ->
|
---|
| 71 | listeners.fireEvent(l -> l.playing(url))
|
---|
| 72 | );
|
---|
[12328] | 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 | }
|
---|