first commit

This commit is contained in:
KeiferJu 2019-08-21 19:19:01 +08:00
commit aea14e50f0
18 changed files with 699 additions and 0 deletions

83
.gitignore vendored Normal file
View File

@ -0,0 +1,83 @@
# Created by .ignore support plugin (hsz.mobi)
### Cordova template
# gitignore template for the Cordova framework
# website: https://cordova.apache.org/
#
# Recommended template: Node.gitignore
# App platform binaries and built files
/platforms
# Optional to ignore plugin Git clones
#/plugins
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser

9
.idea/chineseTTS.iml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/misc.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/chineseTTS.iml" filepath="$PROJECT_DIR$/.idea/chineseTTS.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

1
README.md Normal file
View File

@ -0,0 +1 @@
#

11
package.json Normal file
View File

@ -0,0 +1,11 @@
{
"name": "chinesetts",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "KeiferJu <jkf19980216@163.com> (myllcn.com)",
"license": "MIT"
}

40
plugin.xml Normal file
View File

@ -0,0 +1,40 @@
<?xml version='1.0' encoding='utf-8'?>
<plugin id="cordova-plugin-chinese-tts" version="0.0.1" xmlns="http://apache.org/cordova/ns/plugins/1.0"
xmlns:android="http://schemas.android.com/apk/res/android">
<name>chineseTTS</name>
<js-module name="ChineseTTS" src="www/chineseTTS.js">
<clobbers target="cordova.plugins.chineseTTS"/>
</js-module>
<platform name="android">
<config-file parent="/*" target="res/xml/config.xml">
<feature name="ChineseTTS">
<param name="android-package" value="com.smartmapx.tts.ChineseTTS"/>
</feature>
</config-file>
<config-file parent="/*" target="AndroidManifest.xml"></config-file>
<source-file src="src/android/ChineseTTS.java" target-dir="src/com/smartmapx/tts"/>
<source-file src="src/android/FileUtils.java" target-dir="src/com/smartmapx/tts"/>
<source-file src="src/android/OfflineResource.java" target-dir="src/com/smartmapx/tts"/>
<source-file src="src/android/SpeechUtilOffline.java" target-dir="src/com/smartmapx/tts"/>
<source-file src="src/android/libs/usc.jar" target-dir="libs"/>
<source-file src="src/android/libs/armeabi/libyzstts.so" target-dir="src/main/jniLibs/armeabi"/>
<source-file src="src/android/libs/armeabi/libuscasr.so" target-dir="src/main/jniLibs/armeabi"/>
<source-file src="src/android/assets/backend_lzl" target-dir="src/main/assets"/>
<source-file src="src/android/assets/frontend_model" target-dir="src/main/assets"/>
<config-file target="AndroidManifest.xml" parent="/manifest">
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.VIBRATE"/>
</config-file>
<framework src="com.hjq:xxpermissions:6.0"/>
</platform>
</plugin>

111
src/android/ChineseTTS.java Normal file
View File

