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();
}
}