javafxports how to call android native Media Player

As the javafxports Media is not yet implemented I'm looking to use the Android Native MediaPlayer instead. Does anyone know how to do this.


If you have a look at the GoNative sample here (docs and code), you'll find a way to add Android native code to your JavaFX project.

This is a simple example of adding android.media.MediaPlayer to a JavaFX project using the Gluon plugin.

Based on a Single View project, let's add first an interface with the required audio method signatures:

public interface NativeAudioService {
    void play();
    void pause();
    void resume();
    void stop();
}

Now in our View we can create the buttons to call those methods based on an instance of AndroidNativeAudio class that implements the NativeAudioService interface:

public class BasicView extends View {

    private NativeAudioService service;
    private boolean pause;

    public BasicView(String name) {
        super(name);

        try {
            service = (NativeAudioService) Class.forName("com.gluonhq.nativeaudio.AndroidNativeAudio").newInstance();
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) {
            System.out.println("Error " + ex);
        }

        if (service != null) {
            final HBox hBox = new HBox(10, 
                    MaterialDesignIcon.PLAY_ARROW.button(e -> service.play()),
                    MaterialDesignIcon.PAUSE.button(e -> {
                        if (!pause) {
                            service.pause();
                            pause = true;
                        } else {
                            service.resume();
                            pause = false;
                        }
                    }),
                    MaterialDesignIcon.STOP.button(e -> service.stop()));
            hBox.setAlignment(Pos.CENTER);
            setCenter(new StackPane(hBox));
        } else {
            setCenter(new StackPane(new Label("Only for Android")));
        }
    }

    @Override
    protected void updateAppBar(AppBar appBar) {
        appBar.setNavIcon(MaterialDesignIcon.MUSIC_NOTE.button());
        appBar.setTitleText("Native Audio");
    }
}

Now, we create the native class under the Android folder. It will make use of the android API. It will try to find the audio file audio.mp3 that we have to place under the /src/android/assets folder:

package com.gluonhq.nativeaudio;

import android.content.res.AssetFileDescriptor;
import android.media.AudioManager;
import android.media.MediaPlayer;
import java.io.IOException;
import javafxports.android.FXActivity;

public class AndroidNativeAudio implements NativeAudioService {

    private MediaPlayer mp;
    private int currentPosition;

    public AndroidNativeAudio() { }

    @Override
    public void play() {
        currentPosition = 0;
        try {
            if (mp != null) {
                stop();
            }
            mp = new MediaPlayer();
            AssetFileDescriptor afd = FXActivity.getInstance().getAssets().openFd("audio.mp3");

            mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
            mp.setAudioStreamType(AudioManager.STREAM_RING);
            mp.setOnCompletionListener(mp -> stop());
            mp.prepare();
            mp.start();
        } catch (IOException e) {
            System.out.println("Error playing audio resource " + e);
        }
    }

    @Override
    public void stop() {
        if (mp != null) {
            if (mp.isPlaying()) {
                mp.stop();
            }
            mp.release();
            mp = null;
        }
    }

    @Override
    public void pause() {
        if (mp != null) {
            mp.pause();
            currentPosition = mp.getCurrentPosition();
        }
    }

    @Override
    public void resume() {
        if (mp != null) {
            mp.start();
            mp.seekTo(currentPosition);
        }
    }
}

Finally, we can deploy the project to an Android device running gradlew androidInstall.


The native audio player was used in the following example:

https://gist.github.com/bgmf/d87a2bac0a5623f359637a3da334f980

Beside some prerequisites, the code looks like this:

package my.application;

import my.application.Constants;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import org.robovm.apple.avfoundation.AVAudioPlayer;
import org.robovm.apple.foundation.NSErrorException;
import org.robovm.apple.foundation.NSURL;
import org.robovm.apple.foundation.NSURLScheme;

import java.io.File;
import java.util.logging.Level;
import java.util.logging.Logger;

public class NativeAudioServiceIOS extends PathHelperIOS implements NativeAudioService {
    private static final Logger LOG = Logger.getLogger(NativeAudioServiceIOS.class.getName());
    private static final String DIR_NAME = Constants.OBJECTS_BASE_PATH;

    private final ReadOnlyObjectWrapper<Status> status = new ReadOnlyObjectWrapper<>(this, "status", Status.STOP);
    private String filename = null;
    private AVAudioPlayer player = null;

    public NativeAudioServiceIOS() {
        super();
    }

    @Override
    public void init(String filename) throws NativeServiceException {
        this.filename = filename.startsWith("/") ? filename.substring(1) : filename;
        LOG.warning("Called with file: " + filename);
        status.set(Status.STOP);

        try {
            if(!filename.startsWith("/")) filename = "/" + filename;
            File fullfile = new File(pathBase.getAbsolutePath() + filename);
            if(fullfile.exists()) {
                NSURL fullurl = new NSURL(NSURLScheme.File, "", fullfile.getAbsolutePath());
                LOG.log(Level.SEVERE, "Loading URL: " + fullurl);

                // Create audio player object and initialize with URL to sound
                player = new AVAudioPlayer(fullurl);
                LOG.log(Level.SEVERE, "Player initialized: " + player);

                status.set(Status.STOP);
            } else {
                LOG.log(Level.WARNING, String.format("Audiofile doesn't exist: %s (%s / %s)",
                        fullfile.getAbsolutePath(),
                        pathBase.getAbsolutePath(),
                        filename));
                player = null;
                status.set(Status.ERROR);
            }
        } catch(NSErrorException error) {
            LOG.log(Level.SEVERE, "Audio Setup Failed: " + error.toString(), error);
            status.set(Status.ERROR);
        }
    }

    @Override
    public void play() throws NativeServiceException {
        if(player == null) return;

        player.play();
        status.set(Status.PLAY);
    }

    @Override
    public void pause() throws NativeServiceException {
        if(player == null) return;

        player.pause();
        status.set(Status.PAUSE);
    }

    @Override
    public void resume() throws NativeServiceException {
        if(player == null) return;

        player.play();
        status.set(Status.PLAY);
    }

    @Override
    public void stop() throws NativeServiceException {
        if(player == null) return;

        player.stop();
        player.setCurrentTime(0.0);
        status.set(Status.STOP);
    }

    @Override
    public ReadOnlyObjectProperty<Status> statusProperty() {
        return status.getReadOnlyProperty();
    }

    @Override
    public Status getStatus() {
        return status.get();
    }
}