Android上MUI H5+科大讯飞语音识别功能如何去除默认界面
最近MUI的H5+项目需要实现语音识别功能,但是MUI自带的只有科大讯飞语音功能,而且还有默认的界面效果。由于产品原型要求实现自定义界面,所以需要把默认的界面去掉。
本篇博客是通过反编译DCloud提供的jar包,改写源码来实现的
同理可以直接Android原生实现无界面,通过NativeJS调用的方式来实现
如果不懂反编译,也可以直接使用博客内的代码,外壳版本是Android-SDK@1.9.9.35689_20170825,如果版本相差不大,应该可以直接使用
需要具备一定的java代码阅读能力
核心代码
1.HTML代码如下
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>科大讯飞语音</title>
<link rel="stylesheet" type="text/css" href="css/mui.min.css" />
</head>
<body>
<header class="mui-bar mui-bar-nav mui-badge-blue">
<a class="mui-action-back mui-icon mui-icon-back" style="color: white;"></a>
<h1 class="mui-title" style="color: white;">科大讯飞语音</h1>
</header>
<div class="mui-content">
<div style="text-align: center; padding: 10px 0;">
<button id="start" class="mui-btn-royal">开始录音</button>
</div>
<div class="mui-table-view">
<li class="mui-table-view-cell">
<label class="mui-tab-label">语音内容:</label>
<span id="talkContent"></span>
</li>
</div>
<p style="padding: 5px;">当前正在使用科大讯飞语音</p>
</div>
<script src="js/mui.min.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
var talkContent = document.getElementById("talkContent");
document.addEventListener('plusready', function(){
document.getElementById("start").addEventListener("tap", startRecognize);
});
function startRecognize() {
if(plus.os.name=='Android'&&navigator.userAgent.indexOf('StreamApp')>0){
plus.nativeUI.toast('当前环境暂不支持语音识别插件');
return;
}
if(plus.os.name=='Android') {
var options = {};
options.engine = 'iFly';
options.punctuation = false; // 是否需要标点符号
talkContent.textContent = "";
console.log( "开始语音识别:" );
plus.speech.startRecognize( options, function ( s ) {
console.log( s );
talkContent.textContent += s;
}, function ( e ) {
console.log( "语音识别失败:"+e.message );
} );
}
}
</script>
</body>
</html>
界面效果图
2.重写的Java代码
package io.dcloud.feature.speech;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import com.iflytek.cloud.RecognizerListener;
import com.iflytek.cloud.RecognizerResult;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.SpeechRecognizer;
import com.iflytek.cloud.SpeechUtility;
import io.dcloud.common.DHInterface.IWebview;
import io.dcloud.common.adapter.util.AndroidResources;
import io.dcloud.common.adapter.util.Logger;
import io.dcloud.common.util.JSONUtil;
import io.dcloud.common.util.PdrUtil;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Locale;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
public class IflySpeechEngine extends AbsSpeechEngine {
static String sIflyAppid;
MyRecognizer mRecognizer;
public IflySpeechEngine() {
}
public void init(Context paramContext, IWebview paramIWebview) {
super.init(paramContext, paramIWebview);
sIflyAppid = AndroidResources.getMetaValue("IFLY_APPKEY");
if (!PdrUtil.isEmpty(sIflyAppid)) {
SpeechUtility.createUtility(paramContext, "appid=" + sIflyAppid);
}
}
public void startRecognize(JSONObject paramJSONObject) {
/*this.mIsrDialog = new MyRecognizerDialog(this.mContext, null);
this.mIsrDialog.startRecognize(paramJSONObject);*/
this.mRecognizer = new MyRecognizer(this.mContext);
this.mRecognizer.startRecognize(paramJSONObject);
}
public void stopRecognize(boolean paramBoolean) {
/*if (this.mIsrDialog != null) {
this.mIsrDialog.stopRecognize(paramBoolean);
this.mIsrDialog = null;
}*/
if (this.mRecognizer != null) {
this.mRecognizer.stopRecognize(paramBoolean);
}
}
class MyRecognizer {
SpeechRecognizer speechRecognizer;
RecognizerListener recognizeListener = new RecognizerListener() {
@Override
public void onBeginOfSpeech() {
Log.i("console", "开始说话");
}
@Override
public void onError(SpeechError speechError) {
if (speechError != null) {
IflySpeechEngine.this.mListener.onStateChange(
(byte) 7,
new String[] {
String.valueOf(speechError.getErrorCode()),
speechError.getErrorDescription() }, false);
}
}
@Override
public void onEndOfSpeech() {
Log.i("console", "结束说话");
}
@Override
public void onResult(RecognizerResult recognizerResult,
boolean isLast) {
String str = IflySpeechEngine.MyRecognizer.this
.printResult(recognizerResult);
if (isLast) {
Logger.e("IflySpeechEngine", "onResult parsedresult=="
+ str);
IflySpeechEngine.this.mListener.onStateChange((byte) 8,
str, false);
}
}
@Override
public void onVolumeChanged(int arg0, byte[] arg1) {
// TODO Auto-generated method stub
}
@Override
public void onEvent(int arg0, int arg1, int arg2, Bundle arg3) {
// TODO Auto-generated method stub
}
};
public MyRecognizer(Context paramContext) {
this.speechRecognizer = SpeechRecognizer.createRecognizer(paramContext, null);
}
void startRecognize(JSONObject paramJSONObject) {
String str1 = "zh_cn";
String str2 = "4000";
String str3 = "1";
String str4 = null;
if (!PdrUtil.isEmpty(paramJSONObject)) {
String str5 = JSONUtil.getString(paramJSONObject, "lang");
if (!PdrUtil.isEmpty(str5)) {
str5 = str5.toLowerCase(Locale.ENGLISH);
if ("zh-cantonese".equals(str5)) {
str1 = "zh_cn";
str4 = "cantonese";
} else if ("zh-henanese".equals(str5)) {
str1 = "zh_cn";
str4 = "henanese";
} else if ("zh-cn".equals(str5)) {
str1 = "zh_cn";
} else if ("en-us".equals(str5)) {
str1 = "en_us";
}
}
int i = JSONUtil.getInt(paramJSONObject, "timeout");
if (0 != i) {
str2 = String.valueOf(i);
}
if ((paramJSONObject == null)
|| (!paramJSONObject.has("punctuation"))) {
str3 = "1";
} else if (!JSONUtil.getBoolean(paramJSONObject, "punctuation")) {
str3 = "0";
}
}
this.mIatResults.clear();
this.speechRecognizer.setParameter("language", str1);
this.speechRecognizer.setParameter("accent", str4);
this.speechRecognizer.setParameter("speech_timeout", str2);
this.speechRecognizer.setParameter("asr_ptt", str3);
this.speechRecognizer.startListening(this.recognizeListener);
IflySpeechEngine.this.mListener
.onStateChange((byte) 1, null, false);
}
void stopRecognize(boolean paramBoolean) {
if (paramBoolean) {
IflySpeechEngine.this.mListener.onStateChange((byte) 2, null,
false);
}
this.speechRecognizer.stopListening();
}
private HashMap<String, String> mIatResults = new LinkedHashMap<String, String>();
private String printResult(RecognizerResult paramRecognizerResult) {
String str1 = parseIatResult(paramRecognizerResult
.getResultString());
Logger.e("IflySpeechEngine", "text==" + str1);
String str2 = null;
try {
JSONObject localJSONObject = new JSONObject(
paramRecognizerResult.getResultString());
str2 = localJSONObject.optString("sn");
} catch (JSONException localJSONException) {
localJSONException.printStackTrace();
}
this.mIatResults.put(str2, str1);
StringBuffer localStringBuffer = new StringBuffer();
for (String str3 : this.mIatResults.keySet()) {
Logger.e("IflySpeechEngine", "mIatResults.get(key)"
+ (String) this.mIatResults.get(str3));
localStringBuffer.append((String) this.mIatResults.get(str3));
}
Logger.e("IflySpeechEngine", "resultBuffer.toString()=="
+ localStringBuffer.toString());
return localStringBuffer.toString();
}
public String parseIatResult(String paramString) {
StringBuffer localStringBuffer = new StringBuffer();
try {
JSONTokener localJSONTokener = new JSONTokener(paramString);
JSONObject localJSONObject1 = new JSONObject(localJSONTokener);
JSONArray localJSONArray1 = localJSONObject1.getJSONArray("ws");
for (int i = 0; i < localJSONArray1.length(); i++) {
JSONArray localJSONArray2 = localJSONArray1
.getJSONObject(i).getJSONArray("cw");
JSONObject localJSONObject2 = localJSONArray2
.getJSONObject(0);
localStringBuffer.append(localJSONObject2.getString("w"));
}
} catch (Exception localException) {
localException.printStackTrace();
}
return localStringBuffer.toString();
}
}
}
3.实现方式
首先声明,上面这种改写的方式是在已经集成好科大讯飞语音识别功能的基础上,做的二次开发。所以此博客只能帮助已经实现MUI H5+科大讯飞语音识别功能,但是想要去掉官方默认界面的开发者。
已经具备上述基础的开发者都知道,科大讯飞语音识别功能调起方法是:
if(plus.os.name=='Android') {
var options = {};
options.engine = 'iFly';
options.punctuation = false; // 是否需要标点符号
talkContent.textContent = "";
console.log( "开始语音识别:" );
plus.speech.startRecognize( options, function ( s ) {
console.log( s );
}, function ( e ) {
console.log( "语音识别失败:"+e.message );
} );
}
从科大讯飞的官方文档里得知,语音识别有带界面RecognizerDialog和不带界面SpeechRecognizer两种实现方式。所以很明显,MUI官方封装了RecognizerDialog来实现的语音识别功能。
我们知道speech_ifly.jar、speech.jar和Msc.jar,这三个jar包是集成语音识别功能时要添加的jar包,所以MUI的封装方法一定在这里面。
通过反编译,我发现了MyRecognizerDialog这个类,在speech_ifly.jar的源码里。speech_ifly.jar反编译出来只有一个文件,IflySpeechEngine.java。MyRecognizerDialog就是IflySpeechEngine的内部类。
所以这里就很简单了,改写IflySpeechEngine.java,用SpeechRecognizer取代RecognizerDialog这种实现方式,就可以实现无界面的语音识别功能。这样就可以定制产品要求的界面和效果了。
改写后的代码上面已经贴出,具体改写方式,有兴趣的同学可以反编译speech_ifly.jar包,将改写前后的代码进行比较就会明白。实现方式其实特别简单,所以不细讲了。
注意,完成改写后我们需要把项目中原本的speech_ifly.jar依赖删除,然后在src目录下新建IflySpeechEngine这个类,包名保持和之前完全一样。这样以后调用科大讯飞的语音识别功能,就是调用的这个类了。