@ -0,0 +1,111 @@
package com.smartmapx.tts;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CallbackContext;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.Manifest;
import android.widget.Toast;
import com.hjq.permissions.OnPermission;
import com.hjq.permissions.Permission;
import com.hjq.permissions.XXPermissions;
import java.util.List;
/**
* This class echoes a string called from JavaScript.
*/
public class ChineseTTS extends CordovaPlugin {
private Context context;
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if (action.equals("init")) {
this.init();
return true;
}
if (action.equals("speak")) {
String message = args.getString(0);
this.speak(message, callbackContext);
return true;
}
return false;
}
/**
* 初始化引擎
*
* @param
*/
private void init() {
context = this.cordova.getActivity();
permissionRequest();
}
/**
* 讲话
*
* @param message
* @param callbackContext
*/
private void speak(String message, CallbackContext callbackContext) {
if (message != null && message.length() > 0) {
SpeechUtilOffline.getInstance(context).play(message, SpeechUtilOffline.PLAY_MODE.QUEUED);
} else {
callbackContext.error("信息为空.");
}
}
/**
* 运行时权限--
*/
public void permissionRequest() {
// Manifest.permission.WRITE_EXTERNAL_STORAGE,
// Manifest.permission.ACCESS_FINE_LOCATION,
// Manifest.permission.READ_PHONE_STATE,
// Manifest.permission.RECEIVE_BOOT_COMPLETED
if (XXPermissions.isHasPermission(context, Permission.WRITE_EXTERNAL_STORAGE) && XXPermissions.isHasPermission(context, Permission.ACCESS_FINE_LOCATION) && XXPermissions.isHasPermission(context, Permission.READ_PHONE_STATE)) {
} else {
XXPermissions.with(this.cordova.getActivity())
// 可设置被拒绝后继续申请直到用户授权或者永久拒绝
.constantRequest()
// 支持请求6.0悬浮窗权限8.0请求安装权限
.permission(Permission.WRITE_EXTERNAL_STORAGE, Permission.ACCESS_FINE_LOCATION, Permission.READ_PHONE_STATE)
// 不指定权限则自动获取清单中的危险权限
// .permission(Permission.Group.STORAGE, Permission.Group.CALENDAR)
.request(new OnPermission() {
@Override
public void hasPermission(List<String> granted, boolean isAll) {
if (isAll) {
Toast.makeText(context, "权限获取成功,正在初始化语音包", Toast.LENGTH_SHORT).show();
SpeechUtilOffline.getInstance(context).play("语音包初始化完成", SpeechUtilOffline.PLAY_MODE.QUEUED);
}
}
@Override
public void noPermission(List<String> denied, boolean quick) {
Toast.makeText(context, "权限获取失败,语音包初始化失败", Toast.LENGTH_SHORT).show();
}
});
}
}
}

152
src/android/FileUtils.java Executable file
View File

@ -0,0 +1,152 @@
package com.smartmapx.tts;
import android.content.Context;
import android.content.res.AssetManager;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 文件操作类
*
* @author KeiferJu
* @date 2019/8/
*/
public final class FileUtils {
private static final String LOGTAG ="/ing/tts";
private static FileUtils fileUtils = new FileUtils();
private FileUtils() {
}
public static FileUtils getInstance() {
return fileUtils;
}
/**
* 获取SDCARD根路径
*
* @return
*/
private static StringBuffer getRootDir() throws Exception {
return new StringBuffer().append(Environment
.getExternalStorageDirectory());
}
/**
* 获取存在SDCARD上文件的绝对路径
*
* @param mContext
* @param folderName
*/
public static StringBuffer getExternalFileAbsoluteDir(Context mContext,
String folderName, String fileName) throws Exception {
StringBuffer stringBuffer = getRootDir().append(File.separator);
stringBuffer.append(getExternalFilesDir(mContext, folderName));
if (fileName != null) {
if (0 == fileName.indexOf(File.separator)) {
stringBuffer.append(fileName);
} else {
stringBuffer.append(File.separator);
stringBuffer.append(fileName);
}
}
return stringBuffer;
}
/**
* 获取SDCARD上应用存储路径
*
* @param mContext
* @param folderName
* @return
*/
private static StringBuffer getExternalFilesDir(Context mContext,
String folderName) throws Exception {
String packageName = mContext.getPackageName();
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("Android").append(File.separator).append("data")
.append(File.separator).append(packageName);
if (folderName != null) {
if (0 == folderName.indexOf(File.separator)) {
stringBuffer.append(folderName);
} else {
stringBuffer.append(File.separator);
stringBuffer.append(folderName);
}
}
Log.d(LOGTAG, "FileUtils getExternalFilesDir "
+ stringBuffer.toString());
return stringBuffer;
}
/**
* 创建一个临时目录用于复制临时文件如assets目录下的离线资源文件
* @param context
* @return
*/
public static String createTmpDir(Context context) {
String sampleDir = "/ing/tts";
String tmpDir = Environment.getExternalStorageDirectory().toString() + sampleDir;
if (!FileUtils.makeDir(tmpDir)) {
tmpDir = context.getExternalFilesDir(sampleDir).getAbsolutePath();
if (!FileUtils.makeDir(sampleDir)) {
throw new RuntimeException("create model resources dir failed :" + tmpDir);
}
}
return tmpDir;
}
public static boolean makeDir(String dirPath) {
File file = new File(dirPath);
if (!file.exists()) {
return file.mkdirs();
} else {
return true;
}
}
/**
* assets文件2 sdcard
* @param assets
* @param source
* @param dest
* @param isCover
* @throws IOException
*/
public static void copyFromAssets(AssetManager assets, String source, String dest, boolean isCover) throws IOException {
File file = new File(dest);
if (isCover || (!isCover && !file.exists())) {
InputStream is = null;
FileOutputStream fos = null;
try {
is = assets.open(source);
String path = dest;
fos = new FileOutputStream(path);
byte[] buffer = new byte[1024];
int size = 0;
while ((size = is.read(buffer, 0, 1024)) >= 0) {
fos.write(buffer, 0, size);
}
} finally {
if (fos != null) {
try {
fos.close();
} finally {
if (is != null) {
is.close();
}
}
}
}
}
}
}

