Gluon Mobile iOS Audio Player

2019-07-04 00:58发布

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条回答
手持菜刀,她持情操
2楼-- · 2019-07-04 01:36

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.

查看更多
登录 后发表回答