Commit c44c20f349153da96b94757a6075f252484709f8

Authored by 吴启风
1 parent 842b7132

feat:集成先声sdk android端

Showing 27 changed files with 2300 additions and 1 deletions
android/app/build.gradle
... ... @@ -79,4 +79,8 @@ flutter {
79 79 }
80 80  
81 81 dependencies {
  82 +
  83 + // sing sound
  84 + implementation 'com.singsound.library:evaluating:2.1.9'
  85 + implementation "com.google.code.gson:gson:2.9.0"
82 86 }
... ...
android/app/proguard-rules.pro 0 → 100644
  1 +# Add project specific ProGuard rules here.
  2 +# You can control the set of applied configuration files using the
  3 +# proguardFiles setting in build.gradle.
  4 +#
  5 +# For more details, see
  6 +# http://developer.android.com/guide/developing/tools/proguard.html
  7 +
  8 +# If your project uses WebView with JS, uncomment the following
  9 +# and specify the fully qualified class name to the JavaScript interface
  10 +# class:
  11 +#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
  12 +# public *;
  13 +#}
  14 +
  15 +# Uncomment this to preserve the line number information for
  16 +# debugging stack traces.
  17 +#-keepattributes SourceFile,LineNumberTable
  18 +
  19 +# If you keep the line number information, uncomment this to
  20 +# hide the original source file name.
  21 +#-renamesourcefileattribute SourceFile
  22 +
  23 +# 先声混淆代码
  24 +-keep class com.tt.** { *; }
  25 +-keep class com.xs.** { *; }
  26 +-keep interface com.xs.** { *; }
  27 +-keep enum com.xs.** { *; }
0 28 \ No newline at end of file
... ...
android/app/src/main/AndroidManifest.xml
1   -<manifest xmlns:android="http://schemas.android.com/apk/res/android">
  1 +<manifest xmlns:tools="http://schemas.android.com/tools"
  2 + xmlns:android="http://schemas.android.com/apk/res/android">
2 3 <application
3 4 android:label="wow_english"
4 5 android:name="${applicationName}"
... ... @@ -31,4 +32,9 @@
31 32 android:value="2" />
32 33 </application>
33 34 <uses-permission android:name="android.permission.INTERNET"/>
  35 + <uses-permission android:name="android.permission.RECORD_AUDIO" />
  36 + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
  37 + tools:ignore="ScopedStorage" />
  38 + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  39 + <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
34 40 </manifest>
... ...
android/app/src/main/assets/vad.0.1.bin 0 → 100755
No preview for this file type
android/app/src/main/jniLibs/arm64-v8a/libssound.so 0 → 100755
No preview for this file type
android/app/src/main/jniLibs/armeabi-v7a/libssound.so 0 → 100755
No preview for this file type
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/MainActivity.kt
... ... @@ -8,7 +8,9 @@ import android.view.View
8 8 import androidx.core.view.WindowCompat
9 9 import androidx.core.view.WindowInsetsCompat
10 10 import androidx.core.view.WindowInsetsControllerCompat
  11 +import com.kouyuxingqiu.wow_english.methodChannels.SingSoungMethodChannel
11 12 import io.flutter.embedding.android.FlutterActivity
  13 +import io.flutter.embedding.engine.FlutterEngine
12 14  
13 15 class MainActivity : FlutterActivity() {
14 16 override fun onCreate(savedInstanceState: Bundle?) {
... ... @@ -44,4 +46,9 @@ class MainActivity : FlutterActivity() {
44 46 // 打开沉浸式
45 47 WindowCompat.setDecorFitsSystemWindows(window, false)*/
46 48 }
  49 +
  50 + override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
  51 + super.configureFlutterEngine(flutterEngine)
  52 + SingSoungMethodChannel(this, flutterEngine)
  53 + }
47 54 }
... ...
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/methodChannels/SingSoungMethodChannel.kt 0 → 100644
  1 +package com.kouyuxingqiu.wow_english.methodChannels
  2 +
  3 +import io.flutter.embedding.android.FlutterActivity
  4 +import io.flutter.embedding.engine.FlutterEngine
  5 +import io.flutter.plugin.common.MethodChannel
  6 +import java.lang.ref.WeakReference
  7 +
  8 +/**
  9 + * @author: stay
  10 + * @date: 2023/6/27 00:32
  11 + * @description:
  12 + */
  13 +class SingSoungMethodChannel(val activity: FlutterActivity, val flutterEngine: FlutterEngine) {
  14 + private var methodChannel: MethodChannel? = null
  15 +
  16 + companion object {
  17 + var channel: WeakReference<SingSoungMethodChannel>? = null
  18 +
  19 + fun invokeMethod(method: String, arguments: Any) {
  20 + channel?.get()?.methodChannel?.invokeMethod(method, arguments)
  21 + }
  22 + }
  23 +
  24 + init {
  25 + // name需与flutter端一致
  26 + methodChannel =
  27 + MethodChannel(
  28 + flutterEngine.dartExecutor.binaryMessenger,
  29 + "wow_english/sing_sound_method_channel"
  30 + )
  31 + methodChannel?.setMethodCallHandler { call, result ->
  32 + when (call.method) {
  33 + "startRecord" -> {
  34 + val jsonStr = call.arguments as? String ?: return@setMethodCallHandler
  35 + //do nothing
  36 + }
  37 + else -> {
  38 + result.notImplemented()
  39 + }
  40 + }
  41 +
  42 + }
  43 + channel = WeakReference(this)
  44 + }
  45 +}
0 46 \ No newline at end of file
... ...
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/BaseCloudFragment.kt 0 → 100644
  1 +//package com.ishow.english.module.lesson.question.sound
  2 +//
  3 +//import android.graphics.Color
  4 +//import android.os.Bundle
  5 +//import android.os.Handler
  6 +//import android.os.Looper
  7 +//import android.text.SpannableString
  8 +//import android.text.TextUtils
  9 +//import com.daimajia.androidanimations.library.YoYo
  10 +//import com.ishow.english.common.Constant
  11 +//import com.ishow.english.module.lesson.BaseLessonFragment
  12 +//import com.ishow.english.module.lesson.BaseLessonStrategy
  13 +//import com.ishow.english.module.lesson.LessonMode
  14 +//import com.ishow.english.module.lesson.LessonType
  15 +//import com.ishow.parent.module.question.sound.bean.BaseResultModel
  16 +//import com.ishow.english.module.lesson.question.sound.config.EvalTargetType
  17 +//import com.ishow.parent.module.question.sound.util.VoiceSpannableString
  18 +//import com.ishow.parent.audio.Audio
  19 +//import com.ishow.english.utils.CountDownHelper
  20 +//import com.ishow.english.utils.IWarningStateListenerAdapter
  21 +//import com.ishow.english.utils.WarnToneManager
  22 +//import com.jiongbull.Log.Log
  23 +//import com.perfect.utils.StringUtils
  24 +//import org.json.JSONObject
  25 +//
  26 +//
  27 +///**
  28 +// * @author stay
  29 +// * @date 2019-1-7
  30 +// * @describe 语音测评基类
  31 +// */
  32 +//abstract class BaseCloudFragment : BaseLessonFragment() {
  33 +//
  34 +// val TAG = "BaseCloudFragment"
  35 +// var rope: YoYo.YoYoString? = null
  36 +// val mSingEngineLifecycles: SingEngineLifecycles
  37 +// var mHandler: Handler
  38 +//
  39 +// init {
  40 +// mHandler = Handler(Looper.getMainLooper())
  41 +//
  42 +// mSingEngineLifecycles = object : SingEngineLifecycles.OnSingEngineAdapter() {
  43 +// override fun onResult(result: JSONObject, evalType: Int?) {
  44 +// Log.d(TAG, "evalType = $evalType result = $result")
  45 +// val resultModel = SingEngineManager.get().parseResult(result, evalType)
  46 +// val originText = StringUtils.replaceChinesePunctuationToEnglish(resultModel.originText)
  47 +// var resutlSpan = VoiceSpannableString(getContext(), originText)
  48 +// activity?.runOnUiThread {
  49 +// Log.w(Constant.TAG_THREADID, "onResult = ${android.os.Process.myTid()}")
  50 +// if (!TextUtils.isEmpty(originText)) {
  51 +// if (activity != null) {
  52 +// when (evalType) {
  53 +// EvalTargetType.SENTENCE -> {
  54 +// var startIndex = 0
  55 +// for (r in resultModel.scores) {
  56 +// Log.e(TAG, "startIndex = $startIndex r.char = ${r.char}")
  57 +// startIndex = originText.indexOf(r.char, startIndex)
  58 +// Log.e(TAG, "k = ${r.char} startIndex = $startIndex score = ${r.score}")
  59 +// if (startIndex == -1) { //异常情况,或者音标文字识别不了
  60 +// } else {
  61 +// resutlSpan.first(r.char, startIndex).textColor(parseScoreToColor(r.score))
  62 +// }
  63 +// startIndex += r.char.length
  64 +// }
  65 +// }
  66 +// EvalTargetType.WORD -> {
  67 +// Log.e(TAG, "WORD = ${resultModel.originText} ${resultModel.score}")
  68 +// resutlSpan.all(originText).textColor(parseScoreToColor(resultModel.score))
  69 +// }
  70 +// EvalTargetType.ALPHA -> {
  71 +// if (originText.trim() == "abc") {
  72 +// resutlSpan = VoiceSpannableString(getContext(), "/ɔr/")
  73 +// resutlSpan.all("/ɔr/").textColor(parseScoreToColor(resultModel.score))
  74 +// } else {
  75 +// Log.e(TAG, "ALPHA = ${resultModel.originText} ${resultModel.score}")
  76 +// resutlSpan.all(originText).textColor(parseScoreToColor(resultModel.score))
  77 +// }
  78 +// }
  79 +// }
  80 +// }
  81 +//
  82 +// mStrategy.decreaseChance()
  83 +//
  84 +// val delay = if (mLessonExtroBundle.lessonMode == LessonMode.EXAM) 400L else 1000L
  85 +// mHandler.postDelayed({
  86 +// // // 计分
  87 +// if (mLessonPagePacket.type != LessonType.VOICE_JSBY) { // 角色扮演不需要每句都打分
  88 +// lessonEvaluat(resultModel.score.toInt())
  89 +// }
  90 +//
  91 +// onSoundResult(resutlSpan, resultModel)
  92 +// changeBottomSheetLayoutState(false)
  93 +// if (mStrategy.needNotifyResult) { // 除了角色扮演和测评课,其余都为true
  94 +// // 延迟1s是为了等待RecordRippleView结束提示音以及zoomout动画
  95 +//
  96 +// var warnId: Int? = null
  97 +// if (mLessonPagePacket.type == LessonType.STATEMENT) { // 题干单独处理
  98 +// if (mLessonPagePacket.score >= Constant.VOICE_NICE_SCORE) {
  99 +// warnId = WarnToneManager.RECORD_NICE
  100 +// } else {
  101 +// if (mStrategy.needPlayBack) {
  102 +// SingEngineManager.get().playBack()
  103 +// this@BaseCloudFragment.onPlayBack()
  104 +// } else {
  105 +// this@BaseCloudFragment.onRecordPlayOver()
  106 +// }
  107 +// }
  108 +// } else { // 非题干
  109 +// if (mLessonPagePacket.score >= Constant.VOICE_SUCCESS_SCORE) {
  110 +// warnId = WarnToneManager.RIGHT
  111 +// } else {
  112 +// warnId = WarnToneManager.WRONG
  113 +// }
  114 +// }
  115 +//
  116 +// if (warnId != null) {
  117 +// WarnToneManager.play(warnId, object : IWarningStateListenerAdapter() {
  118 +// override fun onStart(audio: Audio?) {
  119 +// if (mLessonPagePacket.type == LessonType.STATEMENT) {
  120 +// onStatementNice()
  121 +// }
  122 +// }
  123 +//
  124 +// override fun onCompleted(audio: Audio?) {
  125 +// if (mLessonPagePacket.type == LessonType.STATEMENT) { // 题干播放完nice后
  126 +// SingEngineManager.get().playBack()
  127 +// this@BaseCloudFragment.onPlayBack()
  128 +// } else {
  129 +// playCoinSound().subscribe {
  130 +// if (mStrategy.needPlayBack) {
  131 +// SingEngineManager.get().playBack()
  132 +// this@BaseCloudFragment.onPlayBack()
  133 +// } else { // gaming模式和exam模式不需要播放录音
  134 +// if (!mStrategy.checkAnyChance()) {
  135 +// exitFragmentDelay()
  136 +// }
  137 +// }
  138 +// }
  139 +// }
  140 +// }
  141 +// })
  142 +// }
  143 +// } else {
  144 +// if (mLessonExtroBundle.lessonMode == LessonMode.EXAM) {
  145 +// exitFragmentDelay()
  146 +// }
  147 +// }
  148 +// }, delay)
  149 +// }
  150 +// }
  151 +// }
  152 +//
  153 +// override fun onRecordBegin() {
  154 +// startCountDown()
  155 +// this@BaseCloudFragment.onRecordBegin()
  156 +// }
  157 +//
  158 +// override fun onRecordStop() {
  159 +// activity?.runOnUiThread {
  160 +// Log.w(Constant.TAG_THREADID, "onRecordStop = ${android.os.Process.myTid()}")
  161 +// mLessonPagePacket.voiceRecordPath = SingEngineManager.get().getWavePath()
  162 +//// if (mIsOverTime) {
  163 +//// if (mLessonPagePacket.type != LessonType.VOICE_JSBY) {
  164 +//// nextPage()
  165 +//// }
  166 +//// } else {
  167 +//// // 如果没有超时,手动掐断计时器
  168 +//// CountDownHelper.get().stop()
  169 +//// this@BaseCloudFragment.onRecordStop()
  170 +//// }
  171 +// if (mStrategy.needCountDown) {
  172 +// CountDownHelper.get().stop()
  173 +// }
  174 +// this@BaseCloudFragment.onRecordStop()
  175 +// }
  176 +// }
  177 +//
  178 +// override fun onRecordPlayOver() {
  179 +// Log.w(Constant.TAG_THREADID, "onRecordPlayOver = ${android.os.Process.myTid()}")
  180 +// activity?.runOnUiThread {
  181 +// changeBottomSheetLayoutState(false)
  182 +// this@BaseCloudFragment.onRecordPlayOver()
  183 +// }
  184 +// }
  185 +// }
  186 +// }
  187 +//
  188 +// override fun onActivityCreated(savedInstanceState: Bundle?) {
  189 +// super.onActivityCreated(savedInstanceState)
  190 +// SingEngineManager.get().addOnResultListener(mSingEngineLifecycles)
  191 +// }
  192 +//
  193 +// override fun initConfig(): BaseLessonStrategy {
  194 +// if (mLessonExtroBundle.lessonMode == LessonMode.GAMING) {
  195 +// return BaseLessonStrategy.GameingLessonStrategy(mLessonPagePacket)
  196 +// } else if (mLessonExtroBundle.lessonMode == LessonMode.EXAM) {
  197 +// return BaseLessonStrategy.ExamLessonStrategy(mLessonPagePacket)
  198 +// } else {
  199 +// return BaseLessonStrategy.VoiceLessonStrategy()
  200 +// }
  201 +// }
  202 +//
  203 +// /**
  204 +// * 流程开始
  205 +// */
  206 +// open fun action() {
  207 +//
  208 +// }
  209 +//
  210 +// /**
  211 +// * 录音开始
  212 +// */
  213 +// open fun onRecordBegin() {
  214 +//
  215 +// }
  216 +//
  217 +// /**
  218 +// * 录音结束(经测试该方法在子线程)
  219 +// */
  220 +// open fun onRecordStop() {
  221 +//
  222 +// }
  223 +//
  224 +// /**
  225 +// * 开始播放录音
  226 +// */
  227 +// open fun onPlayBack() {
  228 +//
  229 +// }
  230 +//
  231 +// /**
  232 +// * 结束播放录音
  233 +// */
  234 +// open fun onRecordPlayOver() {
  235 +//
  236 +// }
  237 +//
  238 +// /**
  239 +// * coin动画
  240 +// */
  241 +// open fun onStatementNice() {
  242 +//
  243 +// }
  244 +//
  245 +// /**
  246 +// * @param spannableString 根据评测结果返回的带颜色的string
  247 +// * @param resultModel 评测结果解析后的数据
  248 +// * 评测结束并解析完成
  249 +// */
  250 +// open fun onSoundResult(spannableString: SpannableString, resultModel: BaseResultModel) {
  251 +//
  252 +// }
  253 +//
  254 +//
  255 +// override fun onDestroy() {
  256 +// super.onDestroy()
  257 +// SingEngineManager.get().removeOnResultListener(mSingEngineLifecycles)
  258 +// mHandler.removeCallbacksAndMessages(null)
  259 +// }
  260 +//}
  261 +//
  262 +///**
  263 +// * 根据分数给不同文字上色
  264 +// */
  265 +//fun parseScoreToColor(score: Double): Int {
  266 +// if (score < 60) {
  267 +// return Color.parseColor("#FF3B30")
  268 +// } else if (score in 60f..80f) {
  269 +// return Color.parseColor("#33373F")
  270 +// } else {
  271 +// return Color.parseColor("#0ABB08")
  272 +// }
  273 +//}
