diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0011c5c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/.idea/
+/android/build/
diff --git a/README.md b/README.md
index 7d3a8f9..e0f8be1 100644
--- a/README.md
+++ b/README.md
@@ -45,11 +45,22 @@ var Speech = require('react-native-speech');
var YourComponent = React.createClass({
_startHandler() {
Speech.speak({
- text: 'Aujourd\'hui, Maman est morte. Ou peut-ĂȘtre hier, je ne sais pas.',
+ text: 'Nous faisons le test 1',
voice: 'fr-FR'
})
.then(started => {
- console.log('Speech started');
+ console.log('Fin du test 1');
+ return Speech.speak({
+ text: 'Et voilĂ le test 2',
+ voice: 'fr-FR'
+ })
+ })
+ .then(started => {
+ console.log('Fin du test 2');
+ return Speech.speak({
+ text: 'Et maintenant le test 3',
+ voice: 'fr-FR'
+ })
})
.catch(error => {
console.log('You\'ve already started a speech instance.');
@@ -120,6 +131,10 @@ Speech.speak({
});
```
+__Android feature__
+If you don't add forceStop = true argument to speak parameters your next speech will be queue.
+
+
### pause()
Pauses the speech instance.
diff --git a/SpeechSynthesizer.android.js b/SpeechSynthesizer.android.js
index 7e0dcd8..3ba4341 100644
--- a/SpeechSynthesizer.android.js
+++ b/SpeechSynthesizer.android.js
@@ -1,16 +1,48 @@
/**
- * Stub of SpeechSynthesizer for Android.
- *
* @providesModule SpeechSynthesizer
* @flow
*/
'use strict';
-var warning = require('warning');
+var React = require('react-native');
+var { NativeModules } = React;
+var NativeSpeechSynthesizer = NativeModules.SpeechSynthesizer;
+
+/**
+ * High-level docs for the SpeechSynthesizer Android API can be written here.
+ */
var SpeechSynthesizer = {
- test: function() {
- warning("Not yet implemented for Android.");
+ test () {
+ return NativeSpeechSynthesizer.reactNativeSpeech();
+ },
+
+ supportedVoices() {
+ return NativeSpeechSynthesizer.supportedVoices();
+ },
+
+ isSpeaking() {
+ return NativeSpeechSynthesizer.isSpeaking();
+ },
+
+ isPaused() {
+ return NativeSpeechSynthesizer.isPaused();
+ },
+
+ resume() {
+ return NativeSpeechSynthesizer.resume();
+ },
+
+ pause() {
+ return NativeSpeechSynthesizer.pause();
+ },
+
+ stop() {
+ return NativeSpeechSynthesizer.stop();
+ },
+
+ speak(options) {
+ return NativeSpeechSynthesizer.speak(options);
}
};
diff --git a/android/build.gradle b/android/build.gradle
new file mode 100644
index 0000000..6620665
--- /dev/null
+++ b/android/build.gradle
@@ -0,0 +1,23 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion "23.0.2"
+
+ defaultConfig {
+ minSdkVersion 16
+ targetSdkVersion 23
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ }
+ }
+}
+
+dependencies {
+ compile 'com.android.support:appcompat-v7:23.1.0'
+ compile 'com.facebook.react:react-native:+'
+}
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..ad732e4
--- /dev/null
+++ b/android/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/android/src/main/java/com/omega/speech/SpeechSynthesizerModule.java b/android/src/main/java/com/omega/speech/SpeechSynthesizerModule.java
new file mode 100644
index 0000000..235df67
--- /dev/null
+++ b/android/src/main/java/com/omega/speech/SpeechSynthesizerModule.java
@@ -0,0 +1,233 @@
+package com.omega.speech;
+
+import java.util.Locale;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import android.content.Context;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.os.Bundle;
+import android.speech.tts.TextToSpeech.Engine;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.UtteranceProgressListener;
+
+import com.facebook.common.logging.FLog;
+
+import com.facebook.react.common.ReactConstants;
+
+import com.facebook.react.bridge.Callback;
+import com.facebook.react.bridge.Promise;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactMethod;
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
+
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.GuardedAsyncTask;
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.bridge.WritableArray;
+import com.facebook.react.bridge.WritableMap;
+
+import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
+
+
+class SpeechSynthesizerModule extends ReactContextBaseJavaModule {
+ private Context context;
+ private static TextToSpeech tts;
+ private Map ttsPromises = new HashMap();
+
+ public SpeechSynthesizerModule(ReactApplicationContext reactContext) {
+ super(reactContext);
+ this.context = reactContext;
+ this.init();
+ }
+
+ /**
+ * @return the name of this module. This will be the name used to {@code require()} this module
+ * from javascript.
+ */
+ @Override
+ public String getName() {
+ return "SpeechSynthesizer";
+ }
+
+ /**
+ * Intialize the TTS module
+ */
+ public void init(){
+ tts = new TextToSpeech(getReactApplicationContext(), new TextToSpeech.OnInitListener() {
+ @Override
+ public void onInit(int status) {
+ if(status == TextToSpeech.ERROR){
+ FLog.e(ReactConstants.TAG,"Not able to initialized the TTS object");
+ }
+ }
+ });
+ tts.setOnUtteranceProgressListener(new UtteranceProgressListener() {
+ @Override
+ public void onDone(String utteranceId) {
+ WritableMap map = Arguments.createMap();
+ map.putString("utteranceId", utteranceId);
+ getReactApplicationContext().getJSModule(RCTDeviceEventEmitter.class)
+ .emit("FinishSpeechUtterance", map);
+ Promise promise = ttsPromises.get(utteranceId);
+ promise.resolve(utteranceId);
+ }
+
+ @Override
+ public void onError(String utteranceId) {
+ WritableMap map = Arguments.createMap();
+ map.putString("utteranceId", utteranceId);
+ getReactApplicationContext().getJSModule(RCTDeviceEventEmitter.class)
+ .emit("ErrorSpeechUtterance", map);
+ Promise promise = ttsPromises.get(utteranceId);
+ promise.reject(utteranceId);
+ }
+
+ @Override
+ public void onStart(String utteranceId) {
+ WritableMap map = Arguments.createMap();
+ map.putString("utteranceId", utteranceId);
+ getReactApplicationContext().getJSModule(RCTDeviceEventEmitter.class)
+ .emit("StartSpeechUtterance", map);
+ }
+ });
+ }
+
+ @ReactMethod
+ public void supportedVoices(final Promise promise) {
+ new GuardedAsyncTask(getReactApplicationContext()) {
+ @Override
+ protected void doInBackgroundGuarded(Void... params) {
+ try{
+ if(tts == null){
+ init();
+ }
+ Locale[] locales = Locale.getAvailableLocales();
+ WritableArray data = Arguments.createArray();
+ for (Locale locale : locales) {
+ int res = tts.isLanguageAvailable(locale);
+ if(res == TextToSpeech.LANG_COUNTRY_AVAILABLE){
+ data.pushString(locale.getLanguage());
+ }
+ }
+ promise.resolve(data);
+ } catch (Exception e) {
+ promise.reject(e.getMessage());
+ }
+ }
+ }.execute();
+ }
+
+ @ReactMethod
+ public void isSpeaking(final Promise promise) {
+ new GuardedAsyncTask(getReactApplicationContext()){
+ @Override
+ protected void doInBackgroundGuarded(Void... params){
+ try {
+ if (tts.isSpeaking()) {
+ promise.resolve(true);
+ } else {
+ promise.resolve(false);
+ }
+ } catch (Exception e){
+ promise.reject(e.getMessage());
+ }
+ }
+ }.execute();
+ }
+
+ @ReactMethod
+ public void isPaused(final Promise promise) {
+ promise.reject("This function doesn\'t exists on android !");
+ }
+
+ @ReactMethod
+ public void resume(final Promise promise) {
+ promise.reject("This function doesn\'t exists on android !");
+ }
+
+ @ReactMethod
+ public void pause(final Promise promise) {
+ promise.reject("This function doesn\'t exists on android !");
+ }
+
+ @ReactMethod
+ public void stop(final Promise promise) {
+ new GuardedAsyncTask(getReactApplicationContext()){
+ @Override
+ protected void doInBackgroundGuarded(Void... params){
+ try {
+ tts.stop();
+ promise.resolve(true);
+
+ } catch (Exception e){
+ promise.reject(e.getMessage());
+ }
+ }
+ }.execute();
+ }
+
+ @ReactMethod
+ public void speak(final ReadableMap args, final Promise promise) {
+ new GuardedAsyncTask(getReactApplicationContext()) {
+ @Override
+ protected void doInBackgroundGuarded(Void... params) {
+ if(tts == null){
+ init();
+ }
+ String text = args.hasKey("text") ? args.getString("text") : null;
+ String voice = args.hasKey("voice") ? args.getString("voice") : null;
+ Boolean forceStop = args.hasKey("forceStop") ? args.getBoolean("forceStop") : null;
+ Float rate = args.hasKey("rate") ? (float) args.getDouble("rate") : null;
+ int queueMethod = TextToSpeech.QUEUE_FLUSH;
+
+ if(tts.isSpeaking()){
+ //Force to stop and start new speech
+ if(forceStop != null && forceStop){
+ tts.stop();
+ } else {
+ queueMethod = TextToSpeech.QUEUE_ADD;
+ }
+ }
+ if(args.getString("text") == null || text == ""){
+ promise.reject("Text cannot be blank");
+ }
+ try {
+ if (voice != null && voice != "") {
+ tts.setLanguage(new Locale(voice));
+ } else {
+ //Setting up default voice
+ tts.setLanguage(new Locale("en"));
+ }
+ //Set the rate if provided by the user
+ if(rate != null){
+ tts.setPitch(rate);
+ }
+
+ int speakResult = 0;
+ String speechUUID = UUID.randomUUID().toString();
+ if(Build.VERSION.SDK_INT >= 21) {
+ Bundle bundle = new Bundle();
+ bundle.putCharSequence(Engine.KEY_PARAM_UTTERANCE_ID, "");
+ ttsPromises.put(speechUUID, promise);
+ speakResult = tts.speak(text, queueMethod, bundle, speechUUID);
+ } else {
+ HashMap map = new HashMap();
+ map.put(Engine.KEY_PARAM_UTTERANCE_ID, speechUUID);
+ ttsPromises.put(speechUUID, promise);
+ speakResult = tts.speak(text, queueMethod, map);
+ }
+
+ if(speakResult < 0) {
+ throw new Exception("Speak failed, make sure that TTS service is installed on you device");
+ }
+ } catch (Exception e) {
+ promise.reject(e.getMessage());
+ }
+ }
+ }.execute();
+ }
+}
diff --git a/android/src/main/java/com/omega/speech/SpeechSynthesizerPackage.java b/android/src/main/java/com/omega/speech/SpeechSynthesizerPackage.java
new file mode 100644
index 0000000..fad6d4d
--- /dev/null
+++ b/android/src/main/java/com/omega/speech/SpeechSynthesizerPackage.java
@@ -0,0 +1,46 @@
+package com.omega.speech;
+
+import com.facebook.react.ReactPackage;
+import com.facebook.react.bridge.JavaScriptModule;
+import com.facebook.react.bridge.NativeModule;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.uimanager.ViewManager;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class SpeechSynthesizerPackage implements ReactPackage {
+ /**
+ * @param reactContext react application context that can be used to create modules
+ * @return list of native modules to register with the newly created catalyst instance
+ */
+ @Override
+ public List createNativeModules(ReactApplicationContext reactContext) {
+ List modules = new ArrayList<>();
+ modules.add(new SpeechSynthesizerModule(reactContext));
+
+ return modules;
+ }
+
+ /**
+ * @return list of JS modules to register with the newly created catalyst instance.
+ *
+ * IMPORTANT: Note that only modules that needs to be accessible from the native code should be
+ * listed here. Also listing a native module here doesn't imply that the JS implementation of it
+ * will be automatically included in the JS bundle.
+ */
+ @Override
+ public List> createJSModules() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * @param reactContext
+ * @return a list of view managers that should be registered with {@link UIManagerModule}
+ */
+ @Override
+ public List createViewManagers(ReactApplicationContext reactContext) {
+ return Collections.emptyList();
+ }
+}
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..e0819e6
--- /dev/null
+++ b/index.js
@@ -0,0 +1,11 @@
+import { Platform } from 'react-native';
+
+let SpeechSynthesizer = null;
+
+if(Platform.OS === 'ios') {
+ SpeechSynthesizer = require('./SpeechSynthesizer.ios.js');
+} else {
+ SpeechSynthesizer = require('./SpeechSynthesizer.android.js');
+}
+
+export default SpeechSynthesizer;
\ No newline at end of file
diff --git a/package.json b/package.json
index 6d453d2..56a1b8e 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "react-native-speech",
"version": "0.1.2",
"description": "A text-to-speech library for React Native.",
- "main": "SpeechSynthesizer.ios.js",
+ "main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},