Since the JavaFx Media has not been ported to the Mobile Platforms yet, can anyone help me with using the native iOS APi's to play a sound mp3 file that would be stored in my main/resources folder in my gluon project.
问题:
回答1:
While on Android we can easily add native API to the Android folders of a Gluon project (check this solution for using native Media on Android), the use of native code (Objetive-C) with the Media API on iOS folders is not enough, since it has to be compiled, and the compiled files have to be included into the ipa.
Currently, Charm Down is doing this for a bunch of services. If you have a look at the build.gradle
script for iOS, it includes the xcodebuild
task to compile and build the native library libCharm.a
, that later should be included in any iOS project using any of those services.
My suggestion will be cloning Charm Down, and providing a new service: AudioService
, with some basic methods like:
public interface AudioService {
void play(String audioName);
void pause();
void resume();
void stop();
}
This service will be added to the Platform
class:
public abstract class Platform {
...
public abstract AudioService getAudioService();
}
and you should provide implementations for Desktop (empty), Android (like the one here) and iOS.
IOS implementation - Java
You will have to add this to the IOSPlatform
class:
public class IOSPlatform extends Platform {
...
@Override
public AudioService getAudioService() {
if (audioService == null) {
audioService = new IOSAudioService();
}
return audioService;
}
}
and create the IOSAudioService
class:
public class IOSAudioService implements AudioService {
@Override
public void play(String audioName) {
CharmApplication.play(audioName);
}
@Override
public void pause() {
CharmApplication.pause();
}
@Override
public void resume() {
CharmApplication.resume();
}
@Override
public void stop() {
CharmApplication.stop();
}
}
Finally, you will have to add the native calls in CharmApplication
:
public class CharmApplication {
static {
System.loadLibrary("Charm");
_initIDs();
}
...
public static native void play(String audioName);
public static native void pause();
public static native void resume();
public static native void stop();
}
IOS implementation - Objective-C
Now, on the native folder, on CharmApplication.m
, add the implementation of those calls:
#include "CharmApplication.h"
...
#include "Audio.h"
// Audio
Audio *_audio;
JNIEXPORT void JNICALL Java_com_gluonhq_charm_down_ios_CharmApplication_play
(JNIEnv *env, jclass jClass, jstring jTitle)
{
NSLog(@"Play audio");
const jchar *charsTitle = (*env)->GetStringChars(env, jTitle, NULL);
NSString *name = [NSString stringWithCharacters:(UniChar *)charsTitle length:(*env)->GetStringLength(env, jTitle)];
(*env)->ReleaseStringChars(env, jTitle, charsTitle);
_audio = [[Audio alloc] init];
[_audio playAudio:name];
return;
}
JNIEXPORT void JNICALL Java_com_gluonhq_charm_down_ios_CharmApplication_pause
(JNIEnv *env, jclass jClass)
{
if (_audio)
{
[_audio pauseAudio];
}
return;
}
JNIEXPORT void JNICALL Java_com_gluonhq_charm_down_ios_CharmApplication_resume
(JNIEnv *env, jclass jClass)
{
if (_audio)
{
[_audio resumeAudio];
}
return;
}
JNIEXPORT void JNICALL Java_com_gluonhq_charm_down_ios_CharmApplication_stop
(JNIEnv *env, jclass jClass)
{
if (_audio)
{
[_audio stopAudio];
}
return;
}
and create the header file Audio.h
:
#import <AVFoundation/AVFoundation.h>
#import <UIKit/UIKit.h>
@interface Audio :UIViewController <AVAudioPlayerDelegate>
{
}
- (void) playAudio: (NSString *) audioName;
- (void) pauseAudio;
- (void) resumeAudio;
- (void) stopAudio;
@end
and the implementation Audio.m
. This one is based on this tutorial:
#include "Audio.h"
#include "CharmApplication.h"
@implementation Audio
AVAudioPlayer* player;
- (void)playAudio:(NSString *) audioName
{
NSString* fileName = [audioName stringByDeletingPathExtension];
NSString* extension = [audioName pathExtension];
NSURL* url = [[NSBundle mainBundle] URLForResource:[NSString stringWithFormat:@"%@",fileName] withExtension:[NSString stringWithFormat:@"%@",extension]];
NSError* error = nil;
if(player)
{
[player stop];
player = nil;
}
player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
if(!player)
{
NSLog(@"Error creating player: %@", error);
return;
}
player.delegate = self;
[player prepareToPlay];
[player play];
}
- (void)pauseAudio
{
if(!player)
{
return;
}
[player pause];
}
- (void)resumeAudio
{
if(!player)
{
return;
}
[player play];
}
- (void)stopAudio
{
if(!player)
{
return;
}
[player stop];
player = nil;
}
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
NSLog(@"%s successfully=%@", __PRETTY_FUNCTION__, flag ? @"YES" : @"NO");
}
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error
{
NSLog(@"%s error=%@", __PRETTY_FUNCTION__, error);
}
@end
Build the native library
Edit the build.gradle
for the iOS module, and add the Audio service to the xcodebuild
task:
def nativeSources = ["$project.projectDir/src/main/native/CharmApplication.m",
...,
"$project.projectDir/src/main/native/Audio.m"]
...
def compileOutputs = [
"$project.buildDir/native/$arch/CharmApplication.o",
"$project.buildDir/native/$arch/Charm.o",
...,
"$project.buildDir/native/$arch/Audio.o"]
Build the project
Save the project, and from the root of the Charm Down project, on command line run:
./gradlew clean build
If everything is fine, you should have:
- Common/build/libs/charm-down-common-2.1.0-SNAPSHOT.jar
- Desktop/build/libs/charm-down-desktop-2.1.0-SNAPSHOT.jar
- Android/build/libs/charm-down-android-2.1.0-SNAPSHOT.jar
- IOS/build/libs/charm-down-ios-2.1.0-SNAPSHOT.jar
- IOS/build/native/libCharm.a
Gluon Project
With those dependencies and the native library, you will be able to create a new Gluon Project, and add the jars as local dependencies (to the libs
folder).
As for the native library, it should be added to this path: src/ios/jniLibs/libCharm.a
.
Update the build.gradle
script:
repositories {
flatDir {
dirs 'libs'
}
jcenter()
...
}
dependencies {
compile 'com.gluonhq:charm-glisten:3.0.0'
compile 'com.gluonhq:charm-down-common:2.1.0-SNAPSHOT'
compile 'com.gluonhq:charm-glisten-connect-view:3.0.0'
iosRuntime 'com.gluonhq:charm-glisten-ios:3.0.0'
iosRuntime 'com.gluonhq:charm-down-ios:2.1.0-SNAPSHOT'
}
On your View, retrieve the service and provide some basic UI to call the play("audio.mp3")
, pause()
, resume()
and stop()
methods:
private boolean pause;
public BasicView(String name) {
super(name);
AudioService audioService = PlatformFactory.getPlatform().getAudioService();
final HBox hBox = new HBox(10,
MaterialDesignIcon.PLAY_ARROW.button(e -> audioService.play("audio.mp3")),
MaterialDesignIcon.PAUSE.button(e -> {
if (!pause) {
audioService.pause();
pause = true;
} else {
audioService.resume();
pause = false;
}
}),
MaterialDesignIcon.STOP.button(e -> audioService.stop()));
hBox.setAlignment(Pos.CENTER);
setCenter(new StackPane(hBox));
}
Finally, place an audio file like audio.mp3
, under src/ios/assets/audio.mp3
, build and deploy to iOS.
Hopefully, this API will be provided by Charm Down any time soon. Also if you come up with a nice implementation, feel free to share it and create a Pull Request.