0 274 \ No newline at end of file
... ...
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/OnSingEngineLifecycles.kt 0 → 100644
  1 +package com.kouyuxingqiu.wow_english.singsound
  2 +
  3 +import com.kouyuxingqiu.wow_english.singsound.config.EvalTargetType
  4 +import org.json.JSONObject
  5 +
  6 +interface SingEngineLifecycles {
  7 + // 录音开始
  8 + fun onRecordBegin()
  9 +
  10 + // 录音结束
  11 + fun onRecordStop()
  12 +
  13 + // 播放录音结束
  14 + fun onRecordPlayOver()
  15 +
  16 + // 评测完成
  17 + fun onResult(jsonObject: JSONObject, @EvalTargetType evalType: Int? = EvalTargetType.SENTENCE)
  18 +
  19 + // 取消评测
  20 + fun onCancel()
  21 +
  22 +
  23 + abstract class OnSingEngineAdapter : SingEngineLifecycles {
  24 + override fun onRecordBegin() {
  25 +
  26 + }
  27 +
  28 + override fun onRecordStop() {
  29 +
  30 + }
  31 +
  32 + override fun onRecordPlayOver() {
  33 +
  34 + }
  35 +
  36 + override fun onResult(jsonObject: JSONObject, @EvalTargetType evalType: Int?) {
  37 +
  38 + }
  39 +
  40 + override fun onCancel() {
  41 +
  42 + }
  43 + }
  44 +}
0 45 \ No newline at end of file
... ...
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/ParseDataHelper.kt 0 → 100644
  1 +package com.kouyuxingqiu.wow_english.singsound
  2 +
  3 +import android.util.Log
  4 +import com.google.gson.Gson
  5 +import com.kouyuxingqiu.wow_english.singsound.bean.BaseResultModel
  6 +import com.kouyuxingqiu.wow_english.singsound.bean.RealtimeResultEntity
  7 +import com.kouyuxingqiu.wow_english.singsound.config.EvalTargetType
  8 +import com.kouyuxingqiu.wow_english.singsound.util.JsonUtils
  9 +import org.json.JSONObject
  10 +
  11 +/**
  12 + * @author: stay
  13 + * @date: 2020/9/1 11:49
  14 + * @description:
  15 + */
  16 +
  17 +/**
  18 + * 解析评测结果
  19 + * @param result 评测结果
  20 + * @param evalType 评测类型
  21 + */
  22 +fun parseResult(result: JSONObject, evalType: Int? = EvalTargetType.SENTENCE): BaseResultModel {
  23 + val resultModel = BaseResultModel()
  24 + try {
  25 + if (result.has("result")) {
  26 + resultModel.originText = JsonUtils.getString(result, "refText")
  27 + when (evalType) {
  28 + EvalTargetType.SENTENCE -> {
  29 + resultModel.originText = resultModel.originText?.replace("’", "'") // 统一英文符号
  30 + val resultJ = JsonUtils.getJsonObject(result, "result")
  31 +
  32 + if (resultJ != null) {
  33 + resultModel.score = JsonUtils.getDouble(resultJ, "overall")
  34 + resultModel.pronounce = JsonUtils.getDouble(resultJ, "pron")
  35 + resultModel.fluency =
  36 + JsonUtils.getJsonObject(resultJ, "fluency").getDouble("overall")
  37 + // 遍历所有词汇
  38 + val detailsA = JsonUtils.getJsonArray(resultJ, "details")
  39 + for (i in 0 until detailsA.length()) {
  40 + val detailsWords = detailsA.getJSONObject(i)
  41 + var charStr = JsonUtils.getString(detailsWords, "char")
  42 +
  43 + // 过滤掉多余符号
  44 + charStr = charStr.replace(".", "")
  45 + charStr = charStr.replace(",", "")
  46 + charStr = charStr.replace("’", "'") // 统一英文符号
  47 +
  48 + resultModel.scores.add(
  49 + BaseResultModel.SingleResultModel(
  50 + charStr,
  51 + JsonUtils.getDouble(detailsWords, "score")
  52 + )
  53 + )
  54 + }
  55 + }
  56 + }
  57 + EvalTargetType.ALPHA -> {
  58 + val result_JsonObject = result.optJSONObject("result")
  59 + if (result_JsonObject != null) {
  60 + resultModel.score = result_JsonObject.getDouble("overall")
  61 + }
  62 + }
  63 + EvalTargetType.WORD -> {
  64 + val resultW = JsonUtils.getJsonObject(result, "result")
  65 + if (resultW != null) {
  66 + resultModel.score = JsonUtils.getDouble(resultW, "overall")
  67 + }
  68 + }
  69 + }
  70 + }
  71 + } catch (e: Exception) {
  72 + e.printStackTrace()
  73 + Log.e("parseResult", e.message.toString())
  74 + }
  75 + return resultModel
  76 +}
  77 +
  78 +/**
  79 + * 解析Realtime结果
  80 + */
  81 +fun parseResult4Real(result: JSONObject): RealtimeResultEntity? {
  82 + var resultModel: RealtimeResultEntity? = null
  83 + try {
  84 + if (result.has("result")) {
  85 + val resultJson = JsonUtils.getString(result, "result")
  86 + resultModel = Gson().fromJson(resultJson, RealtimeResultEntity::class.java)
  87 + }
  88 + } catch (e: Exception) {
  89 + e.printStackTrace()
  90 + Log.e("parseResult4Real", e.message.toString())
  91 + }
  92 + return resultModel
  93 +}
  94 +
  95 +
  96 +fun filterAllPunctuation(s: String): String? {
  97 + return s.replace(
  98 + "[`qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%……& amp;*()——+|{}【】‘;:”“’。,、?|-]".toRegex(),
  99 + ""
  100 + )
  101 +
  102 + var str =
  103 + ",.!,,D_NAME。!;‘’”“**dfs #$%^&()-+1431221\"\"中 国123漢字かどうかのjavaを決定"
  104 + str = str.replace("[\\pP\\pS]".toRegex(), "")
  105 + println(str)
  106 +}
