package org.apache.cordova.speech;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Locale;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener;
import android.util.Log;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.PluginResult;
public class SpeechSynthesis extends CordovaPlugin implements OnInitListener, OnUtteranceCompletedListener {
private static final String LOG_TAG = "TTS";
private static final int STOPPED = 0;
private static final int INITIALIZING = 1;
private static final int STARTED = 2;
private TextToSpeech mTts = null;
private int state = STOPPED;
private CallbackContext startupCallbackContext;
private CallbackContext callbackContext;
private static LinkedList<Locale> voiceList = new LinkedList<Locale>();
static {
voiceList.add(Locale.US);
voiceList.add(Locale.UK);
voiceList.add(Locale.CHINA);
voiceList.add(Locale.FRANCE);
voiceList.add(Locale.GERMANY);
voiceList.add(Locale.ITALY);
voiceList.add(Locale.JAPAN);
voiceList.add(Locale.KOREA);
voiceList.add(Locale.TAIWAN);
}
//private String startupCallbackId = "";
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
PluginResult.Status status = PluginResult.Status.OK;
String result = "";
this.callbackContext = callbackContext;
try {
if (action.equals("speak")) {
JSONObject utterance = args.getJSONObject(0);
String text = utterance.getString("text");
String lang = utterance.optString("lang", "en");
mTts.setLanguage(new Locale(lang));
float pitch = (float)utterance.optDouble("pitch", 1.0);
mTts.setPitch(pitch);
float volume = (float)utterance.optDouble("volume", 0.5);
// how to set volume
float rate = (float)utterance.optDouble("rate", 1.0);
mTts.setSpeechRate(rate);
if (isReady()) {
HashMap<String, String> map = null;
map = new HashMap<String, String>();
map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, callbackContext.getCallbackId());
JSONObject event = new JSONObject();
event.put("type","start");
event.put("charIndex",0);
event.put("elapsedTime",0);
event.put("name","");
PluginResult pr = new PluginResult(PluginResult.Status.OK, event);
pr.setKeepCallback(true);
callbackContext.sendPluginResult(pr);
mTts.speak(text, TextToSpeech.QUEUE_ADD, map);
} else {
fireErrorEvent(callbackContext);
}
} else if (action.equals("cancel")) {
if (isReady()) {
HashMap<String, String> map = null;
map = new HashMap<String, String>();
//map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, callbackId);
mTts.speak("", TextToSpeech.QUEUE_FLUSH, map);
fireEndEvent(callbackContext);
} else {
fireErrorEvent(callbackContext);
}
} else if (action.equals("pause")) {
Log.d(LOG_TAG, "Not implemented yet");
} else if (action.equals("resume")) {
Log.d(LOG_TAG, "Not implemented yet");
} else if (action.equals("stop")) {
if (isReady()) {
mTts.stop();
callbackContext.sendPluginResult(new PluginResult(status, result));
} else {
fireErrorEvent(callbackContext);
}
} else if (action.equals("silence")) {
if (isReady()) {
HashMap<String, String> map = null;
map = new HashMap<String, String>();
map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, callbackContext.getCallbackId());
mTts.playSilence(args.getLong(0), TextToSpeech.QUEUE_ADD, map);
PluginResult pr = new PluginResult(PluginResult.Status.NO_RESULT);
pr.setKeepCallback(true);
callbackContext.sendPluginResult(pr);
} else {
fireErrorEvent(callbackContext);
}
} else if (action.equals("startup")) {
this.startupCallbackContext = callbackContext;
if (mTts == null) {
state = SpeechSynthesis.INITIALIZING;
mTts = new TextToSpeech(cordova.getActivity().getApplicationContext(), this);
}
PluginResult pluginResult = new PluginResult(status, SpeechSynthesis.INITIALIZING);
pluginResult.setKeepCallback(true);
startupCallbackContext.sendPluginResult(pluginResult);
}
else if (action.equals("shutdown")) {
if (mTts != null) {
mTts.shutdown();
}
callbackContext.sendPluginResult(new PluginResult(status, result));
}
else if (action.equals("isLanguageAvailable")) {
if (mTts != null) {
Locale loc = new Locale(args.getString(0));
int available = mTts.isLanguageAvailable(loc);
result = (available < 0) ? "false" : "true";
callbackContext.sendPluginResult(new PluginResult(status, result));
}
}
return true;
} catch (JSONException e) {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
}
return false;
}
private void fireEndEvent(CallbackContext callbackContext) {
JSONObject event = new JSONObject();
try {
event.put("type","end");
} catch (JSONException e) {
// this will never happen
}
PluginResult pr = new PluginResult(PluginResult.Status.OK, event);
pr.setKeepCallback(false);
callbackContext.sendPluginResult(pr);
}
private void fireErrorEvent(CallbackContext callbackContext)
throws JSONException {
JSONObject error = new JSONObject();
error.put("type","error");
error.put("charIndex",0);
error.put("elapsedTime",0);
error.put("name","");
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, error));
}
/**
* Is the TTS service ready to play yet?
*
* @return
*/
private boolean isReady() {
return (state == SpeechSynthesis.STARTED) ? true : false;
}
/**
* Called when the TTS service is initialized.
*
* @param status
*/
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
state = SpeechSynthesis.STARTED;
JSONArray voices = new JSONArray();
JSONObject voice;
Iterator<Locale> list = voiceList.iterator();
Locale locale;
while (list.hasNext()) {
locale = list.next();
voice = new JSONObject();
if (mTts.isLanguageAvailable(locale) > 0) {
try {
voice.put("voiceURI", "");
voice.put("name", locale.getDisplayLanguage(locale) + " " + locale.getDisplayCountry(locale));
voice.put("lang", locale.getLanguage());
voice.put("localService", true);
voice.put("default", false);
} catch (JSONException e) {
// should never happen
}
voices.put(voice);
}
}
PluginResult result = new PluginResult(PluginResult.Status.OK, voices);
result.setKeepCallback(false);
this.startupCallbackContext.sendPluginResult(result);
mTts.setOnUtteranceCompletedListener(this);
// Putting this code in hear as a place holder. When everything moves to API level 15 or greater
// we'll switch over to this way of tracking progress.
// mTts.setOnUtteranceProgressListener(new UtteranceProgressListener() {
//
// @Override
// public void onDone(String utteranceId) {
// Log.d(LOG_TAG, "got completed utterance");
// PluginResult result = new PluginResult(PluginResult.Status.OK);
// result.setKeepCallback(false);
// callbackContext.sendPluginResult(result);
// }
//
// @Override
// public void onError(String utteranceId) {
// Log.d(LOG_TAG, "got utterance error");
// PluginResult result = new PluginResult(PluginResult.Status.ERROR);
// result.setKeepCallback(false);
// callbackContext.sendPluginResult(result);
// }
//
// @Override
// public void onStart(String utteranceId) {
// Log.d(LOG_TAG, "started talking");
// }
//
// });
}
else if (status == TextToSpeech.ERROR) {
state = SpeechSynthesis.STOPPED;
PluginResult result = new PluginResult(PluginResult.Status.ERROR, SpeechSynthesis.STOPPED);
result.setKeepCallback(false);
this.startupCallbackContext.sendPluginResult(result);
}
}
/**
* Clean up the TTS resources
*/
public void onDestroy() {
if (mTts != null) {
mTts.shutdown();
}
}
/**
* Once the utterance has completely been played call the speak's success callback
*/
public void onUtteranceCompleted(String utteranceId) {
fireEndEvent(callbackContext);
}
}