1

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.

Mike Murphy
  • 1,006
  • 8
  • 16

2 Answers2

6

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.

José Pereda
  • 44,311
  • 7
  • 104
  • 132
  • - Perfect. Works Great. – Mike Murphy Jul 18 '16 at 09:36
  • I currently try to implement it, as you suggested, @josé-pereda - But I can't find the Android class "MediaPlayer" . I inlcuded `compile 'org.javafxports:jfxdvk:8.60.8'` for the FXActivity, but fail to understand, what else is needed. I guess the `android.jar`, right? But where exactly? (btw: I'm using the Eclipse plugin, if that's important.) – dzim Dec 01 '16 at 11:35
  • I was able to implement it, by adding this dependency: `compile files('libs/android-22.jar')` (where the numer 22 marks the targeted SDK). But I explicitly needed to comment this line prior building the app, because otherwise Retrolambda would crash... Additionally, I can't hear a thing and the volumne button events not seem to trigger the usual volume up/down behavior. #edit: I upped the volumne in the settings, but still could not hear a thing. I don't get any errors, so everything seems to be ok, but is not working anyway... – dzim Dec 01 '16 at 12:10
  • Since my edit was not approved: The current example uses the ringtone volume, to allow you application to use the music/media volume, you should add the following line: `FXActivity.getInstance().setVolumeControlStream(AudioManager.STREAM_MUSIC);` Additionally you need to change `mp.setAudioStreamType(AudioManager.STREAM_RING);` to `mp.setAudioStreamType(AudioManager.STREAM_MUSIC);`. – dzim Dec 02 '16 at 08:48
  • @dzim About your issues with the dependencies, if you use the jfxmobile plugin, and provide a valid ANDROID_HOME with your android sdk path, you should have the android jar included by default in your project. If you still have issues create a new question with it. – José Pereda Dec 03 '16 at 13:53
  • Unfortunately I still have the issue. Probably a problem of Eclipse. I will create a new question for this later. Thanks anyway! :-) – dzim Dec 03 '16 at 14:33
1

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();
    }
}
dzim
  • 1,131
  • 2
  • 12
  • 28