0 107 \ No newline at end of file
... ...
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/SingEngineHelper.kt 0 → 100644
  1 +package com.kouyuxingqiu.wow_english.singsound
  2 +
  3 +import android.content.Context
  4 +import android.util.Log
  5 +import com.constraint.CoreProvideTypeEnum
  6 +import com.constraint.ResultBody
  7 +import com.google.gson.Gson
  8 +import com.kouyuxingqiu.wow_english.singsound.config.EvalTargetType
  9 +import com.kouyuxingqiu.wow_english.singsound.config.SingSoundConfig
  10 +import com.kouyuxingqiu.wow_english.singsound.config.VoiceConfig
  11 +import com.kouyuxingqiu.wow_english.singsound.config.WordConfig
  12 +import com.xs.SingEngine
  13 +import com.xs.impl.AudioErrorCallback
  14 +import com.xs.impl.EvalReturnRequestIdCallback
  15 +import com.xs.impl.OnRealTimeResultListener
  16 +import com.xs.utils.AiUtil
  17 +import org.json.JSONObject
  18 +import java.util.*
  19 +
  20 +
  21 +class SingEngineHelper private constructor() :
  22 + AudioErrorCallback, EvalReturnRequestIdCallback, OnRealTimeResultListener {
  23 +
  24 + private val TAG = "SingEngineManager"
  25 + private var mSingEngine: SingEngine? = null
  26 +
  27 + /**
  28 + * 所有逻辑回调集合
  29 + */
  30 + private var mListeners: MutableList<SingEngineLifecycles>? = null
  31 +
  32 + /**
  33 + * 是否初始化完成
  34 + */
  35 + private var mIsReady = false
  36 +
  37 + /**
  38 + * 是否取消测评
  39 + */
  40 + private var mCanceled = false
  41 +
  42 + /**
  43 + * 音标转换表
  44 + */
  45 + private val mAlphaMap = LinkedHashMap<String, String>()
  46 +
  47 +
  48 + private var mCurEvalType: Int? = EvalTargetType.SENTENCE
  49 +
  50 + /**
  51 + * 目前建议在应用入口初始化
  52 + */
  53 + fun init(context: Context) {
  54 + mListeners = mutableListOf()
  55 + if (mSingEngine == null) {
  56 + mSingEngine = SingEngine.newInstance(context)
  57 + }
  58 + Thread {
  59 + try {
  60 + mSingEngine?.run {
  61 + // 设置测评结果监听器
  62 + setListener(this@SingEngineHelper)
  63 + // 设置录音器初始化错误的回调
  64 + setAudioErrorCallback(this@SingEngineHelper)
  65 + setEvalReturnRequestIdCallback(this@SingEngineHelper)
  66 +// // 设置音频格式
  67 +// setAudioType(AudioTypeEnum.WAV)
  68 + // 设置引擎类型。引擎类型(在线CLOUD、 离线NATIVE、混合AUTO),默认使用在线引擎。
  69 + setServerType(CoreProvideTypeEnum.CLOUD)
  70 + // 设置log日志级别
  71 + setLogLevel(4)
  72 + // 禁用实时音量返回
  73 + disableVolume()
  74 + // 设置录音音频路径
  75 + wavPath = AiUtil.getFilesDir(context).path + "/userdata/sound_record/"
  76 + // 设置是否开启 VAD 功能
  77 + setOpenVad(true, "vad.0.1.bin")
  78 + //setOpenVad(false, null);
  79 + // 设置 VAD 前置超时时间
  80 + setFrontVadTime(3000)
  81 +// setServerTimeout(10000)
  82 + // 开启错误日志保存到本地,发生错误时文件中会保存到android/data/包名/files/SSError.txt中
  83 +// setOpenWriteLog(true)
  84 + // 设置在线服务器地址和账号
  85 + setServerAPI("wss://api.cloud.ssapi.cn")
  86 +// // 设置评测语言(针对离线评测)
  87 +// setOffLineSource(OffLineSourceEnum.SOURCE_EN)
  88 + // 设置引擎初始化参数
  89 + setNewCfg(
  90 + buildInitJson(
  91 + SingSoundConfig.APPKEY,
  92 + SingSoundConfig.SECERTKEY
  93 + )
  94 + )
  95 + // 引擎初始化
  96 + createEngine()
  97 + }
  98 +
  99 + getSymbolsMap()
  100 + } catch (e: Exception) {
  101 + e.printStackTrace()
  102 + }
  103 + }.start()
  104 + }
  105 +
  106 + // 开始语音评测
  107 + fun startRecord(originText: String, @EvalTargetType evalTargetType: Int?) {
  108 + try {
  109 + val request = JSONObject()
  110 + when (evalTargetType) {
  111 + EvalTargetType.SENTENCE -> {
  112 + request.put("coreType", VoiceConfig.TYPE_SENT_KID)
  113 + .put("refText", originText.trim())
  114 + .put("rank", 100) // 评分分制,这个值可以任意设置,最终会根据与 100 的比例重新计算
  115 + .put("symbol", 1) // 用后标点符号
  116 + .put("typeThres", SingSoundConfig.BASE_TYPETHRES)
  117 + .put("feedback", 1) // 是否开启实时评测
  118 + }
  119 + EvalTargetType.ALPHA -> {
  120 + if (originText.trim() == "/ɔr/") { // ishow_unexpected1 = ɔr
  121 + val jsonObj = JSONObject()
  122 + jsonObj.put("abc", "ao r")
  123 + request.put("coreType", VoiceConfig.TYPE_WORD)
  124 + .put("refText", "abc")
  125 + // rateScale: 打分宽松度,0.8~1.5,默认 1.0。这个参数可以看作是个乘数,值越高打分越高。未
  126 + //来版本可能会放弃支持此参数,不建议使用,可用 typeThres 参数代替。
  127 + .put("typeThres", SingSoundConfig.BASE_TYPETHRES)
  128 + .put("precision", 1) // 评分精度,默认1
  129 + .put("attachAudioUrl", 1) // 评分结果中是否包含音频 url
  130 + .put("userId", "test")
  131 + .put("phones", jsonObj) // 指定单词的发音。
  132 + .put("rank", 100)
  133 + } else {
  134 + request.put("coreType", VoiceConfig.TYPE_ALPHA)
  135 + .put("typeThres", SingSoundConfig.BASE_TYPETHRES)
  136 + .put("refText", getSymbolText(originText.trim()))
  137 + .put("rank", 100)
  138 + }
  139 + }
  140 + EvalTargetType.WORD -> {
  141 + request.put("coreType", VoiceConfig.TYPE_WORD)
  142 + .put("refText", originText.trim())
  143 + .put("typeThres", SingSoundConfig.BASE_TYPETHRES)
  144 + .put("typeThres", 0)
  145 + .put("phdet", 1) // 音素检错,1 表示使用此功能,默认为 0,不启动; 只能设置 0 和 1
  146 + .put("syldet", 1) // 音节检错,1 表示使用此功能,默认为 0,不启动 只能设置 0 和 1
  147 + // .put("syllable", 1) // (单词题型支持评测音节;可以设置 syllable 字段)评测音节信息,1 表示使用此功能,默认为 0,不启动;只能设置 0 和 1
  148 + .put("rank", 100)
  149 + }
  150 + else -> {
  151 + request.put("coreType", VoiceConfig.TYPE_SENT_KID)
  152 + .put("refText", originText.trim())
  153 + .put("typeThres", SingSoundConfig.BASE_TYPETHRES)
  154 + .put("rank", 100) // 评分分制,这个值可以任意设置,最终会根据与 100 的比例重新计算
  155 + .put("symbol", 1) // 用后标点符号
  156 + .put("feedback", false) // 是否开启实时评测
  157 + }
  158 + }
  159 +
  160 + //构建评测请求参数
  161 + val startCfg = mSingEngine?.buildStartJson(VoiceConfig.UserID, request)
  162 + //设置评测请求参数
  163 + mSingEngine?.setStartCfg(startCfg)
  164 + //开始测评
  165 + mSingEngine?.start()
  166 + mCurEvalType = evalTargetType
  167 + } catch (e: Exception) {
  168 + e.printStackTrace()
  169 + }
  170 + Log.w(TAG, "startRecord originText=$originText evalTargetType =$evalTargetType")
  171 + }
  172 +
  173 + fun stopRecord() { // 停止录音(有回调)
  174 + if (mIsReady) {
  175 + mSingEngine?.stop()
  176 + Log.w(TAG, "stopRecord")
  177 + }
  178 + }
  179 +
  180 + fun cancel() { // 取消录音(无回调onResult)
  181 + if (mIsReady) {
  182 + mCanceled = true
  183 + mSingEngine?.cancel()
  184 + mListeners?.let {
  185 + for (callback in it) {
  186 + callback.onCancel()
  187 + }
  188 + }
  189 + Log.w(TAG, "cancel")
  190 + }
  191 + }
  192 +
  193 + // 播放录音
  194 + fun playBack() {
  195 +// if (mSingEngine != null) {
  196 +// val tokenid = SPUtils.getInstance().getString(VoiceConfig.cloud_sentece + 1)
  197 +// if (tokenid != null) {
  198 +// mSingEngine!!.playback()
  199 +// }
  200 +// }
  201 + if (mIsReady) {
  202 + mSingEngine?.playback()
  203 + Log.w(TAG, "playBack")
  204 + mCanceled = false
  205 + }
  206 + }
  207 +
  208 + /**
  209 + * 获取录音文件
  210 + */
  211 + fun getRecordFilePath(): String? {
  212 + return mSingEngine?.wavPath
  213 + }
  214 +
  215 + /**
  216 + * 停止播放录音
  217 + */
  218 + fun stopPlayBack() {
  219 + if (mIsReady) {
  220 + mSingEngine?.stopPlayBack()
  221 + Log.w(TAG, "stopPlayBack")
  222 + }
  223 + }
  224 +
  225 + // (录音播放无法暂停)中断并重新播放
  226 + fun playWithInterrupt() {
  227 + if (mIsReady) {
  228 + mSingEngine?.playWithInterrupt()
  229 + Log.w(TAG, "playWithInterrupt")
  230 + }
  231 + }
  232 +
  233 + /**
  234 + * 停止录音、停止播放录音
  235 + */
  236 + fun release() {
  237 + if (mIsReady) {
  238 +// mSingEngine?.stopPlayBack()
  239 + mSingEngine?.deleteSafe()
  240 + }
  241 + mListeners?.clear()
  242 + mListeners = null
  243 + mCanceled = false
  244 + }
  245 +
  246 + override fun onAudioError(i: Int) {
  247 + Log.e(TAG, "onAudioError $i")
  248 + }
  249 +
  250 + /**
  251 + * 实时反馈回调
  252 + * @param jsonObject
  253 + */
  254 + override fun onRealTimeEval(jsonObject: JSONObject) {
  255 + Log.d(TAG, "onRealTimeEval = $jsonObject")
  256 + val realTimeResult = parseResult4Real(jsonObject)
  257 + if (realTimeResult?.realtime_details?.all { it.dp_type == 0 } == true) {
  258 + stopRecord()
  259 + }
  260 + }
  261 +
  262 + /**
  263 + * 录音开始回调(可以提示用户录音开始或者开始动画等逻辑)
  264 + */
  265 + override fun onBegin() {
  266 + Log.i(TAG, "onBegin")
  267 + mListeners?.let {
  268 + for (callback in it) {
  269 + callback.onRecordBegin()
  270 + }
  271 + }
  272 + mCanceled = false
  273 + }
  274 +
  275 + /**
  276 + * 返回评测结果,评测结果为JSON格式
  277 + */
  278 + override fun onResult(jsonObject: JSONObject) {
  279 + Log.i(TAG, "onResult = $jsonObject")
  280 + parseResult(jsonObject)
  281 + setTokenToCache(jsonObject)
  282 + mListeners?.let {
  283 + for (callback in it) {
  284 + callback.onResult(jsonObject, mCurEvalType)
  285 + }
  286 + }
  287 + }
  288 +
  289 + private fun setTokenToCache(result: JSONObject) {
  290 + try {
  291 + if (result.has("tokenId")) {
  292 + val tokenID = result.getString("tokenId")
  293 + Log.e("tokenid", tokenID)
  294 + }
  295 + } catch (e: Exception) {
  296 + e.printStackTrace()
  297 + }
  298 + }
  299 +
  300 + /**
  301 + * 实时返回用户录音的音量大小 录音过程中会不断的回调此方法,
  302 + * 实时返回音量大小,volume取值范围为0\~100。
  303 + * 用户可以根据volume的大小来实现用户录音音量大小的动画效果。
  304 + */
  305 + override fun onUpdateVolume(volume: Int) {}
  306 +
  307 + /**
  308 + * 开启录音后,一直没有声音输入,前置超时(检测到没有录音)会调用此方法,
  309 + * 用户可自己决定操作stop()或者cancel()。
  310 + */
  311 + override fun onFrontVadTimeOut() {
  312 + Log.i(TAG, "onFrontVadTimeOut")
  313 + stopRecord()
  314 + }
  315 +
  316 + /**
  317 + * 录音一段时间后不说话,后置超时,引擎自动调用stop(),结束录音并返回结果。
  318 + * 用户可监听此方法用于更新录音的UI界面
  319 + */
  320 + override fun onBackVadTimeOut() {
  321 + Log.i(TAG, "onBackVadTimeOut")
  322 + }
  323 +
  324 + override fun onRecordingBuffer(bytes: ByteArray, i: Int) {
  325 + }
  326 +
  327 + /**
  328 + * 评测录音长度超时回调
  329 + * 开发者可在该回调里调用stop()方法等待返回测评结果,或不做任何处理等待录音超时的错误码。
  330 + * 通过超时错误码提示产品的用户。
  331 + */
  332 + override fun onRecordLengthOut() {
  333 + Log.i(TAG, "onRecordLengthOut")
  334 + stopRecord()
  335 + }
  336 +
  337 + override fun onReady() {
  338 + Log.i(TAG, "onReady")
  339 + mIsReady = true
  340 + mCanceled = false
  341 + }
  342 +
  343 + /**
  344 + * 播放录音完成回调
  345 + */
  346 + override fun onPlayCompeleted() {
  347 + Log.i(TAG, "onPlayCompeleted")
  348 + mListeners?.let {
  349 + for (callback in it) {
  350 + callback.onRecordPlayOver()
  351 + }
  352 + }
  353 + }
  354 +
  355 + /**
  356 + * 当录音停止并写入成功后回调
  357 + */
  358 + override fun onRecordStop() {
  359 + Log.i(TAG, "onRecordStop mCanceled = $mCanceled")
  360 + if (!mCanceled) {
  361 + mListeners?.let {
  362 + for (callback in it) {
  363 + callback.onRecordStop()
  364 + }
  365 + }
  366 + } else {
  367 + mCanceled = false
  368 + }
  369 + }
  370 +
  371 + /**
  372 + * 返回评测或初始化引擎失败原因,resultBody.getCode() 等于0为正确返回,
  373 + * 其他错误码见下错误码说明
  374 + */
  375 + override fun onEnd(resultBody: ResultBody) {
  376 + Log.i(TAG, "onEnd resultBody=$resultBody")
  377 + }
  378 +
  379 + override fun onGetEvalRequestId(p0: String?) {
  380 +
  381 + }
  382 +
  383 + fun addOnResultListener(listener: SingEngineLifecycles) {
  384 + if (mListeners?.contains(listener) == true) {
  385 + return
  386 + }
  387 + mListeners?.add(listener)
  388 + }
  389 +
  390 + fun removeOnResultListener(listener: SingEngineLifecycles) {
  391 + if (mListeners?.contains(listener) == true) {
  392 + mListeners?.remove(listener)
  393 + }
  394 + }
  395 +
  396 + /**
  397 + * 音标转化表
  398 + */
  399 + private fun getSymbolsMap() {
  400 + val linkedHashMap =
  401 + Gson().fromJson(WordConfig.getMapJSONObject().toString(), LinkedHashMap::class.java)
  402 + for (key in linkedHashMap.keys) {
  403 +// mAlphaMap[linkedHashMap[key].toString()] = key.toString()
  404 + mAlphaMap[key.toString()] = linkedHashMap[key].toString()
  405 + }
  406 + }
  407 +
  408 + /**
  409 + * 音标文本前后有"/",去掉
  410 + */
  411 + private fun getSymbolText(s: String): String? {
  412 + val substring = s.substring(1, s.length - 1)
  413 + return if (mAlphaMap.containsKey(substring)) {
  414 + mAlphaMap[substring]
  415 + } else ""
  416 + }
  417 +}
  418 +
