Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android compatibility #20

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/.idea/
/android/build/
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.');
Expand Down Expand Up @@ -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.

Expand Down
42 changes: 37 additions & 5 deletions SpeechSynthesizer.android.js
Original file line number Diff line number Diff line change
@@ -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);
}
};

Expand Down
23 changes: 23 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -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:+'
}
3 changes: 3 additions & 0 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.omega.speech">
</manifest>
233 changes: 233 additions & 0 deletions android/src/main/java/com/omega/speech/SpeechSynthesizerModule.java
Original file line number Diff line number Diff line change
@@ -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<String, Promise> ttsPromises = new HashMap<String, Promise>();

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<Void, Void>(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<Void,Void>(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<Void,Void>(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<Void, Void>(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<String, String> map = new HashMap<String, String>();
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();
}
}
Loading