View File

@ -0,0 +1,56 @@
package com.smartmapx.tts;
import android.content.Context;
import android.content.res.AssetManager;
import android.util.Log;
import java.io.IOException;
import static android.content.ContentValues.TAG;
/**
* 离线文件
*
* @author ing
* @date 2018/3/27
*/
public class OfflineResource {
private AssetManager assets;
private String destPath;
private String backFilename;
private String modelFilename;
public OfflineResource(Context context) throws IOException {
this.assets = context.getAssets();
this.destPath = FileUtils.createTmpDir(context);
setOfflineVoiceType();
}
public String getModelFilename() {
return modelFilename;
}
public String getBackFilename() {
return backFilename;
}
public void setOfflineVoiceType() throws IOException {
String back = "backend_lzl";
String model = "frontend_model";
backFilename = copyAssetsFile(back);
modelFilename = copyAssetsFile(model);
}
private String copyAssetsFile(String sourceFilename) throws IOException {
String destFilename = destPath + "/" + sourceFilename;
FileUtils.copyFromAssets(assets, sourceFilename, destFilename, false);
Log.i(TAG, "Assets to sdcard successed" + destFilename);
return destFilename;
}
}

View File

@ -0,0 +1,207 @@
package com.smartmapx.tts;
import android.content.Context;
import android.media.AudioManager;
import android.util.Log;
import com.unisound.client.SpeechConstants;
import com.unisound.client.SpeechSynthesizer;
import com.unisound.client.SpeechSynthesizerListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* 离线语音
*
* @author KeiferJu
* @date 2019/8/15
*/
public class SpeechUtilOffline {
public static final String appKey = "_appKey_";
public static final String secret = "_secret_";
private static SpeechUtilOffline instance;
private SpeechSynthesizer mTTSPlayer;
private boolean isSpeaking = false;
private List<SpeechItem> speechList = new ArrayList<>();
private boolean released = false;
protected OfflineResource offlineResource;
private SpeechUtilOffline(Context context) {
init(context);
released = false;
}
public static SpeechUtilOffline getInstance(Context context) {
if (instance == null) {
instance = new SpeechUtilOffline(context);
}
return instance;
}
/**
* 初始化引擎
*
*/
private void init(final Context context) {
try {
offlineResource = new OfflineResource(context);
} catch (IOException e) {
Log.e("ing","offlineResouce failed , error msg : "+e.getMessage());
e.printStackTrace();
}
// 初始化语音合成对象
mTTSPlayer = new SpeechSynthesizer(context, appKey, secret);
// 设置本地合成
mTTSPlayer.setOption(SpeechConstants.TTS_SERVICE_MODE, SpeechConstants.TTS_SERVICE_MODE_LOCAL);
mTTSPlayer.setOption(SpeechConstants.TTS_KEY_VOICE_PITCH, 50);//音调
mTTSPlayer.setOption(SpeechConstants.TTS_KEY_VOICE_SPEED, 52);//语速
mTTSPlayer.setOption(SpeechConstants.TTS_KEY_VOICE_VOLUME, 100);//音量
mTTSPlayer.setOption(SpeechConstants.TTS_KEY_STREAM_TYPE, AudioManager.STREAM_NOTIFICATION);
mTTSPlayer.setOption(SpeechConstants.TTS_KEY_FRONTEND_MODEL_PATH, offlineResource.getModelFilename());
// 设置后端模型
mTTSPlayer.setOption(SpeechConstants.TTS_KEY_BACKEND_MODEL_PATH, offlineResource.getBackFilename());
// 设置回调监听
mTTSPlayer.setTTSListener(new SpeechSynthesizerListener() {
@Override
public void onEvent(int type) {
switch (type) {
case SpeechConstants.TTS_EVENT_INIT:
// 初始化成功回调
break;
case SpeechConstants.TTS_EVENT_SYNTHESIZER_START:
// 开始合成回调
break;
case SpeechConstants.TTS_EVENT_SYNTHESIZER_END:
// 合成结束回调
break;
case SpeechConstants.TTS_EVENT_BUFFER_BEGIN:
// 开始缓存回调
break;
case SpeechConstants.TTS_EVENT_BUFFER_READY:
// 缓存完毕回调
break;
case SpeechConstants.TTS_EVENT_PLAYING_START:
// 开始播放回调
break;
case SpeechConstants.TTS_EVENT_PLAYING_END:
// 播放完成回调
break;
case SpeechConstants.TTS_EVENT_PAUSE:
// 暂停回调
break;
case SpeechConstants.TTS_EVENT_RESUME:
// 恢复回调
break;
case SpeechConstants.TTS_EVENT_STOP:
// 停止回调
break;
case SpeechConstants.TTS_EVENT_RELEASE:
// 释放资源回调
break;
default:
break;
}
}
@Override
public void onError(int type, String errorMSG) {
// 语音合成错误回调
Log.e("ing","TTS onError __ type : "+ type +" errorMsg : " +errorMSG );
}
});
// 初始化合成引擎
mTTSPlayer.init("");
}
/**
* 停止播放
*
*/
public void stop() {
mTTSPlayer.stop();
}
/**
* 播放
*
*/
public void play(String content) {
playImmediately(content);
}
public void play(String content, PLAY_MODE playMode) {
switch (playMode) {
case QUEUED: {
playQueued(content);
break;
}
case IMMEDIATELY: {
playImmediately(content);
break;
}
}
}
private void updateSpeech() {
if (!isSpeaking) {
if (speechList.size() > 0) {
speak(speechList.remove(speechList.size() - 1).content);
}
}
}
private void speak(String content) {
mTTSPlayer.playText(content);
}
public void playQueued(String content) {
speechList.add(new SpeechItem(content, PLAY_MODE.QUEUED));
updateSpeech();
}
public void playImmediately(String content) {
speak(content);
}
/**
* 释放资源
*
*/
public void release() {
// 主动释放离线引擎
if (released) {
return;
}
if (mTTSPlayer != null) {
mTTSPlayer.stop();
mTTSPlayer.release(SpeechConstants.TTS_RELEASE_ENGINE, null);
}
instance = null;
released = true;
}
public enum PLAY_MODE {
QUEUED,
IMMEDIATELY
}
private class SpeechItem {
public String content;
public PLAY_MODE playMode;
public SpeechItem(String content, PLAY_MODE mode) {
this.content = content;
this.playMode = mode;
}
}
}

BIN
src/android/assets/backend_lzl Executable file

Binary file not shown.

BIN
src/android/assets/frontend_model Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
src/android/libs/usc.jar Executable file

Binary file not shown.

9
www/chineseTTS.js Normal file
View File

@ -0,0 +1,9 @@
var exec = require('cordova/exec');
exports.init = function (arg0, success, error) {
exec(success, error, 'ChineseTTS', 'init', [arg0]);
}
exports.speak = function (arg0, success, error) {
exec(success, error, 'ChineseTTS', 'speak', [arg0]);
}