... ...
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/bean/BaseResultModel.kt 0 → 100644
  1 +package com.kouyuxingqiu.wow_english.singsound.bean
  2 +
  3 +import android.os.Parcel
  4 +import android.os.Parcelable
  5 +
  6 +/**
  7 + * @author stay
  8 + * @date 2019-1-7
  9 + * @describe 语音测评结果model
  10 + */
  11 +class BaseResultModel(
  12 + var originText: String? = "", // 原始句子
  13 + var score: Double = 0.0, // 总评分
  14 + var scores: MutableList<SingleResultModel> = mutableListOf(),
  15 + var pronounce: Double = 0.0, // 发音得分
  16 + var fluency: Double = 0.0 // 流利度得分
  17 +) : Parcelable {
  18 +
  19 + constructor(parcel: Parcel) : this(
  20 + parcel.readString(),
  21 + parcel.readDouble(),
  22 + TODO("scores"),
  23 + parcel.readDouble(),
  24 + parcel.readDouble()
  25 + ) {
  26 + }
  27 +
  28 + // 每个单词测评结果model
  29 + class SingleResultModel(
  30 + val char: String?,
  31 + val score: Double
  32 + ) : Parcelable {
  33 + constructor(parcel: Parcel) : this(
  34 + parcel.readString(),
  35 + parcel.readDouble()
  36 + ) {
  37 + }
  38 +
  39 + override fun writeToParcel(parcel: Parcel, flags: Int) {
  40 + parcel.writeString(char)
  41 + parcel.writeDouble(score)
  42 + }
  43 +
  44 + override fun describeContents(): Int {
  45 + return 0
  46 + }
  47 +
  48 + companion object CREATOR : Parcelable.Creator<SingleResultModel> {
  49 + override fun createFromParcel(parcel: Parcel): SingleResultModel {
  50 + return SingleResultModel(parcel)
  51 + }
  52 +
  53 + override fun newArray(size: Int): Array<SingleResultModel?> {
  54 + return arrayOfNulls(size)
  55 + }
  56 + }
  57 + }
  58 +
  59 + override fun writeToParcel(parcel: Parcel, flags: Int) {
  60 + parcel.writeString(originText)
  61 + parcel.writeDouble(score)
  62 + parcel.writeDouble(pronounce)
  63 + parcel.writeDouble(fluency)
  64 + }
  65 +
  66 + override fun describeContents(): Int {
  67 + return 0
  68 + }
  69 +
  70 + companion object CREATOR : Parcelable.Creator<BaseResultModel> {
  71 + override fun createFromParcel(parcel: Parcel): BaseResultModel {
  72 + return BaseResultModel(parcel)
  73 + }
  74 +
  75 + override fun newArray(size: Int): Array<BaseResultModel?> {
  76 + return arrayOfNulls(size)
  77 + }
  78 + }
  79 +}
  80 +
... ...
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/bean/FullEvaluationResult.txt 0 → 100644
  1 +{
  2 + "tokenId":"5fdcaf04332793000004f230",
  3 + "applicationId":"t418",
  4 + "audioUrl":"http://trial-files.api.cloud.ssapi.cn:8080/t418/11eb41353b71e226b8b2t418n29112b3",
  5 + "connect":{
  6 + "param":{
  7 + "app":{
  8 + "timestamp":"1608298240",
  9 + "applicationId":"t418",
  10 + "sig":"684607db2bcbb3ee675681e0a43cb461a6540649"
  11 + },
  12 + "sdk":{
  13 + "os":"android",
  14 + "product":"fake",
  15 + "os_version":"0.0",
  16 + "source":1,
  17 + "protocol":1,
  18 + "type":1,
  19 + "arch":"armv8l",
  20 + "version":16779008
  21 + }
  22 + },
  23 + "cmd":"connect"
  24 + },
  25 + "params":{
  26 + "app":{
  27 + "timestamp":"1608298244",
  28 + "userId":"guest",
  29 + "sig":"23591de4a3fbc5a568acf221cdd63769693f7053",
  30 + "connect_id":"5fdcaf003327930000038230",
  31 + "clientId":"",
  32 + "applicationId":"t418"
  33 + },
  34 + "audio":{
  35 + "saveAudio":0,
  36 + "sampleBytes":2,
  37 + "audioType":"ogg",
  38 + "sampleRate":16000,
  39 + "channel":1
  40 + },
  41 + "request":{
  42 + "request_id":"5fdcaf04332793000005f230",
  43 + "tokenId":"5fdcaf04332793000004f230",
  44 + "coreType":"en.sent_kid.score",
  45 + "attachAudioUrl":1,
  46 + "typeThres":2,
  47 + "feedback":1,
  48 + "refText":"hello",
  49 + "symbol":1,
  50 + "rank":100
  51 + }
  52 + },
  53 + "recordId":"11eb41353b71e226b8b2t418n29112b3",
  54 + "refText":"hello",
  55 + "dtLastResponse":"2020-12-18 21:30:48:128",
  56 + "cloud_platform":{
  57 + "origin_audio_length":10143
  58 + },
  59 + "result":{
  60 + "overall":0,
  61 + "forceout":0,
  62 + "precision":1,
  63 + "systime":2758,
  64 + "res":"eng.snt_kid.online.1.0",
  65 + "rank":100,
  66 + "rhythm":{
  67 + "stress":0,
  68 + "overall":50,
  69 + "tone":0,
  70 + "sense":100
  71 + },
  72 + "fluency":{
  73 + "pause":0,
  74 + "overall":0,
  75 + "speed":0
  76 + },
  77 + "pron":0,
  78 + "wavetime":2610,
  79 + "accuracy":0,
  80 + "details":[
  81 + {
  82 + "dp_type":1,
  83 + "tonescore":0,
  84 + "dur":0,
  85 + "liaisonref":0,
  86 + "stressref":0,
  87 + "senseref":1,
  88 + "start":0,
  89 + "liaisonscore":0,
  90 + "fluency":0,
  91 + "char":"hello",
  92 + "toneref":0,
  93 + "stressscore":0,
  94 + "score":0,
  95 + "end":0,
  96 + "sensescore":0
  97 + }
  98 + ],
  99 + "info":{
  100 + "tipId":10004,
  101 + "clip":0,
  102 + "snr":0,
  103 + "volume":51
  104 + },
  105 + "statics":[
  106 + {
  107 + "score":0,
  108 + "char":"hh",
  109 + "count":1
  110 + },
  111 + {
  112 + "score":0,
  113 + "char":"eh",
  114 + "count":1
  115 + },
  116 + {
  117 + "score":0,
  118 + "char":"l",
  119 + "count":1
  120 + },
  121 + {
  122 + "score":0,
  123 + "char":"ow",
  124 + "count":1
  125 + }
  126 + ],
  127 + "delaytime":107,
  128 + "integrity":0,
  129 + "pretime":1,
  130 + "version":"0.0.80.2020.11.18.19:18:30"
  131 + },
  132 + "eof":1
  133 +}
