Run a Capacitor plugin on a different thread in Android

I am currently working on a Capacitor Plugin within my Ionic app with Custom Native Code (not a full separate plugin). The plugin is to play audio in the background. Right now I am working on the Android portion using a foreground service and ExoPlayer to manage playing the audio and the required foreground notification.

For the foreground notification, I am using ExoPlayer’s PlayerNotificationManager. This requires the player to be running on the main thread of the app (reference).

This leads me to my question. How does one tell Capacitor to run a plugin on a specific thread? By default, Capacitor creates it’s own thread named CapacitorPlugins.

The code ExoPlayer is using to check the thread is:

Looper.myLooper() == Looper.getMainLooper()

// Looper.myLooper() = CapacitorPlugins
// Looper.getMainLooper() = main

So far, I’ve only found a way to run a Capacitor plugin method on the main thread by using Activity.runOnUiThread.

Example:

@PluginMethod
public void play(PluginCall call) {
    getBridge().getActivity().runOnUiThread(new Runnable() {
        @Override
        public void run() {
            audioPlayerService.play(audioId(call));
        }
    });

    call.resolve();
}

Is this a bad idea? Is there a better way? I see you can set a custom Bridge in Capacitor by using Plugin.setBridge where I could override the HandlerThread but not sure if that is a good idea and it isn’t 100% clear on where I would call that or how to set it up.

Any pointers/help is greatly appreciated!

runOnUiThread should be used to run things on the UI Thread, for updating native views and such.

For just using the main thread you can do something like:

@PluginMethod
public void play(PluginCall call) {
    new Handler(Looper.getMainLooper())
        .post(
            () -> {
                audioPlayerService.play(audioId(call));
            }
        );
    call.resolve();
}

Oh! I didn’t think about doing that. From my testing and from what I’ve read, the UI Thread is typically the main thread; however, I like your solution as it is explicit. Also with your solution you in theory could run it on a different thread other than the main one.

One other quick question. If the audio service needs to return a value like the duration, do you see any issues of calling call.resolve within the same thread?

Example:

new Handler(Looper.getMainLooper()).post(
        () -> {
            float result = audioPlayerService.getDuration(audioId(call));
            call.resolve(new JSObject().put("duration", result));
        }
);

There shouldn’t be any problem, we do it in some of the core plugins, I’ve put it outside because in your example it was outside.

1 Like

Great! Thank you for the help!