Commit 4fb7b37a authored by Jason Chase's avatar Jason Chase Committed by Andrew Grieve
Browse files

CB-8252 Fire audio events from native via message channel (close #41)

- Add startup logic to initialize a message channel for native -> Javascript
- Applies only to android and amazon-fireos (as this reuses the android native code)
- Change audio status events to send via plugin message channel, instead
using eval() (i.e. webView.sendJavascript())
parent ab6563aa
Loading
Loading
Loading
Loading
+26 −0
Changes for src/android/AudioHandler.java: 26 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -25,12 +25,15 @@ import org.apache.cordova.CordovaResourceApi;
import android.content.Context;
import android.media.AudioManager;
import android.net.Uri;
import android.util.Log;

import java.util.ArrayList;

import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.HashMap;

/**
@@ -50,6 +53,7 @@ public class AudioHandler extends CordovaPlugin {
    HashMap<String, AudioPlayer> players;	// Audio player object
    ArrayList<AudioPlayer> pausedForPhone;     // Audio players that were paused when phone call came in
    private int origVolumeStream = -1;
    private CallbackContext messageChannel;

    /**
     * Constructor.
@@ -130,6 +134,10 @@ public class AudioHandler extends CordovaPlugin {
            callbackContext.sendPluginResult(new PluginResult(status, b));
            return true;
        }
        else if (action.equals("messageChannel")) {
            messageChannel = callbackContext;
            return true;
        }
        else { // Unrecognized action.
            return false;
        }
@@ -381,4 +389,22 @@ public class AudioHandler extends CordovaPlugin {
            origVolumeStream = -1;
        }
    }

    void sendEventMessage(String action, JSONObject actionData) {
        JSONObject message = new JSONObject();
        try {
            message.put("action", action);
            if (actionData != null) {
                message.put(action, actionData);
            }
        } catch (JSONException e) {
            Log.e(TAG, "Failed to create event message", e);
        }

        PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, message);
        pluginResult.setKeepCallback(true);
        if (messageChannel != null) {
            messageChannel.sendPluginResult(pluginResult);
        }
    }
}
+48 −16
Changes for src/android/AudioPlayer.java: 48 added lines, 16 removed lines.
Original line number Diff line number Diff line
@@ -27,6 +27,9 @@ import android.media.MediaRecorder;
import android.os.Environment;
import android.util.Log;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -133,7 +136,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
        switch (this.mode) {
        case PLAY:
            Log.d(LOG_TAG, "AudioPlayer Error: Can't record in play mode.");
            this.handler.webView.sendJavascript("cordova.require('org.apache.cordova.media.Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
            sendErrorStatus(MEDIA_ERR_ABORTED);
            break;
        case NONE:
            this.audioFile = file;
@@ -152,11 +155,11 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
                e.printStackTrace();
            }

            this.handler.webView.sendJavascript("cordova.require('org.apache.cordova.media.Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
            sendErrorStatus(MEDIA_ERR_ABORTED);
            break;
        case RECORD:
            Log.d(LOG_TAG, "AudioPlayer Error: Already recording.");
            this.handler.webView.sendJavascript("cordova.require('org.apache.cordova.media.Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
            sendErrorStatus(MEDIA_ERR_ABORTED);
        }
    }

@@ -227,7 +230,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
        if (this.readyPlayer(this.audioFile)) {
            this.player.seekTo(milliseconds);
            Log.d(LOG_TAG, "Send a onStatus update for the new seek");
            this.handler.webView.sendJavascript("cordova.require('org.apache.cordova.media.Media').onStatus('" + this.id + "', " + MEDIA_POSITION + ", " + milliseconds / 1000.0f + ");");
            sendStatusChange(MEDIA_POSITION, null, (milliseconds / 1000.0f));
        }
        else {
            this.seekOnPrepared = milliseconds;
@@ -246,7 +249,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
        }
        else {
            Log.d(LOG_TAG, "AudioPlayer Error: pausePlaying() called during invalid state: " + this.state.ordinal());
            this.handler.webView.sendJavascript("cordova.require('org.apache.cordova.media.Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_NONE_ACTIVE + "});");
            sendErrorStatus(MEDIA_ERR_NONE_ACTIVE);
        }
    }

@@ -262,7 +265,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
        }
        else {
            Log.d(LOG_TAG, "AudioPlayer Error: stopPlaying() called during invalid state: " + this.state.ordinal());
            this.handler.webView.sendJavascript("cordova.require('org.apache.cordova.media.Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_NONE_ACTIVE + "});");
            sendErrorStatus(MEDIA_ERR_NONE_ACTIVE);
        }
    }

@@ -284,7 +287,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
    public long getCurrentPosition() {
        if ((this.state == STATE.MEDIA_RUNNING) || (this.state == STATE.MEDIA_PAUSED)) {
            int curPos = this.player.getCurrentPosition();
            this.handler.webView.sendJavascript("cordova.require('org.apache.cordova.media.Media').onStatus('" + this.id + "', " + MEDIA_POSITION + ", " + curPos / 1000.0f + ");");
            sendStatusChange(MEDIA_POSITION, null, (curPos / 1000.0f));
            return curPos;
        }
        else {
@@ -363,7 +366,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
        this.prepareOnly = true;

        // Send status notification to JavaScript
        this.handler.webView.sendJavascript("cordova.require('org.apache.cordova.media.Media').onStatus('" + this.id + "', " + MEDIA_DURATION + "," + this.duration + ");");
        sendStatusChange(MEDIA_DURATION, null, this.duration);
    }

    /**
@@ -391,7 +394,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
        this.player.release();

        // Send error notification to JavaScript
        this.handler.webView.sendJavascript("cordova.require('org.apache.cordova.media.Media').onStatus('" + this.id + "', { \"code\":" + arg1 + "});");
        sendErrorStatus(arg1);
        return false;
    }

@@ -402,7 +405,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
     */
    private void setState(STATE state) {
        if (this.state != state) {
            this.handler.webView.sendJavascript("cordova.require('org.apache.cordova.media.Media').onStatus('" + this.id + "', " + MEDIA_STATE + ", " + state.ordinal() + ");");
            sendStatusChange(MEDIA_STATE, null, (float)state.ordinal());
        }
        this.state = state;
    }
@@ -410,7 +413,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
    /**
     * Set the mode and send it to JavaScript.
     *
     * @param state
     * @param mode
     */
    private void setMode(MODE mode) {
        if (this.mode != mode) {
@@ -451,7 +454,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
            break;
        case RECORD:
            Log.d(LOG_TAG, "AudioPlayer Error: Can't play in record mode.");
            this.handler.webView.sendJavascript("cordova.require('org.apache.cordova.media.Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
            sendErrorStatus(MEDIA_ERR_ABORTED);
            return false; //player is not ready
        }
        return true;
@@ -472,7 +475,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
                    try {
                        this.loadAudioFile(file);
                    } catch (Exception e) {
                        this.handler.webView.sendJavascript("cordova.require('org.apache.cordova.media.Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
                        sendErrorStatus(MEDIA_ERR_ABORTED);
                    }
                    return false;
                case MEDIA_LOADING:
@@ -497,14 +500,14 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
                        try {
                            this.loadAudioFile(file);
                        } catch (Exception e) {
                            this.handler.webView.sendJavascript("cordova.require('org.apache.cordova.media.Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
                            sendErrorStatus(MEDIA_ERR_ABORTED);
                        }
                        //if we had to prepare= the file, we won't be in the correct state for playback
                        //if we had to prepare the file, we won't be in the correct state for playback
                        return false;
                    }
                default:
                    Log.d(LOG_TAG, "AudioPlayer Error: startPlaying() called during invalid state: " + this.state);
                    this.handler.webView.sendJavascript("cordova.require('org.apache.cordova.media.Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
                    sendErrorStatus(MEDIA_ERR_ABORTED);
            }
        }
        return false;
@@ -552,4 +555,33 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
                this.duration = getDurationInSeconds();
            }
    }

    private void sendErrorStatus(int errorCode) {
        sendStatusChange(MEDIA_ERROR, errorCode, null);
    }

    private void sendStatusChange(int messageType, Integer additionalCode, Float value) {

        if (additionalCode != null && value != null) {
            throw new IllegalArgumentException("Only one of additionalCode or value can be specified, not both");
        }

        JSONObject statusDetails = new JSONObject();
        try {
            statusDetails.put("id", this.id);
            statusDetails.put("msgType", messageType);
            if (additionalCode != null) {
                JSONObject code = new JSONObject();
                code.put("code", additionalCode.intValue());
                statusDetails.put("value", code);
            }
            else if (value != null) {
                statusDetails.put("value", value.floatValue());
            }
        } catch (JSONException e) {
            Log.e(LOG_TAG, "Failed to create status details", e);
        }

        this.handler.sendEventMessage("status", statusDetails);
    }
}
+21 −0
Changes for www/Media.js: 21 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -193,3 +193,24 @@ Media.onStatus = function(id, msgType, value) {
};

module.exports = Media;

function onMessageFromNative(msg) {
    if (msg.action == 'status') {
        Media.onStatus(msg.status.id, msg.status.msgType, msg.status.value);
    } else {
        throw new Error('Unknown media action' + msg.action);
    }
}

if (cordova.platformId === 'android' || cordova.platformId === 'amazon-fireos') {

    var channel = require('cordova/channel');

    channel.createSticky('onMediaPluginReady');
    channel.waitForInitialization('onMediaPluginReady');

    channel.onCordovaReady.subscribe(function() {
        exec(onMessageFromNative, undefined, 'Media', 'messageChannel', []);
        channel.initializationComplete('onMediaPluginReady');
    });
}