0 134 \ No newline at end of file
... ...
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/bean/RealtimeResultEntity.kt 0 → 100644
  1 +package com.kouyuxingqiu.wow_english.singsound.bean
  2 +
  3 +/**
  4 + * @author: stay
  5 + * @date: 2020/9/1 11:40
  6 + * @description:
  7 + */
  8 +data class RealtimeResultEntity(
  9 + val result_desc: String = "",
  10 + val eof: Int = -1, // 0 表示返回未结束,后续还有其它的返回结果 1:表示本次评测所有的返回结束
  11 + val realtime_details: List<RealtimeDetailEntity>? = mutableListOf(),
  12 + val result_type: Int = 0
  13 +)
  14 +
  15 +
  16 +data class RealtimeDetailEntity(
  17 + val dp_type: Int = -1, // 0:表示正常读 1:表示漏读或者未读 2:表示重读
  18 + val char: String = "", // 单词发音得分
  19 + val start: Int = 0, // 单词在音频中的起始时间,单位为毫秒 (ms)
  20 + val end: Int = 0, // 单词在音频中的结束时间,单位为毫秒 (ms)
  21 + val dur: Int = 0, // 单词发音时间,单位为毫秒(ms)
  22 + val score: Int = 0
  23 +)
0 24 \ No newline at end of file
... ...
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/bean/SentenceCode.java 0 → 100644
  1 +package com.kouyuxingqiu.wow_english.singsound.bean;
  2 +
  3 +import android.os.Parcel;
  4 +import android.os.Parcelable;
  5 +
  6 +/**
  7 + * 句子的详细信息
  8 + * Created by wangz on 2017/8/30.
  9 + */
  10 +public class SentenceCode implements Parcelable {
  11 + public String charStr; // 单词
  12 + public int score; // 单词发音得分
  13 + public int fakePron; // 词典中未找到单词对应的发音
  14 + public int start; // 单词在音频中的起始时间,单位为毫秒(ms)
  15 + public int end; // 单词在音频中的结束时间,单位为毫秒(ms)
  16 + public int dur; // 单词发音时间
  17 + public double fluency; // 流利度评分(0-100)
  18 + public int stressref; // 重读标识
  19 + public int stressscore; // 重读得分(0、1)
  20 + public int toneref; // 升调标识
  21 + public int tonescore; // 升降调得分(0、1)
  22 + public int senseref; // 意群停顿标识
  23 + public int sensescore; // 意群停顿得分(0、1)
  24 + public int liaisonref; // 连读标识
  25 + public int liaisonscore; // 连读得分(0、1)
  26 + public int dpType; // 单词正常朗读(不输出dp_type字段)、漏读(1)、重复读(2)
  27 + public int isPause; // 停顿标记
  28 +
  29 + @Override
  30 + public int describeContents() {
  31 + return 0;
  32 + }
  33 +
  34 + @Override
  35 + public void writeToParcel(Parcel dest, int flags) {
  36 + dest.writeString(this.charStr);
  37 + dest.writeInt(this.score);
  38 + dest.writeInt(this.fakePron);
  39 + dest.writeInt(this.start);
  40 + dest.writeInt(this.end);
  41 + dest.writeInt(this.dur);
  42 + dest.writeDouble(this.fluency);
  43 + dest.writeInt(this.stressref);
  44 + dest.writeInt(this.stressscore);
  45 + dest.writeInt(this.toneref);
  46 + dest.writeInt(this.tonescore);
  47 + dest.writeInt(this.senseref);
  48 + dest.writeInt(this.sensescore);
  49 + dest.writeInt(this.liaisonref);
  50 + dest.writeInt(this.liaisonscore);
  51 + dest.writeInt(this.dpType);
  52 + dest.writeInt(this.isPause);
  53 + }
  54 +
  55 + public SentenceCode() {
  56 + }
  57 +
  58 + protected SentenceCode(Parcel in) {
  59 + this.charStr = in.readString();
  60 + this.score = in.readInt();
  61 + this.fakePron = in.readInt();
  62 + this.start = in.readInt();
  63 + this.end = in.readInt();
  64 + this.dur = in.readInt();
  65 + this.fluency = in.readDouble();
  66 + this.stressref = in.readInt();
  67 + this.stressscore = in.readInt();
  68 + this.toneref = in.readInt();
  69 + this.tonescore = in.readInt();
  70 + this.senseref = in.readInt();
  71 + this.sensescore = in.readInt();
  72 + this.liaisonref = in.readInt();
  73 + this.liaisonscore = in.readInt();
  74 + this.dpType = in.readInt();
  75 + this.isPause = in.readInt();
  76 + }
  77 +
  78 + public static final Creator<SentenceCode> CREATOR = new Creator<SentenceCode>() {
  79 + @Override
  80 + public SentenceCode createFromParcel(Parcel source) {
  81 + return new SentenceCode(source);
  82 + }
  83 +
  84 + @Override
  85 + public SentenceCode[] newArray(int size) {
  86 + return new SentenceCode[size];
  87 + }
  88 + };
  89 +
  90 + @Override
  91 + public String toString() {
  92 + return "SentenceCode{" +
  93 + "charStr='" + charStr + '\'' +
  94 + ", score=" + score +
  95 + ", fakePron=" + fakePron +
  96 + ", start=" + start +
  97 + ", end=" + end +
  98 + ", dur=" + dur +
  99 + ", fluency=" + fluency +
  100 + ", stressref=" + stressref +
  101 + ", stressscore=" + stressscore +
  102 + ", toneref=" + toneref +
  103 + ", tonescore=" + tonescore +
  104 + ", senseref=" + senseref +
  105 + ", sensescore=" + sensescore +
  106 + ", liaisonref=" + liaisonref +
  107 + ", liaisonscore=" + liaisonscore +
  108 + ", dpType=" + dpType +
  109 + ", isPause=" + isPause +
  110 + '}';
  111 + }
  112 +}
... ...
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/bean/SentenceRealTimeEntity.java 0 → 100644
  1 +package com.kouyuxingqiu.wow_english.singsound.bean;
  2 +
  3 +import android.os.Parcel;
  4 +import android.os.Parcelable;
  5 +
  6 +/**
  7 + * 实时评测句子的实体
  8 + * Created by yxw on 2018/9/13
  9 + */
  10 +public class SentenceRealTimeEntity implements Parcelable {
  11 + public String charStr;
  12 + public int dp_type;
  13 +
  14 + public SentenceRealTimeEntity() {
  15 +
  16 + }
  17 +
  18 +
  19 + protected SentenceRealTimeEntity(Parcel in) {
  20 + charStr = in.readString();
  21 + dp_type = in.readInt();
  22 + }
  23 +
  24 + @Override
  25 + public void writeToParcel(Parcel dest, int flags) {
  26 + dest.writeString(charStr);
  27 + dest.writeInt(dp_type);
  28 + }
  29 +
  30 + @Override
  31 + public int describeContents() {
  32 + return 0;
  33 + }
  34 +
  35 + public static final Creator<SentenceRealTimeEntity> CREATOR = new Creator<SentenceRealTimeEntity>() {
  36 + @Override
  37 + public SentenceRealTimeEntity createFromParcel(Parcel in) {
  38 + return new SentenceRealTimeEntity(in);
  39 + }
  40 +
  41 + @Override
  42 + public SentenceRealTimeEntity[] newArray(int size) {
  43 + return new SentenceRealTimeEntity[size];
  44 + }
  45 + };
  46 +}
... ...
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/bean/SentenceResultEntity.java 0 → 100644
  1 +package com.kouyuxingqiu.wow_english.singsound.bean;
  2 +
  3 +/**
  4 + * 句子
  5 + * Created by wangz on 2017/8/29.
  6 + */
  7 +public class SentenceResultEntity {
  8 + public String oldSent; // 原始句子
  9 + public String resultSent; // 返回的结果
  10 +
  11 + // 返回的评测结果
  12 + public double overall; // 单词的总分数
  13 +
  14 + public double integrity; // 完整度
  15 + public String missing = "无"; // 遗漏词汇
  16 + public String repeat = "无"; // 复读词汇
  17 + public String points; // 识别要点
  18 +
  19 + public double accuracy; // 准确度
  20 + public String continuity = "无"; // 连续现象
  21 + public int intonation; // 句子语调
  22 + public String errorWords; // 错词统计
  23 +
  24 + public double fluency; // 流利度
  25 + public double speed; // 平均语速
  26 + public int pause; // 停顿过长
  27 +
  28 + public int toneref; // 升降调标识
  29 + public int tonescore; // 升降调得分(0、1)
  30 +
  31 +}
... ...
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/bean/StressCode.java 0 → 100644
  1 +package com.kouyuxingqiu.wow_english.singsound.bean;
  2 +
  3 +import android.os.Parcel;
  4 +import android.os.Parcelable;
  5 +
  6 +/**
  7 + * Created by wangz on 2017/8/29.
  8 + */
  9 +
  10 +public class StressCode implements Parcelable {
  11 + private String charText; // 重音
  12 + private int ref; // 标识当前音节是否需要重读
  13 + private int score; // 重音得分(0、1)
  14 +
  15 + public String getCharText() {
  16 + return charText;
  17 + }
  18 +
  19 + public void setCharText(String charText) {
  20 + this.charText = charText;
  21 + }
  22 +
  23 + public int getRef() {
  24 + return ref;
  25 + }
  26 +
  27 + public void setRef(int ref) {
  28 + this.ref = ref;
  29 + }
  30 +
  31 + public int getScore() {
  32 + return score;
  33 + }
  34 +
  35 + public void setScore(int score) {
  36 + this.score = score;
  37 + }
  38 +
  39 + @Override
  40 + public int describeContents() {
  41 + return 0;
  42 + }
  43 +
  44 + @Override
  45 + public void writeToParcel(Parcel dest, int flags) {
  46 + dest.writeString(this.charText);
  47 + dest.writeInt(this.ref);
  48 + dest.writeInt(this.score);
  49 + }
  50 +
  51 + public StressCode() {
  52 + }
  53 +
  54 + protected StressCode(Parcel in) {
  55 + this.charText = in.readString();
  56 + this.ref = in.readInt();
  57 + this.score = in.readInt();
  58 + }
  59 +
  60 + public static final Creator<StressCode> CREATOR = new Creator<StressCode>() {
  61 + @Override
  62 + public StressCode createFromParcel(Parcel source) {
  63 + return new StressCode(source);
  64 + }
  65 +
  66 + @Override
  67 + public StressCode[] newArray(int size) {
  68 + return new StressCode[size];
  69 + }
  70 + };
  71 +
  72 + @Override
  73 + public String toString() {
  74 + return "StressCode{" +
  75 + "charText='" + charText + '\'' +
  76 + ", ref=" + ref +
  77 + ", score=" + score +
  78 + '}';
  79 + }
  80 +}
... ...
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/bean/WordCode.java 0 → 100644
  1 +package com.kouyuxingqiu.wow_english.singsound.bean;
  2 +
  3 +/**
  4 + * Created by wang on 2016/12/8.
  5 + */
  6 +public class WordCode {
  7 +
  8 + private String charText; // 单个的单词
  9 + private double score; // 单个单词的得分
  10 + private double refScore = -999; // 重音的单词得分
  11 +
  12 + public String getCharText() {
  13 + return charText;
  14 + }
  15 +
  16 + public void setCharText(String charText) {
  17 + this.charText = charText;
  18 + }
  19 +
  20 + public double getScore() {
  21 + return score;
  22 + }
  23 +
  24 + public void setScore(double score) {
  25 + this.score = score;
  26 + }
  27 +
  28 + public double getRefScore() {
  29 + return refScore;
  30 + }
  31 +
  32 + public void setRefScore(double refScore) {
  33 + this.refScore = refScore;
  34 + }
  35 +}
... ...
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/config/EvalTargetType.java 0 → 100644
  1 +package com.kouyuxingqiu.wow_english.singsound.config;
  2 +
  3 +import androidx.annotation.IntDef;
  4 +
  5 +import java.lang.annotation.Retention;
  6 +import java.lang.annotation.RetentionPolicy;
  7 +
  8 +/**
  9 + * @author stay
  10 + * @date 2019-2-1
  11 + * @describe 语音评测类型
  12 + */
  13 +@Retention(RetentionPolicy.SOURCE)
  14 +@IntDef({EvalTargetType.SENTENCE, EvalTargetType.ALPHA, EvalTargetType.WORD})
  15 +public @interface EvalTargetType {
  16 + int SENTENCE = 1; // 句子
  17 + int ALPHA = 2; // 音标
  18 + int WORD = 3; // 单词
  19 +}
