Index: applications/editors/josm/plugins/javafx/src/org/openstreetmap/josm/plugins/javafx/JavafxPlugin.java
===================================================================
--- applications/editors/josm/plugins/javafx/src/org/openstreetmap/josm/plugins/javafx/JavafxPlugin.java	(revision 34700)
+++ applications/editors/josm/plugins/javafx/src/org/openstreetmap/josm/plugins/javafx/JavafxPlugin.java	(revision 34700)
@@ -0,0 +1,120 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.javafx;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.security.CodeSource;
+import java.util.Enumeration;
+import java.util.Objects;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.openstreetmap.josm.data.Preferences;
+import org.openstreetmap.josm.io.audio.AudioPlayer;
+import org.openstreetmap.josm.plugins.DynamicURLClassLoader;
+import org.openstreetmap.josm.plugins.Plugin;
+import org.openstreetmap.josm.plugins.PluginInformation;
+import org.openstreetmap.josm.plugins.javafx.io.audio.JavaFxMediaPlayer;
+import org.openstreetmap.josm.tools.Logging;
+import org.openstreetmap.josm.tools.PlatformManager;
+
+/**
+ * OpenJFX plugin brings OpenJFX (JavaFX) to other plugins.
+ */
+public class JavafxPlugin extends Plugin {
+
+    /**
+     * Constructs a new {@code OpenJfxPlugin}.
+     * @param info plugin info
+     */
+    public JavafxPlugin(PluginInformation info) {
+        super(info);
+        AudioPlayer.setSoundPlayerClass(JavaFxMediaPlayer.class);
+        String ext = null;
+        if (PlatformManager.isPlatformWindows()) {
+            ext = ".dll";
+        } else if (PlatformManager.isPlatformUnixoid()) {
+            ext = ".so";
+        } else if (PlatformManager.isPlatformOsx()) {
+            ext = ".dylib";
+        }
+        extractNativeLibs(ext);
+        loadNativeLibs(ext);
+    }
+
+    private static void extractNativeLibs(String ext) {
+        CodeSource src = JavafxPlugin.class.getProtectionDomain().getCodeSource();
+        if (src != null) {
+            try (ZipFile zf = new ZipFile(Paths.get(src.getLocation().toURI()).toFile(), StandardCharsets.UTF_8)) {
+                Path dir = getNativeDir();
+                Enumeration<? extends ZipEntry> es = zf.entries();
+                while (es.hasMoreElements()) {
+                    ZipEntry ze = es.nextElement();
+                    String name = ze.getName();
+                    if (name.endsWith(ext) || name.endsWith(".jar")) {
+                        Path targetPath = dir.resolve(name);
+                        File targetFile = targetPath.toFile();
+                        if (!targetFile.exists() || targetFile.lastModified() < ze.getTime()) {
+                            try (InputStream is = zf.getInputStream(ze)) {
+                                Logging.debug("Extracting " + targetPath);
+                                Files.copy(is, targetPath, StandardCopyOption.REPLACE_EXISTING);
+                            }
+                        }
+                    }
+                }
+            } catch (IOException | URISyntaxException e) {
+                Logging.error(e);
+            }
+        } else {
+            Logging.error("Unable to locate openjfx jar file");
+        }
+    }
+
+    private static Path getNativeDir() throws IOException {
+        return Files.createDirectories(new File(Preferences.main().getPluginsDirectory(), "openjfx").toPath());
+    }
+
+    private static class LibVisitor extends SimpleFileVisitor<Path> {
+        private final ClassLoader ccl = Thread.currentThread().getContextClassLoader();
+        private final String ext;
+
+        public LibVisitor(String ext) {
+            this.ext = Objects.requireNonNull(ext);
+        }
+
+        @Override
+        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+            if (ccl instanceof DynamicURLClassLoader) {
+                if (file.endsWith(ext)) {
+                    Logging.debug("Loading " + file);
+                    System.load(file.toAbsolutePath().toString());
+                } else if (file.endsWith(".jar")) {
+                    Logging.debug("Loading " + file);
+                    ((DynamicURLClassLoader) ccl).addURL(file.toUri().toURL());
+                }
+            } else {
+                Logging.error("Unexpected context class loader: " + ccl);
+                return FileVisitResult.TERMINATE;
+            }
+            return FileVisitResult.CONTINUE;
+        }
+    }
+
+    private void loadNativeLibs(String ext) {
+        try {
+            Files.walkFileTree(getNativeDir(), new LibVisitor(ext));
+        } catch (IOException e) {
+            Logging.error(e);
+        }
+    }
+}
Index: applications/editors/josm/plugins/javafx/src/org/openstreetmap/josm/plugins/javafx/io/audio/JavaFxMediaPlayer.java
===================================================================
--- applications/editors/josm/plugins/javafx/src/org/openstreetmap/josm/plugins/javafx/io/audio/JavaFxMediaPlayer.java	(revision 34700)
+++ applications/editors/josm/plugins/javafx/src/org/openstreetmap/josm/plugins/javafx/io/audio/JavaFxMediaPlayer.java	(revision 34700)
@@ -0,0 +1,125 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.javafx.io.audio;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.concurrent.CountDownLatch;
+
+import org.openstreetmap.josm.io.audio.AudioException;
+import org.openstreetmap.josm.io.audio.AudioListener;
+import org.openstreetmap.josm.io.audio.AudioPlayer.Execute;
+import org.openstreetmap.josm.io.audio.AudioPlayer.State;
+import org.openstreetmap.josm.io.audio.SoundPlayer;
+import org.openstreetmap.josm.tools.JosmRuntimeException;
+import org.openstreetmap.josm.tools.ListenerList;
+
+import com.sun.javafx.application.PlatformImpl;
+
+import javafx.scene.media.Media;
+import javafx.scene.media.MediaException;
+import javafx.scene.media.MediaPlayer;
+import javafx.scene.media.MediaPlayer.Status;
+import javafx.util.Duration;
+
+/**
+ * Default sound player based on the Java FX Media API.
+ * It supports the following audio codecs:<ul>
+ * <li>MP3</li>
+ * <li>AIFF containing uncompressed PCM</li>
+ * <li>WAV containing uncompressed PCM</li>
+ * <li>MPEG-4 multimedia container with Advanced Audio Coding (AAC) audio</li>
+ * </ul>
+ */
+public class JavaFxMediaPlayer implements SoundPlayer {
+
+    private final ListenerList<AudioListener> listeners = ListenerList.create();
+
+    private MediaPlayer mediaPlayer;
+
+    JavaFxMediaPlayer() {
+        try {
+            initFxPlatform();
+        } catch (InterruptedException e) {
+            throw new JosmRuntimeException(e);
+        }
+    }
+
+    /**
+     * Initializes the JavaFX platform runtime.
+     * @throws InterruptedException if the current thread is interrupted while waiting
+     */
+    public static void initFxPlatform() throws InterruptedException {
+        final CountDownLatch startupLatch = new CountDownLatch(1);
+
+        // Note, this method is called on the FX Application Thread
+        PlatformImpl.startup(startupLatch::countDown);
+
+        // Wait for FX platform to start
+        startupLatch.await();
+    }
+
+    @Override
+    public synchronized void play(Execute command, State stateChange, URL playingUrl) throws AudioException, IOException {
+        try {
+            final URL url = command.url();
+            if (playingUrl != url) {
+                if (mediaPlayer != null) {
+                    mediaPlayer.stop();
+                }
+                // Fail fast in case of invalid local URI (JavaFX Media locator retries 5 times with a 1 second delay)
+                if ("file".equals(url.getProtocol()) && !new File(url.toURI()).exists()) {
+                    throw new FileNotFoundException(url.toString());
+                }
+                mediaPlayer = new MediaPlayer(new Media(url.toString()));
+                mediaPlayer.setOnPlaying(() ->
+                    listeners.fireEvent(l -> l.playing(url))
+                );
+            }
+            mediaPlayer.setRate(command.speed());
+            if (Status.PLAYING == mediaPlayer.getStatus()) {
+                Duration seekTime = Duration.seconds(command.offset());
+                if (!seekTime.equals(mediaPlayer.getCurrentTime())) {
+                    mediaPlayer.seek(seekTime);
+                }
+            }
+            mediaPlayer.play();
+        } catch (MediaException | URISyntaxException e) {
+            throw new AudioException(e);
+        }
+    }
+
+    @Override
+    public synchronized void pause(Execute command, State stateChange, URL playingUrl) throws AudioException, IOException {
+        if (mediaPlayer != null) {
+            try {
+                mediaPlayer.pause();
+            } catch (MediaException e) {
+                throw new AudioException(e);
+            }
+        }
+    }
+
+    @Override
+    public boolean playing(Execute command) throws AudioException, IOException, InterruptedException {
+        // Not used: JavaFX handles the low-level audio playback
+        return false;
+    }
+
+    @Override
+    public synchronized double position() {
+        return mediaPlayer != null ? mediaPlayer.getCurrentTime().toSeconds() : -1;
+    }
+
+    @Override
+    public synchronized double speed() {
+        return mediaPlayer != null ? mediaPlayer.getCurrentRate() : -1;
+    }
+
+    @Override
+    public void addAudioListener(AudioListener listener) {
+        listeners.addWeakListener(listener);
+    }
+}
Index: applications/editors/josm/plugins/javafx/src/org/openstreetmap/josm/plugins/javafx/io/audio/package-info.java
===================================================================
--- applications/editors/josm/plugins/javafx/src/org/openstreetmap/josm/plugins/javafx/io/audio/package-info.java	(revision 34700)
+++ applications/editors/josm/plugins/javafx/src/org/openstreetmap/josm/plugins/javafx/io/audio/package-info.java	(revision 34700)
@@ -0,0 +1,6 @@
+// License: GPL. For details, see LICENSE file.
+
+/**
+ * Provides the classes for Audio mapping features requiring JavaFX.
+ */
+package org.openstreetmap.josm.plugins.javafx.io.audio;
Index: applications/editors/josm/plugins/javafx/src/org/openstreetmap/josm/plugins/javafx/package-info.java
===================================================================
--- applications/editors/josm/plugins/javafx/src/org/openstreetmap/josm/plugins/javafx/package-info.java	(revision 34700)
+++ applications/editors/josm/plugins/javafx/src/org/openstreetmap/josm/plugins/javafx/package-info.java	(revision 34700)
@@ -0,0 +1,6 @@
+// License: GPL. For details, see LICENSE file.
+
+/**
+ * Provides Main plugin class.
+ */
+package org.openstreetmap.josm.plugins.javafx;
