Skip to content

Commit

Permalink
TTS supported voices listing
Browse files Browse the repository at this point in the history
- Added new method getSupportedVoices()
  • Loading branch information
Pravinkumar Putta committed Aug 28, 2020
1 parent ad8511b commit 8364a76
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 14 deletions.
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ Speech.supportedLanguages

`[]String` - Returns list of supported Speech to Text language models.

### getSupportedVoices()

```js
Speech.getSupportedVoices(Function successCallback, Function errorCallback)
```

Result of success callback is an Array of supported voices.

### defaultLanguage

```js
Expand All @@ -43,7 +51,8 @@ Speech.supportedLanguages

let options = {
Number pitchRate,
Number speechRate
Number speechRate,
String language
}

Speech.speakOut(String message, Function successCallback, Function errorCallback, Object options)
Expand All @@ -58,12 +67,15 @@ Result of success callback is an `String` of TTS states as below:

This method has an options parameter with the following optional values:

- `pitchRate` {Number} used language for TTS pitch rate.
- `pitchRate` {Number} used for TTS pitch rate.
1. `iOS` - The default pitch is 1.0. Allowed values are in the range from 0.5 (for lower pitch) to 2.0 (for higher pitch).
1. `Android` - The default pitch is 1.0, lower values lower the tone of the synthesized voice, greater values increase it.
- `speechRate` {Number} used language for TTS speech rate.
- `speechRate` {Number} used for TTS speech rate.
1. `iOS` - The default speech rate is 0.5. Lower values correspond to slower speech, and vice versa.
1. `Android` - The default speech rate is 1.0. Lower values correspond to slower speech, and vice versa.
- `language` {String} used for TTS speech voice.
1. `iOS` - Use `Speech.getSupportedVoices()` to get voices and use `selectedVoice.language` as input.
1. `Android` - Use `Speech.getSupportedVoices()` to get voices and use `selectedVoice.name` as input.

### initRecognition()

Expand Down
2 changes: 1 addition & 1 deletion plugin.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<plugin id="cordova-plugin-speech" version="0.0.2" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
<plugin id="cordova-plugin-speech" version="0.0.3" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
<name>Speech</name>
<js-module name="Speech" src="www/Speech.js">
<clobbers target="Speech" />
Expand Down
54 changes: 46 additions & 8 deletions src/android/com/pravinkumarp/Speech.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import android.speech.SpeechRecognizer;
import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
import android.util.Log;
import android.speech.tts.Voice;

import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
Expand All @@ -23,6 +23,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

/**
* This class echoes a string called from JavaScript.
Expand All @@ -31,6 +32,7 @@ public class Speech extends CordovaPlugin {
// API lists
private final static String TAG = "Speech";
private final static String GET_SUPPORTED_LANGUAGES = "getSupportedLanguages";
private final static String GET_SUPPORTED_VOICES = "getSupportedVoices";
private final static String GET_DEFAULT_LANGUAGE = "getDefaultLanguage";
private final static String SPEAK_OUT = "speakOut";
private final static String INIT_RECOGNITION = "initRecognition";
Expand Down Expand Up @@ -60,6 +62,9 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo
case GET_SUPPORTED_LANGUAGES:
this.getSupportedLanguages(callbackContext);
break;
case GET_SUPPORTED_VOICES:
this.getSupportedVoices(callbackContext);
break;
case GET_DEFAULT_LANGUAGE:
this.getDefaultLanguage(callbackContext);
break;
Expand All @@ -73,7 +78,8 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo
if (speechRate == 0f) {
speechRate = 1f;
}
this.speakOut(message, pitchRate, speechRate, callbackContext);
String speechLanguageName = (String) args.getString(3);
this.speakOut(message, pitchRate, speechRate, speechLanguageName, callbackContext);
break;
}
case INIT_RECOGNITION:
Expand Down Expand Up @@ -110,7 +116,27 @@ private void getSupportedLanguages(CallbackContext callbackContext) {
return;
}

this.loadLanugageDetails(() -> Speech.this.publishSupportedLanguagesResult(callbackContext));
this.loadLanguageDetails(() -> Speech.this.publishSupportedLanguagesResult(callbackContext));
}

private void getSupportedVoices(CallbackContext callbackContext) {
this.textToSpeech = new TextToSpeech(cordova.getActivity(), status -> {
if (status == TextToSpeech.SUCCESS) {
ArrayList<String> supportedVoices = new ArrayList<>();
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
Set<Voice> voices = textToSpeech.getVoices();
if (voices != null) {
for (Voice voice : voices) {
supportedVoices.add(voice.getLocale() + ":" + voice.getName());
}
}
}
JSONArray voicesArray = new JSONArray(supportedVoices);
callbackContext.success(voicesArray);
} else {
callbackContext.error("Failed to init tts.");
}
});
}

private void publishSupportedLanguagesResult(CallbackContext callbackContext) {
Expand All @@ -129,7 +155,7 @@ private void getDefaultLanguage(CallbackContext callbackContext) {
return;
}

this.loadLanugageDetails(() -> Speech.this.publishDefaultLanguageResult(callbackContext));
this.loadLanguageDetails(() -> Speech.this.publishDefaultLanguageResult(callbackContext));
}

private void publishDefaultLanguageResult(CallbackContext callbackContext) {
Expand All @@ -141,7 +167,7 @@ private void publishDefaultLanguageResult(CallbackContext callbackContext) {
}
}

private void loadLanugageDetails(LanguageDetailsChecker.LanguageDetailsCheckerListener languageDetailsCheckerListener) {
private void loadLanguageDetails(LanguageDetailsChecker.LanguageDetailsCheckerListener languageDetailsCheckerListener) {
languageDetailsChecker = new LanguageDetailsChecker(languageDetailsCheckerListener);
Intent detailsIntent = new Intent(RecognizerIntent.ACTION_GET_LANGUAGE_DETAILS);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
Expand All @@ -150,7 +176,7 @@ private void loadLanugageDetails(LanguageDetailsChecker.LanguageDetailsCheckerLi
cordova.getActivity().sendOrderedBroadcast(detailsIntent, null, languageDetailsChecker, null, Activity.RESULT_OK, null, null);
}

private void speakOut(String message, float pitchRate, float speechRate, CallbackContext callbackContext) {
private void speakOut(String message, float pitchRate, float speechRate, String speechLanguageName, CallbackContext callbackContext) {
this.ttsCallbackContext = callbackContext;
this.textToSpeech = new TextToSpeech(cordova.getActivity(), status -> {
if (status == TextToSpeech.SUCCESS) {
Expand All @@ -174,18 +200,30 @@ public void onError(String utteranceId) {
Speech.this.ttsCallbackContext.error("Failed to speak.");
}
});
Speech.this.speak(message, pitchRate, speechRate);
Speech.this.speak(message, pitchRate, speechRate, speechLanguageName);
} else {
Speech.this.ttsCallbackContext.error("Failed to init tts.");
}
});
}

private void speak(String message, float pitchRate, float speechRate) {
private void speak(String message, float pitchRate, float speechRate, String speechLanguageName) {
HashMap<String, String> params = new HashMap<>();
params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "id");
textToSpeech.setPitch(pitchRate);
textToSpeech.setSpeechRate(speechRate);

if (!speechLanguageName.isEmpty() && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
Set<Voice> voices = textToSpeech.getVoices();
if (voices != null) {
for (Voice voice : voices) {
if (voice.getName().equals(speechLanguageName)) {
textToSpeech.setVoice(voice);
break;
}
}
}
}
Speech.this.textToSpeech.speak(message, TextToSpeech.QUEUE_FLUSH, params);
}

Expand Down
38 changes: 38 additions & 0 deletions src/ios/Speech.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Speech
@objc(Speech) class Speech : CDVPlugin, AVSpeechSynthesizerDelegate {

static var supportedLanguages: [String] = []
static var supportedVoices: [String] = []
var defaultLanugage = ""
private var speechSynthesizer: AVSpeechSynthesizer?
private var ttsCommand: CDVInvokedUrlCommand?
Expand Down Expand Up @@ -61,6 +62,23 @@ import Speech
self.commandDelegate!.send(pluginResult, callbackId: command.callbackId);
}

@objc(getSupportedVoices:) // Declare your function name.
func getSupportedVoices(command: CDVInvokedUrlCommand) {
// fetch languages
if(Speech.supportedVoices.count == 0) {
self.loadVoiceDetails()
}

// prepare result
var pluginResult = CDVPluginResult (status: CDVCommandStatus_ERROR, messageAs: "Failed to get supported languages.");
if (Speech.supportedLanguages.count > 0) {
pluginResult = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: Speech.supportedVoices);
}

// publish result
self.commandDelegate!.send(pluginResult, callbackId: command.callbackId);
}

@objc(getDefaultLanguage:) // Declare your function name.
func getDefaultLanguage(command: CDVInvokedUrlCommand) {
// fetch languages
Expand Down Expand Up @@ -89,12 +107,24 @@ import Speech
if (speechRate == 0.0) {
speechRate = 0.5
}

var speechLanguage = command.arguments[3] as? String ?? ""

ttsCommand = command;
// fetch languages
let speechUtterance = AVSpeechUtterance(string: message)
speechUtterance.pitchMultiplier = pitchRate
speechUtterance.rate = speechRate

// set voice is added
if (speechLanguage != "") {
for voice in AVSpeechSynthesisVoice.speechVoices() {
if (voice.language == speechLanguage) {
speechUtterance.voice = voice
break
}
}
}

if(speechSynthesizer == nil) {
// initialize speechSynthesizer
Expand Down Expand Up @@ -133,6 +163,14 @@ import Speech
}
}

private func loadVoiceDetails() {
// fetch supported voices
AVSpeechSynthesisVoice.speechVoices().forEach { (locale) in
Speech.supportedVoices.append("\(locale.language):\(locale.name)")
}
print("supportedVoices", Speech.supportedVoices)
}

@objc(initRecognition:) // Declare your function name.
func initRecognition(command: CDVInvokedUrlCommand) {
self.sttCommand = command
Expand Down
20 changes: 18 additions & 2 deletions www/Speech.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ var channel = require('cordova/channel');
const SPEECH = 'Speech'
const API_LIST = {
GET_SUPPORTED_LANGUAGES: "getSupportedLanguages",
GET_SUPPORTED_VOICES: "getSupportedVoices",
GET_DEFAULT_LANGUAGE: "getDefaultLanguage",
SPEAK_OUT: "speakOut",
INIT_RECOGNITION: "initRecognition",
START_RECOGNITION: "startRecognition",
STOP_RECOGNITION: "stopRecognition"
STOP_RECOGNITION: "stopRecognition",
}

function Speech() {
Expand All @@ -35,14 +36,29 @@ Speech.prototype[API_LIST.GET_SUPPORTED_LANGUAGES] = function (success, error) {
exec(success, error, SPEECH, API_LIST.GET_SUPPORTED_LANGUAGES, [])
}

Speech.prototype[API_LIST.GET_SUPPORTED_VOICES] = function (success, error) {
exec((res) => {
let voiceList = []
res.forEach(element => {
let dataParts = element.split(":")
voiceList.push({
language: dataParts[0],
name: dataParts[1]
})
});
success(voiceList)
}, error, SPEECH, API_LIST.GET_SUPPORTED_VOICES, [])
}

Speech.prototype[API_LIST.GET_DEFAULT_LANGUAGE] = function (success, error) {
exec(success, error, SPEECH, API_LIST.GET_DEFAULT_LANGUAGE, [])
}

Speech.prototype[API_LIST.SPEAK_OUT] = function (message, success, error, options) {
let pitchRate = options && options.pitchRate ? options.pitchRate : 0.0
let speechRate = options && options.speechRate ? options.speechRate : 0.0
exec(success, error, SPEECH, API_LIST.SPEAK_OUT, [message, pitchRate, speechRate])
let language = options && options.language ? options.language : ""
exec(success, error, SPEECH, API_LIST.SPEAK_OUT, [message, pitchRate, speechRate, language])
}

Speech.prototype[API_LIST.INIT_RECOGNITION] = function (success, error, options) {
Expand Down

0 comments on commit 8364a76

Please sign in to comment.