0 20 \ No newline at end of file
... ...
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/config/SentenceConfig.java 0 → 100644
  1 +package com.kouyuxingqiu.wow_english.singsound.config;
  2 +
  3 +import com.kouyuxingqiu.wow_english.singsound.bean.SentenceResultEntity;
  4 +import com.kouyuxingqiu.wow_english.singsound.util.JsonUtils;
  5 +
  6 +import org.json.JSONArray;
  7 +import org.json.JSONObject;
  8 +
  9 +
  10 +/**
  11 + * @author stay
  12 + * @date 2019-1-7
  13 + * @describe
  14 + */
  15 +public class SentenceConfig {
  16 +
  17 + /**
  18 + * 解析得到英文句子的一些信息
  19 + *
  20 + * @param result
  21 + * @return SentenceResultEntity
  22 + */
  23 + public static SentenceResultEntity resultJson(JSONObject result) {
  24 + SentenceResultEntity sentenceEntity = new SentenceResultEntity();
  25 + double overall = 0;
  26 + double integrity = 0;
  27 + double accuracy = 0;
  28 + double fluencyOverall = 0;
  29 + int fluencyPause = 0;
  30 + double fluencySpeed = 0;
  31 + int intonation = 0;
  32 + int toneref = -999;
  33 + int tonescore = -999; // 升降调
  34 +
  35 + String missingStr = ""; // 漏读
  36 + String repeatStr = ""; // 复读
  37 + int pauseStr = 0;
  38 + String continuity = "";
  39 +
  40 + try {
  41 + if (result.has("result")) {
  42 + JSONObject resultJ = JsonUtils.getJsonObject(result, "result");
  43 + overall = JsonUtils.getDouble(resultJ, "overall");
  44 + // 完整度
  45 + integrity = JsonUtils.getDouble(resultJ, "integrity");
  46 + // 准确度
  47 + accuracy = JsonUtils.getDouble(resultJ, "accuracy");
  48 + // 流利度
  49 + JSONObject fluency = JsonUtils.getJsonObject(resultJ, "fluency");
  50 + fluencyOverall = JsonUtils.getInt(fluency, "overall"); // 总分
  51 + fluencyPause = JsonUtils.getInt(fluency, "pause"); // 停顿次数
  52 + fluencySpeed = JsonUtils.getInt(fluency, "speed"); // 0:慢,1:正常,2:快
  53 +
  54 + // 遍历所有词汇
  55 + JSONArray detailsA = JsonUtils.getJsonArray(resultJ, "details");
  56 + boolean isLiaisonscore = false; // 下一个单词是否连续
  57 + int missingIndex = 0;
  58 + int repeatIndex = 0;
  59 + for (int i = 0; i < detailsA.length(); i++) {
  60 + JSONObject detailsWords = detailsA.getJSONObject(i);
  61 +
  62 + String charStr = JsonUtils.getString(detailsWords, "char");
  63 + int dpType = JsonUtils.getInt(detailsWords, "dp_type"); // 漏读的才会有
  64 +
  65 + // 过滤掉多余符号
  66 + charStr = charStr.replace(".", "");
  67 + charStr = charStr.replace(",", "");
  68 +
  69 + // TODO 漏读与重复读
  70 + if (dpType == 1 && missingIndex < 3) { // 漏读
  71 + missingStr += charStr + (detailsA.length() - 1 == i ? "" : ", ");
  72 +// missingIndex++;
  73 + } else if (dpType == 2 && repeatIndex < 3) { // 重复读
  74 + repeatStr += charStr + (detailsA.length() - 1 == i ? "" : ", ");
  75 +// repeatIndex++;
  76 + }
  77 +
  78 +// if (missingIndex == 3) {
  79 +// missingStr += "...";
  80 +// missingIndex++;
  81 +// }
  82 +// if (repeatIndex == 3) {
  83 +// repeatStr += "...";
  84 +// repeatIndex++;
  85 +// }
  86 +
  87 + pauseStr += JsonUtils.getInt(detailsWords, "is_pause");
  88 +
  89 + int liaisonscore = JsonUtils.getInt(detailsWords, "liaisonscore");
  90 + if (isLiaisonscore) {
  91 + continuity += charStr + (detailsA.length() - 1 == i ? "" : ", ");
  92 + isLiaisonscore = false;
  93 + }
  94 +
  95 + if (liaisonscore == 1) {
  96 + continuity += charStr + " ";
  97 + isLiaisonscore = true;
  98 + }
  99 +
  100 + // TODO 添加升降调
  101 + toneref = JsonUtils.getInt(detailsWords, "toneref");
  102 + tonescore = JsonUtils.getInt(detailsWords, "tonescore");
  103 + if (detailsA.length() - 1 == i) {
  104 + tonescore = tonescore == toneref ? 90 : 10;
  105 + }
  106 + }
  107 + }
  108 + // 更新
  109 + sentenceEntity.overall = overall;
  110 + sentenceEntity.integrity = integrity;
  111 + sentenceEntity.missing = "".equals(missingStr) ? "无" : missingStr;
  112 + sentenceEntity.repeat = "".equals(repeatStr) ? "无" : repeatStr;
  113 + sentenceEntity.accuracy = accuracy;
  114 + sentenceEntity.intonation = tonescore;
  115 + sentenceEntity.fluency = fluencyOverall;
  116 + sentenceEntity.speed = fluencySpeed;
  117 + sentenceEntity.pause = fluencyPause;
  118 + sentenceEntity.continuity = "".equals(continuity) ? "无" : continuity;
  119 + sentenceEntity.toneref = toneref;
  120 + sentenceEntity.tonescore = tonescore;
  121 + } catch (Exception e) {
  122 + e.printStackTrace();
  123 + }
  124 + return sentenceEntity;
  125 + }
  126 +}
... ...
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/config/SingSoundConfig.java 0 → 100644
  1 +package com.kouyuxingqiu.wow_english.singsound.config;
  2 +
  3 +/**
  4 + * 页面描述: 先声配置类
  5 + * create by yxw on 2018/12/20
  6 + */
  7 +public class SingSoundConfig {
  8 + public static final String APPKEY_DEBUG = "t418";
  9 + public static final String SECERTKEY_DEBUG = "1a16f31f2611bf32fb7b3fc38f5b2c81";
  10 +
  11 + public static final String APPKEY = "a418";
  12 + public static final String SECERTKEY = "c11163aa6c834a028da4a4b30955be99";
  13 +
  14 + public static final float BASE_TYPETHRES = 2f; // 打分宽松度
  15 +}
... ...
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/config/VoiceConfig.java 0 → 100644
  1 +package com.kouyuxingqiu.wow_english.singsound.config;
  2 +
  3 +/**
  4 + * Created by wang on 2016/8/25.
  5 + */
  6 +public class VoiceConfig {
  7 +
  8 + // 英文段落朗读
  9 + public static final String TYPE_Paragraph = "en.pred.score";
  10 + public static final String TYPE_WORD = "en.word.score";
  11 + public static final String TYPE_ALPHA = "en.alpha.score";
  12 + public static final String TYPE_SENT = "en.sent.score";
  13 + public static final String TYPE_WORD_KID = "en.word_kid.score";
  14 + public static final String TYPE_SENT_KID = "en.sent_kid.score";
  15 + public static final String TYPE_CN_WORD = "cn.word.score";
  16 + public static final String TYPE_CN_SENT = "cn.sent.score";
  17 + public static final String TYPE_PCHA = "en.pcha.score";
  18 + public static final String TYPE_choc = "en.choc.score";
  19 + public static final String TYPE_Question_answer = "en.pqan.score";
  20 + public static final String TYPE_pic_article = "en.pict.score";
  21 +
  22 + public static final String UserID = "guest";
  23 +
  24 + public static final String QVA_PARA = " Where can I buy some medicine? I get seasick. Go to Lang's Drugstore. How do I get to Lang's Drugstore? Do you have a map? No. OK. I can draw it. Go two blocks and turn left. Pardon? Do I turn left at the bank? No, don't turn left at the bank. Turn left at the post office. At the post office? Yes. There. Turn left there. Then what? Go through the park and around the circle. Then go straight on Main Street to the coffee shop. How far is that? About two miles. Don't go more than two miles. And then am I there? No, there's a big sign: Lang's Drugstore. Turn right at the sign. Then you're there. Park your car and go up the steps. OK. Thanks for the directions, Rita. Yes, I often walk around my city. You see, I often go shopping and in my free time I would like to stay with my friends at the coffee shop. Generally, I am familiar with the roads around my house. I often walk back home along those roads after work, and I can remember their names quite well. Yes, I can. I am ready to help others. I think helping others will make me feel happy and useful. So I would answer others’ questions with patience. How many blocks should the man go before he turns left? Where should the man turn left? What does he often do in his free time? How does he go home after work? What does he think of helping others? ";
  25 +
  26 + public static final String[] paragraphs = {
  27 + "It is very good for our health to do some sports. Basketball is my favourite sport. At weekends, I often play basketball with my friends in the park. I think it is the best way to release my pressure. Also, I can feel more enjoyable from playing basketball. My favourite basketball star is Yaoming. I am going to practise playing basketball everyday so that I can become a basketball player when I grow up.",
  28 + "It is dawn and the sun is rising, as it has every day for the last 5 billion years. It has been a constant golden disk, shining its unchanging light onto the Earth. But look through the glare, and the true face of the sun is revealed. Not constant, but constantly changing. To understand the sun is to understand the forces that drive the universe. If we can control those forces, we can unlock the power of the stars. All life on Earth owes its existence to the sun. It powers every natural system and sustains every plant and animal."};
  29 +
  30 +
  31 + public static final String native_word = "native_word";
  32 + public static final String native_sentece = "native_sentece";
  33 + public static final String cloud_word = "cloud_word";
  34 + public static final String cloud_acom_sentece = "cloud_acom_sentece";//音频对比
  35 + public static final String cloud_sentece = "cloud_sentece";
  36 + public static final String cloud_cn_word = "cloud_cn_word";
  37 + public static final String cloud_cn_sentece = "cloud_cn_sentece";
  38 + public static final String cloud_para = "cloud_para";
  39 + public static final String cloud_choic = "cloud_choic";
  40 + public static final String cloud_quest = "cloud_quest";
  41 + public static final String cloud_article = "cloud_article";
  42 +
  43 +
  44 +}
