├── README.md ├── package.json ├── plugin.xml ├── src ├── android │ └── TTS.java ├── ios │ ├── CDVTTS.h │ └── CDVTTS.m └── wp │ └── TTS.cs └── www └── tts.js /README.md: -------------------------------------------------------------------------------- 1 | # Cordova Text-to-Speech Plugin 2 | 3 | ## Platforms 4 | 5 | iOS 7+ 6 | Windows Phone 8 7 | Android 4.0.3+ (API Level 15+) 8 | 9 | ## Installation 10 | 11 | ```sh 12 | cordova plugin add cordova-plugin-tts 13 | ``` 14 | 15 | ## Usage 16 | 17 | ```javascript 18 | // make sure your the code gets executed only after `deviceready`. 19 | document.addEventListener('deviceready', function () { 20 | // basic usage 21 | TTS 22 | .speak('hello, world!').then(function () { 23 | alert('success'); 24 | }, function (reason) { 25 | alert(reason); 26 | }); 27 | 28 | // or with more options 29 | TTS 30 | .speak({ 31 | text: 'hello, world!', 32 | locale: 'en-GB', 33 | rate: 0.75 34 | }).then(function () { 35 | alert('success'); 36 | }, function (reason) { 37 | alert(reason); 38 | }); 39 | }, false); 40 | ``` 41 | 42 | **Tips:** `speak` an empty string to interrupt. 43 | 44 | ```typescript 45 | declare namespace TTS { 46 | interface IOptions { 47 | /** text to speak */ 48 | text: string; 49 | /** a string like 'en-US', 'zh-CN', etc */ 50 | locale?: string; 51 | /** speed rate, 0 ~ 1 */ 52 | rate?: number; 53 | /** ambient(iOS) */ 54 | category?: string; 55 | } 56 | 57 | function speak(options: IOptions): Promise; 58 | function speak(text: string): Promise; 59 | function stop(): Promise; 60 | function checkLanguage(): Promise; 61 | function openInstallTts(): Promise; 62 | } 63 | ``` 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cordova-plugin-tts", 3 | "version": "0.2.3", 4 | "description": "Cordova Text-to-Speech Plugin", 5 | "cordova": { 6 | "id": "cordova-plugin-tts", 7 | "platforms": [ 8 | "ios", 9 | "wp8", 10 | "android" 11 | ] 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/vilic/cordova-plugin-tts.git" 16 | }, 17 | "keywords": [ 18 | "cordova", 19 | "tts", 20 | "text-to-speech", 21 | "ecosystem:cordova", 22 | "cordova-ios", 23 | "cordova-wp8", 24 | "cordova-android" 25 | ], 26 | "author": "VILIC VANE", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/vilic/cordova-plugin-tts/issues" 30 | }, 31 | "homepage": "https://github.com/vilic/cordova-plugin-tts#readme" 32 | } 33 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | TTS 9 | Cordova Text-to-Speech Plugin 10 | VILIC VANE 11 | MIT 12 | cordova,tts,text-to-speech 13 | https://github.com/vilic/cordova-plugin-tts.git 14 | https://github.com/vilic/cordova-plugin-tts/issues 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/android/TTS.java: -------------------------------------------------------------------------------- 1 | package com.wordsbaking.cordova.tts; 2 | 3 | import org.apache.cordova.CallbackContext; 4 | import org.apache.cordova.CordovaPlugin; 5 | 6 | import org.apache.cordova.CordovaWebView; 7 | import org.apache.cordova.CordovaInterface; 8 | 9 | import org.json.JSONArray; 10 | import org.json.JSONException; 11 | import org.json.JSONObject; 12 | 13 | import android.os.Build; 14 | import android.speech.tts.TextToSpeech; 15 | import android.speech.tts.TextToSpeech.OnInitListener; 16 | import android.speech.tts.UtteranceProgressListener; 17 | 18 | import java.util.HashMap; 19 | import java.util.Locale; 20 | import java.util.*; 21 | 22 | import org.apache.cordova.PluginResult; 23 | import org.apache.cordova.PluginResult.Status; 24 | 25 | import android.content.Intent; 26 | import android.content.Context; 27 | import android.app.Activity; 28 | import android.content.pm.PackageManager; 29 | import android.content.pm.ResolveInfo; 30 | 31 | /* 32 | Cordova Text-to-Speech Plugin 33 | https://github.com/vilic/cordova-plugin-tts 34 | 35 | by VILIC VANE 36 | https://github.com/vilic 37 | 38 | MIT License 39 | */ 40 | 41 | public class TTS extends CordovaPlugin implements OnInitListener { 42 | 43 | public static final String ERR_INVALID_OPTIONS = "ERR_INVALID_OPTIONS"; 44 | public static final String ERR_NOT_INITIALIZED = "ERR_NOT_INITIALIZED"; 45 | public static final String ERR_ERROR_INITIALIZING = "ERR_ERROR_INITIALIZING"; 46 | public static final String ERR_UNKNOWN = "ERR_UNKNOWN"; 47 | 48 | boolean ttsInitialized = false; 49 | TextToSpeech tts = null; 50 | Context context = null; 51 | 52 | @Override 53 | public void initialize(CordovaInterface cordova, final CordovaWebView webView) { 54 | context = cordova.getActivity().getApplicationContext(); 55 | tts = new TextToSpeech(cordova.getActivity().getApplicationContext(), this); 56 | tts.setOnUtteranceProgressListener(new UtteranceProgressListener() { 57 | @Override 58 | public void onStart(String s) { 59 | // do nothing 60 | } 61 | 62 | @Override 63 | public void onDone(String callbackId) { 64 | if (!callbackId.equals("")) { 65 | CallbackContext context = new CallbackContext(callbackId, webView); 66 | context.success(); 67 | } 68 | } 69 | 70 | @Override 71 | public void onError(String callbackId) { 72 | if (!callbackId.equals("")) { 73 | CallbackContext context = new CallbackContext(callbackId, webView); 74 | context.error(ERR_UNKNOWN); 75 | } 76 | } 77 | }); 78 | } 79 | 80 | @Override 81 | public boolean execute(String action, JSONArray args, CallbackContext callbackContext) 82 | throws JSONException { 83 | if (action.equals("speak")) { 84 | speak(args, callbackContext); 85 | } else if (action.equals("stop")) { 86 | stop(args, callbackContext); 87 | } else if (action.equals("checkLanguage")) { 88 | checkLanguage(args, callbackContext); 89 | } else if (action.equals("openInstallTts")) { 90 | callInstallTtsActivity(args, callbackContext); 91 | } else { 92 | return false; 93 | } 94 | return true; 95 | } 96 | 97 | @Override 98 | public void onInit(int status) { 99 | if (status != TextToSpeech.SUCCESS) { 100 | tts = null; 101 | } else { 102 | // warm up the tts engine with an empty string 103 | HashMap ttsParams = new HashMap(); 104 | ttsParams.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, ""); 105 | tts.setLanguage(new Locale("en", "US")); 106 | tts.speak("", TextToSpeech.QUEUE_FLUSH, ttsParams); 107 | 108 | ttsInitialized = true; 109 | } 110 | } 111 | 112 | private void stop(JSONArray args, CallbackContext callbackContext) 113 | throws JSONException, NullPointerException { 114 | tts.stop(); 115 | } 116 | 117 | private void callInstallTtsActivity(JSONArray args, CallbackContext callbackContext) 118 | throws JSONException, NullPointerException { 119 | 120 | PackageManager pm = context.getPackageManager(); 121 | Intent installIntent = new Intent(); 122 | installIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA); 123 | ResolveInfo resolveInfo = pm.resolveActivity( installIntent, PackageManager.MATCH_DEFAULT_ONLY ); 124 | 125 | if( resolveInfo == null ) { 126 | // Not able to find the activity which should be started for this intent 127 | } else { 128 | installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 129 | context.startActivity(installIntent); 130 | } 131 | } 132 | 133 | 134 | private void checkLanguage(JSONArray args, CallbackContext callbackContext) 135 | throws JSONException, NullPointerException { 136 | Set supportedLanguages = tts.getAvailableLanguages(); 137 | String languages = ""; 138 | if(supportedLanguages!= null) { 139 | for (Locale lang : supportedLanguages) { 140 | languages = languages + "," + lang; 141 | } 142 | } 143 | if (languages != "") { 144 | languages = languages.substring(1); 145 | } 146 | 147 | final PluginResult result = new PluginResult(PluginResult.Status.OK, languages); 148 | callbackContext.sendPluginResult(result); 149 | } 150 | 151 | private void speak(JSONArray args, CallbackContext callbackContext) 152 | throws JSONException, NullPointerException { 153 | JSONObject params = args.getJSONObject(0); 154 | 155 | if (params == null) { 156 | callbackContext.error(ERR_INVALID_OPTIONS); 157 | return; 158 | } 159 | 160 | String text; 161 | String locale; 162 | double rate; 163 | 164 | if (params.isNull("text")) { 165 | callbackContext.error(ERR_INVALID_OPTIONS); 166 | return; 167 | } else { 168 | text = params.getString("text"); 169 | } 170 | 171 | if (params.isNull("locale")) { 172 | locale = "en-US"; 173 | } else { 174 | locale = params.getString("locale"); 175 | } 176 | 177 | if (params.isNull("rate")) { 178 | rate = 1.0; 179 | } else { 180 | rate = params.getDouble("rate"); 181 | } 182 | 183 | if (tts == null) { 184 | callbackContext.error(ERR_ERROR_INITIALIZING); 185 | return; 186 | } 187 | 188 | if (!ttsInitialized) { 189 | callbackContext.error(ERR_NOT_INITIALIZED); 190 | return; 191 | } 192 | 193 | HashMap ttsParams = new HashMap(); 194 | ttsParams.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, callbackContext.getCallbackId()); 195 | 196 | String[] localeArgs = locale.split("-"); 197 | tts.setLanguage(new Locale(localeArgs[0], localeArgs[1])); 198 | 199 | if (Build.VERSION.SDK_INT >= 27) { 200 | tts.setSpeechRate((float) rate * 0.7f); 201 | } else { 202 | tts.setSpeechRate((float) rate); 203 | } 204 | 205 | tts.speak(text, TextToSpeech.QUEUE_FLUSH, ttsParams); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/ios/CDVTTS.h: -------------------------------------------------------------------------------- 1 | /* 2 | Cordova Text-to-Speech Plugin 3 | https://github.com/vilic/cordova-plugin-tts 4 | 5 | by VILIC VANE 6 | https://github.com/vilic 7 | 8 | MIT License 9 | */ 10 | 11 | #import 12 | #import 13 | 14 | @interface CDVTTS : CDVPlugin { 15 | AVSpeechSynthesizer* synthesizer; 16 | NSString* lastCallbackId; 17 | NSString* callbackId; 18 | } 19 | 20 | - (void)speak:(CDVInvokedUrlCommand*)command; 21 | - (void)stop:(CDVInvokedUrlCommand*)command; 22 | - (void)checkLanguage:(CDVInvokedUrlCommand*)command; 23 | @end 24 | -------------------------------------------------------------------------------- /src/ios/CDVTTS.m: -------------------------------------------------------------------------------- 1 | /* 2 | Cordova Text-to-Speech Plugin 3 | https://github.com/vilic/cordova-plugin-tts 4 | 5 | by VILIC VANE 6 | https://github.com/vilic 7 | 8 | MIT License 9 | */ 10 | 11 | #import 12 | #import 13 | #import "CDVTTS.h" 14 | 15 | @implementation CDVTTS 16 | 17 | - (void)pluginInitialize { 18 | synthesizer = [AVSpeechSynthesizer new]; 19 | synthesizer.delegate = self; 20 | } 21 | 22 | - (void)speechSynthesizer:(AVSpeechSynthesizer*)synthesizer didFinishSpeechUtterance:(AVSpeechUtterance*)utterance { 23 | CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 24 | if (lastCallbackId) { 25 | [self.commandDelegate sendPluginResult:result callbackId:lastCallbackId]; 26 | lastCallbackId = nil; 27 | } else { 28 | [self.commandDelegate sendPluginResult:result callbackId:callbackId]; 29 | callbackId = nil; 30 | } 31 | 32 | [[AVAudioSession sharedInstance] setActive:NO withOptions:0 error:nil]; 33 | [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient 34 | withOptions: 0 error: nil]; 35 | [[AVAudioSession sharedInstance] setActive:YES withOptions: 0 error:nil]; 36 | } 37 | 38 | - (void)speak:(CDVInvokedUrlCommand*)command { 39 | 40 | NSDictionary* options = [command.arguments objectAtIndex:0]; 41 | 42 | NSString* text = [options objectForKey:@"text"]; 43 | NSString* locale = [options objectForKey:@"locale"]; 44 | double rate = [[options objectForKey:@"rate"] doubleValue]; 45 | NSString* category = [options objectForKey:@"category"]; 46 | 47 | [[AVAudioSession sharedInstance] setActive:NO withOptions:0 error:nil]; 48 | if ([category isEqualToString:@"ambient"]) { 49 | [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient 50 | withOptions:0 error:nil]; 51 | } else { 52 | [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback 53 | withOptions:AVAudioSessionCategoryOptionDuckOthers error:nil]; 54 | } 55 | 56 | if (callbackId) { 57 | lastCallbackId = callbackId; 58 | } 59 | 60 | callbackId = command.callbackId; 61 | 62 | [synthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate]; 63 | 64 | NSDictionary* options = [command.arguments objectAtIndex:0]; 65 | 66 | NSString* text = [options objectForKey:@"text"]; 67 | NSString* locale = [options objectForKey:@"locale"]; 68 | double rate = [[options objectForKey:@"rate"] doubleValue]; 69 | double pitch = [[options objectForKey:@"pitch"] doubleValue]; 70 | 71 | if (!locale || (id)locale == [NSNull null]) { 72 | locale = @"en-US"; 73 | } 74 | 75 | if (!rate) { 76 | rate = 1.0; 77 | } 78 | 79 | if (!pitch) { 80 | pitch = 1.2; 81 | } 82 | 83 | AVSpeechUtterance* utterance = [[AVSpeechUtterance new] initWithString:text]; 84 | utterance.voice = [AVSpeechSynthesisVoice voiceWithLanguage:locale]; 85 | // Rate expression adjusted manually for a closer match to other platform. 86 | utterance.rate = (AVSpeechUtteranceMinimumSpeechRate * 1.5 + AVSpeechUtteranceDefaultSpeechRate) / 2.25 * rate * rate; 87 | // workaround for https://github.com/vilic/cordova-plugin-tts/issues/21 88 | if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 9.0) { 89 | utterance.rate = utterance.rate * 2; 90 | // see http://stackoverflow.com/questions/26097725/avspeechuterrance-speed-in-ios-8 91 | } 92 | utterance.pitchMultiplier = pitch; 93 | [synthesizer speakUtterance:utterance]; 94 | } 95 | 96 | - (void)stop:(CDVInvokedUrlCommand*)command { 97 | [synthesizer pauseSpeakingAtBoundary:AVSpeechBoundaryImmediate]; 98 | [synthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate]; 99 | } 100 | 101 | - (void)checkLanguage:(CDVInvokedUrlCommand *)command { 102 | NSArray *voices = [AVSpeechSynthesisVoice speechVoices]; 103 | NSString *languages = @""; 104 | for (id voiceName in voices) { 105 | languages = [languages stringByAppendingString:@","]; 106 | languages = [languages stringByAppendingString:[voiceName valueForKey:@"language"]]; 107 | } 108 | if ([languages hasPrefix:@","] && [languages length] > 1) { 109 | languages = [languages substringFromIndex:1]; 110 | } 111 | 112 | CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:languages]; 113 | [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; 114 | } 115 | @end 116 | -------------------------------------------------------------------------------- /src/wp/TTS.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Cordova Text-to-Speech Plugin 3 | https://github.com/vilic/cordova-plugin-tts 4 | 5 | by VILIC VANE 6 | https://github.com/vilic 7 | 8 | MIT License 9 | */ 10 | 11 | using System; 12 | using System.Diagnostics; 13 | using System.Runtime.Serialization; 14 | using System.Threading.Tasks; 15 | using Windows.Phone.Speech.Synthesis; 16 | using WPCordovaClassLib.Cordova; 17 | using WPCordovaClassLib.Cordova.Commands; 18 | using WPCordovaClassLib.Cordova.JSON; 19 | 20 | namespace Cordova.Extension.Commands { 21 | [DataContract] 22 | class Options { 23 | [DataMember] 24 | public string text; 25 | [DataMember] 26 | public string locale; 27 | [DataMember] 28 | public double? rate; 29 | } 30 | 31 | class TTS : BaseCommand { 32 | SpeechSynthesizer synth = new SpeechSynthesizer(); 33 | 34 | string lastCallbackId; 35 | public async void stop(string argsJSON) { 36 | synth.CancelAll(); 37 | } 38 | 39 | public async void speak(string argsJSON) { 40 | if (lastCallbackId != null) { 41 | DispatchCommandResult(new PluginResult(PluginResult.Status.OK), lastCallbackId); 42 | lastCallbackId = null; 43 | synth.CancelAll(); 44 | } 45 | 46 | var args = JsonHelper.Deserialize(argsJSON); 47 | var options = JsonHelper.Deserialize(args[0]); 48 | lastCallbackId = args[1]; 49 | 50 | var locale = options.locale != null ? options.locale : "en-US"; 51 | var rate = options.rate != null ? options.rate : 1.0; 52 | 53 | var ssml = 54 | @" 55 | 58 | " + xmlEncode(options.text) + @" 59 | "; 60 | 61 | try { 62 | await synth.SpeakSsmlAsync(ssml); 63 | lastCallbackId = null; 64 | DispatchCommandResult(new PluginResult(PluginResult.Status.OK)); 65 | } catch (OperationCanceledException) { 66 | // do nothing 67 | } catch (Exception e) { 68 | Debug.WriteLine(e.Message); 69 | lastCallbackId = null; 70 | DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message)); 71 | } 72 | } 73 | 74 | string xmlEncode(string text) { 75 | return text 76 | .Replace("&", "&") 77 | .Replace("<", "<") 78 | .Replace(">", ">"); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /www/tts.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Cordova Text-to-Speech Plugin 4 | https://github.com/vilic/cordova-plugin-tts 5 | 6 | by VILIC VANE 7 | https://github.com/vilic 8 | 9 | MIT License 10 | 11 | */ 12 | 13 | exports.speak = function (text) { 14 | return new Promise(function (resolve, reject) { 15 | var options = {}; 16 | 17 | if (typeof text == 'string') { 18 | options.text = text; 19 | } else { 20 | options = text; 21 | } 22 | 23 | cordova.exec(resolve, reject, 'TTS', 'speak', [options]); 24 | }); 25 | }; 26 | 27 | exports.stop = function() { 28 | return new Promise(function (resolve, reject) { 29 | cordova.exec(resolve, reject, 'TTS', 'stop', []); 30 | }); 31 | }; 32 | 33 | exports.checkLanguage = function() { 34 | return new Promise(function (resolve, reject) { 35 | cordova.exec(resolve, reject, 'TTS', 'checkLanguage', []); 36 | }); 37 | }; 38 | 39 | exports.openInstallTts = function() { 40 | return new Promise(function (resolve, reject) { 41 | cordova.exec(resolve, reject, 'TTS', 'openInstallTts', []); 42 | }); 43 | }; 44 | --------------------------------------------------------------------------------