diff --git a/CHANGELOG.md b/CHANGELOG.md index 420e6f2..58a0495 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,16 @@ # Change Log + +2018/05/30 +========== + +Improved compatibility with the W3C specification (https://w3c.github.io/speech-api/webspeechapi.html) and other improvements. +* Added `voicechanged` event. +* Error events from Android now include the `error` value and message. +* Removed SpeechSynthesisVoiceList. + +2019/03/13 +========== + +Added iOS implementation. Apple's implementation of speechSynthesis +crashes in heavy use. In particular, crashes were seen when used with +wifisher's phonegap-plugin-speechsynthesis plugin. diff --git a/README.md b/README.md index bf324b1..197b4ca 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,33 @@ # SpeechSynthesisPlugin -W3C Web Speech API - Speech synthesis plugin for PhoneGap +W3C Web Speech API - Speech synthesis plugin for Cordova/PhoneGap. + +This Cordova plugin provides speech synthesis support for Android, Apple iOS, Windows 10, and the Browser. +It attempts to closely follow the +[SpeechSynthesis](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis) +interface as defined by the +[Web Speech API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API). + +This plugin provides a native implementation for Android and iOS. +For Windows 10, and Browser, the native webview implementation is used. # Installation -## Phone Gap +## Cordova Using the command line tools run: - phonegap plugin add https://github.com/macdonst/SpeechSynthesisPlugin - cordova plugin add https://github.com/macdonst/SpeechSynthesisPlugin + cordova plugin add https://github.com/wifisher/SpeechSynthesisPlugin -## Cordova +# Cordova Usage -This plugin also works with the Apache Cordova toolset. See this Github project for an example for Android: +This plugin works with the Apache Cordova toolset. See this Github project for an old example for Android: https://github.com/andysylvester/talk-to-me-cordova More info on using this plugin with Cordova is available at this blog post. - -# Example Code - +## Example Code This code from the above Github project shows how to read the value of a text field, set up the plugin to speak that text, and vibrate the phone for 2 seconds: @@ -32,7 +38,7 @@ This code from the above Github project shows how to read the value of a text fi txt = x.elements[0].value u.text = txt; u.lang = 'en-US'; - speechSynthesis.speak(u); + speechSynthesis.speak(u); navigator.notification.vibrate(2000); document.getElementById("frm1").reset(); } diff --git a/package-lock.json b/package-lock.json index ef4ec3c..f5a5874 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,94 +1,1291 @@ { "name": "phonegap-plugin-speech-synthesis", - "version": "0.1.0", + "version": "0.2.2", "lockfileVersion": 1, + "requires": true, "dependencies": { - "coffee-script": { - "version": "1.12.6", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.6.tgz", - "integrity": "sha1-KFo/cRVokGUGTWv570Vy22ZpXL8=" + "ansi-align": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-1.1.0.tgz", + "integrity": "sha1-LwwWWIKXOa3V67FeawxuNCPwFro=", + "requires": { + "string-width": "^1.0.1" + } }, - "fileset": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/fileset/-/fileset-0.1.8.tgz", - "integrity": "sha1-UGuRqTluqn4y+0KoQHfHoMc2t0E=" + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=" }, - "gaze": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.3.4.tgz", - "integrity": "sha1-X5S92gr+U7xxCWm81vKCVI1gwnk=" + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, - "glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", - "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "any-observable": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.2.0.tgz", + "integrity": "sha1-xnhwBYADV5AJCD9UrAq6+1wz0kI=" + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "boxen": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-0.6.0.tgz", + "integrity": "sha1-g2TUJIrDT/DvGy8r9JpsYM4NgbY=", + "requires": { + "ansi-align": "^1.1.0", + "camelcase": "^2.1.0", + "chalk": "^1.1.1", + "cli-boxes": "^1.0.0", + "filled-array": "^1.0.0", + "object-assign": "^4.0.1", + "repeating": "^2.0.0", + "string-width": "^1.0.1", + "widest-line": "^1.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + } + }, + "capture-stack-trace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", + "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "cli-boxes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "requires": { + "restore-cursor": "^1.0.1" + } + }, + "cli-spinners": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-0.1.2.tgz", + "integrity": "sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw=" + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "coffeescript": { + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.12.7.tgz", + "integrity": "sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "configstore": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-2.1.0.tgz", + "integrity": "sha1-c3o6cDbpiGECqmCZ5HuzOrGroaE=", + "requires": { + "dot-prop": "^3.0.0", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "object-assign": "^4.0.1", + "os-tmpdir": "^1.0.0", + "osenv": "^0.1.0", + "uuid": "^2.0.1", + "write-file-atomic": "^1.1.2", + "xdg-basedir": "^2.0.0" + }, "dependencies": { - "minimatch": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", - "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=" + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } } } }, - "growl": { + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, + "cross-spawn-async": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/cross-spawn-async/-/cross-spawn-async-2.2.5.tgz", + "integrity": "sha1-hF/wwINKPe2dFg2sptOQkGuyiMw=", + "requires": { + "lru-cache": "^4.0.0", + "which": "^1.2.8" + } + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "requires": { + "array-find-index": "^1.0.1" + } + }, + "dateformat": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", + "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=" + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "requires": { + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + } + }, + "dot-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz", + "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=", + "requires": { + "is-obj": "^1.0.0" + } + }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "requires": { + "readable-stream": "^2.0.2" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "execa": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.4.0.tgz", + "integrity": "sha1-TrZGejaglfq7KXD/nV4/t7zm68M=", + "requires": { + "cross-spawn-async": "^2.1.1", + "is-stream": "^1.1.0", + "npm-run-path": "^1.0.0", + "object-assign": "^4.0.1", + "path-key": "^1.0.0", + "strip-eof": "^1.0.0" + } + }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=" + }, + "figures": { "version": "1.7.0", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", - "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=" + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "filled-array": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filled-array/-/filled-array-1.1.0.tgz", + "integrity": "sha1-w8T2xmO5I0WamqKZEtLQMfFQf4Q=" + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "requires": { + "globule": "^1.0.0" + } + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "requires": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "globule": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.3.tgz", + "integrity": "sha512-mb1aYtDbIjTu4ShMB85m3UzjX9BVKe9WCzsnfMSZk+K5GpIbBOexgg4PPCt5eHDEG5/ZQAUX2Kct02zfiPLsKg==", + "requires": { + "glob": "~7.1.1", + "lodash": "~4.17.10", + "minimatch": "~3.0.2" + } + }, + "got": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/got/-/got-5.7.1.tgz", + "integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=", + "requires": { + "create-error-class": "^3.0.1", + "duplexer2": "^0.1.4", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "node-status-codes": "^1.0.0", + "object-assign": "^4.0.1", + "parse-json": "^2.1.0", + "pinkie-promise": "^2.0.0", + "read-all-stream": "^3.0.0", + "readable-stream": "^2.0.5", + "timed-out": "^3.0.0", + "unzip-response": "^1.0.2", + "url-parse-lax": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==" + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "requires": { + "repeating": "^2.0.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } }, "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "is-core-module": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "requires": { + "has": "^1.0.3" + } + }, + "is-finite": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-npm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=" + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=" + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" + }, + "is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==" + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "jasmine-growl-reporter": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/jasmine-growl-reporter/-/jasmine-growl-reporter-0.0.3.tgz", - "integrity": "sha1-uHrlUeNZ0orVIXdl6u9sB7dj9sg=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jasmine-growl-reporter/-/jasmine-growl-reporter-2.0.0.tgz", + "integrity": "sha512-RYwVfPaGgxQQSHDOt6jQ99/KAkFQ/Fiwg/AzBS+uO9A4UhGhxb7hwXaUUSU/Zs0MxBoFNqmIRC+7P4/+5O3lXg==", + "requires": { + "growl": "^1.10.5" + } }, "jasmine-node": { - "version": "1.14.5", - "resolved": "https://registry.npmjs.org/jasmine-node/-/jasmine-node-1.14.5.tgz", - "integrity": "sha1-GOg5e4VpJO53ADZmw3MbWupQw50=" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jasmine-node/-/jasmine-node-3.0.0.tgz", + "integrity": "sha512-vUa5Q7bQYwHHqi6FlJYndiKqZp+d+c3MKe0QUMwwrC4JRmoRV3zkg0buxB/uQ6qLh0NO34TNstpAnvaZ6xGlAA==", + "requires": { + "coffeescript": "~1.12.7", + "gaze": "~1.1.2", + "jasmine-growl-reporter": "~2.0.0", + "jasmine-reporters": "~1.0.0", + "mkdirp": "~0.3.5", + "requirejs": "~2.3.6", + "underscore": "~1.9.1", + "walkdir": "~0.0.12" + } }, "jasmine-reporters": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/jasmine-reporters/-/jasmine-reporters-1.0.2.tgz", - "integrity": "sha1-q2E+1Zd9x0h+hbPBL2qOqNsq3jE=" + "integrity": "sha1-q2E+1Zd9x0h+hbPBL2qOqNsq3jE=", + "requires": { + "mkdirp": "~0.3.5" + } + }, + "latest-version": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-2.0.0.tgz", + "integrity": "sha1-VvjWE5YghHuAF/jx9NeOIRMkFos=", + "requires": { + "package-json": "^2.0.0" + } + }, + "lazy-req": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/lazy-req/-/lazy-req-1.1.0.tgz", + "integrity": "sha1-va6+rTD42CQDnODOFJ1Nqge6H6w=" + }, + "listr": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/listr/-/listr-0.4.3.tgz", + "integrity": "sha1-IwLLJNKvAqrYSNEzEZy6//q50j0=", + "requires": { + "chalk": "^1.1.3", + "figures": "^1.7.0", + "indent-string": "^2.1.0", + "is-stream": "^1.1.0", + "log-symbols": "^1.0.2", + "log-update": "^1.0.2", + "ora": "^0.2.3", + "stream-to-observable": "^0.1.0", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "stream-to-observable": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/stream-to-observable/-/stream-to-observable-0.1.0.tgz", + "integrity": "sha1-Rb8dny19wJvtgfHDB8Qw5ouEz/4=" + } + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "log-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", + "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", + "requires": { + "chalk": "^1.0.0" + } + }, + "log-update": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-1.0.2.tgz", + "integrity": "sha1-GZKfZMQJPS0ucHWh2tivWcKWuNE=", + "requires": { + "ansi-escapes": "^1.0.0", + "cli-cursor": "^1.0.2" + } + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" }, "lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + } }, "minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=" + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "mkdirp": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=" }, + "node-status-codes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz", + "integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8=" + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "npm-run-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-1.0.0.tgz", + "integrity": "sha1-9cMr9ZX+ga6Sfa7FLoL4sACsPI8=", + "requires": { + "path-key": "^1.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=" + }, + "ora": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/ora/-/ora-0.2.3.tgz", + "integrity": "sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=", + "requires": { + "chalk": "^1.1.1", + "cli-cursor": "^1.0.2", + "cli-spinners": "^0.1.2", + "object-assign": "^4.0.1" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "package-json": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz", + "integrity": "sha1-DRW9Z9HLvduyyiIv8u24a8sxqLs=", + "requires": { + "got": "^5.0.0", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "requires": { + "error-ex": "^1.2.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" + }, + "path-key": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-1.0.0.tgz", + "integrity": "sha1-XVPVeAGWRsDWiADbThRua9wqx68=" + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "^2.0.0" + } + }, + "pluginpub": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/pluginpub/-/pluginpub-0.0.9.tgz", + "integrity": "sha512-ulgYcVIUnPcFY9bsaIlHd8G1rRTwzq4DldR2H5etnM8Gzf2JB151yT9MWqIs+9LI3ad/746HrRKnrLzE5jYLNA==", + "requires": { + "any-observable": "^0.2.0", + "dateformat": "^2.0.0", + "del": "^2.2.0", + "execa": "^0.4.0", + "listr": "^0.4.3", + "meow": "^3.7.0", + "pify": "^2.3.0", + "rxjs": "^5.0.0-beta.9", + "semver": "^5.1.0", + "split": "^1.0.0", + "stream-to-observable": "^0.2.0", + "update-notifier": "^1.0.1", + "xml2js": "^0.4.17" + } + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "read-all-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.1.0.tgz", + "integrity": "sha1-NcPhd/IHjveJ7kv6+kNzB06u9Po=", + "requires": { + "pinkie-promise": "^2.0.0", + "readable-stream": "^2.0.0" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "registry-auth-token": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", + "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "requires": { + "rc": "^1.0.1" + } + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "requires": { + "is-finite": "^1.0.0" + } + }, "requirejs": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.3.tgz", - "integrity": "sha1-qln9OgKH6vQHlZoTgigES13WpqM=" + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz", + "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==" + }, + "resolve": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz", + "integrity": "sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA==", + "requires": { + "is-core-module": "^2.8.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "requires": { + "exit-hook": "^1.0.0", + "onetime": "^1.0.0" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + }, + "rxjs": { + "version": "5.5.12", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.12.tgz", + "integrity": "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==", + "requires": { + "symbol-observable": "1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "requires": { + "semver": "^5.0.3" + } + }, + "signal-exit": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", + "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==" + }, + "slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=" + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } }, - "sigmund": { + "spdx-license-ids": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", + "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==" + }, + "split": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "requires": { + "through": "2" + } + }, + "stream-to-observable": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/stream-to-observable/-/stream-to-observable-0.2.0.tgz", + "integrity": "sha1-WdbqOT2HwsDdrBCqDVYbxrpvDhA=", + "requires": { + "any-observable": "^0.2.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "requires": { + "get-stdin": "^4.0.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, + "symbol-observable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", + "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=" + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "timed-out": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-3.1.3.tgz", + "integrity": "sha1-lYYL/MXHbCd/j4Mm/Q9bLiDrohc=" + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=" }, "underscore": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", - "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.2.tgz", + "integrity": "sha512-D39qtimx0c1fI3ya1Lnhk3E9nONswSKhnffBI0gME9C99fYOkNi04xs8K6pePLhvl1frbDemkaBQ5ikWllR2HQ==" + }, + "unzip-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz", + "integrity": "sha1-uYTwh3/AqJwsdzzB73tbIytbBv4=" + }, + "update-notifier": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-1.0.3.tgz", + "integrity": "sha1-j5LFFUgr1oMbfJMBPnD4dVLHz1o=", + "requires": { + "boxen": "^0.6.0", + "chalk": "^1.0.0", + "configstore": "^2.0.0", + "is-npm": "^1.0.0", + "latest-version": "^2.0.0", + "lazy-req": "^1.1.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^2.0.0" + } + }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "requires": { + "prepend-http": "^1.0.1" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", + "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=" + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } }, "walkdir": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.11.tgz", - "integrity": "sha1-oW0CXrkxvQO1LzCMrtD0D86+lTI=" + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.12.tgz", + "integrity": "sha512-HFhaD4mMWPzFSqhpyDG48KDdrjfn409YQuVW7ckZYhW4sE87mYtWifdB/+73RA7+p4s4K18n5Jfx1kHthE1gBw==" + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "widest-line": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-1.0.0.tgz", + "integrity": "sha1-DAnIXCqUaD0Nfq+O4JfVZL8OEFw=", + "requires": { + "string-width": "^1.0.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write-file-atomic": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", + "integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=", + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "slide": "^1.1.5" + } + }, + "xdg-basedir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-2.0.0.tgz", + "integrity": "sha1-7byQPMOF/ARSPZZqM1UEtVBNG9I=", + "requires": { + "os-homedir": "^1.0.0" + } + }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" } } } diff --git a/package.json b/package.json index c97630d..9d4ce36 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,24 @@ { "name": "phonegap-plugin-speech-synthesis", - "version": "0.1.1", + "version": "0.2.2", "description": "W3C Web Speech API - Speech synthesis plugin for PhoneGap", - "homepage": "https://github.com/macdonst/SpeechSynthesisPlugin#readme", + "homepage": "https://github.com/wifisher/SpeechSynthesisPlugin#readme", "scripts": { "test": "jasmine-node --color spec" }, "repository": { "type": "git", - "url": "git+https://github.com/macdonst/SpeechSynthesisPlugin.git" + "url": "git+https://github.com/wifisher/SpeechSynthesisPlugin.git" }, "bugs": { - "url": "https://github.com/macdonst/SpeechSynthesisPlugin/issues" + "url": "https://github.com/wifisher/SpeechSynthesisPlugin/issues" }, "cordova": { "id": "phonegap-plugin-speech-synthesis", "platforms": [ "ios", "android", + "windows", "browser" ] }, @@ -26,12 +27,17 @@ "ecosystem:phonegap", "cordova-ios", "cordova-android", + "cordova-windows", "cordova-browser" ], "author": "Simon MacDonald", + "contributors": [ + "Wayne Fisher - + SpeechSynthesis Cordova Speech Synthesis Plugin MIT @@ -10,6 +10,13 @@ + + + + + + + @@ -20,11 +27,37 @@ + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/src/android/SpeechSynthesis.java b/src/android/SpeechSynthesis.java index cfa0406..c6ff97d 100644 --- a/src/android/SpeechSynthesis.java +++ b/src/android/SpeechSynthesis.java @@ -1,22 +1,22 @@ package org.apache.cordova.speech; import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.ArrayList; -import java.util.AbstractList; -import java.util.List; +import java.util.Hashtable; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.Locale; import java.util.Set; +import java.lang.IllegalArgumentException; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.os.Build; +import android.os.Bundle; import android.speech.tts.TextToSpeech; import android.speech.tts.TextToSpeech.OnInitListener; -import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener; +import android.speech.tts.UtteranceProgressListener; import android.speech.tts.Voice; import android.util.Log; @@ -24,9 +24,9 @@ import org.apache.cordova.CordovaPlugin; import org.apache.cordova.PluginResult; -public class SpeechSynthesis extends CordovaPlugin implements OnInitListener, OnUtteranceCompletedListener { +public class SpeechSynthesis extends CordovaPlugin implements OnInitListener { - private static final String LOG_TAG = "TTS"; + private static final String LOG_TAG = "SpeechSynthesis"; private static final int STOPPED = 0; private static final int INITIALIZING = 1; private static final int STARTED = 2; @@ -34,10 +34,11 @@ public class SpeechSynthesis extends CordovaPlugin implements OnInitListener, On private int state = STOPPED; private CallbackContext startupCallbackContext; private CallbackContext callbackContext; - + private Hashtable contextMap = new Hashtable(); + private ConcurrentLinkedQueue contextQueue = new ConcurrentLinkedQueue(); private Set voiceList = null; - //private String startupCallbackId = ""; + private int androidAPILevel = android.os.Build.VERSION.SDK_INT; @Override public boolean execute(String action, JSONArray args, CallbackContext callbackContext) { @@ -48,61 +49,16 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo 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)); - - String voiceCode = utterance.optString("voiceURI", null); - if (voiceCode == null) { - JSONObject voice = utterance.optJSONObject("voice"); - if (voice != null) { - voiceCode = voice.optString("voiceURI", null); - } - } - if (voiceCode != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - for (Voice v : this.voiceList) { - if (voiceCode.equals(v.getName())) { - mTts.setVoice(v); - //text+=" yay! found the voice!"; - } - } - } - - 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 map = null; - map = new HashMap(); - 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); - } + speak(callbackContext, utterance, false); } else if (action.equals("cancel")) { if (isReady()) { - HashMap map = null; - map = new HashMap(); - //map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, callbackId); - mTts.speak("", TextToSpeech.QUEUE_FLUSH, map); - fireEndEvent(callbackContext); + // Speak an empty message but with flush set. + // Hopefully, this will allow for error events to be sent to unfinished utterances. + JSONObject utterance = new JSONObject(); + utterance.put("text", ""); + speak(callbackContext, utterance, true); } else { - fireErrorEvent(callbackContext); + fireErrorEvent(callbackContext, 6, "Not ready."); } } else if (action.equals("pause")) { Log.d(LOG_TAG, "Not implemented yet"); @@ -113,7 +69,7 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo mTts.stop(); callbackContext.sendPluginResult(new PluginResult(status, result)); } else { - fireErrorEvent(callbackContext); + fireErrorEvent(callbackContext, 6, "Not ready."); } } else if (action.equals("silence")) { if (isReady()) { @@ -125,7 +81,7 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo pr.setKeepCallback(true); callbackContext.sendPluginResult(pr); } else { - fireErrorEvent(callbackContext); + fireErrorEvent(callbackContext, 6, "Not ready."); } } else if (action.equals("startup")) { this.startupCallbackContext = callbackContext; @@ -139,9 +95,6 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo pluginResult.setKeepCallback(true); startupCallbackContext.sendPluginResult(pluginResult); } - - - else if (action.equals("shutdown")) { if (mTts != null) { mTts.shutdown(); @@ -163,6 +116,64 @@ else if (action.equals("isLanguageAvailable")) { return false; } + private void speak(CallbackContext callbackContext, JSONObject utterance, boolean flush) { + String text = null; + + // Track the utterance CallbackContext so that we can later send the events to the right place. + contextMap.put(callbackContext.getCallbackId(), callbackContext); + contextQueue.add(callbackContext.getCallbackId()); + + try { + text = utterance.getString("text"); + } catch (JSONException e) { + // this should never happen + } + + String lang = utterance.optString("lang", "en"); + mTts.setLanguage(new Locale(lang)); + + String voiceCode = utterance.optString("voiceURI", null); + if (voiceCode == null) { + JSONObject voice = utterance.optJSONObject("voice"); + if (voice != null) { + voiceCode = voice.optString("voiceURI", null); + } + } + if (voiceCode != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + for (Voice v : this.voiceList) { + if (voiceCode.equals(v.getName())) { + mTts.setVoice(v); + //text+=" yay! found the voice!"; + } + } + } + + 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()) { + Log.d(LOG_TAG, "utterance id: " + callbackContext.getCallbackId() + ", text: " + text); + if (androidAPILevel < 21) { + HashMap map = new HashMap(); + map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, callbackContext.getCallbackId()); + map.put(TextToSpeech.Engine.KEY_PARAM_VOLUME, Float.toString(volume)); + mTts.speak(text, flush ? TextToSpeech.QUEUE_FLUSH : TextToSpeech.QUEUE_ADD, map); + } else { // android API level is 21 or higher... + Bundle params = new Bundle(); + params.putFloat(TextToSpeech.Engine.KEY_PARAM_VOLUME, volume); + mTts.speak(text, flush ? TextToSpeech.QUEUE_FLUSH : TextToSpeech.QUEUE_ADD, params, callbackContext.getCallbackId()); + } + } else { + fireErrorEvent(callbackContext, 6, "Not ready."); + } + } + private void getVoices(CallbackContext callbackContext) { JSONArray voices = new JSONArray(); JSONObject voice; @@ -195,47 +206,81 @@ private void getVoices(CallbackContext callbackContext) { // locale = list.next(); for (int i = 0; i < list.length; i++) { locale = list[i]; - voice = new JSONObject(); - if (mTts.isLanguageAvailable(locale) > 0) { // ie LANG_COUNTRY_AVAILABLE or LANG_COUNTRY_VAR_AVAILABLE - try { + try { + // PMPA-680: Filter out just English and French since the app doesn't support anything else. + if ((locale.getLanguage().equals("en") || locale.getLanguage().equals("fr")) && + mTts.isLanguageAvailable(locale) > 0) { // ie LANG_COUNTRY_AVAILABLE or LANG_COUNTRY_VAR_AVAILABLE + voice = new JSONObject(); voice.put("voiceURI", locale.getLanguage()+"-"+locale.getCountry()); voice.put("name", locale.getDisplayLanguage(locale) + " " + locale.getDisplayCountry(locale)); voice.put("lang", locale.getLanguage()+"-"+locale.getCountry()); voice.put("localService", true); voice.put("default", false); - } catch (JSONException e) { - // should never happen + voices.put(voice); } - voices.put(voice); + } catch (JSONException e) { + // should never happen + } catch (IllegalArgumentException e) { + // PMPA-680: See some exceptions here from some Android variants. + Log.d(LOG_TAG, "Caught IllegalArgumentException for locale " + locale + " - " + e); + } catch (Exception e) { + // PMPA-680: See some exceptions here from some Android variants. + Log.d(LOG_TAG, "Caught Exception for locale " + locale + " - " + e); } } } PluginResult result = new PluginResult(PluginResult.Status.OK, voices); result.setKeepCallback(false); startupCallbackContext.sendPluginResult(result); - mTts.setOnUtteranceCompletedListener(this); } - private void fireEndEvent(CallbackContext callbackContext) { + private void fireEvent(CallbackContext callbackContext, String type) { JSONObject event = new JSONObject(); + + Log.d(LOG_TAG, "fire event: " + type); + try { - event.put("type","end"); + event.put("type",type); + if(type.equals("start")) { + event.put("charIndex", 0); + event.put("elapsedTime", 0); + event.put("name", ""); + } } catch (JSONException e) { - // this will never happen + // this should never happen } PluginResult pr = new PluginResult(PluginResult.Status.OK, event); - pr.setKeepCallback(false); - callbackContext.sendPluginResult(pr); + if(type.equals("end")) { + pr.setKeepCallback(false); + } else { + pr.setKeepCallback(true); + } + + if(callbackContext == null) { + this.callbackContext.sendPluginResult(pr); + } else { + callbackContext.sendPluginResult(pr); + } } - private void fireErrorEvent(CallbackContext callbackContext) - throws JSONException { + private void fireErrorEvent(CallbackContext callbackContext, int errCode, String message) { 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)); + Log.d(LOG_TAG, "fire event: error!"); + try { + error.put("type","error"); + error.put("charIndex",0); + error.put("elapsedTime",0); + error.put("name",""); + error.put("error", errCode); + error.put("message", message); + } catch (JSONException e) { + // this should never happen + } + if(callbackContext == null) { + this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, error)); + } else { + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, error)); + } } /** @@ -256,36 +301,104 @@ public void onInit(int status) { if (mTts != null && status == TextToSpeech.SUCCESS) { state = SpeechSynthesis.STARTED; getVoices(this.startupCallbackContext); - - -// 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) { + + mTts.setOnUtteranceProgressListener(new UtteranceProgressListener() { + @Override + public void onDone(String utteranceId) { + Log.d(LOG_TAG, utteranceId + ": got completed utterance"); + fireEvent(contextMap.get(utteranceId), "end"); + + // No longer need the CallbackContext for this utterance. + contextMap.remove(utteranceId); + if(utteranceId.equals(contextQueue.peek())) { + contextQueue.poll(); + } + } + + @Override + public void onError(String utteranceId) { + this.onError(utteranceId, -99); + } + + @Override + public void onError(String utteranceId, int errorCode) { + int error; + String message; + + Log.d(LOG_TAG, utteranceId + ": got utterance error"); + + switch(errorCode) { + case TextToSpeech.ERROR: + error = 6; + message = "Generic operation failure."; + break; + + case TextToSpeech.ERROR_INVALID_REQUEST: + error = 6; + message = "Invalid request."; + break; + + case TextToSpeech.ERROR_NETWORK: + error = 4; + message = "Network connectivity problem."; + break; + + case TextToSpeech.ERROR_NETWORK_TIMEOUT: + error = 4; + message = "Network timeout."; + break; + + case TextToSpeech.ERROR_NOT_INSTALLED_YET: + error = 5; + message = "Unfinished download of the voice data."; + break; + + case TextToSpeech.ERROR_OUTPUT: + error = 3; + message = "Audio output issue."; + break; + + case TextToSpeech.ERROR_SERVICE: + error = 6; + message = "Text-To-Speech failure."; + break; + + case TextToSpeech.ERROR_SYNTHESIS: + error = 6; + message = "Unable to synthesize the text."; + break; + + default: + error = 6; + message = "Unknown error."; + break; + } + fireErrorEvent(contextMap.get(utteranceId), error, message); + + // No longer need the CallbackContext for this utterance. + contextMap.remove(utteranceId); + if(utteranceId.equals(contextQueue.peek())) { + contextQueue.poll(); + } + } + + @Override + public void onStart(String utteranceId) { + String id; + + Log.d(LOG_TAG, utteranceId + ": started talking"); + fireEvent(contextMap.get(utteranceId),"start"); + + // Any contexts that have not been processed should be flagged with an error. + while(((id = contextQueue.peek()) != null) && !utteranceId.equals(id)) { + fireErrorEvent(contextMap.get(id), 6, "Lost event."); + contextMap.remove(id); + contextQueue.poll(); + } + } + + }); + } else if (status == TextToSpeech.ERROR) { state = SpeechSynthesis.STOPPED; PluginResult result = new PluginResult(PluginResult.Status.ERROR, SpeechSynthesis.STOPPED); result.setKeepCallback(false); @@ -301,11 +414,4 @@ public void onDestroy() { mTts.shutdown(); } } - - /** - * Once the utterance has completely been played call the speak's success callback - */ - public void onUtteranceCompleted(String utteranceId) { - fireEndEvent(callbackContext); - } } diff --git a/src/ios/NSMutableArray+QueueAdditions.h b/src/ios/NSMutableArray+QueueAdditions.h new file mode 100644 index 0000000..7152761 --- /dev/null +++ b/src/ios/NSMutableArray+QueueAdditions.h @@ -0,0 +1,15 @@ +/* + Generic queue. + */ +#import + +@interface NSMutableArray (QueueAdditions) + +-(id) dequeue; +-(void) enqueue:(id)obj; +-(id) peek:(int)index; +-(id) peekHead; +-(id) peekTail; +-(BOOL) empty; + +@end diff --git a/src/ios/NSMutableArray+QueueAdditions.m b/src/ios/NSMutableArray+QueueAdditions.m new file mode 100644 index 0000000..cdcc065 --- /dev/null +++ b/src/ios/NSMutableArray+QueueAdditions.m @@ -0,0 +1,66 @@ +#import "NSMutableArray+QueueAdditions.h" + +@implementation NSMutableArray (QueueAdditions) + +// Add to the tail of the queue +-(void) enqueue: (id) anObject { + // Push the item in + [self addObject: anObject]; +} + +// Grab the next item in the queue, if there is one +-(id) dequeue { + // Set aside a reference to the object to pass back + id queueObject = nil; + + // Do we have any items? + if ([self lastObject]) { + // Pick out the first one +#if !__has_feature(objc_arc) + queueObject = [[[self objectAtIndex: 0] retain] autorelease]; +#else + queueObject = [self objectAtIndex: 0]; +#endif + // Remove it from the queue + [self removeObjectAtIndex: 0]; + } + + // Pass back the dequeued object, if any + return queueObject; +} + +// Takes a look at an object at a given location +-(id) peek: (int) index { + // Set aside a reference to the peeked at object + id peekObject = nil; + // Do we have any items at all? + if ([self lastObject]) { + // Is this within range? + if (index < [self count]) { + // Get the object at this index + peekObject = [self objectAtIndex: index]; + } + } + + // Pass back the peeked at object, if any + return peekObject; +} + +// Let's take a look at the next item to be dequeued +-(id) peekHead { + // Peek at the next item + return [self peek: 0]; +} + +// Let's take a look at the last item to have been added to the queue +-(id) peekTail { + // Pick out the last item + return [self lastObject]; +} + +// Checks if the queue is empty +-(BOOL) empty { + return ([self lastObject] == nil); +} + +@end diff --git a/src/ios/SpeechSynthesis.h b/src/ios/SpeechSynthesis.h new file mode 100644 index 0000000..5bf4ab8 --- /dev/null +++ b/src/ios/SpeechSynthesis.h @@ -0,0 +1,30 @@ +/* + Modified for use in the Speech Synthesis plugin by Wayne Fisher. + Copyright (c) 2019 Fisherlea Systems. + + Original code from: + Cordova Text-to-Speech Plugin + https://github.com/vilic/cordova-plugin-tts + + by VILIC VANE + https://github.com/vilic + + MIT License +*/ + +#import +#import + +@interface SpeechSynthesis : CDVPlugin { + AVSpeechSynthesizer* synthesizer; + AVAudioSession *audioSession; + CDVPluginResult* pluginResult; + NSMutableArray* commandQueue; +} + +- (void)speak:(CDVInvokedUrlCommand*)command; +- (void)cancel:(CDVInvokedUrlCommand*)command; +- (void)pause:(CDVInvokedUrlCommand*)command; +- (void)resume:(CDVInvokedUrlCommand*)command; +- (void)startup:(CDVInvokedUrlCommand*)command; +@end diff --git a/src/ios/SpeechSynthesis.m b/src/ios/SpeechSynthesis.m new file mode 100644 index 0000000..81a9c51 --- /dev/null +++ b/src/ios/SpeechSynthesis.m @@ -0,0 +1,271 @@ +/* + Modified for use in the Speech Synthesis plugin by Wayne Fisher. + Copyright (c) 2019 Fisherlea Systems. + + Original code from: + Cordova Text-to-Speech Plugin + https://github.com/vilic/cordova-plugin-tts + + by VILIC VANE + https://github.com/vilic + + MIT License +*/ + +#import +#import +#import "SpeechSynthesis.h" +#import "NSMutableArray+QueueAdditions.h" + +#if 0 +#define DBG(a) NSLog(a) +#define DBG1(a, b) NSLog(a, b) +#define DBG2(a, b, c) NSLog(a, b, c) +#else +#define DBG(a) +#define DBG1(a, b) +#define DBG2(a, b, c) +#endif + +@implementation SpeechSynthesis + +- (void)pluginInitialize { + DBG(@"[ss] pluginInitialize()"); + + synthesizer = [AVSpeechSynthesizer new]; + synthesizer.delegate = self; + + audioSession = [AVAudioSession sharedInstance]; + commandQueue = [[NSMutableArray alloc] init]; + + // Log changes to the audio route. + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(routeChanged:) name:AVAudioSessionRouteChangeNotification object:nil]; +} + +- (void)routeChanged:(NSNotification *)notification { + AVAudioSession *audioSession = [AVAudioSession sharedInstance]; + NSNumber *reason = [notification.userInfo objectForKey:AVAudioSessionRouteChangeReasonKey]; + + DBG(@"[ss] routeChanged()"); + + AVAudioSessionRouteDescription *route; + AVAudioSessionPortDescription *port; + + if ([reason unsignedIntegerValue] == AVAudioSessionRouteChangeReasonNewDeviceAvailable) { + NSLog(@"[ss] AVAudioSessionRouteChangeReasonNewDeviceAvailable"); + + route = audioSession.currentRoute; + port = route.inputs[0]; + NSLog(@"[ss] New device is %@", port.portType); + } else if ([reason unsignedIntegerValue] == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) { + NSLog(@"[ss] AVAudioSessionRouteChangeReasonOldDeviceUnavailable"); + + route = [notification.userInfo objectForKey:AVAudioSessionRouteChangePreviousRouteKey]; + port = route.inputs[0]; + NSLog(@"[ss] Removed device %@", port.portType); + + route = audioSession.currentRoute; + port = route.inputs[0]; + NSLog(@"[ss] Now using device %@", port.portType); + } else if ([reason unsignedIntegerValue] == AVAudioSessionRouteChangeReasonCategoryChange) { + NSLog(@"[ss] AVAudioSessionRouteChangeReasonCategoryChange"); + + AVAudioSessionCategory category = [audioSession category]; + + NSLog(@"[ss] AVAudioSession category: %@, categoryOptions = %d", + category, (int) audioSession.categoryOptions); + + if(![category isEqualToString:AVAudioSessionCategoryPlayback] && + ![category isEqualToString:AVAudioSessionCategoryPlayAndRecord]) { + if([category isEqualToString:AVAudioSessionCategoryRecord]) { + category = AVAudioSessionCategoryPlayAndRecord; + } else { + category = AVAudioSessionCategoryPlayback; + } + + [audioSession setCategory:category error:nil]; + } + } +} + +- (void)speak:(CDVInvokedUrlCommand*)command { + NSDictionary* options = [command.arguments objectAtIndex:0]; + + NSString* text = [options objectForKey:@"text"]; + + DBG1(@"[ss] speak(%@)", text); + [commandQueue enqueue:command]; + + NSString* lang = [options objectForKey:@"lang"]; + NSDictionary* voice = [options objectForKey:@"voice"]; + float volume = [[options objectForKey:@"volume"] doubleValue]; + double rate = [[options objectForKey:@"rate"] doubleValue]; + double pitch = [[options objectForKey:@"pitch"] doubleValue]; + + AVAudioSessionCategory category = [audioSession category]; + if(![category isEqualToString:AVAudioSessionCategoryPlayback] && + ![category isEqualToString:AVAudioSessionCategoryPlayAndRecord]) { + [audioSession setActive:NO withOptions:0 error:nil]; + + if([category isEqualToString:AVAudioSessionCategoryRecord]) { + category = AVAudioSessionCategoryPlayAndRecord; + } else { + category = AVAudioSessionCategoryPlayback; + } + + NSUInteger options = [audioSession categoryOptions] | AVAudioSessionCategoryOptionDuckOthers; + [audioSession setCategory:category withOptions:options error:nil]; + } + + if (!lang || (id)lang == [NSNull null]) { + lang = @"en-US"; + } + + if (!volume) { + volume = 1.0; + } + + if (!rate) { + rate = 1.0; + } + + if (!pitch) { + pitch = 1.2; + } + + AVSpeechUtterance* utterance = [[AVSpeechUtterance new] initWithString:text]; + utterance.voice = [AVSpeechSynthesisVoice voiceWithLanguage:lang]; + // The rate appears to be exponential. Adjust our linear scale accordingly. + // The equation was worked out by mapping iOS rate to actual rate by timing between start and end. + utterance.rate = (log(rate) + 1) / 2.14; + if(utterance.rate > (double) AVSpeechUtteranceMaximumSpeechRate) { + utterance.rate = (double) AVSpeechUtteranceMaximumSpeechRate; + } + utterance.volume = volume; + utterance.pitchMultiplier = pitch; + + if(voice) { + NSString *identifier = [voice valueForKey:@"voiceURI"]; + + utterance.voice = [AVSpeechSynthesisVoice voiceWithIdentifier:identifier]; + } + + [synthesizer speakUtterance:utterance]; +} + +- (void)cancel:(CDVInvokedUrlCommand*)command { + DBG(@"[ss] cancel()"); + + [synthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate]; + // TODO: Need to dequeue and send cancels to any queued commands +} + +- (void)pause:(CDVInvokedUrlCommand*)command { + DBG(@"[ss] pause()"); + + [synthesizer pauseSpeakingAtBoundary:AVSpeechBoundaryImmediate]; + // TODO: Need to send pause event +} + +- (void)resume:(CDVInvokedUrlCommand*)command { + DBG(@"[ss] resume()"); + + if(synthesizer.paused) { + [synthesizer continueSpeaking]; + // TODO: Need to send resume event + } +} + +- (void)startup:(CDVInvokedUrlCommand *)command { + DBG(@"[ss] startup()"); + + NSMutableArray* list = [[NSMutableArray alloc] init]; + NSArray *voices = [AVSpeechSynthesisVoice speechVoices]; + + DBG1(@"Number of voices: %d", (int) voices.count); + + for (id voiceName in voices) { + NSMutableDictionary * voiceDict = [[NSMutableDictionary alloc] init]; + [voiceDict setValue:[voiceName valueForKey:@"identifier"] forKey:@"voiceURI"]; + [voiceDict setValue:[voiceName valueForKey:@"name"] forKey:@"name"]; + [voiceDict setValue:[voiceName valueForKey:@"language"] forKey:@"lang"]; + [voiceDict setValue:[NSNumber numberWithBool:true] forKey:@"localService"]; + [voiceDict setValue:[NSNumber numberWithBool:false] forKey:@"default"]; + + [list addObject:voiceDict]; + } + + DBG1(@"Number of voices in list: %d", (int) list.count); + + CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:list]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +-(void) sendEvent:(NSString *) eventType +{ + NSMutableDictionary * event = [[NSMutableDictionary alloc]init]; + DBG1(@"[ss] sendEvent(%@)", eventType); + [event setValue:eventType forKey:@"type"]; + [event setValue:@"" forKey:@"name"]; + [event setValue:0 forKey:@"charIndex"]; + [event setValue:0 forKey:@"elapsedTime"]; + + CDVInvokedUrlCommand *command = [commandQueue peekHead]; + if(command) { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:event]; + if(![eventType isEqualToString:@"end"]) { + [pluginResult setKeepCallbackAsBool:YES]; + } + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } + DBG1(@"[ss] sendEvent(%@) complete", eventType); +} + +-(void) sendErrorEvent:(NSString *) errorCode +{ + NSMutableDictionary * event = [[NSMutableDictionary alloc]init]; + DBG1(@"[ss] sendErrorEvent(%@)", errorCode); + [event setValue:@"error" forKey:@"type"]; + [event setValue:errorCode forKey:@"error"]; + + CDVInvokedUrlCommand *command = [commandQueue peekHead]; + if(command) { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:event]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } + DBG1(@"[ss] sendErrorEvent(%@) complete", errorCode); +} + +- (void)speechSynthesizer:(AVSpeechSynthesizer*)synthesizer didStartSpeechUtterance:(AVSpeechUtterance*)utterance { + DBG1(@"[ss] didStartSpeechUtterance(%@)", utterance.speechString); + + [self sendEvent:@"start"]; +} + +- (void)speechSynthesizer:(AVSpeechSynthesizer*)synthesizer didFinishSpeechUtterance:(AVSpeechUtterance*)utterance { + DBG1(@"[ss] didFinishSpeechUtterance(%@)", utterance.speechString); + + [self sendEvent:@"end"]; + [commandQueue dequeue]; +} + +- (void)speechSynthesizer:(AVSpeechSynthesizer*)synthesizer didPauseSpeechUtterance:(AVSpeechUtterance*)utterance { + DBG1(@"[ss] didPauseSpeechUtterance(%@)", utterance.speechString); + + [self sendEvent:@"pause"]; +} + +- (void)speechSynthesizer:(AVSpeechSynthesizer*)synthesizer didContinueSpeechUtterance:(AVSpeechUtterance*)utterance { + DBG1(@"[ss] didContinueSpeechUtterance(%@)", utterance.speechString); + + [self sendEvent:@"resume"]; +} + +- (void)speechSynthesizer:(AVSpeechSynthesizer*)synthesizer didCancelSpeechUtterance:(AVSpeechUtterance*)utterance { + DBG1(@"[ss] didFinishSpeechUtterance(%@)", utterance.speechString); + + [self sendEvent:@"end"]; + [commandQueue dequeue]; +} + +@end diff --git a/www/SpeechSynthesis.js b/www/SpeechSynthesis.js index dde4398..8d1941d 100644 --- a/www/SpeechSynthesis.js +++ b/www/SpeechSynthesis.js @@ -1,15 +1,22 @@ var exec = require("cordova/exec"); -var SpeechSynthesisVoiceList = require("./SpeechSynthesisVoiceList"); var SpeechSynthesis = function() { this.pending = false; this.speaking = false; this.paused = false; - this._voices = null; + this._voices = []; + this.onvoiceschanged = null; + var that = this; - var successCallback = function(data) { - that._voices = new SpeechSynthesisVoiceList(data); + var successCallback = function (data) { + if (Array.isArray(data)) { + that._voices = data; + + if (that._voices.length && typeof that.onvoiceschanged === "function") { + that.onvoiceschanged({ type: "voiceschanged" }); + } + } }; exec(successCallback, null, "SpeechSynthesis", "startup", []); }; @@ -30,9 +37,17 @@ SpeechSynthesis.prototype.speak = function(utterance) { utterance.onboundry(event); } }; - var errorCallback = function() { + var errorCallback = function(err) { if (typeof utterance.onerror === "function") { - utterance.onerror(); + var error = new SpeechSynthesisErrorEvent(); + + error.error = SpeechSynthesisErrorEvent._errorCodes[err.error]; + error.message = err.message; + error.charIndex = err.charIndex; + error.elapsedTime = err.elapsedTime; + error.name = err.name; + + utterance.onerror(error); } }; diff --git a/www/SpeechSynthesisErrorEvent.js b/www/SpeechSynthesisErrorEvent.js new file mode 100644 index 0000000..b4d294d --- /dev/null +++ b/www/SpeechSynthesisErrorEvent.js @@ -0,0 +1,38 @@ +var SpeechSynthesisErrorEvent = function () { + SpeechSynthesisEvent.call(this); + + this.type = "error"; + this.error = null; + this.message = null; +}; + +SpeechSynthesisErrorEvent.prototype = new SpeechSynthesisEvent; +SpeechSynthesisErrorEvent.prototype.constructor = SpeechSynthesisErrorEvent; + +SpeechSynthesisErrorEvent['canceled'] = 0; +SpeechSynthesisErrorEvent['interrupted'] = 1; +SpeechSynthesisErrorEvent['audio-busy'] = 2; +SpeechSynthesisErrorEvent['audio-hardware'] = 3; +SpeechSynthesisErrorEvent['network'] = 4; +SpeechSynthesisErrorEvent['synthesis-unavailable'] = 5; +SpeechSynthesisErrorEvent['synthesis-failed'] = 6; +SpeechSynthesisErrorEvent['language-unavailable'] = 7; +SpeechSynthesisErrorEvent['voice-unavailable'] = 8; +SpeechSynthesisErrorEvent['text-too-long'] = 9; +SpeechSynthesisErrorEvent['invalid-argument'] = 10; + +SpeechSynthesisErrorEvent._errorCodes = [ + 'canceled', + 'interrupted', + 'audio-busy', + 'audio-hardware', + 'network', + 'synthesis-unavailable', + 'synthesis-failed', + 'language-unavailable', + 'voice-unavailable', + 'text-too-long', + 'invalid-argument' +]; + +module.exports = SpeechSynthesisErrorEvent; diff --git a/www/SpeechSynthesisEvent.js b/www/SpeechSynthesisEvent.js index fb2c7d5..5c277ea 100644 --- a/www/SpeechSynthesisEvent.js +++ b/www/SpeechSynthesisEvent.js @@ -1,7 +1,8 @@ -var SpeechSynthesisEvent = function() { - this.charIndex; - this.elapsedTime; - this.name; +var SpeechSynthesisEvent = function () { + this.type = ""; + this.charIndex = 0; + this.elapsedTime = 0; + this.name = null; }; module.exports = SpeechSynthesisEvent; diff --git a/www/SpeechSynthesisUtterance.js b/www/SpeechSynthesisUtterance.js index e44eabe..3c777ff 100644 --- a/www/SpeechSynthesisUtterance.js +++ b/www/SpeechSynthesisUtterance.js @@ -1,7 +1,7 @@ var SpeechSynthesisUtterance = function(text) { this.text = text; this.lang; - this.voiceURI; + this.voice; this.volume; this.rate; this.pitch; diff --git a/www/SpeechSynthesisVoice.js b/www/SpeechSynthesisVoice.js index eb51943..e4693bf 100644 --- a/www/SpeechSynthesisVoice.js +++ b/www/SpeechSynthesisVoice.js @@ -4,7 +4,7 @@ var SpeechSynthesisVoice = function() { this.name; this.lang; this.localService; - this._default; + this.default; }; -module.exports = SpeechSynthesisVoice; \ No newline at end of file +module.exports = SpeechSynthesisVoice; diff --git a/www/SpeechSynthesisVoiceList.js b/www/SpeechSynthesisVoiceList.js deleted file mode 100644 index b6e208d..0000000 --- a/www/SpeechSynthesisVoiceList.js +++ /dev/null @@ -1,11 +0,0 @@ - -var SpeechSynthesisVoiceList = function(data) { - this._list = data; - this.length = this._list.length; -}; - -SpeechSynthesisVoiceList.prototype.item = function(item) { - return this._list[item]; -}; - -module.exports = SpeechSynthesisVoiceList; \ No newline at end of file