... ...
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/config/WordConfig.java 0 → 100644
  1 +package com.kouyuxingqiu.wow_english.singsound.config;
  2 +
  3 +import android.util.Log;
  4 +
  5 +import com.google.gson.Gson;
  6 +import com.kouyuxingqiu.wow_english.singsound.bean.SentenceCode;
  7 +import com.kouyuxingqiu.wow_english.singsound.bean.SentenceRealTimeEntity;
  8 +import com.kouyuxingqiu.wow_english.singsound.bean.StressCode;
  9 +import com.kouyuxingqiu.wow_english.singsound.bean.WordCode;
  10 +import com.kouyuxingqiu.wow_english.singsound.util.JsonUtils;
  11 +
  12 +import org.json.JSONArray;
  13 +import org.json.JSONException;
  14 +import org.json.JSONObject;
  15 +
  16 +import java.util.ArrayList;
  17 +import java.util.LinkedHashMap;
  18 +import java.util.List;
  19 +
  20 +/**
  21 + * Created by wang on 2016/12/8.
  22 + */
  23 +
  24 +public class WordConfig {
  25 +
  26 + /**
  27 + * 单词:获取音素
  28 + */
  29 + public static List<WordCode> getWordsPhoneList(JSONObject json) {
  30 + List<WordCode> list = new ArrayList<>();
  31 + JSONObject map_json = getMapJSONObject();
  32 +
  33 + if (map_json != null) {
  34 + // 数据正确
  35 + if (json.has("result")) {
  36 + JSONObject json_result = JsonUtils.getJsonObject(json, "result");
  37 + JSONArray json_details = JsonUtils.getJsonArray(json_result, "details");
  38 + JSONObject jsonObject = JsonUtils.getJsonObject(json_details, 0);
  39 +
  40 + if (jsonObject != null) {
  41 + // 获得重音发音
  42 + if (jsonObject.has("phone")) {
  43 + JSONArray phoneJA = JsonUtils.getJsonArray(jsonObject, "phone");
  44 +
  45 + // 前/
  46 + WordCode fontItem = new WordCode();
  47 + fontItem.setCharText("/ ");
  48 + fontItem.setScore(-1);
  49 + list.add(fontItem);
  50 +
  51 + // 解析中间的音素
  52 + for (int i = 0; i < phoneJA.length(); i++) {
  53 + // ------------------------ 解析音素相关逻辑 -----------------------------
  54 + JSONObject wordJB = JsonUtils.getJsonObject(phoneJA, i);
  55 + WordCode wordCodeItem = new WordCode();
  56 + String text = JsonUtils.getString(wordJB, "char");
  57 + String newText = JsonUtils.getString(map_json, text);
  58 + if (newText != null) {
  59 + text = newText;
  60 + }
  61 +
  62 + wordCodeItem.setCharText(text);
  63 + wordCodeItem.setScore(JsonUtils.getDouble(wordJB, "score"));
  64 + list.add(wordCodeItem);
  65 + }
  66 +
  67 + // 后/
  68 + WordCode backItem = new WordCode();
  69 + backItem.setCharText(" /");
  70 + backItem.setScore(-1);
  71 + list.add(backItem);
  72 +
  73 + // ------------------------ 解析重音相关逻辑 -----------------------------
  74 + List<StressCode> stressCodesList = getWordsStressList(json);
  75 + for (int i = 0; i < stressCodesList.size(); i++) {
  76 + StressCode stressCode = stressCodesList.get(i);
  77 + WordCode wordCode = list.get(i);
  78 + if (!wordCode.getCharText().equals(stressCode.getCharText())) {
  79 + WordCode moreWordCode = new WordCode();
  80 + moreWordCode.setCharText(stressCode.getCharText());
  81 + moreWordCode.setScore(stressCode.getScore() == 1 ? 90 : 10);
  82 +
  83 + // 显示黑色
  84 + if (" · ".equals(stressCode.getCharText())) {
  85 + moreWordCode.setScore(70);
  86 + }
  87 + list.add(i, moreWordCode);
  88 + }
  89 + // TODO good 写死
  90 + if ("g".equals(wordCode.getCharText())) {
  91 + WordCode c = list.get(i - 1);
  92 + c.setScore(70);
  93 + }
  94 + }
  95 + }
  96 + }
  97 + }
  98 + }
  99 + return list;
  100 + }
  101 +
  102 + /**
  103 + * 单词:获取重音
  104 + */
  105 + public static List<StressCode> getWordsStressList(JSONObject json) {
  106 + List<StressCode> list = new ArrayList<>();
  107 + JSONObject map_json = getMapJSONObject();
  108 +
  109 + try {
  110 + if (json != null) {
  111 + // 数据正确
  112 + if (json.has("result")) {
  113 + JSONObject json_result = json.getJSONObject("result");
  114 + if (json_result.has("details")) {
  115 + JSONArray json_details = json_result.getJSONArray("details");
  116 + JSONObject jsonObject = (JSONObject) json_details.get(0);
  117 +
  118 + if (jsonObject != null) {
  119 + // 获得重音发音
  120 + if (jsonObject.has("stress")) {
  121 + JSONArray json_phone = jsonObject.getJSONArray("stress");
  122 + if (json_phone != null) {
  123 + //add 前/
  124 + StressCode fontItem = new StressCode();
  125 + fontItem.setCharText("/ ");
  126 + fontItem.setScore(-1);
  127 + list.add(fontItem);
  128 +
  129 + // 解析重音相关
  130 + for (int i = 0; i < json_phone.length(); i++) {
  131 + JSONObject json_bean = (JSONObject) json_phone.get(i);
  132 +
  133 + int ref = json_bean.getInt("ref");
  134 +
  135 + // 判断是不是重音
  136 + if (ref == 1) { // 1 是重读
  137 + StressCode stressItem = new StressCode();
  138 + stressItem.setCharText("'");
  139 + stressItem.setScore(json_bean.getInt("score"));
  140 + list.add(stressItem);
  141 + }
  142 +
  143 + String text = json_bean.getString("char");
  144 + String[] texts = text.split("_");
  145 +
  146 + for (String text1 : texts) {
  147 + StressCode wordCodeItem = new StressCode();
  148 + wordCodeItem.setScore(-1);
  149 + wordCodeItem.setRef(ref);
  150 + wordCodeItem.setCharText((String) map_json.get(text1));
  151 +
  152 + // TODO 直接写死 如果是 good 前面添加一个重音
  153 + if ("g".equals(map_json.get(text1))) {
  154 + StressCode stressItem = new StressCode();
  155 + stressItem.setCharText("'");
  156 + stressItem.setScore(70);
  157 + list.add(stressItem);
  158 + }
  159 +
  160 + list.add(wordCodeItem);
  161 + }
  162 +
  163 + if (i + 1 != json_phone.length()) {
  164 + StressCode pointItem = new StressCode();
  165 + pointItem.setCharText(" · ");
  166 + pointItem.setScore(-1);
  167 + list.add(pointItem);
  168 + }
  169 + }
  170 +
  171 + //add 后/
  172 + StressCode backItem = new StressCode();
  173 + backItem.setCharText(" /");
  174 + backItem.setScore(-1);
  175 + list.add(backItem);
  176 + }
  177 + }
  178 + }
  179 + }
  180 + }
  181 + }
  182 + } catch (JSONException e) {
  183 + e.printStackTrace();
  184 + }
  185 + return list;
  186 + }
  187 +
  188 + public static List<SentenceRealTimeEntity> getSentenceRealTimeList(JSONObject json) {
  189 + List<SentenceRealTimeEntity> list = new ArrayList<>();
  190 + if (json != null) {
  191 + if (json.has("result")) {
  192 + JSONObject resultJB = JsonUtils.getJsonObject(json, "result");
  193 + if (resultJB.has("realtime_details")) {
  194 + JSONArray jsonDetails = JsonUtils.getJsonArray(resultJB, "realtime_details");
  195 + int length = jsonDetails.length();
  196 + for (int i = 0; i < length; i++) {
  197 + JSONObject itemSentence = JsonUtils.getJsonObject(jsonDetails, i);
  198 + SentenceRealTimeEntity sentenceRealTime = new SentenceRealTimeEntity();
  199 + sentenceRealTime.charStr = JsonUtils.getString(itemSentence, "char") + " ";
  200 + sentenceRealTime.dp_type = JsonUtils.getInt(itemSentence, "dp_type");
  201 + list.add(sentenceRealTime);
  202 + }
  203 + }
  204 + }
  205 + }
  206 + return list;
  207 +
  208 + }
  209 +
  210 + public static List<SentenceCode> getCnSentenceList(JSONObject json) {
  211 + return getSentenceList(json, "chn_char");
  212 + }
  213 +
  214 + public static List<SentenceCode> getEnSentenceList(JSONObject json) {
  215 + return getSentenceList(json, "char");
  216 + }
  217 +
  218 + /**
  219 + * 获得句子的高亮
  220 + * 每个单词的评分,拥有升降调与停顿的
  221 + *
  222 + * @return
  223 + */
  224 + private static List<SentenceCode> getSentenceList(JSONObject json, String type) {
  225 + List<SentenceCode> list = new ArrayList<>();
  226 + if (json != null) {
  227 + // 数据正确
  228 + if (json.has("result")) {
  229 + JSONObject resultJB = JsonUtils.getJsonObject(json, "result");
  230 + if (resultJB.has("details")) {
  231 + JSONArray jsonDetails = JsonUtils.getJsonArray(resultJB, "details");
  232 +
  233 + for (int i = 0; i < jsonDetails.length(); i++) {
  234 + JSONObject itemSentence = JsonUtils.getJsonObject(jsonDetails, i);
  235 + // 解析具体数据
  236 + SentenceCode sentenceCode = new SentenceCode();
  237 + sentenceCode.charStr = JsonUtils.getString(itemSentence, type) + " ";
  238 + sentenceCode.score = JsonUtils.getInt(itemSentence, "score");
  239 + // 重复
  240 + int dpType = JsonUtils.getInt(itemSentence, "dp_type");
  241 + list.add(sentenceCode);
  242 + sentenceCode.score = dpType == 2 ? 120 : sentenceCode.score; // 120 显示黄色
  243 +
  244 + // TODO 停顿
  245 + sentenceCode.isPause = JsonUtils.getInt(itemSentence, "is_pause");
  246 + if (sentenceCode.isPause == 1) {
  247 + // 句子停顿了,在后面的添加三个省略号
  248 + SentenceCode pauseCode = new SentenceCode();
  249 + pauseCode.charStr = "... ";
  250 + pauseCode.score = 10;
  251 + list.add(pauseCode);
  252 + }
  253 +
  254 + // TODO 添加升降调
  255 + sentenceCode.toneref = JsonUtils.getInt(itemSentence, "toneref");
  256 + sentenceCode.tonescore = JsonUtils.getInt(itemSentence, "tonescore");
  257 + if (jsonDetails.length() - 1 == i) {
  258 + // 句子停顿了,在后面的添加三个省略号
  259 + SentenceCode tonescoreCode = new SentenceCode();
  260 + tonescoreCode.charStr = sentenceCode.toneref == 1 ? " ↗ " : " ↘ ";
  261 + tonescoreCode.score = sentenceCode.tonescore == sentenceCode.toneref ? 90 : 10;
  262 + list.add(tonescoreCode);
  263 + }
  264 +
  265 + // TODO 获得连续
  266 + sentenceCode.liaisonscore = JsonUtils.getInt(itemSentence, "liaisonscore");
  267 + }
  268 + }
  269 + }
  270 + }
  271 + return list;
  272 + }
  273 +
  274 + /**
  275 + * 获得句子的高亮
  276 + * 每个单词的评分
  277 + */
  278 + public static List<SentenceCode> getSentenceBaseList(JSONObject json) {
  279 + List<SentenceCode> list = new ArrayList<>();
  280 + if (json != null) {
  281 + // 数据正确
  282 + if (json.has("result")) {
  283 + JSONObject resultJB = JsonUtils.getJsonObject(json, "result");
  284 + if (resultJB.has("details")) {
  285 + JSONArray jsonDetails = JsonUtils.getJsonArray(resultJB, "details");
  286 + for (int i = 0; i < jsonDetails.length(); i++) {
  287 + JSONObject itemOb = JsonUtils.getJsonObject(jsonDetails, i);
  288 + JSONArray itemA = JsonUtils.getJsonArray(itemOb, "snt_details");
  289 + for (int j = 0; j < itemA.length(); j++) {
  290 + JSONObject detailsWords = JsonUtils.getJsonObject(itemA, j);
  291 + // 解析具体数据
  292 + SentenceCode sentenceCode = new SentenceCode();
  293 + sentenceCode.charStr = JsonUtils.getString(detailsWords, "char") + " ";
  294 + sentenceCode.score = JsonUtils.getInt(detailsWords, "score");
  295 + list.add(sentenceCode);
  296 +
  297 + // TODO 停顿
  298 + sentenceCode.isPause = JsonUtils.getInt(detailsWords, "is_pause");
  299 + if (sentenceCode.isPause == 1) {
  300 + // 句子停顿了,在后面的添加三个省略号
  301 + SentenceCode pauseCode = new SentenceCode();
  302 + pauseCode.charStr = "... ";
  303 + pauseCode.score = 10;
  304 + list.add(pauseCode);
  305 + }
  306 + }
  307 + }
  308 + }
  309 + }
  310 + }
  311 + Log.w("ffffff", "itemA: " + list);
  312 + return list;
  313 + }
  314 +
  315 + private static List<WordCode> getStressWordCodeList(JSONArray jsonArray_stress, JSONArray jsonArray_phone) throws JSONException {
  316 + List<WordCode> stressList = getStressList(jsonArray_stress);
  317 + List<WordCode> phoneList = getPhoneList(jsonArray_phone);
  318 + JSONObject map_json = getMapJSONObject();
  319 +
  320 + //把 phoneList 的分数 赋给 stressList
  321 + int m = 0;
  322 + for (int i = 0; i < stressList.size(); i++) {
  323 + for (int j = m; j < phoneList.size(); j++) {
  324 + WordCode stressItem = stressList.get(i);
  325 + if (stressItem.getCharText().equals(phoneList.get(j).getCharText())) {
  326 + stressItem.setScore(phoneList.get(j).getScore());
  327 + m = j;
  328 + break;
  329 + }
  330 + }
  331 + }
  332 +
  333 +
  334 + for (int i = 0; i < stressList.size(); i++) {
  335 + Log.e("-----stressList-----", i + "-------" + stressList.get(i).getCharText() + "-------" + stressList.get(i).getScore());
  336 + }
  337 +
  338 + for (int i = 0; i < phoneList.size(); i++) {
  339 + Log.d("-----phoneList-----", i + "-------" + phoneList.get(i).getCharText() + "-------" + phoneList.get(i).getScore());
  340 + }
  341 +
  342 +
  343 + for (int i = 0; i < stressList.size(); i++) {
  344 + String stressText = stressList.get(i).getCharText();
  345 + if (map_json.has(stressText)) {
  346 + String newstressText = map_json.getString(stressText);
  347 + if (newstressText != null) {
  348 + stressList.get(i).setCharText(newstressText);
  349 + }
  350 + }
  351 + }
  352 +
  353 + return stressList;
  354 +
  355 + }
  356 +
  357 + private static List<WordCode> getStressList(JSONArray jsonArray_stress) throws JSONException {
  358 + List<WordCode> stressList = new ArrayList<>();
  359 +
  360 + //add 前/
  361 + WordCode fontItem = new WordCode();
  362 + fontItem.setCharText("/ ");
  363 + fontItem.setScore(-1);
  364 + stressList.add(fontItem);
  365 +
  366 + for (int i = 0; i < jsonArray_stress.length(); i++) {
  367 + JSONObject object = jsonArray_stress.getJSONObject(i);
  368 +
  369 + if (object.has("ref") && object.getInt("ref") == 1) {
  370 + WordCode fontStressItem = new WordCode();
  371 + fontStressItem.setCharText("'");
  372 +
  373 + if (object.getInt("score") == 1) {
  374 + fontStressItem.setScore(90);
  375 + } else {
  376 + fontStressItem.setScore(10);
  377 + }
  378 + stressList.add(fontStressItem);
  379 + }
  380 +
  381 + String charX = object.getString("char");
  382 + String[] charXs = charX.split("_");
  383 +
  384 + for (int j = 0; j < charXs.length; j++) {
  385 + WordCode stressItem = new WordCode();
  386 + stressItem.setCharText(charXs[j]);
  387 + stressItem.setScore(0);
  388 + stressList.add(stressItem);
  389 + }
  390 + }
  391 +
  392 + //add 后/
  393 + WordCode backItem = new WordCode();
  394 + backItem.setCharText(" /");
  395 + backItem.setScore(-1);
  396 + stressList.add(backItem);
  397 +
  398 + return stressList;
  399 + }
  400 +
  401 +
  402 + private static List<WordCode> getPhoneList(JSONArray json_phone) throws JSONException {
  403 + List<WordCode> list = new ArrayList<>();
  404 +
  405 +
  406 + if (json_phone != null) {
  407 +
  408 + for (int i = 0; i < json_phone.length(); i++) {
  409 + JSONObject json_bean = (JSONObject) json_phone.get(i);
  410 + WordCode wordCodeItem = new WordCode();
  411 + String text = json_bean.getString("char");
  412 + wordCodeItem.setCharText(text);
  413 + wordCodeItem.setScore(json_bean.getDouble("score"));
  414 + list.add(wordCodeItem);
  415 + }
  416 +
  417 +//
  418 +// //add 后/
  419 +// WordCode backItem = new WordCode();
  420 +// backItem.setCharText(" /");
  421 +// backItem.setScore(-1);
  422 +// list.add(backItem);
  423 +
  424 + }
  425 +
  426 + return list;
  427 + }
  428 +
  429 +
  430 + public static JSONObject getMapJSONObject() {
  431 +
  432 + LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
  433 + try {
  434 + map.put("ɪ", "ih");
  435 + map.put("I", "ih"); // 15
  436 + map.put("ә", "ax");
  437 + map.put("ə", "ax"); // 12
  438 +// map.put("ɒ", "oo");
  439 +// map.put("ɔ", "oo");
  440 + map.put("ɒ", "aa");
  441 + map.put("ɑ", "aa"); // 5
  442 + map.put("ʊ", "uh");
  443 + map.put("U", "uh"); // 14
  444 +
  445 + map.put("ʌ", "ah");
  446 + map.put("∧", "ah"); // 13
  447 + map.put("e", "eh");
  448 + map.put("ɛ", "eh"); // 4
  449 + map.put("æ", "ae");
  450 + map.put("i:", "iy");
  451 + map.put("i", "iy"); // 8
  452 + map.put("ɜ:", "er");
  453 +
  454 + map.put("ɝ:", "axr");
  455 + map.put("ɝ", "axr"); // 10
  456 + map.put("ɚ", "axr"); // 10
  457 + map.put("ɔ:", "ao");
  458 + map.put("ɔ", "ao"); // 1
  459 +//* map.put("ɔr", "ao r");
  460 +// map.put("ɔr", "ao");
  461 + map.put("u:", "uw");
  462 + map.put("u", "uw"); // 9
  463 + map.put("ju:", "y uw");
  464 +
  465 + map.put("ɑr", "aa r");
  466 + map.put("eɪ", "ey");
  467 + map.put("aɪ", "ay");
  468 + map.put("ɔɪ", "oy");
  469 + map.put("aʊ", "aw");
  470 + map.put("au", "aw"); // 11
  471 +
  472 + map.put("әʊ", "ow");
  473 + map.put("o", "ow"); // 2
  474 +// map.put("ɪə", "ir");
  475 +// map.put("ɪə", "ih r");
  476 +//* map.put("ɪr", "ih r"); // 3
  477 + map.put("ɪr", "ir"); // 3
  478 +// map.put("eə", "ar");
  479 +// map.put("eə", "eh r");
  480 +//* map.put("ɛr", "eh r"); // 6
  481 + map.put("ɛr", "ar"); // 6
  482 +
  483 +// map.put("ʊə", "ur");
  484 + map.put("ur", "ur");
  485 + map.put("ʊə", "uh r");
  486 + map.put("ʊr", "uh r"); // 7
  487 +
  488 +
  489 + map.put("p", "p");
  490 + map.put("k", "k");
  491 + map.put("m", "mb");
  492 + map.put("s", "s");
  493 + map.put("f", "f");
  494 + map.put("ʃ", "sh");
  495 + map.put("ts", "ts");
  496 +
  497 + map.put("b", "b");
  498 + map.put("g", "g");
  499 + map.put("n", "nb");
  500 + map.put("z", "z");
  501 + map.put("v", "v");
  502 + map.put("ʒ", "zh");
  503 + map.put("dz", "dz");
  504 +
  505 + map.put("t", "t");
  506 + map.put("l", "l");
  507 + map.put("ŋ", "ng");
  508 + map.put("θ", "th");
  509 + map.put("w", "w");
  510 + map.put("tʃ", "ch");
  511 + map.put("tr", "tr");
  512 +
  513 + map.put("d", "d");
  514 + map.put("r", "r");
  515 + map.put("h", "hh");
  516 + map.put("ð", "dh");
  517 + map.put("j", "y");
  518 + map.put("dʒ", "jh");
  519 + map.put("dr", "dr");
  520 +
  521 +
  522 + Gson gson = new Gson();
  523 + String s = gson.toJson(map);
  524 +
  525 + JSONObject json = new JSONObject(s);
  526 +
  527 + return json;
  528 +
  529 + } catch (JSONException e) {
  530 + e.printStackTrace();
  531 + return null;
  532 + }
  533 +
  534 + }
  535 +
  536 +
  537 +}
... ...
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/util/JsonUtils.java 0 → 100644
  1 +package com.kouyuxingqiu.wow_english.singsound.util;
  2 +
  3 +import android.util.Log;
  4 +
  5 +import org.json.JSONArray;
  6 +import org.json.JSONException;
  7 +import org.json.JSONObject;
  8 +
  9 +/**
  10 + * Created by wangz on 2017/8/29.
  11 + */
  12 +
  13 +public class JsonUtils {
  14 + private static final String TAG = "JSONUtils";
  15 +
  16 + public static JSONObject getJsonObject(JSONArray array, int postion) {
  17 + try {
  18 + return array.getJSONObject(postion);
  19 + } catch (JSONException e) {
  20 + e.printStackTrace();
  21 + return new JSONObject();
  22 + }
  23 + }
  24 +
  25 + public static JSONObject getJsonObject(JSONObject object, String key) {
  26 + try {
  27 + return object.getJSONObject(key);
  28 + } catch (Exception e) {
  29 + Log.e(TAG, e.getLocalizedMessage());
  30 + return new JSONObject();
  31 + }
  32 + }
  33 +
  34 + public static JSONArray getJsonArray(JSONArray object, int key) {
  35 + try {
  36 + return object.getJSONArray(key);
  37 + } catch (Exception e) {
  38 + Log.e(TAG, e.getLocalizedMessage());
  39 + return new JSONArray();
  40 + }
  41 + }
  42 +
  43 + public static JSONArray getJsonArray(JSONObject object, String key) {
  44 + try {
  45 + return object.getJSONArray(key);
  46 + } catch (Exception e) {
  47 + Log.e(TAG, e.getLocalizedMessage());
  48 + return new JSONArray();
  49 + }
  50 + }
  51 +
  52 + public static String getString(JSONObject object, String key) {
  53 + try {
  54 + return object.getString(key);
  55 + } catch (Exception e) {
  56 + Log.e(TAG, e.getLocalizedMessage());
  57 + return "";
  58 + }
  59 + }
  60 +
  61 + public static int getInt(JSONObject object, String key) {
  62 + try {
  63 + return object.getInt(key);
  64 + } catch (Exception e) {
  65 + Log.e(TAG, e.getLocalizedMessage());
  66 + return 0;
  67 + }
  68 + }
  69 +
  70 + public static double getDouble(JSONObject object, String key) {
  71 + try {
  72 + return object.getDouble(key);
  73 + } catch (Exception e) {
  74 + Log.e(TAG, e.getLocalizedMessage());
  75 + return 0;
  76 + }
  77 + }
  78 +
  79 + public static boolean getBoolean(JSONObject object, String key) {
  80 + try {
  81 + return object.getBoolean(key);
  82 + } catch (Exception e) {
  83 + Log.e(TAG, e.getLocalizedMessage());
  84 + return false;
  85 + }
  86 + }
  87 +}
... ...
android/build.gradle
... ... @@ -16,6 +16,7 @@ allprojects {
16 16 repositories {
17 17 google()
18 18 mavenCentral()
  19 + maven { url 'https://repo.singsound.com/repository/singsound_ginger_android_sdk/' }
19 20 }
20 21 }
21 22  
... ...