Commit 0fa4e405a692ec6cb797f1ff6047158e54b72ff6
1 parent
383462ef
feat: 将语音评测从先声迁移至驰声
Showing
76 changed files
with
2943 additions
and
3136 deletions
Too many changes.
To preserve performance only 50 of 76 files are displayed.
android/app/build.gradle
| @@ -101,8 +101,6 @@ flutter { | @@ -101,8 +101,6 @@ flutter { | ||
| 101 | } | 101 | } |
| 102 | 102 | ||
| 103 | dependencies { | 103 | dependencies { |
| 104 | - // sing sound | ||
| 105 | - implementation 'com.singsound.library:evaluating:2.1.9' | ||
| 106 | implementation "com.google.code.gson:gson:2.10" | 104 | implementation "com.google.code.gson:gson:2.10" |
| 107 | // 基础依赖包,必须要依赖 | 105 | // 基础依赖包,必须要依赖 |
| 108 | implementation 'com.geyifeng.immersionbar:immersionbar:3.2.2' | 106 | implementation 'com.geyifeng.immersionbar:immersionbar:3.2.2' |
android/app/proguard-rules.pro
| @@ -20,12 +20,6 @@ | @@ -20,12 +20,6 @@ | ||
| 20 | # hide the original source file name. | 20 | # hide the original source file name. |
| 21 | #-renamesourcefileattribute SourceFile | 21 | #-renamesourcefileattribute SourceFile |
| 22 | 22 | ||
| 23 | -# 先声混淆代码 | ||
| 24 | --keep class com.tt.** { *; } | ||
| 25 | --keep class com.xs.** { *; } | ||
| 26 | --keep interface com.xs.** { *; } | ||
| 27 | --keep enum com.xs.** { *; } | ||
| 28 | - | ||
| 29 | # 友盟混淆 | 23 | # 友盟混淆 |
| 30 | -keep class com.umeng.** { *; } | 24 | -keep class com.umeng.** { *; } |
| 31 | 25 |
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/MainActivity.kt
| @@ -9,7 +9,6 @@ import androidx.core.view.WindowInsetsControllerCompat | @@ -9,7 +9,6 @@ import androidx.core.view.WindowInsetsControllerCompat | ||
| 9 | import com.gyf.immersionbar.BarHide | 9 | import com.gyf.immersionbar.BarHide |
| 10 | import com.gyf.immersionbar.ImmersionBar | 10 | import com.gyf.immersionbar.ImmersionBar |
| 11 | import com.kouyuxingqiu.wow_english.methodChannels.GameMethodChannel | 11 | import com.kouyuxingqiu.wow_english.methodChannels.GameMethodChannel |
| 12 | -import com.kouyuxingqiu.wow_english.methodChannels.SingSoungMethodChannel | ||
| 13 | import com.umeng.commonsdk.UMConfigure | 12 | import com.umeng.commonsdk.UMConfigure |
| 14 | import com.umeng.umcrash.UMCrash | 13 | import com.umeng.umcrash.UMCrash |
| 15 | import io.flutter.embedding.android.FlutterActivity | 14 | import io.flutter.embedding.android.FlutterActivity |
| @@ -25,7 +24,6 @@ class MainActivity : FlutterActivity() { | @@ -25,7 +24,6 @@ class MainActivity : FlutterActivity() { | ||
| 25 | //隐藏状态栏和导航栏 | 24 | //隐藏状态栏和导航栏 |
| 26 | ImmersionBar.with(this).hideBar(BarHide.FLAG_HIDE_BAR).init() | 25 | ImmersionBar.with(this).hideBar(BarHide.FLAG_HIDE_BAR).init() |
| 27 | flutterEngine?.let { | 26 | flutterEngine?.let { |
| 28 | - SingSoungMethodChannel(this, it) | ||
| 29 | GameMethodChannel(this, it) | 27 | GameMethodChannel(this, it) |
| 30 | } | 28 | } |
| 31 | } | 29 | } |
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/methodChannels/SingSoungMethodChannel.kt deleted
| 1 | -package com.kouyuxingqiu.wow_english.methodChannels | ||
| 2 | - | ||
| 3 | -import android.util.Log | ||
| 4 | -import com.kouyuxingqiu.wow_english.singsound.SingEngineHelper | ||
| 5 | -import com.kouyuxingqiu.wow_english.singsound.SingEngineHelper.init | ||
| 6 | -import com.kouyuxingqiu.wow_english.singsound.SingEngineLifecycles | ||
| 7 | -import com.kouyuxingqiu.wow_english.util.GlobalHandler | ||
| 8 | -import io.flutter.embedding.android.FlutterActivity | ||
| 9 | -import io.flutter.embedding.engine.FlutterEngine | ||
| 10 | -import io.flutter.plugin.common.MethodChannel | ||
| 11 | -import java.lang.ref.WeakReference | ||
| 12 | - | ||
| 13 | -/** | ||
| 14 | - * @author: stay | ||
| 15 | - * @date: 2023/6/27 00:32 | ||
| 16 | - * @description: | ||
| 17 | - */ | ||
| 18 | -class SingSoungMethodChannel(activity: FlutterActivity, flutterEngine: FlutterEngine): SingEngineLifecycles.OnSingEngineAdapter() { | ||
| 19 | - private var methodChannel: MethodChannel? = null | ||
| 20 | - private val TAG = "SingSoungMethodChannel" | ||
| 21 | - | ||
| 22 | - companion object { | ||
| 23 | - var channel: WeakReference<SingSoungMethodChannel>? = null | ||
| 24 | - | ||
| 25 | - fun invokeMethod(method: String, arguments: Any?) { | ||
| 26 | - channel?.get()?.methodChannel?.invokeMethod(method, arguments) | ||
| 27 | - } | ||
| 28 | - } | ||
| 29 | - | ||
| 30 | - init { | ||
| 31 | - // name需与flutter端一致 | ||
| 32 | - methodChannel = | ||
| 33 | - MethodChannel( | ||
| 34 | - flutterEngine.dartExecutor.binaryMessenger, | ||
| 35 | - "wow_english/sing_sound_method_channel" | ||
| 36 | - ) | ||
| 37 | - init(activity) | ||
| 38 | - methodChannel?.setMethodCallHandler { call, result -> | ||
| 39 | - Log.d(TAG, "SingSoungMethodChannel CALL=${call.method} ${call.arguments}") | ||
| 40 | - when (call.method) { | ||
| 41 | - "initVoiceSdk" -> { | ||
| 42 | - | ||
| 43 | - } | ||
| 44 | - "startVoice" -> { | ||
| 45 | - val paramMap = call.arguments as HashMap<String, String> | ||
| 46 | - paramMap["word"]?.let { SingEngineHelper.startRecord(it) } | ||
| 47 | - } | ||
| 48 | - "stopVoice" -> { | ||
| 49 | - SingEngineHelper.stopRecord() | ||
| 50 | - } | ||
| 51 | - "startLocalVoice" -> { | ||
| 52 | - val paramMap = call.arguments as HashMap<String, String> | ||
| 53 | - paramMap["voicePath"]?.let { voiceFilePath -> | ||
| 54 | - paramMap["word"]?.let { evaluateContent -> | ||
| 55 | - SingEngineHelper.evaluate(voiceFilePath, evaluateContent) } | ||
| 56 | - } | ||
| 57 | - | ||
| 58 | - } | ||
| 59 | - "cancelVoice" -> { | ||
| 60 | - SingEngineHelper.cancel() | ||
| 61 | - } | ||
| 62 | - else -> { | ||
| 63 | - result.notImplemented() | ||
| 64 | - } | ||
| 65 | - } | ||
| 66 | - | ||
| 67 | - } | ||
| 68 | - channel = WeakReference(this) | ||
| 69 | - | ||
| 70 | - SingEngineHelper.addOnResultListener(this) | ||
| 71 | - } | ||
| 72 | - | ||
| 73 | - override fun onResult(map: Map<String, Any>, evalType: Int?) { | ||
| 74 | - //先声回调在子线程,需要切换到主线程 | ||
| 75 | - GlobalHandler.runOnMainThread { | ||
| 76 | - invokeMethod("voiceResult", map) | ||
| 77 | - } | ||
| 78 | - } | ||
| 79 | - | ||
| 80 | - override fun onRecordFail(code: Int, message: String) { | ||
| 81 | - GlobalHandler.runOnMainThread { | ||
| 82 | - invokeMethod("voiceFail", mapOf("code" to code, "message" to message)) | ||
| 83 | - } | ||
| 84 | - } | ||
| 85 | - | ||
| 86 | - override fun onRecordBegin() { | ||
| 87 | - GlobalHandler.runOnMainThread { | ||
| 88 | - invokeMethod("voiceStart", null) | ||
| 89 | - } | ||
| 90 | - } | ||
| 91 | - | ||
| 92 | - override fun onRecordStop() { | ||
| 93 | - GlobalHandler.runOnMainThread { | ||
| 94 | - invokeMethod("voiceEnd", null) | ||
| 95 | - } | ||
| 96 | - } | ||
| 97 | - | ||
| 98 | - override fun onCancel() { | ||
| 99 | - GlobalHandler.runOnMainThread { | ||
| 100 | - invokeMethod("voiceCancel", null) | ||
| 101 | - } | ||
| 102 | - } | ||
| 103 | -} | ||
| 104 | \ No newline at end of file | 0 | \ No newline at end of file |
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/BaseCloudFragment.kt deleted
| 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 | -//} | ||
| 274 | \ No newline at end of file | 0 | \ No newline at end of file |
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/OnSingEngineLifecycles.kt deleted
| 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(map: Map<String, Any>, @EvalTargetType evalType: Int? = EvalTargetType.SENTENCE) | ||
| 18 | - | ||
| 19 | - // 取消评测 | ||
| 20 | - fun onCancel() | ||
| 21 | - | ||
| 22 | - /** | ||
| 23 | - * 评测失败 | ||
| 24 | - * @param code 失败错误码 | ||
| 25 | - * @param message 失败错误信息 | ||
| 26 | - */ | ||
| 27 | - fun onRecordFail(code: Int, message: String) | ||
| 28 | - | ||
| 29 | - | ||
| 30 | - abstract class OnSingEngineAdapter : SingEngineLifecycles { | ||
| 31 | - override fun onRecordBegin() { | ||
| 32 | - | ||
| 33 | - } | ||
| 34 | - | ||
| 35 | - override fun onRecordStop() { | ||
| 36 | - | ||
| 37 | - } | ||
| 38 | - | ||
| 39 | - override fun onRecordPlayOver() { | ||
| 40 | - | ||
| 41 | - } | ||
| 42 | - | ||
| 43 | - override fun onResult(map: Map<String, Any>, @EvalTargetType evalType: Int?) { | ||
| 44 | - | ||
| 45 | - } | ||
| 46 | - | ||
| 47 | - override fun onCancel() { | ||
| 48 | - | ||
| 49 | - } | ||
| 50 | - | ||
| 51 | - override fun onRecordFail(code: Int, message: String) { | ||
| 52 | - | ||
| 53 | - } | ||
| 54 | - } | ||
| 55 | -} | ||
| 56 | \ No newline at end of file | 0 | \ No newline at end of file |
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/ParseDataHelper.kt deleted
| 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 | -} | ||
| 107 | \ No newline at end of file | 0 | \ No newline at end of file |
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/SingEngineHelper.kt deleted
| 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.kouyuxingqiu.wow_english.singsound.util.JsonUtils.toMap | ||
| 13 | -import com.xs.SingEngine | ||
| 14 | -import com.xs.impl.AudioErrorCallback | ||
| 15 | -import com.xs.impl.EvalReturnRequestIdCallback | ||
| 16 | -import com.xs.impl.OnRealTimeResultListener | ||
| 17 | -import com.xs.utils.AiUtil | ||
| 18 | -import org.json.JSONObject | ||
| 19 | -import java.util.* | ||
| 20 | - | ||
| 21 | - | ||
| 22 | -object SingEngineHelper : | ||
| 23 | - AudioErrorCallback, EvalReturnRequestIdCallback, OnRealTimeResultListener { | ||
| 24 | - | ||
| 25 | - private val TAG = "SingEngineManager" | ||
| 26 | - private var mSingEngine: SingEngine? = null | ||
| 27 | - | ||
| 28 | - /** | ||
| 29 | - * 所有逻辑回调集合 | ||
| 30 | - */ | ||
| 31 | - private var mListeners: MutableList<SingEngineLifecycles>? = null | ||
| 32 | - | ||
| 33 | - /** | ||
| 34 | - * 是否初始化完成 | ||
| 35 | - */ | ||
| 36 | - private var mIsReady = false | ||
| 37 | - | ||
| 38 | - /** | ||
| 39 | - * 是否取消测评 | ||
| 40 | - */ | ||
| 41 | - private var mCanceled = false | ||
| 42 | - | ||
| 43 | - /** | ||
| 44 | - * 音标转换表 | ||
| 45 | - */ | ||
| 46 | - private val mAlphaMap = LinkedHashMap<String, String>() | ||
| 47 | - | ||
| 48 | - | ||
| 49 | - private var mCurEvalType: Int? = EvalTargetType.SENTENCE | ||
| 50 | - | ||
| 51 | - /** | ||
| 52 | - * 目前建议在应用入口初始化 | ||
| 53 | - */ | ||
| 54 | - fun init(context: Context) { | ||
| 55 | - mListeners = mutableListOf() | ||
| 56 | - if (mSingEngine == null) { | ||
| 57 | - mSingEngine = SingEngine.newInstance(context) | ||
| 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("1") | ||
| 97 | - | ||
| 98 | - Log.w(TAG, "createEngine") | ||
| 99 | - } | ||
| 100 | - | ||
| 101 | - getSymbolsMap() | ||
| 102 | - } catch (e: Exception) { | ||
| 103 | - e.printStackTrace() | ||
| 104 | - } | ||
| 105 | - }.start() | ||
| 106 | - } | ||
| 107 | - } | ||
| 108 | - | ||
| 109 | - // 开始语音评测(先声录音+评测) | ||
| 110 | - fun startRecord(originText: String, @EvalTargetType evalTargetType: Int? = EvalTargetType.SENTENCE, userId: String? = VoiceConfig.UserID) { | ||
| 111 | - buildEvaluateConfig(originText, evalTargetType, userId) | ||
| 112 | - //开始测评 | ||
| 113 | - mSingEngine?.start() | ||
| 114 | - mCurEvalType = evalTargetType | ||
| 115 | - Log.w(TAG, "startRecord originText=$originText evalTargetType =$evalTargetType") | ||
| 116 | - } | ||
| 117 | - | ||
| 118 | - /** | ||
| 119 | - * 评测外部录音文件 | ||
| 120 | - * @param voiceFilePath 录音文件路径 | ||
| 121 | - */ | ||
| 122 | - fun evaluate(voiceFilePath: String, originText: String, @EvalTargetType evalTargetType: Int? = EvalTargetType.SENTENCE, userId: String? = VoiceConfig.UserID) { | ||
| 123 | - buildEvaluateConfig(originText, evalTargetType, userId) | ||
| 124 | - if (mIsReady) { | ||
| 125 | - mSingEngine?.startWithPCM(voiceFilePath) | ||
| 126 | - Log.w(TAG, "startWithPCM") | ||
| 127 | - } | ||
| 128 | - mCurEvalType = evalTargetType | ||
| 129 | - } | ||
| 130 | - | ||
| 131 | - private fun buildEvaluateConfig(originText: String, @EvalTargetType evalTargetType: Int? = EvalTargetType.SENTENCE, userId: String? = VoiceConfig.UserID) { | ||
| 132 | - try { | ||
| 133 | - val request = JSONObject() | ||
| 134 | - when (evalTargetType) { | ||
| 135 | - EvalTargetType.SENTENCE -> { | ||
| 136 | - request.put("coreType", VoiceConfig.TYPE_SENT_KID) | ||
| 137 | - .put("refText", originText.trim()) | ||
| 138 | - .put("rank", 100) // 评分分制,这个值可以任意设置,最终会根据与 100 的比例重新计算 | ||
| 139 | - .put("symbol", 1) // 用后标点符号 | ||
| 140 | - .put("typeThres", SingSoundConfig.BASE_TYPETHRES) | ||
| 141 | - .put("feedback", 1) // 是否开启实时评测 | ||
| 142 | - } | ||
| 143 | - EvalTargetType.ALPHA -> { | ||
| 144 | - if (originText.trim() == "/ɔr/") { // ishow_unexpected1 = ɔr | ||
| 145 | - val jsonObj = JSONObject() | ||
| 146 | - jsonObj.put("abc", "ao r") | ||
| 147 | - request.put("coreType", VoiceConfig.TYPE_WORD) | ||
| 148 | - .put("refText", "abc") | ||
| 149 | - // rateScale: 打分宽松度,0.8~1.5,默认 1.0。这个参数可以看作是个乘数,值越高打分越高。未 | ||
| 150 | - //来版本可能会放弃支持此参数,不建议使用,可用 typeThres 参数代替。 | ||
| 151 | - .put("typeThres", SingSoundConfig.BASE_TYPETHRES) | ||
| 152 | - .put("precision", 1) // 评分精度,默认1 | ||
| 153 | - .put("attachAudioUrl", 1) // 评分结果中是否包含音频 url | ||
| 154 | - .put("phones", jsonObj) // 指定单词的发音。 | ||
| 155 | - .put("rank", 100) | ||
| 156 | - } else { | ||
| 157 | - request.put("coreType", VoiceConfig.TYPE_ALPHA) | ||
| 158 | - .put("typeThres", SingSoundConfig.BASE_TYPETHRES) | ||
| 159 | - .put("refText", getSymbolText(originText.trim())) | ||
| 160 | - .put("rank", 100) | ||
| 161 | - } | ||
| 162 | - } | ||
| 163 | - EvalTargetType.WORD -> { | ||
| 164 | - request.put("coreType", VoiceConfig.TYPE_WORD) | ||
| 165 | - .put("refText", originText.trim()) | ||
| 166 | - .put("typeThres", SingSoundConfig.BASE_TYPETHRES) | ||
| 167 | - .put("typeThres", 0) | ||
| 168 | - .put("phdet", 1) // 音素检错,1 表示使用此功能,默认为 0,不启动; 只能设置 0 和 1 | ||
| 169 | - .put("syldet", 1) // 音节检错,1 表示使用此功能,默认为 0,不启动 只能设置 0 和 1 | ||
| 170 | - // .put("syllable", 1) // (单词题型支持评测音节;可以设置 syllable 字段)评测音节信息,1 表示使用此功能,默认为 0,不启动;只能设置 0 和 1 | ||
| 171 | - .put("rank", 100) | ||
| 172 | - } | ||
| 173 | - else -> { | ||
| 174 | - request.put("coreType", VoiceConfig.TYPE_SENT_KID) | ||
| 175 | - .put("refText", originText.trim()) | ||
| 176 | - .put("typeThres", SingSoundConfig.BASE_TYPETHRES) | ||
| 177 | - .put("rank", 100) // 评分分制,这个值可以任意设置,最终会根据与 100 的比例重新计算 | ||
| 178 | - .put("symbol", 1) // 用后标点符号 | ||
| 179 | - .put("feedback", false) // 是否开启实时评测 | ||
| 180 | - } | ||
| 181 | - } | ||
| 182 | - | ||
| 183 | - //构建评测请求参数 | ||
| 184 | - val startCfg = mSingEngine?.buildStartJson(userId, request) | ||
| 185 | - //设置评测请求参数 | ||
| 186 | - mSingEngine?.setStartCfg(startCfg) | ||
| 187 | - } catch (e: Exception) { | ||
| 188 | - e.printStackTrace() | ||
| 189 | - } | ||
| 190 | - } | ||
| 191 | - | ||
| 192 | - fun stopRecord() { // 停止录音(有回调) | ||
| 193 | - if (mIsReady) { | ||
| 194 | - mSingEngine?.stop() | ||
| 195 | - Log.w(TAG, "stopRecord") | ||
| 196 | - } | ||
| 197 | - } | ||
| 198 | - | ||
| 199 | - fun cancel() { // 取消录音(无回调onResult) | ||
| 200 | - if (mIsReady) { | ||
| 201 | - mCanceled = true | ||
| 202 | - mSingEngine?.cancel() | ||
| 203 | - mListeners?.let { | ||
| 204 | - for (callback in it) { | ||
| 205 | - callback.onCancel() | ||
| 206 | - } | ||
| 207 | - } | ||
| 208 | - Log.w(TAG, "cancel") | ||
| 209 | - } | ||
| 210 | - } | ||
| 211 | - | ||
| 212 | - // 播放录音 | ||
| 213 | - fun playBack() { | ||
| 214 | -// if (mSingEngine != null) { | ||
| 215 | -// val tokenid = SPUtils.getInstance().getString(VoiceConfig.cloud_sentece + 1) | ||
| 216 | -// if (tokenid != null) { | ||
| 217 | -// mSingEngine!!.playback() | ||
| 218 | -// } | ||
| 219 | -// } | ||
| 220 | - if (mIsReady) { | ||
| 221 | - mSingEngine?.playback() | ||
| 222 | - Log.w(TAG, "playBack") | ||
| 223 | - mCanceled = false | ||
| 224 | - } | ||
| 225 | - } | ||
| 226 | - | ||
| 227 | - /** | ||
| 228 | - * 获取录音文件 | ||
| 229 | - */ | ||
| 230 | - fun getRecordFilePath(): String? { | ||
| 231 | - return mSingEngine?.wavPath | ||
| 232 | - } | ||
| 233 | - | ||
| 234 | - /** | ||
| 235 | - * 停止播放录音 | ||
| 236 | - */ | ||
| 237 | - fun stopPlayBack() { | ||
| 238 | - if (mIsReady) { | ||
| 239 | - mSingEngine?.stopPlayBack() | ||
| 240 | - Log.w(TAG, "stopPlayBack") | ||
| 241 | - } | ||
| 242 | - } | ||
| 243 | - | ||
| 244 | - // (录音播放无法暂停)中断并重新播放 | ||
| 245 | - fun playWithInterrupt() { | ||
| 246 | - if (mIsReady) { | ||
| 247 | - mSingEngine?.playWithInterrupt() | ||
| 248 | - Log.w(TAG, "playWithInterrupt") | ||
| 249 | - } | ||
| 250 | - } | ||
| 251 | - | ||
| 252 | - /** | ||
| 253 | - * 停止录音、停止播放录音 | ||
| 254 | - */ | ||
| 255 | - fun release() { | ||
| 256 | - if (mIsReady) { | ||
| 257 | -// mSingEngine?.stopPlayBack() | ||
| 258 | - mSingEngine?.deleteSafe() | ||
| 259 | - } | ||
| 260 | - mListeners?.clear() | ||
| 261 | - mListeners = null | ||
| 262 | - mCanceled = false | ||
| 263 | - } | ||
| 264 | - | ||
| 265 | - override fun onAudioError(i: Int) { | ||
| 266 | - Log.e(TAG, "onAudioError $i") | ||
| 267 | - } | ||
| 268 | - | ||
| 269 | - /** | ||
| 270 | - * 实时反馈回调 | ||
| 271 | - * @param jsonObject | ||
| 272 | - */ | ||
| 273 | - override fun onRealTimeEval(jsonObject: JSONObject) { | ||
| 274 | - Log.d(TAG, "onRealTimeEval = $jsonObject") | ||
| 275 | - val realTimeResult = parseResult4Real(jsonObject) | ||
| 276 | - if (realTimeResult?.realtime_details?.all { it.dp_type == 0 } == true) { | ||
| 277 | - stopRecord() | ||
| 278 | - } | ||
| 279 | - } | ||
| 280 | - | ||
| 281 | - /** | ||
| 282 | - * 录音开始回调(可以提示用户录音开始或者开始动画等逻辑) | ||
| 283 | - */ | ||
| 284 | - override fun onBegin() { | ||
| 285 | - Log.i(TAG, "onBegin") | ||
| 286 | - mListeners?.let { | ||
| 287 | - for (callback in it) { | ||
| 288 | - callback.onRecordBegin() | ||
| 289 | - } | ||
| 290 | - } | ||
| 291 | - mCanceled = false | ||
| 292 | - } | ||
| 293 | - | ||
| 294 | - /** | ||
| 295 | - * 返回评测结果,评测结果为JSON格式 | ||
| 296 | - */ | ||
| 297 | - override fun onResult(jsonObject: JSONObject) { | ||
| 298 | - Log.i(TAG, "onResult = $jsonObject") | ||
| 299 | - setTokenToCache(jsonObject) | ||
| 300 | - mListeners?.let { | ||
| 301 | - for (callback in it) { | ||
| 302 | - callback.onResult(toMap(jsonObject), mCurEvalType) | ||
| 303 | - } | ||
| 304 | - } | ||
| 305 | - } | ||
| 306 | - | ||
| 307 | - private fun setTokenToCache(result: JSONObject) { | ||
| 308 | - try { | ||
| 309 | - if (result.has("tokenId")) { | ||
| 310 | - val tokenID = result.getString("tokenId") | ||
| 311 | - Log.e("tokenid", tokenID) | ||
| 312 | - } | ||
| 313 | - } catch (e: Exception) { | ||
| 314 | - e.printStackTrace() | ||
| 315 | - } | ||
| 316 | - } | ||
| 317 | - | ||
| 318 | - /** | ||
| 319 | - * 实时返回用户录音的音量大小 录音过程中会不断的回调此方法, | ||
| 320 | - * 实时返回音量大小,volume取值范围为0\~100。 | ||
| 321 | - * 用户可以根据volume的大小来实现用户录音音量大小的动画效果。 | ||
| 322 | - */ | ||
| 323 | - override fun onUpdateVolume(volume: Int) {} | ||
| 324 | - | ||
| 325 | - /** | ||
| 326 | - * 开启录音后,一直没有声音输入,前置超时(检测到没有录音)会调用此方法, | ||
| 327 | - * 用户可自己决定操作stop()或者cancel()。 | ||
| 328 | - */ | ||
| 329 | - override fun onFrontVadTimeOut() { | ||
| 330 | - Log.i(TAG, "onFrontVadTimeOut") | ||
| 331 | - stopRecord() | ||
| 332 | - } | ||
| 333 | - | ||
| 334 | - /** | ||
| 335 | - * 录音一段时间后不说话,后置超时,引擎自动调用stop(),结束录音并返回结果。 | ||
| 336 | - * 用户可监听此方法用于更新录音的UI界面 | ||
| 337 | - */ | ||
| 338 | - override fun onBackVadTimeOut() { | ||
| 339 | - Log.i(TAG, "onBackVadTimeOut") | ||
| 340 | - } | ||
| 341 | - | ||
| 342 | - override fun onRecordingBuffer(bytes: ByteArray, i: Int) { | ||
| 343 | - } | ||
| 344 | - | ||
| 345 | - /** | ||
| 346 | - * 评测录音长度超时回调 | ||
| 347 | - * 开发者可在该回调里调用stop()方法等待返回测评结果,或不做任何处理等待录音超时的错误码。 | ||
| 348 | - * 通过超时错误码提示产品的用户。 | ||
| 349 | - */ | ||
| 350 | - override fun onRecordLengthOut() { | ||
| 351 | - Log.i(TAG, "onRecordLengthOut") | ||
| 352 | - stopRecord() | ||
| 353 | - } | ||
| 354 | - | ||
| 355 | - override fun onReady() { | ||
| 356 | - Log.i(TAG, "onReady") | ||
| 357 | - mIsReady = true | ||
| 358 | - mCanceled = false | ||
| 359 | - } | ||
| 360 | - | ||
| 361 | - /** | ||
| 362 | - * 播放录音完成回调 | ||
| 363 | - */ | ||
| 364 | - override fun onPlayCompeleted() { | ||
| 365 | - Log.i(TAG, "onPlayCompeleted") | ||
| 366 | - mListeners?.let { | ||
| 367 | - for (callback in it) { | ||
| 368 | - callback.onRecordPlayOver() | ||
| 369 | - } | ||
| 370 | - } | ||
| 371 | - } | ||
| 372 | - | ||
| 373 | - /** | ||
| 374 | - * 当录音停止并写入成功后回调 | ||
| 375 | - */ | ||
| 376 | - override fun onRecordStop() { | ||
| 377 | - Log.i(TAG, "onRecordStop mCanceled = $mCanceled") | ||
| 378 | - if (!mCanceled) { | ||
| 379 | - mListeners?.let { | ||
| 380 | - for (callback in it) { | ||
| 381 | - callback.onRecordStop() | ||
| 382 | - } | ||
| 383 | - } | ||
| 384 | - } else { | ||
| 385 | - mCanceled = false | ||
| 386 | - } | ||
| 387 | - } | ||
| 388 | - | ||
| 389 | - /** | ||
| 390 | - * 返回评测或初始化引擎失败原因,resultBody.getCode() 等于0为正确返回, | ||
| 391 | - * 其他错误码见下错误码说明 | ||
| 392 | - */ | ||
| 393 | - override fun onEnd(resultBody: ResultBody) { | ||
| 394 | - Log.i(TAG, "onEnd resultBody=$resultBody") | ||
| 395 | - if (resultBody.code != 0) { | ||
| 396 | - mListeners?.let { | ||
| 397 | - for (callback in it) { | ||
| 398 | - callback.onRecordFail(resultBody.code, resultBody.message) | ||
| 399 | - } | ||
| 400 | - } | ||
| 401 | - } | ||
| 402 | - } | ||
| 403 | - | ||
| 404 | - override fun onGetEvalRequestId(p0: String?) { | ||
| 405 | - | ||
| 406 | - } | ||
| 407 | - | ||
| 408 | - fun addOnResultListener(listener: SingEngineLifecycles) { | ||
| 409 | - Log.i(TAG, "addOnResultListener") | ||
| 410 | - if (mListeners?.contains(listener) == true) { | ||
| 411 | - return | ||
| 412 | - } | ||
| 413 | - mListeners?.add(listener) | ||
| 414 | - } | ||
| 415 | - | ||
| 416 | - fun removeOnResultListener(listener: SingEngineLifecycles) { | ||
| 417 | - Log.i(TAG, "removeOnResultListener") | ||
| 418 | - if (mListeners?.contains(listener) == true) { | ||
| 419 | - mListeners?.remove(listener) | ||
| 420 | - } | ||
| 421 | - } | ||
| 422 | - | ||
| 423 | - /** | ||
| 424 | - * 音标转化表 | ||
| 425 | - */ | ||
| 426 | - private fun getSymbolsMap() { | ||
| 427 | - val linkedHashMap = | ||
| 428 | - Gson().fromJson(WordConfig.getMapJSONObject().toString(), LinkedHashMap::class.java) | ||
| 429 | - for (key in linkedHashMap.keys) { | ||
| 430 | -// mAlphaMap[linkedHashMap[key].toString()] = key.toString() | ||
| 431 | - mAlphaMap[key.toString()] = linkedHashMap[key].toString() | ||
| 432 | - } | ||
| 433 | - } | ||
| 434 | - | ||
| 435 | - /** | ||
| 436 | - * 音标文本前后有"/",去掉 | ||
| 437 | - */ | ||
| 438 | - private fun getSymbolText(s: String): String? { | ||
| 439 | - val substring = s.substring(1, s.length - 1) | ||
| 440 | - return if (mAlphaMap.containsKey(substring)) { | ||
| 441 | - mAlphaMap[substring] | ||
| 442 | - } else "" | ||
| 443 | - } | ||
| 444 | -} | ||
| 445 | - |
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/bean/BaseResultModel.kt deleted
| 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 | - mutableListOf<SingleResultModel>().apply { | ||
| 23 | - parcel.readTypedList(this, SingleResultModel.CREATOR) | ||
| 24 | - }, | ||
| 25 | - parcel.readDouble(), | ||
| 26 | - parcel.readDouble() | ||
| 27 | - ) { | ||
| 28 | - } | ||
| 29 | - | ||
| 30 | - // 每个单词测评结果model | ||
| 31 | - class SingleResultModel( | ||
| 32 | - val char: String?, | ||
| 33 | - val score: Double | ||
| 34 | - ) : Parcelable { | ||
| 35 | - constructor(parcel: Parcel) : this( | ||
| 36 | - parcel.readString(), | ||
| 37 | - parcel.readDouble() | ||
| 38 | - ) { | ||
| 39 | - } | ||
| 40 | - | ||
| 41 | - override fun writeToParcel(parcel: Parcel, flags: Int) { | ||
| 42 | - parcel.writeString(char) | ||
| 43 | - parcel.writeDouble(score) | ||
| 44 | - } | ||
| 45 | - | ||
| 46 | - override fun describeContents(): Int { | ||
| 47 | - return 0 | ||
| 48 | - } | ||
| 49 | - | ||
| 50 | - companion object CREATOR : Parcelable.Creator<SingleResultModel> { | ||
| 51 | - override fun createFromParcel(parcel: Parcel): SingleResultModel { | ||
| 52 | - return SingleResultModel(parcel) | ||
| 53 | - } | ||
| 54 | - | ||
| 55 | - override fun newArray(size: Int): Array<SingleResultModel?> { | ||
| 56 | - return arrayOfNulls(size) | ||
| 57 | - } | ||
| 58 | - } | ||
| 59 | - } | ||
| 60 | - | ||
| 61 | - override fun writeToParcel(parcel: Parcel, flags: Int) { | ||
| 62 | - parcel.writeString(originText) | ||
| 63 | - parcel.writeDouble(score) | ||
| 64 | - parcel.writeTypedList(scores) | ||
| 65 | - parcel.writeDouble(pronounce) | ||
| 66 | - parcel.writeDouble(fluency) | ||
| 67 | - } | ||
| 68 | - | ||
| 69 | - override fun describeContents(): Int { | ||
| 70 | - return 0 | ||
| 71 | - } | ||
| 72 | - | ||
| 73 | - companion object CREATOR : Parcelable.Creator<BaseResultModel> { | ||
| 74 | - override fun createFromParcel(parcel: Parcel): BaseResultModel { | ||
| 75 | - return BaseResultModel(parcel) | ||
| 76 | - } | ||
| 77 | - | ||
| 78 | - override fun newArray(size: Int): Array<BaseResultModel?> { | ||
| 79 | - return arrayOfNulls(size) | ||
| 80 | - } | ||
| 81 | - } | ||
| 82 | -} | ||
| 83 | - |
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/bean/FullEvaluationResult.txt deleted
| 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 | -} | ||
| 134 | \ No newline at end of file | 0 | \ No newline at end of file |
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/bean/RealtimeResultEntity.kt deleted
| 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 | -) | ||
| 24 | \ No newline at end of file | 0 | \ No newline at end of file |
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/bean/SentenceCode.java deleted
| 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 deleted
| 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 deleted
| 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 deleted
| 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 deleted
| 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 deleted
| 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 | -} | ||
| 20 | \ No newline at end of file | 0 | \ No newline at end of file |
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/config/SentenceConfig.java deleted
| 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 deleted
| 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 deleted
| 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 deleted
| 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 deleted
| 1 | -package com.kouyuxingqiu.wow_english.singsound.util; | ||
| 2 | - | ||
| 3 | -import android.util.Log; | ||
| 4 | - | ||
| 5 | -import com.google.gson.Gson; | ||
| 6 | -import com.google.gson.JsonElement; | ||
| 7 | -import com.google.gson.JsonObject; | ||
| 8 | - | ||
| 9 | -import org.json.JSONArray; | ||
| 10 | -import org.json.JSONException; | ||
| 11 | -import org.json.JSONObject; | ||
| 12 | - | ||
| 13 | -import java.util.ArrayList; | ||
| 14 | -import java.util.HashMap; | ||
| 15 | -import java.util.Iterator; | ||
| 16 | -import java.util.List; | ||
| 17 | -import java.util.Map; | ||
| 18 | -import java.util.Set; | ||
| 19 | - | ||
| 20 | -/** | ||
| 21 | - * Created by wangz on 2017/8/29. | ||
| 22 | - */ | ||
| 23 | - | ||
| 24 | -public class JsonUtils { | ||
| 25 | - private static final String TAG = "JSONUtils"; | ||
| 26 | - | ||
| 27 | - public static JSONObject getJsonObject(JSONArray array, int postion) { | ||
| 28 | - try { | ||
| 29 | - return array.getJSONObject(postion); | ||
| 30 | - } catch (JSONException e) { | ||
| 31 | - e.printStackTrace(); | ||
| 32 | - return new JSONObject(); | ||
| 33 | - } | ||
| 34 | - } | ||
| 35 | - | ||
| 36 | - public static JSONObject getJsonObject(JSONObject object, String key) { | ||
| 37 | - try { | ||
| 38 | - return object.getJSONObject(key); | ||
| 39 | - } catch (Exception e) { | ||
| 40 | - Log.e(TAG, e.getLocalizedMessage()); | ||
| 41 | - return new JSONObject(); | ||
| 42 | - } | ||
| 43 | - } | ||
| 44 | - | ||
| 45 | - public static JSONArray getJsonArray(JSONArray object, int key) { | ||
| 46 | - try { | ||
| 47 | - return object.getJSONArray(key); | ||
| 48 | - } catch (Exception e) { | ||
| 49 | - Log.e(TAG, e.getLocalizedMessage()); | ||
| 50 | - return new JSONArray(); | ||
| 51 | - } | ||
| 52 | - } | ||
| 53 | - | ||
| 54 | - public static JSONArray getJsonArray(JSONObject object, String key) { | ||
| 55 | - try { | ||
| 56 | - return object.getJSONArray(key); | ||
| 57 | - } catch (Exception e) { | ||
| 58 | - Log.e(TAG, e.getLocalizedMessage()); | ||
| 59 | - return new JSONArray(); | ||
| 60 | - } | ||
| 61 | - } | ||
| 62 | - | ||
| 63 | - public static String getString(JSONObject object, String key) { | ||
| 64 | - try { | ||
| 65 | - return object.getString(key); | ||
| 66 | - } catch (Exception e) { | ||
| 67 | - Log.e(TAG, e.getLocalizedMessage()); | ||
| 68 | - return ""; | ||
| 69 | - } | ||
| 70 | - } | ||
| 71 | - | ||
| 72 | - public static int getInt(JSONObject object, String key) { | ||
| 73 | - try { | ||
| 74 | - return object.getInt(key); | ||
| 75 | - } catch (Exception e) { | ||
| 76 | - Log.e(TAG, e.getLocalizedMessage()); | ||
| 77 | - return 0; | ||
| 78 | - } | ||
| 79 | - } | ||
| 80 | - | ||
| 81 | - public static double getDouble(JSONObject object, String key) { | ||
| 82 | - try { | ||
| 83 | - return object.getDouble(key); | ||
| 84 | - } catch (Exception e) { | ||
| 85 | - Log.e(TAG, e.getLocalizedMessage()); | ||
| 86 | - return 0; | ||
| 87 | - } | ||
| 88 | - } | ||
| 89 | - | ||
| 90 | - public static boolean getBoolean(JSONObject object, String key) { | ||
| 91 | - try { | ||
| 92 | - return object.getBoolean(key); | ||
| 93 | - } catch (Exception e) { | ||
| 94 | - Log.e(TAG, e.getLocalizedMessage()); | ||
| 95 | - return false; | ||
| 96 | - } | ||
| 97 | - } | ||
| 98 | - | ||
| 99 | - public static Map<String, Object> toMap(JSONObject jsonObject) throws JSONException { | ||
| 100 | - Map<String, Object> map = new HashMap<>(); | ||
| 101 | - Iterator<String> keysIterator = jsonObject.keys(); | ||
| 102 | - while (keysIterator.hasNext()) { | ||
| 103 | - String key = keysIterator.next(); | ||
| 104 | - Object value = jsonObject.get(key); | ||
| 105 | - if (value instanceof JSONObject) { | ||
| 106 | - value = toMap((JSONObject) value); | ||
| 107 | - } | ||
| 108 | - if (value instanceof JSONArray) { | ||
| 109 | - value = toList((JSONArray) value); | ||
| 110 | - } | ||
| 111 | - map.put(key, value); | ||
| 112 | - } | ||
| 113 | - return map; | ||
| 114 | - } | ||
| 115 | - | ||
| 116 | - public static List<Object> toList(JSONArray jsonArray) throws JSONException { | ||
| 117 | - List<Object> list = new ArrayList<>(); | ||
| 118 | - for (int i = 0; i < jsonArray.length(); i++) { | ||
| 119 | - Object value = jsonArray.get(i); | ||
| 120 | - if (value instanceof JSONObject || value instanceof JSONArray) { | ||
| 121 | - value = toObject(value); | ||
| 122 | - } | ||
| 123 | - list.add(value); | ||
| 124 | - } | ||
| 125 | - return list; | ||
| 126 | - } | ||
| 127 | - | ||
| 128 | - public static Object toObject(Object json) throws JSONException { | ||
| 129 | - if (json instanceof JSONObject) { | ||
| 130 | - return toMap((JSONObject) json); | ||
| 131 | - } else if (json instanceof JSONArray) { | ||
| 132 | - return toList((JSONArray) json); | ||
| 133 | - } | ||
| 134 | - return json; | ||
| 135 | - } | ||
| 136 | -} |
android/build.gradle
| @@ -26,7 +26,6 @@ allprojects { | @@ -26,7 +26,6 @@ allprojects { | ||
| 26 | // maven { url 'https://maven.aliyun.com/nexus/content/groups/public' } | 26 | // maven { url 'https://maven.aliyun.com/nexus/content/groups/public' } |
| 27 | google() | 27 | google() |
| 28 | mavenCentral() | 28 | mavenCentral() |
| 29 | - maven { url 'https://repo.singsound.com/repository/singsound_ginger_android_sdk/' } | ||
| 30 | maven { url 'https://maven.zjzxsl.com/repository/android-public/' } | 29 | maven { url 'https://maven.zjzxsl.com/repository/android-public/' } |
| 31 | } | 30 | } |
| 32 | } | 31 | } |
assets/chivox/aiengine.provision
0 → 100644
assets/chivox/vad.0.13.bin
0 → 100644
No preview for this file type
ios/Podfile
| 1 | -source 'https://github.com/CocoaPods/Specs.git' | ||
| 2 | -source 'https://pt.singsound.com:10081/singsound-public/SingSoundSDKCocoaPodRepo.git' | 1 | +source 'https://cdn.cocoapods.org/' |
| 3 | 2 | ||
| 4 | platform :ios, '12.0' | 3 | platform :ios, '12.0' |
| 5 | 4 | ||
| @@ -32,7 +31,6 @@ flutter_ios_podfile_setup | @@ -32,7 +31,6 @@ flutter_ios_podfile_setup | ||
| 32 | target 'Runner' do | 31 | target 'Runner' do |
| 33 | use_frameworks! | 32 | use_frameworks! |
| 34 | use_modular_headers! | 33 | use_modular_headers! |
| 35 | - pod 'SingSoundSDK' | ||
| 36 | pod 'DMProgressHUD' | 34 | pod 'DMProgressHUD' |
| 37 | 35 | ||
| 38 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) | 36 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) |
ios/Runner.xcodeproj/project.pbxproj
| @@ -10,8 +10,6 @@ | @@ -10,8 +10,6 @@ | ||
| 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; | 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; |
| 11 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; | 11 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; |
| 12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; | 12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; |
| 13 | - 52450AF12A4C415B007B3E4B /* XSMessageMehtodChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52450AF02A4C415B007B3E4B /* XSMessageMehtodChannel.swift */; }; | ||
| 14 | - 525E171A2A4BD03900104CDF /* VoiceXSMessageChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 525E17192A4BD03900104CDF /* VoiceXSMessageChannel.swift */; }; | ||
| 15 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; | 13 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; |
| 16 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; | 14 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; |
| 17 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; | 15 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; |
| @@ -450,9 +448,7 @@ | @@ -450,9 +448,7 @@ | ||
| 450 | 3563EC8D55A646823FD26A83 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; }; | 448 | 3563EC8D55A646823FD26A83 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; }; |
| 451 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; }; | 449 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; }; |
| 452 | 48BCA0827DCB98991774F5AC /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; }; | 450 | 48BCA0827DCB98991774F5AC /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; }; |
| 453 | - 52450AF02A4C415B007B3E4B /* XSMessageMehtodChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSMessageMehtodChannel.swift; sourceTree = "<group>"; }; | ||
| 454 | 52450AF22A4ED0EC007B3E4B /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; }; | 451 | 52450AF22A4ED0EC007B3E4B /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; }; |
| 455 | - 525E17192A4BD03900104CDF /* VoiceXSMessageChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceXSMessageChannel.swift; sourceTree = "<group>"; }; | ||
| 456 | 6DEBBC1D861BE053F3ECE0B9 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; | 452 | 6DEBBC1D861BE053F3ECE0B9 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; |
| 457 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; }; | 453 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; }; |
| 458 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; | 454 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; |
| @@ -1027,8 +1023,6 @@ | @@ -1027,8 +1023,6 @@ | ||
| 1027 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, | 1023 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, |
| 1028 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, | 1024 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, |
| 1029 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, | 1025 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, |
| 1030 | - 52450AF02A4C415B007B3E4B /* XSMessageMehtodChannel.swift */, | ||
| 1031 | - 525E17192A4BD03900104CDF /* VoiceXSMessageChannel.swift */, | ||
| 1032 | B852C1342BCABB5E00A53FC4 /* GameMessageChannel.swift */, | 1026 | B852C1342BCABB5E00A53FC4 /* GameMessageChannel.swift */, |
| 1033 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, | 1027 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, |
| 1034 | ); | 1028 | ); |
| @@ -1955,7 +1949,6 @@ | @@ -1955,7 +1949,6 @@ | ||
| 1955 | B891A85B2BD24EFB006CB06E /* AniSimpleButton.cpp in Sources */, | 1949 | B891A85B2BD24EFB006CB06E /* AniSimpleButton.cpp in Sources */, |
| 1956 | B891A8282BD24EFB006CB06E /* TwoStateButton.cpp in Sources */, | 1950 | B891A8282BD24EFB006CB06E /* TwoStateButton.cpp in Sources */, |
| 1957 | B891A8B42BD24EFB006CB06E /* ToyTouchableSprite.cpp in Sources */, | 1951 | B891A8B42BD24EFB006CB06E /* ToyTouchableSprite.cpp in Sources */, |
| 1958 | - 525E171A2A4BD03900104CDF /* VoiceXSMessageChannel.swift in Sources */, | ||
| 1959 | B891A8842BD24EFB006CB06E /* ToyDragAndDropHandler.cpp in Sources */, | 1952 | B891A8842BD24EFB006CB06E /* ToyDragAndDropHandler.cpp in Sources */, |
| 1960 | B891A88C2BD24EFB006CB06E /* ToyLayoutObject.cpp in Sources */, | 1953 | B891A88C2BD24EFB006CB06E /* ToyLayoutObject.cpp in Sources */, |
| 1961 | B891A81D2BD24EFB006CB06E /* SimpleLevelPickerView.cpp in Sources */, | 1954 | B891A81D2BD24EFB006CB06E /* SimpleLevelPickerView.cpp in Sources */, |
| @@ -2024,7 +2017,6 @@ | @@ -2024,7 +2017,6 @@ | ||
| 2024 | B891A8102BD24EFB006CB06E /* ParentalGateShowInterface.cpp in Sources */, | 2017 | B891A8102BD24EFB006CB06E /* ParentalGateShowInterface.cpp in Sources */, |
| 2025 | B891A82F2BD24EFB006CB06E /* AniBasicSteveMapCharacterController.cpp in Sources */, | 2018 | B891A82F2BD24EFB006CB06E /* AniBasicSteveMapCharacterController.cpp in Sources */, |
| 2026 | B891A80A2BD24EFA006CB06E /* LayoutParser.cpp in Sources */, | 2019 | B891A80A2BD24EFA006CB06E /* LayoutParser.cpp in Sources */, |
| 2027 | - 52450AF12A4C415B007B3E4B /* XSMessageMehtodChannel.swift in Sources */, | ||
| 2028 | B891A88A2BD24EFB006CB06E /* ToyGeometryUtils.cpp in Sources */, | 2020 | B891A88A2BD24EFB006CB06E /* ToyGeometryUtils.cpp in Sources */, |
| 2029 | B891A8A32BD24EFB006CB06E /* ToyScenarioHandler.cpp in Sources */, | 2021 | B891A8A32BD24EFB006CB06E /* ToyScenarioHandler.cpp in Sources */, |
| 2030 | B891A84A2BD24EFB006CB06E /* AniMathUtils.cpp in Sources */, | 2022 | B891A84A2BD24EFB006CB06E /* AniMathUtils.cpp in Sources */, |
| @@ -2420,7 +2412,6 @@ | @@ -2420,7 +2412,6 @@ | ||
| 2420 | "-framework", | 2412 | "-framework", |
| 2421 | "\"Reachability\"", | 2413 | "\"Reachability\"", |
| 2422 | "-framework", | 2414 | "-framework", |
| 2423 | - "\"SingSound\"", | ||
| 2424 | "-framework", | 2415 | "-framework", |
| 2425 | "\"SystemConfiguration\"", | 2416 | "\"SystemConfiguration\"", |
| 2426 | "-framework", | 2417 | "-framework", |
| @@ -2779,7 +2770,6 @@ | @@ -2779,7 +2770,6 @@ | ||
| 2779 | "-framework", | 2770 | "-framework", |
| 2780 | "\"Reachability\"", | 2771 | "\"Reachability\"", |
| 2781 | "-framework", | 2772 | "-framework", |
| 2782 | - "\"SingSound\"", | ||
| 2783 | "-framework", | 2773 | "-framework", |
| 2784 | "\"SystemConfiguration\"", | 2774 | "\"SystemConfiguration\"", |
| 2785 | "-framework", | 2775 | "-framework", |
| @@ -2965,7 +2955,6 @@ | @@ -2965,7 +2955,6 @@ | ||
| 2965 | "-framework", | 2955 | "-framework", |
| 2966 | "\"Reachability\"", | 2956 | "\"Reachability\"", |
| 2967 | "-framework", | 2957 | "-framework", |
| 2968 | - "\"SingSound\"", | ||
| 2969 | "-framework", | 2958 | "-framework", |
| 2970 | "\"SystemConfiguration\"", | 2959 | "\"SystemConfiguration\"", |
| 2971 | "-framework", | 2960 | "-framework", |
ios/Runner/AppDelegate.swift
| @@ -14,8 +14,6 @@ import Flutter | @@ -14,8 +14,6 @@ import Flutter | ||
| 14 | 14 | ||
| 15 | GeneratedPluginRegistrant.register(with: self) | 15 | GeneratedPluginRegistrant.register(with: self) |
| 16 | let controller : FlutterViewController = window?.rootViewController as! FlutterViewController | 16 | let controller : FlutterViewController = window?.rootViewController as! FlutterViewController |
| 17 | - _ = VoiceXSMessageChannel(messager: controller.binaryMessenger) | ||
| 18 | - _ = XSMessageMehtodChannel(message: controller.binaryMessenger); | ||
| 19 | _ = GameMessageChannel(message: controller.binaryMessenger); | 17 | _ = GameMessageChannel(message: controller.binaryMessenger); |
| 20 | 18 | ||
| 21 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) | 19 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) |
ios/Runner/Runner-Bridging-Header.h
| @@ -3,9 +3,6 @@ | @@ -3,9 +3,6 @@ | ||
| 3 | #ifndef Runner_Bridging_Header_h | 3 | #ifndef Runner_Bridging_Header_h |
| 4 | #define Runner_Bridging_Header_h | 4 | #define Runner_Bridging_Header_h |
| 5 | 5 | ||
| 6 | -// SingSound | ||
| 7 | -#import <SingSound/SSOralEvaluatingManager.h> | ||
| 8 | - | ||
| 9 | // UMCommon | 6 | // UMCommon |
| 10 | #import <UMCommon/UMCommon.h> | 7 | #import <UMCommon/UMCommon.h> |
| 11 | #import <UMCommon/UMConfigure.h> | 8 | #import <UMCommon/UMConfigure.h> |
ios/Runner/VoiceXSMessageChannel.swift deleted
| 1 | -// | ||
| 2 | -// VoiceXSMessageChannel.swift | ||
| 3 | -// Runner | ||
| 4 | -// | ||
| 5 | -// Created by MacBook Pro on 2023/6/28. | ||
| 6 | -// | ||
| 7 | - | ||
| 8 | -import UIKit | ||
| 9 | - | ||
| 10 | -class VoiceXSMessageChannel: NSObject,SSOralEvaluatingManagerDelegate { | ||
| 11 | - var resultData:Dictionary<String, Any>? | ||
| 12 | - var channel:FlutterBasicMessageChannel? | ||
| 13 | - init(messager:FlutterBinaryMessenger) { | ||
| 14 | - super.init() | ||
| 15 | - resultData = Dictionary() | ||
| 16 | - self.setEvaluateConfig() | ||
| 17 | - channel = FlutterBasicMessageChannel(name: "com.owEnglish.voiceXs.BasicMessageChannel", binaryMessenger: messager) | ||
| 18 | - channel!.setMessageHandler { message, reply in | ||
| 19 | - if let dict = message as? Dictionary<String, Any> { | ||
| 20 | - self.evaluateVioce(dict: dict); | ||
| 21 | - } | ||
| 22 | - } | ||
| 23 | - } | ||
| 24 | - | ||
| 25 | - //配置评测信息 | ||
| 26 | - func setEvaluateConfig() { | ||
| 27 | - let config = SSOralEvaluatingManagerConfig.init() | ||
| 28 | - config.appKey = "a418" | ||
| 29 | - config.secretKey = "1a16f31f2611bf32fb7b3fc38f5b2c81" | ||
| 30 | - config.vad = true | ||
| 31 | - config.frontTime = 3 | ||
| 32 | - config.backTime = 3 | ||
| 33 | - config.isOutputLog = false | ||
| 34 | - SSOralEvaluatingManager.register(config) | ||
| 35 | - SSOralEvaluatingManager.share().register(.line, userId: "321") | ||
| 36 | - SSOralEvaluatingManager.share().delegate = self | ||
| 37 | - } | ||
| 38 | - | ||
| 39 | - //开始评测 | ||
| 40 | - func evaluateVioce(dict:Dictionary<String, Any>) { | ||
| 41 | - let text = dict["word"] as! String | ||
| 42 | - let type = dict["type"] as! Int | ||
| 43 | - let userId = dict["userId"] as! String | ||
| 44 | - let config = SSOralEvaluatingConfig() | ||
| 45 | - config.oralContent = text | ||
| 46 | - if (type == 0) { | ||
| 47 | - config.oralType = .word | ||
| 48 | - } else { | ||
| 49 | - config.oralType = .sentence | ||
| 50 | - } | ||
| 51 | - config.userId = userId | ||
| 52 | - SSOralEvaluatingManager.share().startEvaluateOral(with: config) | ||
| 53 | - } | ||
| 54 | - | ||
| 55 | - //评测结果回调 | ||
| 56 | - func evaluateResult() { | ||
| 57 | - channel!.sendMessage(resultData) {(reply) in | ||
| 58 | - self.resultData?.removeAll() | ||
| 59 | - } | ||
| 60 | - } | ||
| 61 | - | ||
| 62 | - //SSOralEvaluatingManagerDelegate | ||
| 63 | - /** | ||
| 64 | - 评测开始 | ||
| 65 | - */ | ||
| 66 | - func oralEvaluatingDidStart() { | ||
| 67 | - print("评测开始") | ||
| 68 | - } | ||
| 69 | - | ||
| 70 | - /** | ||
| 71 | - 评测停止 | ||
| 72 | - */ | ||
| 73 | - func oralEvaluatingDidStop() { | ||
| 74 | - print("评测结束") | ||
| 75 | - } | ||
| 76 | - | ||
| 77 | - /** | ||
| 78 | - 评测完成后的结果 | ||
| 79 | - */ | ||
| 80 | - func oralEvaluatingDidEnd(withResult result: [AnyHashable : Any]?, requestId request_id: String?) { | ||
| 81 | - print("评测完成结果") | ||
| 82 | - let resultDict:Dictionary<String, Any> = result?["result"] as! Dictionary | ||
| 83 | - resultData!["result"] = "1" | ||
| 84 | - //分数 | ||
| 85 | - resultData!["overall"] = resultDict["overall"] | ||
| 86 | - self.evaluateResult() | ||
| 87 | - } | ||
| 88 | - | ||
| 89 | - /** | ||
| 90 | - 评测失败回调 | ||
| 91 | - */ | ||
| 92 | - func oralEvaluatingDidEndError(_ error: Error?, requestId request_id: String?) { | ||
| 93 | - print("评测失败") | ||
| 94 | - resultData!["result"] = "0" | ||
| 95 | - self.evaluateResult() | ||
| 96 | - } | ||
| 97 | - | ||
| 98 | - /** | ||
| 99 | - VAD(前置时间)超时回调 | ||
| 100 | - */ | ||
| 101 | - func oralEvaluatingDidVADFrontTimeOut() { | ||
| 102 | - print("前置超时--->取消") | ||
| 103 | - SSOralEvaluatingManager.share().cancelEvaluate() | ||
| 104 | - if(resultData?.keys.count == 0) { | ||
| 105 | - resultData!["result"] = "0" | ||
| 106 | - self.evaluateResult(); | ||
| 107 | - } | ||
| 108 | - } | ||
| 109 | - | ||
| 110 | - /** | ||
| 111 | - VAD(后置时间)超时回调 | ||
| 112 | - */ | ||
| 113 | - func oralEvaluatingDidVADBackTimeOut() { | ||
| 114 | - print("后置超时--->结束") | ||
| 115 | - ///结束回调 | ||
| 116 | - SSOralEvaluatingManager.share().stopEvaluate(); | ||
| 117 | - } | ||
| 118 | -} |
ios/Runner/XSMessageMehtodChannel.swift deleted
| 1 | -// | ||
| 2 | -// XSMessageMehtodChannel.swift | ||
| 3 | -// Runner | ||
| 4 | -// | ||
| 5 | -// Created by MacBook Pro on 2023/6/28. | ||
| 6 | -// | ||
| 7 | - | ||
| 8 | -import UIKit | ||
| 9 | - | ||
| 10 | -class XSMessageMehtodChannel: NSObject,SSOralEvaluatingManagerDelegate { | ||
| 11 | - var resultData:Dictionary<String, Any>? | ||
| 12 | - var messageChannel:FlutterMethodChannel? | ||
| 13 | - init(message:FlutterBinaryMessenger) { | ||
| 14 | - super.init() | ||
| 15 | - resultData = Dictionary() | ||
| 16 | - messageChannel = FlutterMethodChannel.init(name: "wow_english/sing_sound_method_channel", binaryMessenger: message) | ||
| 17 | - messageChannel!.setMethodCallHandler { call, result in | ||
| 18 | - self.handle(call, result) | ||
| 19 | - } | ||
| 20 | - } | ||
| 21 | - | ||
| 22 | - //配置评测信息 | ||
| 23 | - func setEvaluateConfig(dict:Dictionary<String, Any>) { | ||
| 24 | - var appKey = "a418" | ||
| 25 | - var secretKey = "c11163aa6c834a028da4a4b30955be99" | ||
| 26 | - var service = "wss://api.cloud.ssapi.cn" | ||
| 27 | - var userId = "guest" | ||
| 28 | - var frontTime = "3" | ||
| 29 | - var backTime = "3" | ||
| 30 | - if (!dict.keys.isEmpty) { | ||
| 31 | - appKey = dict["appKey"] as? String ?? "" | ||
| 32 | - secretKey = dict["secretKey"] as? String ?? "" | ||
| 33 | - service = dict["service"] as? String ?? "" | ||
| 34 | - userId = dict["userId"] as? String ?? "guest" | ||
| 35 | - frontTime = dict["frontTime"] as? String ?? "3" | ||
| 36 | - backTime = dict["frontTime"] as? String ?? "3" | ||
| 37 | - } | ||
| 38 | - let config = SSOralEvaluatingManagerConfig.init() | ||
| 39 | - config.vad = true | ||
| 40 | - config.isOutputLog = false | ||
| 41 | - config.appKey = appKey | ||
| 42 | - config.secretKey = secretKey | ||
| 43 | - config.frontTime = Double(frontTime)! | ||
| 44 | - config.backTime = Double(backTime)! | ||
| 45 | - config.setValue(service, forKey: "service") | ||
| 46 | - SSOralEvaluatingManager.register(config) | ||
| 47 | - SSOralEvaluatingManager.share().register(.line, userId: userId) | ||
| 48 | - SSOralEvaluatingManager.share().delegate = self | ||
| 49 | - } | ||
| 50 | - | ||
| 51 | - //开始评测 | ||
| 52 | - func evaluateVoice(dict:Dictionary<String, Any>) { | ||
| 53 | - let text = dict["word"] as? String ?? "" | ||
| 54 | - let type = dict["type"] as? String ?? "0" | ||
| 55 | - let userId = dict["userId"] as? String ?? "guest" | ||
| 56 | - let config = SSOralEvaluatingConfig() | ||
| 57 | - config.oralContent = text | ||
| 58 | - if (type == "0") { | ||
| 59 | - config.oralType = .word | ||
| 60 | - } else { | ||
| 61 | - config.oralType = .sentence | ||
| 62 | - } | ||
| 63 | - config.oralType = .kidSent | ||
| 64 | - config.userId = userId | ||
| 65 | - SSOralEvaluatingManager.share().startEvaluateOral(with: config) | ||
| 66 | - } | ||
| 67 | - | ||
| 68 | - //开始评测(本地音频文件) | ||
| 69 | - func evaluateLocalVoice(dict:Dictionary<String, Any>) { | ||
| 70 | - let text = dict["word"] as? String ?? "" | ||
| 71 | - let type = dict["type"] as? String ?? "0" | ||
| 72 | - let userId = dict["userId"] as? String ?? "guest" | ||
| 73 | - let voicePath = dict["voicePath"] as? String ?? "" | ||
| 74 | - let config = SSOralEvaluatingConfig() | ||
| 75 | - config.oralContent = text | ||
| 76 | - if (type == "0") { | ||
| 77 | - config.oralType = .word | ||
| 78 | - } else { | ||
| 79 | - config.oralType = .sentence | ||
| 80 | - } | ||
| 81 | - config.oralType = .sentence | ||
| 82 | - config.userId = userId | ||
| 83 | - SSOralEvaluatingManager.share().startEvaluateOral(withWavPath: voicePath, config: config) | ||
| 84 | - } | ||
| 85 | - | ||
| 86 | - func handle(_ call: FlutterMethodCall,_ result: @escaping FlutterResult) { | ||
| 87 | - if (call.method == "initVoiceSdk") { | ||
| 88 | - self.setEvaluateConfig(dict:call.arguments as! Dictionary<String, Any>) | ||
| 89 | - return | ||
| 90 | - } | ||
| 91 | - if (call.method == "startVoice") { | ||
| 92 | - self.evaluateVoice(dict: call.arguments as! Dictionary<String, Any>) | ||
| 93 | - return | ||
| 94 | - } | ||
| 95 | - | ||
| 96 | - if (call.method == "startLocalVoice") { | ||
| 97 | - self.evaluateLocalVoice(dict: call.arguments as! Dictionary<String, Any>) | ||
| 98 | - return | ||
| 99 | - } | ||
| 100 | - | ||
| 101 | - if (call.method == "stopVoice") { | ||
| 102 | - SSOralEvaluatingManager.share().stopEvaluate(); | ||
| 103 | - return | ||
| 104 | - } | ||
| 105 | - | ||
| 106 | - if (call.method == "cancelVoice") { | ||
| 107 | - SSOralEvaluatingManager.share().cancelEvaluate(); | ||
| 108 | - messageChannel!.invokeMethod("voiceCancel",arguments: nil); | ||
| 109 | - return; | ||
| 110 | - } | ||
| 111 | - } | ||
| 112 | - | ||
| 113 | - //评测结果回调 | ||
| 114 | - func evaluateResult() { | ||
| 115 | - messageChannel!.invokeMethod("voiceResult", arguments: resultData) | ||
| 116 | - } | ||
| 117 | - | ||
| 118 | - //SSOralEvaluatingManagerDelegate | ||
| 119 | - /** | ||
| 120 | - 评测开始 | ||
| 121 | - */ | ||
| 122 | - func oralEvaluatingDidStart() { | ||
| 123 | - print("评测开始") | ||
| 124 | - messageChannel!.invokeMethod("voiceStart", arguments: nil) | ||
| 125 | - } | ||
| 126 | - | ||
| 127 | - /** | ||
| 128 | - 评测停止 | ||
| 129 | - */ | ||
| 130 | - func oralEvaluatingDidStop() { | ||
| 131 | - print("评测结束") | ||
| 132 | - messageChannel!.invokeMethod("voiceEnd",arguments: nil) | ||
| 133 | - } | ||
| 134 | - | ||
| 135 | - /** | ||
| 136 | - 评测完成后的结果 | ||
| 137 | - */ | ||
| 138 | - func oralEvaluatingDidEnd(withResult result: [AnyHashable : Any]?, requestId request_id: String?) { | ||
| 139 | - let resultDict:Dictionary<String, Any> = result as! Dictionary | ||
| 140 | - resultData! = resultDict; | ||
| 141 | - self.evaluateResult() | ||
| 142 | - } | ||
| 143 | - | ||
| 144 | - /** | ||
| 145 | - 评测失败回调 | ||
| 146 | - */ | ||
| 147 | - func oralEvaluatingDidEndError(_ error: Error?, requestId request_id: String?) { | ||
| 148 | - let nsError = error as? NSError | ||
| 149 | - var map = Dictionary<String, Any>() | ||
| 150 | - map["code"] = nsError?.code | ||
| 151 | - map["message"] = error?.localizedDescription | ||
| 152 | - messageChannel!.invokeMethod("voiceFail", arguments:map) | ||
| 153 | - } | ||
| 154 | - | ||
| 155 | - /** | ||
| 156 | - VAD(前置时间)超时回调 | ||
| 157 | - */ | ||
| 158 | - func oralEvaluatingDidVADFrontTimeOut() { | ||
| 159 | - SSOralEvaluatingManager.share().cancelEvaluate() | ||
| 160 | - messageChannel!.invokeMethod("voiceCancel",arguments: nil) | ||
| 161 | - } | ||
| 162 | - | ||
| 163 | - /** | ||
| 164 | - VAD(后置时间)超时回调 | ||
| 165 | - */ | ||
| 166 | - func oralEvaluatingDidVADBackTimeOut() { | ||
| 167 | - ///结束回调 | ||
| 168 | - SSOralEvaluatingManager.share().stopEvaluate(); | ||
| 169 | - } | ||
| 170 | -} |
lib/common/core/app_consts.dart
| 1 | -import '../request/basic_config.dart'; | ||
| 2 | - | ||
| 3 | class AppConsts { | 1 | class AppConsts { |
| 4 | /// 隐私协议 | 2 | /// 隐私协议 |
| 5 | static const String userPrivacyPolicyUrl = | 3 | static const String userPrivacyPolicyUrl = |
| @@ -16,12 +14,7 @@ class AppConsts { | @@ -16,12 +14,7 @@ class AppConsts { | ||
| 16 | static const String userTermSdkUrl = | 14 | static const String userTermSdkUrl = |
| 17 | 'http://page.kouyuxingqiu.com/term_sdk.html'; | 15 | 'http://page.kouyuxingqiu.com/term_sdk.html'; |
| 18 | 16 | ||
| 19 | - /// 先声SDK | ||
| 20 | - static String xsAppKey = 'a418'; | ||
| 21 | - static String xsAppSecretKey = BasicConfig.isTestDev | ||
| 22 | - ? '1a16f31f2611bf32fb7b3fc38f5b2c81' | ||
| 23 | - : 'c11163aa6c834a028da4a4b30955be99'; | ||
| 24 | - static String xsAppService = BasicConfig.isTestDev | ||
| 25 | - ? 'ws://trial.cloud.ssapi.cn:8080' | ||
| 26 | - : '"wss://api.cloud.ssapi.cn'; | 17 | + /// 驰声语音评测 SDK |
| 18 | + static const String chivoxAppKey = '1718784836000171'; | ||
| 19 | + static const String chivoxSecretKey = '3ce4e362e86b6c95e3b39a29fa486167'; | ||
| 27 | } | 20 | } |
lib/common/speech/chivox_evaluation_channel.dart
0 → 100644
| 1 | +import 'dart:async'; | ||
| 2 | +import 'dart:convert'; | ||
| 3 | +import 'dart:io'; | ||
| 4 | +import 'dart:typed_data'; | ||
| 5 | + | ||
| 6 | +import 'package:audio_session/audio_session.dart'; | ||
| 7 | +import 'package:chivox_aiengine/chivox_aiengine.dart'; | ||
| 8 | +import 'package:flutter/services.dart'; | ||
| 9 | +import 'package:path_provider/path_provider.dart'; | ||
| 10 | + | ||
| 11 | +import '../core/app_consts.dart'; | ||
| 12 | + | ||
| 13 | +/// 将驰声 Flutter SDK 适配为原评测桥接的事件协议,业务页面无需感知 SDK 差异。 | ||
| 14 | +class ChivoxEvaluationChannel { | ||
| 15 | + ChivoxAiengine? _engine; | ||
| 16 | + Future<void>? _initializing; | ||
| 17 | + Future<dynamic> Function(MethodCall)? _methodCallHandler; | ||
| 18 | + int _sessionId = 0; | ||
| 19 | + bool _active = false; | ||
| 20 | + bool _stopping = false; | ||
| 21 | + bool _stopNotified = false; | ||
| 22 | + String? _recordFilePath; | ||
| 23 | + | ||
| 24 | + void setMethodCallHandler( | ||
| 25 | + Future<dynamic> Function(MethodCall)? methodCallHandler) { | ||
| 26 | + _methodCallHandler = methodCallHandler; | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + Future<T?> invokeMethod<T>(String method, [dynamic arguments]) async { | ||
| 30 | + final params = _asStringMap(arguments); | ||
| 31 | + switch (method) { | ||
| 32 | + case 'initVoiceSdk': | ||
| 33 | + await _initialize(); | ||
| 34 | + break; | ||
| 35 | + case 'startVoice': | ||
| 36 | + await _startInnerRecorder(params); | ||
| 37 | + break; | ||
| 38 | + case 'startLocalVoice': | ||
| 39 | + await _evaluateWaveFile(params); | ||
| 40 | + break; | ||
| 41 | + case 'stopVoice': | ||
| 42 | + await _stop(); | ||
| 43 | + break; | ||
| 44 | + case 'cancelVoice': | ||
| 45 | + await _cancel(notify: true); | ||
| 46 | + break; | ||
| 47 | + default: | ||
| 48 | + throw MissingPluginException('Unsupported evaluation method: $method'); | ||
| 49 | + } | ||
| 50 | + return null; | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + Future<void> dispose() async { | ||
| 54 | + _sessionId++; | ||
| 55 | + await _cancel(notify: false); | ||
| 56 | + final engine = _engine; | ||
| 57 | + _engine = null; | ||
| 58 | + _initializing = null; | ||
| 59 | + await engine?.destroy(); | ||
| 60 | + _methodCallHandler = null; | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + Future<void> _initialize() { | ||
| 64 | + if (_engine != null) return Future.value(); | ||
| 65 | + return _initializing ??= _createEngine().catchError((Object error) { | ||
| 66 | + _initializing = null; | ||
| 67 | + throw error; | ||
| 68 | + }); | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + Future<void> _createEngine() async { | ||
| 72 | + final supportDirectory = await getApplicationSupportDirectory(); | ||
| 73 | + final resourceDirectory = Directory('${supportDirectory.path}/chivox'); | ||
| 74 | + if (!await resourceDirectory.exists()) { | ||
| 75 | + await resourceDirectory.create(recursive: true); | ||
| 76 | + } | ||
| 77 | + final provisionPath = await _copyAssetIfNeeded( | ||
| 78 | + 'assets/chivox/aiengine.provision', | ||
| 79 | + '${resourceDirectory.path}/aiengine.provision', | ||
| 80 | + ); | ||
| 81 | + final vadPath = await _copyAssetIfNeeded( | ||
| 82 | + 'assets/chivox/vad.0.13.bin', | ||
| 83 | + '${resourceDirectory.path}/vad.0.13.bin', | ||
| 84 | + ); | ||
| 85 | + | ||
| 86 | + final config = jsonEncode({ | ||
| 87 | + 'appKey': AppConsts.chivoxAppKey, | ||
| 88 | + 'secretKey': AppConsts.chivoxSecretKey, | ||
| 89 | + 'provision': provisionPath, | ||
| 90 | + 'vad': { | ||
| 91 | + 'enable': 1, | ||
| 92 | + 'res': vadPath, | ||
| 93 | + 'speechLowSeek': 125, | ||
| 94 | + 'sampleRate': 16000, | ||
| 95 | + 'strip': 0, | ||
| 96 | + }, | ||
| 97 | + 'cloud': {'enable': 1}, | ||
| 98 | + }); | ||
| 99 | + _engine = await ChivoxAiengine.create(config); | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + Future<void> _startInnerRecorder(Map<String, String> params) async { | ||
| 103 | + await _prepareAudioSession(); | ||
| 104 | + await _startEvaluation( | ||
| 105 | + params, | ||
| 106 | + audioSourceBuilder: (recordFilePath) => { | ||
| 107 | + 'srcType': 'innerRecorder', | ||
| 108 | + 'innerRecorderParam': { | ||
| 109 | + 'channel': 1, | ||
| 110 | + 'sampleBytes': 2, | ||
| 111 | + 'sampleRate': 16000, | ||
| 112 | + 'saveFile': recordFilePath, | ||
| 113 | + }, | ||
| 114 | + }, | ||
| 115 | + ); | ||
| 116 | + } | ||
| 117 | + | ||
| 118 | + Future<void> _evaluateWaveFile(Map<String, String> params) async { | ||
| 119 | + final wavePath = params['voicePath'] ?? ''; | ||
| 120 | + if (wavePath.isEmpty || !await File(wavePath).exists()) { | ||
| 121 | + await _emit('voiceFail', { | ||
| 122 | + 'code': -1, | ||
| 123 | + 'message': '录音文件不存在', | ||
| 124 | + }); | ||
| 125 | + return; | ||
| 126 | + } | ||
| 127 | + | ||
| 128 | + await _startEvaluation( | ||
| 129 | + params, | ||
| 130 | + fallbackRecordPath: wavePath, | ||
| 131 | + audioSourceBuilder: (_) => {'srcType': 'outerFeed'}, | ||
| 132 | + afterStarted: (sessionId) async { | ||
| 133 | + final pcmBytes = await _readWavePcm(File(wavePath)); | ||
| 134 | + const chunkSize = 8192; | ||
| 135 | + for (var offset = 0; | ||
| 136 | + offset < pcmBytes.length && _isActiveSession(sessionId); | ||
| 137 | + offset += chunkSize) { | ||
| 138 | + final end = (offset + chunkSize).clamp(0, pcmBytes.length); | ||
| 139 | + final chunk = Uint8List.sublistView(pcmBytes, offset, end); | ||
| 140 | + await _engine?.feed(chunk, chunk.length); | ||
| 141 | + } | ||
| 142 | + if (_isActiveSession(sessionId)) await _stop(); | ||
| 143 | + }, | ||
| 144 | + ); | ||
| 145 | + } | ||
| 146 | + | ||
| 147 | + Future<void> _startEvaluation( | ||
| 148 | + Map<String, String> params, { | ||
| 149 | + required Map<String, dynamic> Function(String recordFilePath) | ||
| 150 | + audioSourceBuilder, | ||
| 151 | + String? fallbackRecordPath, | ||
| 152 | + Future<void> Function(int sessionId)? afterStarted, | ||
| 153 | + }) async { | ||
| 154 | + try { | ||
| 155 | + await _initialize(); | ||
| 156 | + if (_active) await _cancel(notify: false); | ||
| 157 | + | ||
| 158 | + final text = _normalizeReferenceText(params['word'] ?? ''); | ||
| 159 | + if (text.isEmpty) { | ||
| 160 | + throw const FormatException('评测文本不能为空'); | ||
| 161 | + } | ||
| 162 | + final sessionId = ++_sessionId; | ||
| 163 | + _active = true; | ||
| 164 | + _stopping = false; | ||
| 165 | + _stopNotified = false; | ||
| 166 | + _recordFilePath = fallbackRecordPath ?? await _newRecordFilePath(); | ||
| 167 | + | ||
| 168 | + final request = jsonEncode({ | ||
| 169 | + 'coreProvideType': 'cloud', | ||
| 170 | + 'vad': { | ||
| 171 | + 'vadEnable': fallbackRecordPath == null ? 1 : 0, | ||
| 172 | + 'refDuration': 3, | ||
| 173 | + 'speechLowSeek': 125, | ||
| 174 | + }, | ||
| 175 | + 'app': {'userId': params['userId'] ?? 'guest'}, | ||
| 176 | + 'audio': { | ||
| 177 | + 'audioType': 'wav', | ||
| 178 | + 'channel': 1, | ||
| 179 | + 'sampleBytes': 2, | ||
| 180 | + 'sampleRate': 16000, | ||
| 181 | + 'compress': 'speex', | ||
| 182 | + }, | ||
| 183 | + 'request': { | ||
| 184 | + 'coreType': 'en.sent.score', | ||
| 185 | + 'refText': text, | ||
| 186 | + 'rank': 100, | ||
| 187 | + 'attachAudioUrl': 0, | ||
| 188 | + 'result': { | ||
| 189 | + 'details': {'gop_adjust': 0} | ||
| 190 | + }, | ||
| 191 | + }, | ||
| 192 | + }); | ||
| 193 | + | ||
| 194 | + final listener = ChivoxAiengineResultListener( | ||
| 195 | + onEvalResult: (result) => _handleResult(sessionId, text, result), | ||
| 196 | + onError: (result) => unawaited(_handleError(sessionId, result)), | ||
| 197 | + onVad: (result) => _handleVad(sessionId, result), | ||
| 198 | + ); | ||
| 199 | + await _engine!.start( | ||
| 200 | + audioSourceBuilder(_recordFilePath!), | ||
| 201 | + request, | ||
| 202 | + listener, | ||
| 203 | + ); | ||
| 204 | + if (!_isActiveSession(sessionId)) return; | ||
| 205 | + await _emit('voiceStart', null); | ||
| 206 | + await afterStarted?.call(sessionId); | ||
| 207 | + } catch (error) { | ||
| 208 | + _active = false; | ||
| 209 | + _stopping = false; | ||
| 210 | + try { | ||
| 211 | + await _engine?.cancel(); | ||
| 212 | + } catch (_) {} | ||
| 213 | + await _emit('voiceFail', { | ||
| 214 | + 'code': error is PlatformException ? error.code : -1, | ||
| 215 | + 'message': error.toString(), | ||
| 216 | + }); | ||
| 217 | + } | ||
| 218 | + } | ||
| 219 | + | ||
| 220 | + void _handleResult( | ||
| 221 | + int sessionId, String referenceText, ChivoxAiengineResult result) { | ||
| 222 | + if (!_isActiveSession(sessionId)) return; | ||
| 223 | + try { | ||
| 224 | + final raw = jsonDecode(result.text ?? '{}') as Map<String, dynamic>; | ||
| 225 | + final resultJson = raw['result'] as Map<String, dynamic>? ?? const {}; | ||
| 226 | + final detailsJson = resultJson['details'] as List<dynamic>? ?? const []; | ||
| 227 | + final details = detailsJson | ||
| 228 | + .whereType<Map>() | ||
| 229 | + .map((detail) { | ||
| 230 | + return { | ||
| 231 | + 'char': detail['char']?.toString() ?? '', | ||
| 232 | + 'score': _asScore(detail['score']), | ||
| 233 | + }; | ||
| 234 | + }) | ||
| 235 | + .where((detail) => (detail['char'] as String).isNotEmpty) | ||
| 236 | + .toList(); | ||
| 237 | + | ||
| 238 | + _active = false; | ||
| 239 | + _stopping = false; | ||
| 240 | + unawaited(_emit('voiceResult', { | ||
| 241 | + 'result': { | ||
| 242 | + 'overall': _asScore(resultJson['overall']), | ||
| 243 | + 'details': details, | ||
| 244 | + 'refText': referenceText, | ||
| 245 | + }, | ||
| 246 | + 'audioUrl': result.recFilePath ?? _recordFilePath ?? '', | ||
| 247 | + })); | ||
| 248 | + } catch (error) { | ||
| 249 | + unawaited(_handleError(sessionId, null, error: error)); | ||
| 250 | + } | ||
| 251 | + } | ||
| 252 | + | ||
| 253 | + Future<void> _handleError( | ||
| 254 | + int sessionId, | ||
| 255 | + ChivoxAiengineResult? result, { | ||
| 256 | + Object? error, | ||
| 257 | + }) async { | ||
| 258 | + if (!_isActiveSession(sessionId)) return; | ||
| 259 | + _active = false; | ||
| 260 | + _stopping = false; | ||
| 261 | + try { | ||
| 262 | + await _engine?.cancel(); | ||
| 263 | + } catch (_) {} | ||
| 264 | + var code = -1; | ||
| 265 | + var message = error?.toString() ?? result?.text ?? '评测失败'; | ||
| 266 | + try { | ||
| 267 | + final errorJson = jsonDecode(result?.text ?? '') as Map<String, dynamic>; | ||
| 268 | + code = _asScore(errorJson['errId']); | ||
| 269 | + message = errorJson['error']?.toString() ?? message; | ||
| 270 | + } catch (_) {} | ||
| 271 | + await _emit('voiceFail', {'code': code, 'message': message}); | ||
| 272 | + } | ||
| 273 | + | ||
| 274 | + void _handleVad(int sessionId, ChivoxAiengineResult result) { | ||
| 275 | + if (!_isActiveSession(sessionId) || _stopping) return; | ||
| 276 | + try { | ||
| 277 | + final vad = jsonDecode(result.text ?? '{}') as Map<String, dynamic>; | ||
| 278 | + if (_asScore(vad['vad_status']) == 2) unawaited(_stop()); | ||
| 279 | + } catch (_) {} | ||
| 280 | + } | ||
| 281 | + | ||
| 282 | + Future<void> _stop() async { | ||
| 283 | + if (!_active || _stopping) return; | ||
| 284 | + _stopping = true; | ||
| 285 | + try { | ||
| 286 | + await _engine?.stop(); | ||
| 287 | + if (!_stopNotified) { | ||
| 288 | + _stopNotified = true; | ||
| 289 | + await _emit('voiceEnd', null); | ||
| 290 | + } | ||
| 291 | + } catch (error) { | ||
| 292 | + _active = false; | ||
| 293 | + _stopping = false; | ||
| 294 | + _sessionId++; | ||
| 295 | + try { | ||
| 296 | + await _engine?.cancel(); | ||
| 297 | + } catch (_) {} | ||
| 298 | + await _emit('voiceFail', { | ||
| 299 | + 'code': error is PlatformException ? error.code : -1, | ||
| 300 | + 'message': error.toString(), | ||
| 301 | + }); | ||
| 302 | + } | ||
| 303 | + } | ||
| 304 | + | ||
| 305 | + Future<void> _cancel({required bool notify}) async { | ||
| 306 | + final wasActive = _active; | ||
| 307 | + _active = false; | ||
| 308 | + _stopping = false; | ||
| 309 | + _sessionId++; | ||
| 310 | + try { | ||
| 311 | + await _engine?.cancel(); | ||
| 312 | + } catch (_) {} | ||
| 313 | + if (notify && wasActive) await _emit('voiceCancel', null); | ||
| 314 | + } | ||
| 315 | + | ||
| 316 | + bool _isActiveSession(int sessionId) => _active && sessionId == _sessionId; | ||
| 317 | + | ||
| 318 | + Future<void> _prepareAudioSession() async { | ||
| 319 | + final session = await AudioSession.instance; | ||
| 320 | + await session.configure(AudioSessionConfiguration( | ||
| 321 | + avAudioSessionCategory: AVAudioSessionCategory.playAndRecord, | ||
| 322 | + avAudioSessionCategoryOptions: | ||
| 323 | + AVAudioSessionCategoryOptions.defaultToSpeaker | | ||
| 324 | + AVAudioSessionCategoryOptions.allowBluetooth, | ||
| 325 | + avAudioSessionMode: AVAudioSessionMode.spokenAudio, | ||
| 326 | + androidAudioAttributes: const AndroidAudioAttributes( | ||
| 327 | + contentType: AndroidAudioContentType.speech, | ||
| 328 | + usage: AndroidAudioUsage.voiceCommunication, | ||
| 329 | + ), | ||
| 330 | + androidAudioFocusGainType: AndroidAudioFocusGainType.gain, | ||
| 331 | + androidWillPauseWhenDucked: true, | ||
| 332 | + )); | ||
| 333 | + await session.setActive(true); | ||
| 334 | + } | ||
| 335 | + | ||
| 336 | + Future<String> _newRecordFilePath() async { | ||
| 337 | + final tempDirectory = await getTemporaryDirectory(); | ||
| 338 | + final directory = Directory('${tempDirectory.path}/chivox_records'); | ||
| 339 | + if (!await directory.exists()) await directory.create(recursive: true); | ||
| 340 | + return '${directory.path}/${DateTime.now().millisecondsSinceEpoch}.wav'; | ||
| 341 | + } | ||
| 342 | + | ||
| 343 | + Future<String> _copyAssetIfNeeded(String assetPath, String targetPath) async { | ||
| 344 | + final data = await rootBundle.load(assetPath); | ||
| 345 | + final file = File(targetPath); | ||
| 346 | + if (!await file.exists() || await file.length() != data.lengthInBytes) { | ||
| 347 | + await file.writeAsBytes(data.buffer.asUint8List(), flush: true); | ||
| 348 | + } | ||
| 349 | + return file.path; | ||
| 350 | + } | ||
| 351 | + | ||
| 352 | + Future<Uint8List> _readWavePcm(File file) async { | ||
| 353 | + final bytes = await file.readAsBytes(); | ||
| 354 | + if (bytes.length < 12 || ascii.decode(bytes.sublist(0, 4)) != 'RIFF') { | ||
| 355 | + return bytes; | ||
| 356 | + } | ||
| 357 | + var offset = 12; | ||
| 358 | + final byteData = ByteData.sublistView(bytes); | ||
| 359 | + while (offset + 8 <= bytes.length) { | ||
| 360 | + final chunkName = | ||
| 361 | + ascii.decode(bytes.sublist(offset, offset + 4), allowInvalid: true); | ||
| 362 | + final chunkLength = byteData.getUint32(offset + 4, Endian.little); | ||
| 363 | + final dataStart = offset + 8; | ||
| 364 | + final dataEnd = (dataStart + chunkLength).clamp(0, bytes.length); | ||
| 365 | + if (chunkName == 'data') { | ||
| 366 | + return Uint8List.sublistView(bytes, dataStart, dataEnd); | ||
| 367 | + } | ||
| 368 | + offset = dataEnd + (chunkLength.isOdd ? 1 : 0); | ||
| 369 | + } | ||
| 370 | + throw const FormatException('无效的 WAV 文件'); | ||
| 371 | + } | ||
| 372 | + | ||
| 373 | + Future<void> _emit(String method, dynamic arguments) async { | ||
| 374 | + await _methodCallHandler?.call(MethodCall(method, arguments)); | ||
| 375 | + } | ||
| 376 | + | ||
| 377 | + Map<String, String> _asStringMap(dynamic arguments) { | ||
| 378 | + if (arguments is! Map) return const {}; | ||
| 379 | + return arguments | ||
| 380 | + .map((key, value) => MapEntry(key.toString(), value?.toString() ?? '')); | ||
| 381 | + } | ||
| 382 | + | ||
| 383 | + int _asScore(dynamic value) { | ||
| 384 | + if (value is num) return value.round(); | ||
| 385 | + return double.tryParse(value?.toString() ?? '')?.round() ?? 0; | ||
| 386 | + } | ||
| 387 | + | ||
| 388 | + String _normalizeReferenceText(String text) => text | ||
| 389 | + .trim() | ||
| 390 | + .replaceAll('’', "'") | ||
| 391 | + .replaceAll('‘', "'") | ||
| 392 | + .replaceAll('“', '"') | ||
| 393 | + .replaceAll('”', '"') | ||
| 394 | + .replaceAll(',', ',') | ||
| 395 | + .replaceAll('。', '.') | ||
| 396 | + .replaceAll('?', '?') | ||
| 397 | + .replaceAll('!', '!'); | ||
| 398 | +} |
lib/pages/practice/bloc/topic_picture_bloc.dart
| @@ -3,12 +3,12 @@ import 'dart:async'; | @@ -3,12 +3,12 @@ import 'dart:async'; | ||
| 3 | import 'package:audioplayers/audioplayers.dart'; | 3 | import 'package:audioplayers/audioplayers.dart'; |
| 4 | import 'package:flutter/cupertino.dart'; | 4 | import 'package:flutter/cupertino.dart'; |
| 5 | import 'package:flutter/foundation.dart'; | 5 | import 'package:flutter/foundation.dart'; |
| 6 | -import 'package:flutter/services.dart'; | ||
| 7 | import 'package:flutter_bloc/flutter_bloc.dart'; | 6 | import 'package:flutter_bloc/flutter_bloc.dart'; |
| 8 | import 'package:flutter_easyloading/flutter_easyloading.dart'; | 7 | import 'package:flutter_easyloading/flutter_easyloading.dart'; |
| 9 | import 'package:permission_handler/permission_handler.dart'; | 8 | import 'package:permission_handler/permission_handler.dart'; |
| 10 | import 'package:wow_english/common/request/dao/listen_dao.dart'; | 9 | import 'package:wow_english/common/request/dao/listen_dao.dart'; |
| 11 | import 'package:wow_english/common/request/exception.dart'; | 10 | import 'package:wow_english/common/request/exception.dart'; |
| 11 | +import 'package:wow_english/common/speech/chivox_evaluation_channel.dart'; | ||
| 12 | import 'package:wow_english/models/course_process_entity.dart'; | 12 | import 'package:wow_english/models/course_process_entity.dart'; |
| 13 | import 'package:wow_english/pages/section/subsection/base_section/bloc.dart'; | 13 | import 'package:wow_english/pages/section/subsection/base_section/bloc.dart'; |
| 14 | import 'package:wow_english/pages/section/subsection/base_section/event.dart'; | 14 | import 'package:wow_english/pages/section/subsection/base_section/event.dart'; |
| @@ -27,7 +27,8 @@ part 'topic_picture_event.dart'; | @@ -27,7 +27,8 @@ part 'topic_picture_event.dart'; | ||
| 27 | part 'topic_picture_state.dart'; | 27 | part 'topic_picture_state.dart'; |
| 28 | 28 | ||
| 29 | class TopicPictureBloc | 29 | class TopicPictureBloc |
| 30 | - extends BaseSectionBloc<TopicPictureEvent, TopicPictureState> with WidgetsBindingObserver { | 30 | + extends BaseSectionBloc<TopicPictureEvent, TopicPictureState> |
| 31 | + with WidgetsBindingObserver { | ||
| 31 | final PageController pageController; | 32 | final PageController pageController; |
| 32 | 33 | ||
| 33 | final String courseLessonId; | 34 | final String courseLessonId; |
| @@ -50,7 +51,7 @@ class TopicPictureBloc | @@ -50,7 +51,7 @@ class TopicPictureBloc | ||
| 50 | 51 | ||
| 51 | bool get isRecording => _isRecording; | 52 | bool get isRecording => _isRecording; |
| 52 | 53 | ||
| 53 | - late MethodChannel methodChannel; | 54 | + final ChivoxEvaluationChannel methodChannel = ChivoxEvaluationChannel(); |
| 54 | 55 | ||
| 55 | late AudioPlayer audioPlayer; | 56 | late AudioPlayer audioPlayer; |
| 56 | 57 | ||
| @@ -66,7 +67,6 @@ class TopicPictureBloc | @@ -66,7 +67,6 @@ class TopicPictureBloc | ||
| 66 | on<CurrentPageIndexChangeEvent>(_pageControllerChange); | 67 | on<CurrentPageIndexChangeEvent>(_pageControllerChange); |
| 67 | on<VoicePlayStateChangeEvent>(_voicePlayStateChange); | 68 | on<VoicePlayStateChangeEvent>(_voicePlayStateChange); |
| 68 | on<XSVoiceResultEvent>(_voiceXsResult); | 69 | on<XSVoiceResultEvent>(_voiceXsResult); |
| 69 | - on<XSVoiceInitEvent>(_initVoiceSdk); | ||
| 70 | on<SelectItemEvent>(_selectItemLoad); | 70 | on<SelectItemEvent>(_selectItemLoad); |
| 71 | on<SelectItemResetEvent>(_selectItemReset); | 71 | on<SelectItemResetEvent>(_selectItemReset); |
| 72 | on<RequestDataEvent>(_requestData); | 72 | on<RequestDataEvent>(_requestData); |
| @@ -74,7 +74,7 @@ class TopicPictureBloc | @@ -74,7 +74,7 @@ class TopicPictureBloc | ||
| 74 | on<XSVoiceStopEvent>(_voiceXsStop); | 74 | on<XSVoiceStopEvent>(_voiceXsStop); |
| 75 | on<VoicePlayEvent>(_questionVoicePlay); | 75 | on<VoicePlayEvent>(_questionVoicePlay); |
| 76 | on<OnXSVoiceStateChangeEvent>(_onVoiceXsStateChange); | 76 | on<OnXSVoiceStateChangeEvent>(_onVoiceXsStateChange); |
| 77 | - on<InitBlocEvent>((event, emit) { | 77 | + on<InitBlocEvent>((event, emit) async { |
| 78 | //音频播放器 | 78 | //音频播放器 |
| 79 | audioPlayer = AudioPlayer(); | 79 | audioPlayer = AudioPlayer(); |
| 80 | audioPlayer.onPlayerStateChanged.listen((event) async { | 80 | audioPlayer.onPlayerStateChanged.listen((event) async { |
| @@ -85,8 +85,6 @@ class TopicPictureBloc | @@ -85,8 +85,6 @@ class TopicPictureBloc | ||
| 85 | add(VoicePlayStateChangeEvent()); | 85 | add(VoicePlayStateChangeEvent()); |
| 86 | }); | 86 | }); |
| 87 | 87 | ||
| 88 | - methodChannel = | ||
| 89 | - const MethodChannel('wow_english/sing_sound_method_channel'); | ||
| 90 | methodChannel.setMethodCallHandler((call) async { | 88 | methodChannel.setMethodCallHandler((call) async { |
| 91 | if (call.method == 'voiceResult') { | 89 | if (call.method == 'voiceResult') { |
| 92 | //评测结果 | 90 | //评测结果 |
| @@ -130,6 +128,8 @@ class TopicPictureBloc | @@ -130,6 +128,8 @@ class TopicPictureBloc | ||
| 130 | } | 128 | } |
| 131 | }); | 129 | }); |
| 132 | 130 | ||
| 131 | + await methodChannel.invokeMethod('initVoiceSdk', {}); | ||
| 132 | + | ||
| 133 | WidgetsBinding.instance.addObserver(this); | 133 | WidgetsBinding.instance.addObserver(this); |
| 134 | }); | 134 | }); |
| 135 | } | 135 | } |
| @@ -153,11 +153,12 @@ class TopicPictureBloc | @@ -153,11 +153,12 @@ class TopicPictureBloc | ||
| 153 | } | 153 | } |
| 154 | 154 | ||
| 155 | @override | 155 | @override |
| 156 | - Future<void> close() { | 156 | + Future<void> close() async { |
| 157 | pageController.dispose(); | 157 | pageController.dispose(); |
| 158 | - audioPlayer.release(); | ||
| 159 | - audioPlayer.dispose(); | ||
| 160 | - _voiceXsCancel(); | 158 | + await audioPlayer.release(); |
| 159 | + await audioPlayer.dispose(); | ||
| 160 | + await _voiceXsCancel(force: true); | ||
| 161 | + await methodChannel.dispose(); | ||
| 161 | WidgetsBinding.instance.removeObserver(this); | 162 | WidgetsBinding.instance.removeObserver(this); |
| 162 | return super.close(); | 163 | return super.close(); |
| 163 | } | 164 | } |
| @@ -239,13 +240,7 @@ class TopicPictureBloc | @@ -239,13 +240,7 @@ class TopicPictureBloc | ||
| 239 | return answerList?.correct != 0; | 240 | return answerList?.correct != 0; |
| 240 | } | 241 | } |
| 241 | 242 | ||
| 242 | - ///初始化SDK | ||
| 243 | - _initVoiceSdk( | ||
| 244 | - XSVoiceInitEvent event, Emitter<TopicPictureState> emitter) async { | ||
| 245 | - methodChannel.invokeMethod('initVoiceSdk', event.data); | ||
| 246 | - } | ||
| 247 | - | ||
| 248 | - ///先声测试 | 243 | + ///驰声测试 |
| 249 | void _voiceXsStart( | 244 | void _voiceXsStart( |
| 250 | XSVoiceStartEvent event, Emitter<TopicPictureState> emitter) async { | 245 | XSVoiceStartEvent event, Emitter<TopicPictureState> emitter) async { |
| 251 | await audioPlayer.stop(); | 246 | await audioPlayer.stop(); |
| @@ -253,7 +248,7 @@ class TopicPictureBloc | @@ -253,7 +248,7 @@ class TopicPictureBloc | ||
| 253 | bool result = await requestPermission( | 248 | bool result = await requestPermission( |
| 254 | context, Permission.microphone, "录音", "用于开启录音,识别您的开口作答并给出反馈"); | 249 | context, Permission.microphone, "录音", "用于开启录音,识别您的开口作答并给出反馈"); |
| 255 | if (result) { | 250 | if (result) { |
| 256 | - methodChannel.invokeMethod('startVoice', { | 251 | + await methodChannel.invokeMethod('startVoice', { |
| 257 | 'word': event.testWord, | 252 | 'word': event.testWord, |
| 258 | 'type': event.type, | 253 | 'type': event.type, |
| 259 | 'userId': event.userId.toString() | 254 | 'userId': event.userId.toString() |
| @@ -264,17 +259,17 @@ class TopicPictureBloc | @@ -264,17 +259,17 @@ class TopicPictureBloc | ||
| 264 | ///终止评测 | 259 | ///终止评测 |
| 265 | Future<void> _voiceXsStop( | 260 | Future<void> _voiceXsStop( |
| 266 | XSVoiceStopEvent event, Emitter<TopicPictureState> emitter) async { | 261 | XSVoiceStopEvent event, Emitter<TopicPictureState> emitter) async { |
| 267 | - methodChannel.invokeMethod('stopVoice'); | 262 | + await methodChannel.invokeMethod('stopVoice'); |
| 268 | } | 263 | } |
| 269 | 264 | ||
| 270 | ///取消评测(用于处理退出页面后录音未停止等异常情况的保护操作) | 265 | ///取消评测(用于处理退出页面后录音未停止等异常情况的保护操作) |
| 271 | Future<void> _voiceXsCancel({bool force = false}) async { | 266 | Future<void> _voiceXsCancel({bool force = false}) async { |
| 272 | if (_isRecording || force) { | 267 | if (_isRecording || force) { |
| 273 | - methodChannel.invokeMethod('cancelVoice'); | 268 | + await methodChannel.invokeMethod('cancelVoice'); |
| 274 | } | 269 | } |
| 275 | } | 270 | } |
| 276 | 271 | ||
| 277 | - ///先声评测结果 | 272 | + ///驰声评测结果 |
| 278 | void _voiceXsResult( | 273 | void _voiceXsResult( |
| 279 | XSVoiceResultEvent event, Emitter<TopicPictureState> emitter) async { | 274 | XSVoiceResultEvent event, Emitter<TopicPictureState> emitter) async { |
| 280 | _isRecording = false; | 275 | _isRecording = false; |
| @@ -286,7 +281,8 @@ class TopicPictureBloc | @@ -286,7 +281,8 @@ class TopicPictureBloc | ||
| 286 | final voiceResult = VoiceResultType.fromScore(score); | 281 | final voiceResult = VoiceResultType.fromScore(score); |
| 287 | if (voiceResult.lottieFilePath != null) { | 282 | if (voiceResult.lottieFilePath != null) { |
| 288 | AudioPlayerUtil.getInstance().playAudio(voiceResult.audioType); | 283 | AudioPlayerUtil.getInstance().playAudio(voiceResult.audioType); |
| 289 | - await showCheerRewardDialog(context, lottieFile: voiceResult.lottieFilePath!, onDismiss: () { | 284 | + await showCheerRewardDialog(context, |
| 285 | + lottieFile: voiceResult.lottieFilePath!, onDismiss: () { | ||
| 290 | autoFlipPageByVoice(score); | 286 | autoFlipPageByVoice(score); |
| 291 | }); | 287 | }); |
| 292 | } else { | 288 | } else { |
lib/pages/practice/bloc/topic_picture_event.dart
| @@ -7,18 +7,12 @@ class InitBlocEvent extends TopicPictureEvent {} | @@ -7,18 +7,12 @@ class InitBlocEvent extends TopicPictureEvent {} | ||
| 7 | 7 | ||
| 8 | class RequestDataEvent extends TopicPictureEvent {} | 8 | class RequestDataEvent extends TopicPictureEvent {} |
| 9 | 9 | ||
| 10 | -///初始化先声SDK | ||
| 11 | -class XSVoiceInitEvent extends TopicPictureEvent { | ||
| 12 | - final Map data; | ||
| 13 | - XSVoiceInitEvent(this.data); | ||
| 14 | -} | ||
| 15 | - | ||
| 16 | ///开始评测 | 10 | ///开始评测 |
| 17 | class XSVoiceStartEvent extends TopicPictureEvent { | 11 | class XSVoiceStartEvent extends TopicPictureEvent { |
| 18 | final String testWord; | 12 | final String testWord; |
| 19 | final String type; | 13 | final String type; |
| 20 | final String userId; | 14 | final String userId; |
| 21 | - XSVoiceStartEvent(this.testWord,this.type,this.userId); | 15 | + XSVoiceStartEvent(this.testWord, this.type, this.userId); |
| 22 | } | 16 | } |
| 23 | 17 | ||
| 24 | ///终止评测 | 18 | ///终止评测 |
| @@ -30,7 +24,7 @@ class XSVoiceResultEvent extends TopicPictureEvent { | @@ -30,7 +24,7 @@ class XSVoiceResultEvent extends TopicPictureEvent { | ||
| 30 | XSVoiceResultEvent(this.message); | 24 | XSVoiceResultEvent(this.message); |
| 31 | } | 25 | } |
| 32 | 26 | ||
| 33 | -///先声评测状态 | 27 | +///驰声评测状态 |
| 34 | class OnXSVoiceStateChangeEvent extends TopicPictureEvent {} | 28 | class OnXSVoiceStateChangeEvent extends TopicPictureEvent {} |
| 35 | 29 | ||
| 36 | ///音频播放状态变化 | 30 | ///音频播放状态变化 |
lib/pages/practice/topic_picture_page.dart
| 1 | import 'package:flutter/material.dart'; | 1 | import 'package:flutter/material.dart'; |
| 2 | import 'package:flutter_bloc/flutter_bloc.dart'; | 2 | import 'package:flutter_bloc/flutter_bloc.dart'; |
| 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; | 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; |
| 4 | -import 'package:wow_english/common/core/app_consts.dart'; | ||
| 5 | import 'package:wow_english/common/core/user_util.dart'; | 4 | import 'package:wow_english/common/core/user_util.dart'; |
| 6 | import 'package:wow_english/common/extension/string_extension.dart'; | 5 | import 'package:wow_english/common/extension/string_extension.dart'; |
| 7 | import 'package:wow_english/common/widgets/ow_image_widget.dart'; | 6 | import 'package:wow_english/common/widgets/ow_image_widget.dart'; |
| @@ -28,13 +27,7 @@ class TopicPicturePage extends StatelessWidget { | @@ -28,13 +27,7 @@ class TopicPicturePage extends StatelessWidget { | ||
| 28 | create: (context) => TopicPictureBloc( | 27 | create: (context) => TopicPictureBloc( |
| 29 | context, PageController(), courseLessonId ?? '', moduleColor) | 28 | context, PageController(), courseLessonId ?? '', moduleColor) |
| 30 | ..add(InitBlocEvent()) | 29 | ..add(InitBlocEvent()) |
| 31 | - ..add(RequestDataEvent()) | ||
| 32 | - ..add(XSVoiceInitEvent({ | ||
| 33 | - 'appKey': AppConsts.xsAppKey, | ||
| 34 | - 'service': AppConsts.xsAppService, | ||
| 35 | - 'secretKey': AppConsts.xsAppSecretKey, | ||
| 36 | - 'userId': UserUtil.getUser()!.id.toString(), | ||
| 37 | - })), | 30 | + ..add(RequestDataEvent()), |
| 38 | child: _TopicPicturePage(), | 31 | child: _TopicPicturePage(), |
| 39 | ); | 32 | ); |
| 40 | } | 33 | } |
| @@ -310,8 +303,7 @@ class _TopicPicturePage extends StatelessWidget { | @@ -310,8 +303,7 @@ class _TopicPicturePage extends StatelessWidget { | ||
| 310 | mainAxisAlignment: MainAxisAlignment.center, | 303 | mainAxisAlignment: MainAxisAlignment.center, |
| 311 | children: [ | 304 | children: [ |
| 312 | SpeakerWidget( | 305 | SpeakerWidget( |
| 313 | - isPlaying: isCurrentPage && | ||
| 314 | - bloc.isAudioPlaying(), | 306 | + isPlaying: isCurrentPage && bloc.isAudioPlaying(), |
| 315 | // 控制动画播放 | 307 | // 控制动画播放 |
| 316 | width: 32.w, | 308 | width: 32.w, |
| 317 | height: 32.w, | 309 | height: 32.w, |
| @@ -391,8 +383,7 @@ class _TopicPicturePage extends StatelessWidget { | @@ -391,8 +383,7 @@ class _TopicPicturePage extends StatelessWidget { | ||
| 391 | child: Column( | 383 | child: Column( |
| 392 | children: [ | 384 | children: [ |
| 393 | SpeakerWidget( | 385 | SpeakerWidget( |
| 394 | - isPlaying: isCurrentPage && | ||
| 395 | - bloc.isAudioPlaying(), | 386 | + isPlaying: isCurrentPage && bloc.isAudioPlaying(), |
| 396 | width: 32.w, | 387 | width: 32.w, |
| 397 | height: 32.w, | 388 | height: 32.w, |
| 398 | onTap: () { | 389 | onTap: () { |
| @@ -504,8 +495,7 @@ class _TopicPicturePage extends StatelessWidget { | @@ -504,8 +495,7 @@ class _TopicPicturePage extends StatelessWidget { | ||
| 504 | Row( | 495 | Row( |
| 505 | children: [ | 496 | children: [ |
| 506 | SpeakerWidget( | 497 | SpeakerWidget( |
| 507 | - isPlaying: isCurrentPage && | ||
| 508 | - bloc.isAudioPlaying(), | 498 | + isPlaying: isCurrentPage && bloc.isAudioPlaying(), |
| 509 | // 控制动画播放 | 499 | // 控制动画播放 |
| 510 | isClickable: !bloc.isRecording, | 500 | isClickable: !bloc.isRecording, |
| 511 | // 控制是否可点击 | 501 | // 控制是否可点击 |
lib/pages/reading/bloc/reading_bloc.dart
| 1 | import 'package:audioplayers/audioplayers.dart'; | 1 | import 'package:audioplayers/audioplayers.dart'; |
| 2 | import 'package:flutter/cupertino.dart'; | 2 | import 'package:flutter/cupertino.dart'; |
| 3 | import 'package:flutter/foundation.dart'; | 3 | import 'package:flutter/foundation.dart'; |
| 4 | -import 'package:flutter/services.dart'; | ||
| 5 | import 'package:flutter_bloc/flutter_bloc.dart'; | 4 | import 'package:flutter_bloc/flutter_bloc.dart'; |
| 6 | import 'package:flutter_easyloading/flutter_easyloading.dart'; | 5 | import 'package:flutter_easyloading/flutter_easyloading.dart'; |
| 7 | import 'package:flutter_screenutil/flutter_screenutil.dart'; | 6 | import 'package:flutter_screenutil/flutter_screenutil.dart'; |
| @@ -15,6 +14,7 @@ import '../../../common/core/user_util.dart'; | @@ -15,6 +14,7 @@ import '../../../common/core/user_util.dart'; | ||
| 15 | import '../../../common/permission/permissionRequester.dart'; | 14 | import '../../../common/permission/permissionRequester.dart'; |
| 16 | import '../../../common/request/dao/listen_dao.dart'; | 15 | import '../../../common/request/dao/listen_dao.dart'; |
| 17 | import '../../../common/request/exception.dart'; | 16 | import '../../../common/request/exception.dart'; |
| 17 | +import '../../../common/speech/chivox_evaluation_channel.dart'; | ||
| 18 | import '../../../common/utils/show_star_reward_dialog.dart'; | 18 | import '../../../common/utils/show_star_reward_dialog.dart'; |
| 19 | import '../../../models/course_process_entity.dart'; | 19 | import '../../../models/course_process_entity.dart'; |
| 20 | import '../../../models/singsound_result_detail_entity.dart'; | 20 | import '../../../models/singsound_result_detail_entity.dart'; |
| @@ -83,7 +83,7 @@ class ReadingPageBloc | @@ -83,7 +83,7 @@ class ReadingPageBloc | ||
| 83 | 83 | ||
| 84 | VoicePlayState get voicePlayState => _voicePlayState; | 84 | VoicePlayState get voicePlayState => _voicePlayState; |
| 85 | 85 | ||
| 86 | - late MethodChannel methodChannel; | 86 | + final ChivoxEvaluationChannel methodChannel = ChivoxEvaluationChannel(); |
| 87 | 87 | ||
| 88 | late AudioPlayer audioPlayer; | 88 | late AudioPlayer audioPlayer; |
| 89 | 89 | ||
| @@ -91,7 +91,8 @@ class ReadingPageBloc | @@ -91,7 +91,8 @@ class ReadingPageBloc | ||
| 91 | 91 | ||
| 92 | final Color? moduleColor; | 92 | final Color? moduleColor; |
| 93 | 93 | ||
| 94 | - ReadingPageBloc(this.context, this.pageController, this.courseLessonId, this.moduleColor) | 94 | + ReadingPageBloc( |
| 95 | + this.context, this.pageController, this.courseLessonId, this.moduleColor) | ||
| 95 | : super(ReadingPageInitial()) { | 96 | : super(ReadingPageInitial()) { |
| 96 | on<CurrentPageIndexChangeEvent>(_pageControllerChange); | 97 | on<CurrentPageIndexChangeEvent>(_pageControllerChange); |
| 97 | on<CurrentModeChangeEvent>(_playModeChange); | 98 | on<CurrentModeChangeEvent>(_playModeChange); |
| @@ -99,7 +100,7 @@ class ReadingPageBloc | @@ -99,7 +100,7 @@ class ReadingPageBloc | ||
| 99 | // _currentPage = pageController.page!.round(); | 100 | // _currentPage = pageController.page!.round(); |
| 100 | // }); | 101 | // }); |
| 101 | on<RequestDataEvent>(_requestData); | 102 | on<RequestDataEvent>(_requestData); |
| 102 | - on<InitBlocEvent>((event, emit) { | 103 | + on<InitBlocEvent>((event, emit) async { |
| 103 | //音频播放器 | 104 | //音频播放器 |
| 104 | audioPlayer = AudioPlayer(); | 105 | audioPlayer = AudioPlayer(); |
| 105 | audioPlayer.onPlayerStateChanged.listen((event) async { | 106 | audioPlayer.onPlayerStateChanged.listen((event) async { |
| @@ -130,9 +131,6 @@ class ReadingPageBloc | @@ -130,9 +131,6 @@ class ReadingPageBloc | ||
| 130 | add(VoicePlayStateChangeEvent()); | 131 | add(VoicePlayStateChangeEvent()); |
| 131 | }); | 132 | }); |
| 132 | 133 | ||
| 133 | - methodChannel = | ||
| 134 | - const MethodChannel('wow_english/sing_sound_method_channel'); | ||
| 135 | - methodChannel.invokeMethod('initVoiceSdk', {}); //初始化评测 | ||
| 136 | methodChannel.setMethodCallHandler((call) async { | 134 | methodChannel.setMethodCallHandler((call) async { |
| 137 | Log.d( | 135 | Log.d( |
| 138 | "setMethodCallHandler method=${call.method} arguments=${call.arguments}"); | 136 | "setMethodCallHandler method=${call.method} arguments=${call.arguments}"); |
| @@ -179,10 +177,10 @@ class ReadingPageBloc | @@ -179,10 +177,10 @@ class ReadingPageBloc | ||
| 179 | return; | 177 | return; |
| 180 | } | 178 | } |
| 181 | }); | 179 | }); |
| 180 | + await methodChannel.invokeMethod('initVoiceSdk', {}); //初始化评测 | ||
| 182 | }); | 181 | }); |
| 183 | on<VoicePlayStateChangeEvent>(_voicePlayStateChange); | 182 | on<VoicePlayStateChangeEvent>(_voicePlayStateChange); |
| 184 | on<PlayOriginalAudioEvent>(_playOriginalAudio); | 183 | on<PlayOriginalAudioEvent>(_playOriginalAudio); |
| 185 | - on<XSVoiceInitEvent>(_initVoiceSdk); | ||
| 186 | on<XSVoiceStartEvent>(_voiceXsStart); | 184 | on<XSVoiceStartEvent>(_voiceXsStart); |
| 187 | on<XSVoiceStopEvent>(_voiceXsStop); | 185 | on<XSVoiceStopEvent>(_voiceXsStop); |
| 188 | on<XSVoiceResultEvent>(_voiceXsResult); | 186 | on<XSVoiceResultEvent>(_voiceXsResult); |
| @@ -192,11 +190,12 @@ class ReadingPageBloc | @@ -192,11 +190,12 @@ class ReadingPageBloc | ||
| 192 | } | 190 | } |
| 193 | 191 | ||
| 194 | @override | 192 | @override |
| 195 | - Future<void> close() { | 193 | + Future<void> close() async { |
| 196 | pageController.dispose(); | 194 | pageController.dispose(); |
| 197 | - audioPlayer.release(); | ||
| 198 | - audioPlayer.dispose(); | ||
| 199 | - _voiceXsCancel(force: true); | 195 | + await audioPlayer.release(); |
| 196 | + await audioPlayer.dispose(); | ||
| 197 | + await _voiceXsCancel(force: true); | ||
| 198 | + await methodChannel.dispose(); | ||
| 200 | return super.close(); | 199 | return super.close(); |
| 201 | } | 200 | } |
| 202 | 201 | ||
| @@ -295,8 +294,11 @@ class ReadingPageBloc | @@ -295,8 +294,11 @@ class ReadingPageBloc | ||
| 295 | Future<void> _playAudio(String? audioUrl) async { | 294 | Future<void> _playAudio(String? audioUrl) async { |
| 296 | if (audioUrl != null && audioUrl.isNotEmpty) { | 295 | if (audioUrl != null && audioUrl.isNotEmpty) { |
| 297 | try { | 296 | try { |
| 298 | - await audioPlayer.play(UrlSource(audioUrl), | ||
| 299 | - balance: 0.0, ctx: AudioContext()); | 297 | + final source = |
| 298 | + audioUrl.startsWith('http://') || audioUrl.startsWith('https://') | ||
| 299 | + ? UrlSource(audioUrl) | ||
| 300 | + : DeviceFileSource(audioUrl); | ||
| 301 | + await audioPlayer.play(source, balance: 0.0, ctx: AudioContext()); | ||
| 300 | } catch (e) { | 302 | } catch (e) { |
| 301 | Log.d('_playAudio error: $e'); | 303 | Log.d('_playAudio error: $e'); |
| 302 | } | 304 | } |
| @@ -329,8 +331,7 @@ class ReadingPageBloc | @@ -329,8 +331,7 @@ class ReadingPageBloc | ||
| 329 | } else { | 331 | } else { |
| 330 | List<String>? wordList = currentPageData()?.word?.split(RegExp(r'\s+')); | 332 | List<String>? wordList = currentPageData()?.word?.split(RegExp(r'\s+')); |
| 331 | resultDetails = (wordList ?? []) | 333 | resultDetails = (wordList ?? []) |
| 332 | - .map( | ||
| 333 | - (word) => SingsoundResultDetailEntity.withCharAndScore(word, 0)) | 334 | + .map((word) => SingsoundResultDetailEntity.withCharAndScore(word, 0)) |
| 334 | .toList(); | 335 | .toList(); |
| 335 | } | 336 | } |
| 336 | List<TextSpan> textSpans = resultDetails.asMap().entries.map((entry) { | 337 | List<TextSpan> textSpans = resultDetails.asMap().entries.map((entry) { |
| @@ -372,13 +373,7 @@ class ReadingPageBloc | @@ -372,13 +373,7 @@ class ReadingPageBloc | ||
| 372 | } | 373 | } |
| 373 | } | 374 | } |
| 374 | 375 | ||
| 375 | - ///初始化SDK | ||
| 376 | - _initVoiceSdk( | ||
| 377 | - XSVoiceInitEvent event, Emitter<ReadingPageState> emitter) async { | ||
| 378 | - methodChannel.invokeMethod('initVoiceSdk', event.data); | ||
| 379 | - } | ||
| 380 | - | ||
| 381 | - ///先声测试 | 376 | + ///驰声测试 |
| 382 | void _voiceXsStart( | 377 | void _voiceXsStart( |
| 383 | XSVoiceStartEvent event, Emitter<ReadingPageState> emitter) async { | 378 | XSVoiceStartEvent event, Emitter<ReadingPageState> emitter) async { |
| 384 | await _stopAudio(); | 379 | await _stopAudio(); |
| @@ -391,7 +386,7 @@ class ReadingPageBloc | @@ -391,7 +386,7 @@ class ReadingPageBloc | ||
| 391 | bool result = await requestPermission( | 386 | bool result = await requestPermission( |
| 392 | context, Permission.microphone, "录音", "用于开启录音,识别您的开口作答并给出反馈"); | 387 | context, Permission.microphone, "录音", "用于开启录音,识别您的开口作答并给出反馈"); |
| 393 | if (result) { | 388 | if (result) { |
| 394 | - methodChannel.invokeMethod('startVoice', { | 389 | + await methodChannel.invokeMethod('startVoice', { |
| 395 | 'word': content, | 390 | 'word': content, |
| 396 | 'type': '0', | 391 | 'type': '0', |
| 397 | 'userId': UserUtil.getUser()?.id.toString() | 392 | 'userId': UserUtil.getUser()?.id.toString() |
| @@ -409,17 +404,17 @@ class ReadingPageBloc | @@ -409,17 +404,17 @@ class ReadingPageBloc | ||
| 409 | // 提取 score 和 char 字段 | 404 | // 提取 score 和 char 字段 |
| 410 | List<SingsoundResultDetailEntity> detailEntities = []; | 405 | List<SingsoundResultDetailEntity> detailEntities = []; |
| 411 | for (var detail in resultDetailsJsons) { | 406 | for (var detail in resultDetailsJsons) { |
| 412 | - int score = detail['score'] as int; | 407 | + int score = (detail['score'] as num?)?.round() ?? 0; |
| 413 | String char = detail['char'] as String; | 408 | String char = detail['char'] as String; |
| 414 | detailEntities | 409 | detailEntities |
| 415 | .add(SingsoundResultDetailEntity.withCharAndScore(char, score)); | 410 | .add(SingsoundResultDetailEntity.withCharAndScore(char, score)); |
| 416 | } | 411 | } |
| 417 | 412 | ||
| 418 | ///todo 后面可以考虑要不要传自己的服务器 | 413 | ///todo 后面可以考虑要不要传自己的服务器 |
| 419 | - final recordFileUrl = args['audioUrl'].toString(); | 414 | + final recordFileUrl = args['audioUrl']?.toString() ?? ''; |
| 420 | int score = int.parse(overall); | 415 | int score = int.parse(overall); |
| 421 | currentPageData()?.recordScore = overall; | 416 | currentPageData()?.recordScore = overall; |
| 422 | - currentPageData()?.recordUrl = args['audioUrl'] + '.mp3'; | 417 | + currentPageData()?.recordUrl = recordFileUrl; |
| 423 | currentPageData()?.resultDetails = detailEntities; | 418 | currentPageData()?.resultDetails = detailEntities; |
| 424 | add(OnXSVoiceStateChangeEvent()); | 419 | add(OnXSVoiceStateChangeEvent()); |
| 425 | 420 | ||
| @@ -427,7 +422,8 @@ class ReadingPageBloc | @@ -427,7 +422,8 @@ class ReadingPageBloc | ||
| 427 | 422 | ||
| 428 | if (voiceResult.lottieFilePath != null) { | 423 | if (voiceResult.lottieFilePath != null) { |
| 429 | AudioPlayerUtil.getInstance().playAudio(voiceResult.audioType); | 424 | AudioPlayerUtil.getInstance().playAudio(voiceResult.audioType); |
| 430 | - await showCheerRewardDialog(context, lottieFile: voiceResult.lottieFilePath!, onDismiss: () async { | 425 | + await showCheerRewardDialog(context, |
| 426 | + lottieFile: voiceResult.lottieFilePath!, onDismiss: () async { | ||
| 431 | await actionAfterRecord(); | 427 | await actionAfterRecord(); |
| 432 | }); | 428 | }); |
| 433 | } else { | 429 | } else { |
| @@ -458,14 +454,14 @@ class ReadingPageBloc | @@ -458,14 +454,14 @@ class ReadingPageBloc | ||
| 458 | ///终止评测 | 454 | ///终止评测 |
| 459 | void _voiceXsStop( | 455 | void _voiceXsStop( |
| 460 | XSVoiceStopEvent event, Emitter<ReadingPageState> emitter) async { | 456 | XSVoiceStopEvent event, Emitter<ReadingPageState> emitter) async { |
| 461 | - methodChannel.invokeMethod('stopVoice'); | 457 | + await methodChannel.invokeMethod('stopVoice'); |
| 462 | } | 458 | } |
| 463 | 459 | ||
| 464 | ///取消评测(用于处理退出页面后录音未停止等异常情况的保护操作) | 460 | ///取消评测(用于处理退出页面后录音未停止等异常情况的保护操作) |
| 465 | - void _voiceXsCancel({bool force = false}) { | 461 | + Future<void> _voiceXsCancel({bool force = false}) async { |
| 466 | Log.d("取消评测 _voiceXsCancel _isRecording=$_isRecording"); | 462 | Log.d("取消评测 _voiceXsCancel _isRecording=$_isRecording"); |
| 467 | if (_isRecording || force) { | 463 | if (_isRecording || force) { |
| 468 | - methodChannel.invokeMethod('cancelVoice'); | 464 | + await methodChannel.invokeMethod('cancelVoice'); |
| 469 | } | 465 | } |
| 470 | } | 466 | } |
| 471 | 467 |
lib/pages/reading/bloc/reading_event.dart
| @@ -22,19 +22,13 @@ class PlayOriginalAudioEvent extends ReadingPageEvent { | @@ -22,19 +22,13 @@ class PlayOriginalAudioEvent extends ReadingPageEvent { | ||
| 22 | PlayOriginalAudioEvent(this.url); | 22 | PlayOriginalAudioEvent(this.url); |
| 23 | } | 23 | } |
| 24 | 24 | ||
| 25 | -///初始化先声SDK | ||
| 26 | -class XSVoiceInitEvent extends ReadingPageEvent { | ||
| 27 | - final Map data; | ||
| 28 | - XSVoiceInitEvent(this.data); | ||
| 29 | -} | ||
| 30 | - | ||
| 31 | ///评测结果 | 25 | ///评测结果 |
| 32 | class XSVoiceResultEvent extends ReadingPageEvent { | 26 | class XSVoiceResultEvent extends ReadingPageEvent { |
| 33 | final dynamic message; | 27 | final dynamic message; |
| 34 | XSVoiceResultEvent(this.message); | 28 | XSVoiceResultEvent(this.message); |
| 35 | } | 29 | } |
| 36 | 30 | ||
| 37 | -///先声测试 | 31 | +///驰声测试 |
| 38 | class XSVoiceStartEvent extends ReadingPageEvent { | 32 | class XSVoiceStartEvent extends ReadingPageEvent { |
| 39 | final String content; | 33 | final String content; |
| 40 | final String type; | 34 | final String type; |
| @@ -42,10 +36,10 @@ class XSVoiceStartEvent extends ReadingPageEvent { | @@ -42,10 +36,10 @@ class XSVoiceStartEvent extends ReadingPageEvent { | ||
| 42 | XSVoiceStartEvent(this.content, this.type, this.userId); | 36 | XSVoiceStartEvent(this.content, this.type, this.userId); |
| 43 | } | 37 | } |
| 44 | 38 | ||
| 45 | -///先声评测停止 | 39 | +///驰声评测停止 |
| 46 | class XSVoiceStopEvent extends ReadingPageEvent {} | 40 | class XSVoiceStopEvent extends ReadingPageEvent {} |
| 47 | 41 | ||
| 48 | -///先声评测状态 | 42 | +///驰声评测状态 |
| 49 | class OnXSVoiceStateChangeEvent extends ReadingPageEvent {} | 43 | class OnXSVoiceStateChangeEvent extends ReadingPageEvent {} |
| 50 | 44 | ||
| 51 | ///音频播放状态 | 45 | ///音频播放状态 |
lib/pages/reading/reading_page.dart
| @@ -7,7 +7,6 @@ import 'package:wow_english/pages/reading/widgets/ReadingModeType.dart'; | @@ -7,7 +7,6 @@ import 'package:wow_english/pages/reading/widgets/ReadingModeType.dart'; | ||
| 7 | import 'package:wow_english/pages/reading/widgets/reading_dialog_widget.dart'; | 7 | import 'package:wow_english/pages/reading/widgets/reading_dialog_widget.dart'; |
| 8 | import 'package:wow_english/route/route.dart'; | 8 | import 'package:wow_english/route/route.dart'; |
| 9 | 9 | ||
| 10 | -import '../../common/core/app_consts.dart'; | ||
| 11 | import '../../common/core/user_util.dart'; | 10 | import '../../common/core/user_util.dart'; |
| 12 | import '../../common/widgets/recorder_widget.dart'; | 11 | import '../../common/widgets/recorder_widget.dart'; |
| 13 | import '../../common/widgets/speaker_widget.dart'; | 12 | import '../../common/widgets/speaker_widget.dart'; |
| @@ -29,13 +28,7 @@ class ReadingPage extends StatelessWidget { | @@ -29,13 +28,7 @@ class ReadingPage extends StatelessWidget { | ||
| 29 | create: (_) => ReadingPageBloc( | 28 | create: (_) => ReadingPageBloc( |
| 30 | context, PageController(), courseLessonId ?? '', moduleColor) | 29 | context, PageController(), courseLessonId ?? '', moduleColor) |
| 31 | ..add(InitBlocEvent()) | 30 | ..add(InitBlocEvent()) |
| 32 | - ..add(RequestDataEvent()) | ||
| 33 | - ..add(XSVoiceInitEvent({ | ||
| 34 | - 'appKey': AppConsts.xsAppKey, | ||
| 35 | - 'service': AppConsts.xsAppService, | ||
| 36 | - 'secretKey': AppConsts.xsAppSecretKey, | ||
| 37 | - 'userId': UserUtil.getUser()!.id.toString(), | ||
| 38 | - })), | 31 | + ..add(RequestDataEvent()), |
| 39 | child: _ReadingPage(), | 32 | child: _ReadingPage(), |
| 40 | ); | 33 | ); |
| 41 | } | 34 | } |
lib/pages/repeataftercontent/bloc/repeat_after_content_bloc.dart
| @@ -3,7 +3,6 @@ import 'dart:async'; | @@ -3,7 +3,6 @@ import 'dart:async'; | ||
| 3 | 3 | ||
| 4 | import 'package:audio_session/audio_session.dart'; | 4 | import 'package:audio_session/audio_session.dart'; |
| 5 | import 'package:flutter/cupertino.dart'; | 5 | import 'package:flutter/cupertino.dart'; |
| 6 | -import 'package:flutter/services.dart'; | ||
| 7 | import 'package:flutter_bloc/flutter_bloc.dart'; | 6 | import 'package:flutter_bloc/flutter_bloc.dart'; |
| 8 | import 'package:flutter_sound/flutter_sound.dart'; | 7 | import 'package:flutter_sound/flutter_sound.dart'; |
| 9 | import 'package:path_provider/path_provider.dart'; | 8 | import 'package:path_provider/path_provider.dart'; |
| @@ -12,58 +11,70 @@ import 'package:wow_english/common/request/dao/listen_dao.dart'; | @@ -12,58 +11,70 @@ import 'package:wow_english/common/request/dao/listen_dao.dart'; | ||
| 12 | import 'package:wow_english/route/route.dart'; | 11 | import 'package:wow_english/route/route.dart'; |
| 13 | import '../../../common/dialogs/show_dialog.dart'; | 12 | import '../../../common/dialogs/show_dialog.dart'; |
| 14 | import '../../../common/request/exception.dart'; | 13 | import '../../../common/request/exception.dart'; |
| 14 | +import '../../../common/speech/chivox_evaluation_channel.dart'; | ||
| 15 | import '../../../models/read_content_entity.dart'; | 15 | import '../../../models/read_content_entity.dart'; |
| 16 | import '../../../utils/loading.dart'; | 16 | import '../../../utils/loading.dart'; |
| 17 | import '../../../utils/toast_util.dart'; | 17 | import '../../../utils/toast_util.dart'; |
| 18 | 18 | ||
| 19 | - | ||
| 20 | part 'repeat_after_content_event.dart'; | 19 | part 'repeat_after_content_event.dart'; |
| 21 | part 'repeat_after_content_state.dart'; | 20 | part 'repeat_after_content_state.dart'; |
| 22 | 21 | ||
| 23 | enum VoiceRecordState { | 22 | enum VoiceRecordState { |
| 24 | ///未知 | 23 | ///未知 |
| 25 | voiceRecordUnkonw, | 24 | voiceRecordUnkonw, |
| 25 | + | ||
| 26 | ///开始录音 | 26 | ///开始录音 |
| 27 | voiceRecordStat, | 27 | voiceRecordStat, |
| 28 | + | ||
| 28 | ///正在录音 | 29 | ///正在录音 |
| 29 | voiceRecording, | 30 | voiceRecording, |
| 31 | + | ||
| 30 | ///录音结束 | 32 | ///录音结束 |
| 31 | voiceRecordEnd | 33 | voiceRecordEnd |
| 32 | } | 34 | } |
| 33 | 35 | ||
| 34 | -///先声测评状态 | 36 | +///驰声测评状态 |
| 35 | enum XSVoiceCheckState { | 37 | enum XSVoiceCheckState { |
| 36 | ///未知 | 38 | ///未知 |
| 37 | unKnow, | 39 | unKnow, |
| 40 | + | ||
| 38 | ///测评开始 | 41 | ///测评开始 |
| 39 | start, | 42 | start, |
| 43 | + | ||
| 40 | ///评测结果 | 44 | ///评测结果 |
| 41 | result, | 45 | result, |
| 46 | + | ||
| 42 | ///测评结束 | 47 | ///测评结束 |
| 43 | stop, | 48 | stop, |
| 44 | } | 49 | } |
| 45 | 50 | ||
| 46 | -class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterContentState> { | ||
| 47 | - | 51 | +class RepeatAfterContentBloc |
| 52 | + extends Bloc<RepeatAfterContentEvent, RepeatAfterContentState> { | ||
| 48 | final String courseLessonId; | 53 | final String courseLessonId; |
| 49 | 54 | ||
| 50 | /// 是否正在播放视频 | 55 | /// 是否正在播放视频 |
| 51 | bool _videoPlaying = true; | 56 | bool _videoPlaying = true; |
| 52 | bool get videoPlaying => _videoPlaying; | 57 | bool get videoPlaying => _videoPlaying; |
| 58 | + | ||
| 53 | /// 是否正在录音 | 59 | /// 是否正在录音 |
| 54 | bool _isRecord = false; | 60 | bool _isRecord = false; |
| 55 | bool get isRecord => _isRecord; | 61 | bool get isRecord => _isRecord; |
| 56 | - /// 先声评测状态 | 62 | + |
| 63 | + /// 驰声评测状态 | ||
| 57 | XSVoiceCheckState _xSCheckState = XSVoiceCheckState.unKnow; | 64 | XSVoiceCheckState _xSCheckState = XSVoiceCheckState.unKnow; |
| 58 | XSVoiceCheckState get xSCheckState => _xSCheckState; | 65 | XSVoiceCheckState get xSCheckState => _xSCheckState; |
| 66 | + | ||
| 59 | /// 评测结果 | 67 | /// 评测结果 |
| 60 | Map? _voiceTestResult; | 68 | Map? _voiceTestResult; |
| 61 | Map? get voiceTestResult => _voiceTestResult; | 69 | Map? get voiceTestResult => _voiceTestResult; |
| 70 | + | ||
| 62 | /// 录音的次数 | 71 | /// 录音的次数 |
| 63 | int _recordNumber = 0; | 72 | int _recordNumber = 0; |
| 73 | + | ||
| 64 | /// 录音文件地址 | 74 | /// 录音文件地址 |
| 65 | String _path = ''; | 75 | String _path = ''; |
| 66 | String get path => _path; | 76 | String get path => _path; |
| 77 | + | ||
| 67 | /// 当前播放的视频位置 | 78 | /// 当前播放的视频位置 |
| 68 | int _currentPlayIndex = 0; | 79 | int _currentPlayIndex = 0; |
| 69 | int get currentPlayIndex => _currentPlayIndex; | 80 | int get currentPlayIndex => _currentPlayIndex; |
| @@ -74,17 +85,18 @@ class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterCo | @@ -74,17 +85,18 @@ class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterCo | ||
| 74 | 85 | ||
| 75 | /// 跟读内容数字 | 86 | /// 跟读内容数字 |
| 76 | List<ReadContentEntity?>? _entityList; | 87 | List<ReadContentEntity?>? _entityList; |
| 77 | - List<ReadContentEntity?>? get entityList => _entityList ; | 88 | + List<ReadContentEntity?>? get entityList => _entityList; |
| 78 | 89 | ||
| 79 | /// 方法 | 90 | /// 方法 |
| 80 | - late MethodChannel methodChannel; | 91 | + final ChivoxEvaluationChannel methodChannel = ChivoxEvaluationChannel(); |
| 81 | 92 | ||
| 82 | ///录音 | 93 | ///录音 |
| 83 | late FlutterSoundRecorder _soundRecorder; | 94 | late FlutterSoundRecorder _soundRecorder; |
| 84 | late FlutterSoundPlayer _soundPlayer; | 95 | late FlutterSoundPlayer _soundPlayer; |
| 85 | // StreamSubscription? _soundPlayerListen; | 96 | // StreamSubscription? _soundPlayerListen; |
| 86 | 97 | ||
| 87 | - RepeatAfterContentBloc(this.courseLessonId) : super(RepeatAfterContentInitial()) { | 98 | + RepeatAfterContentBloc(this.courseLessonId) |
| 99 | + : super(RepeatAfterContentInitial()) { | ||
| 88 | on<VoiceRecordStateChangeEvent>(_voiceRecordStateChange); | 100 | on<VoiceRecordStateChangeEvent>(_voiceRecordStateChange); |
| 89 | on<PostFollowReadContentEvent>(_postFollowReadContent); | 101 | on<PostFollowReadContentEvent>(_postFollowReadContent); |
| 90 | on<ChangeVideoPlayIndexEvent>(_changeVideoPlayIndex); | 102 | on<ChangeVideoPlayIndexEvent>(_changeVideoPlayIndex); |
| @@ -93,7 +105,6 @@ class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterCo | @@ -93,7 +105,6 @@ class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterCo | ||
| 93 | on<StarRecordVoiceEvent>(_starRecordVoice); | 105 | on<StarRecordVoiceEvent>(_starRecordVoice); |
| 94 | on<StopRecordVoiceEvent>(_stopRecordVoice); | 106 | on<StopRecordVoiceEvent>(_stopRecordVoice); |
| 95 | on<XSVoiceResultEvent>(_voiceXsResult); | 107 | on<XSVoiceResultEvent>(_voiceXsResult); |
| 96 | - on<XSVoiceInitEvent>(_initVoiceSdk); | ||
| 97 | on<RequestDataEvent>(_requestData); | 108 | on<RequestDataEvent>(_requestData); |
| 98 | on<XSVoiceTestEvent>(_voiceXsTest); | 109 | on<XSVoiceTestEvent>(_voiceXsTest); |
| 99 | on<XSVoiceStopEvent>(_voiceXsStop); | 110 | on<XSVoiceStopEvent>(_voiceXsStop); |
| @@ -102,17 +113,19 @@ class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterCo | @@ -102,17 +113,19 @@ class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterCo | ||
| 102 | } | 113 | } |
| 103 | 114 | ||
| 104 | @override | 115 | @override |
| 105 | - Future<void> close() { | ||
| 106 | - _releaseFlauto(); | ||
| 107 | - _voiceXsCancel(); | 116 | + Future<void> close() async { |
| 117 | + await _releaseFlauto(); | ||
| 118 | + await _voiceXsCancel(); | ||
| 119 | + await methodChannel.dispose(); | ||
| 108 | return super.close(); | 120 | return super.close(); |
| 109 | } | 121 | } |
| 110 | 122 | ||
| 111 | ///初始化功能 | 123 | ///初始化功能 |
| 112 | - void _initBlocData(InitBlocEvent event, Emitter<RepeatAfterContentState> emitter) async { | ||
| 113 | - methodChannel = const MethodChannel('wow_english/sing_sound_method_channel'); | 124 | + void _initBlocData( |
| 125 | + InitBlocEvent event, Emitter<RepeatAfterContentState> emitter) async { | ||
| 114 | methodChannel.setMethodCallHandler((call) async { | 126 | methodChannel.setMethodCallHandler((call) async { |
| 115 | - if (call.method == 'voiceResult') {//评测结果 | 127 | + if (call.method == 'voiceResult') { |
| 128 | + //评测结果 | ||
| 116 | add(XSVoiceResultEvent(call.arguments)); | 129 | add(XSVoiceResultEvent(call.arguments)); |
| 117 | add(PostFollowReadContentEvent()); | 130 | add(PostFollowReadContentEvent()); |
| 118 | return; | 131 | return; |
| @@ -121,28 +134,36 @@ class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterCo | @@ -121,28 +134,36 @@ class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterCo | ||
| 121 | if (call.method == 'voiceEnd') { | 134 | if (call.method == 'voiceEnd') { |
| 122 | return; | 135 | return; |
| 123 | } | 136 | } |
| 137 | + | ||
| 138 | + if (call.method == 'voiceFail') { | ||
| 139 | + _xSCheckState = XSVoiceCheckState.unKnow; | ||
| 140 | + add(VoiceRecordStateChangeEvent(_voiceRecordState)); | ||
| 141 | + showToast('评测失败'); | ||
| 142 | + } | ||
| 124 | }); | 143 | }); |
| 125 | 144 | ||
| 126 | //录音 | 145 | //录音 |
| 127 | _soundRecorder = FlutterSoundRecorder(); | 146 | _soundRecorder = FlutterSoundRecorder(); |
| 128 | //音屏 | 147 | //音屏 |
| 129 | _soundPlayer = FlutterSoundPlayer(); | 148 | _soundPlayer = FlutterSoundPlayer(); |
| 130 | - _init(); | 149 | + await _init(); |
| 150 | + await methodChannel.invokeMethod('initVoiceSdk', {}); | ||
| 131 | } | 151 | } |
| 132 | 152 | ||
| 133 | - void _init() async { | 153 | + Future<void> _init() async { |
| 134 | await _soundRecorder.openRecorder(); | 154 | await _soundRecorder.openRecorder(); |
| 135 | - await _soundRecorder.setSubscriptionDuration(const Duration(milliseconds: 10)); | 155 | + await _soundRecorder |
| 156 | + .setSubscriptionDuration(const Duration(milliseconds: 10)); | ||
| 136 | //设置音频 | 157 | //设置音频 |
| 137 | final session = await AudioSession.instance; | 158 | final session = await AudioSession.instance; |
| 138 | await session.configure(AudioSessionConfiguration( | 159 | await session.configure(AudioSessionConfiguration( |
| 139 | avAudioSessionCategory: AVAudioSessionCategory.playAndRecord, | 160 | avAudioSessionCategory: AVAudioSessionCategory.playAndRecord, |
| 140 | avAudioSessionCategoryOptions: | 161 | avAudioSessionCategoryOptions: |
| 141 | - AVAudioSessionCategoryOptions.allowBluetooth | | ||
| 142 | - AVAudioSessionCategoryOptions.defaultToSpeaker, | 162 | + AVAudioSessionCategoryOptions.allowBluetooth | |
| 163 | + AVAudioSessionCategoryOptions.defaultToSpeaker, | ||
| 143 | avAudioSessionMode: AVAudioSessionMode.spokenAudio, | 164 | avAudioSessionMode: AVAudioSessionMode.spokenAudio, |
| 144 | avAudioSessionRouteSharingPolicy: | 165 | avAudioSessionRouteSharingPolicy: |
| 145 | - AVAudioSessionRouteSharingPolicy.defaultPolicy, | 166 | + AVAudioSessionRouteSharingPolicy.defaultPolicy, |
| 146 | avAudioSessionSetActiveOptions: AVAudioSessionSetActiveOptions.none, | 167 | avAudioSessionSetActiveOptions: AVAudioSessionSetActiveOptions.none, |
| 147 | androidAudioAttributes: const AndroidAudioAttributes( | 168 | androidAudioAttributes: const AndroidAudioAttributes( |
| 148 | contentType: AndroidAudioContentType.speech, | 169 | contentType: AndroidAudioContentType.speech, |
| @@ -154,11 +175,13 @@ class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterCo | @@ -154,11 +175,13 @@ class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterCo | ||
| 154 | )); | 175 | )); |
| 155 | await _soundPlayer.closePlayer(); | 176 | await _soundPlayer.closePlayer(); |
| 156 | await _soundPlayer.openPlayer(); | 177 | await _soundPlayer.openPlayer(); |
| 157 | - await _soundPlayer.setSubscriptionDuration(const Duration(milliseconds: 10)); | 178 | + await _soundPlayer |
| 179 | + .setSubscriptionDuration(const Duration(milliseconds: 10)); | ||
| 158 | } | 180 | } |
| 159 | 181 | ||
| 160 | ///请求数据 | 182 | ///请求数据 |
| 161 | - void _requestData(RequestDataEvent event,Emitter<RepeatAfterContentState> emitter) async { | 183 | + void _requestData( |
| 184 | + RequestDataEvent event, Emitter<RepeatAfterContentState> emitter) async { | ||
| 162 | try { | 185 | try { |
| 163 | await loading(() async { | 186 | await loading(() async { |
| 164 | _entityList = await ListenDao.readContent(courseLessonId); | 187 | _entityList = await ListenDao.readContent(courseLessonId); |
| @@ -166,107 +189,101 @@ class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterCo | @@ -166,107 +189,101 @@ class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterCo | ||
| 166 | }); | 189 | }); |
| 167 | } catch (e) { | 190 | } catch (e) { |
| 168 | if (e is ApiException) { | 191 | if (e is ApiException) { |
| 169 | - showToast(e.message??'请求失败,请检查网络连接'); | 192 | + showToast(e.message ?? '请求失败,请检查网络连接'); |
| 170 | } | 193 | } |
| 171 | } | 194 | } |
| 172 | } | 195 | } |
| 173 | 196 | ||
| 174 | ///提交跟读结果 | 197 | ///提交跟读结果 |
| 175 | - void _postFollowReadContent(PostFollowReadContentEvent event,Emitter<RepeatAfterContentState> emitter) async { | 198 | + void _postFollowReadContent(PostFollowReadContentEvent event, |
| 199 | + Emitter<RepeatAfterContentState> emitter) async { | ||
| 176 | try { | 200 | try { |
| 177 | ReadContentEntity entity = _entityList![_currentPlayIndex]!; | 201 | ReadContentEntity entity = _entityList![_currentPlayIndex]!; |
| 178 | - await ListenDao.followResult(_recordNumber.toString(),entity.id); | 202 | + await ListenDao.followResult(_recordNumber.toString(), entity.id); |
| 179 | } catch (e) { | 203 | } catch (e) { |
| 180 | - if (e is ApiException) { | ||
| 181 | - | ||
| 182 | - } | 204 | + if (e is ApiException) {} |
| 183 | } | 205 | } |
| 184 | } | 206 | } |
| 185 | 207 | ||
| 186 | - void _videoPlayStateChange(VideoPlayChangeEvent event,Emitter<RepeatAfterContentState> emitter) async { | 208 | + void _videoPlayStateChange(VideoPlayChangeEvent event, |
| 209 | + Emitter<RepeatAfterContentState> emitter) async { | ||
| 187 | _videoPlaying = !_videoPlaying; | 210 | _videoPlaying = !_videoPlaying; |
| 188 | emitter(VideoPlayChangeState()); | 211 | emitter(VideoPlayChangeState()); |
| 189 | } | 212 | } |
| 190 | 213 | ||
| 191 | - void _voiceRecord(VoiceRecordEvent event,Emitter<RepeatAfterContentState> emitter) async { | 214 | + void _voiceRecord( |
| 215 | + VoiceRecordEvent event, Emitter<RepeatAfterContentState> emitter) async { | ||
| 192 | _isRecord = !_isRecord; | 216 | _isRecord = !_isRecord; |
| 193 | emitter(VoiceRecordChangeState()); | 217 | emitter(VoiceRecordChangeState()); |
| 194 | } | 218 | } |
| 195 | 219 | ||
| 196 | - void _voiceRecordStateChange(VoiceRecordStateChangeEvent event,Emitter<RepeatAfterContentState> emitter) async { | 220 | + void _voiceRecordStateChange(VoiceRecordStateChangeEvent event, |
| 221 | + Emitter<RepeatAfterContentState> emitter) async { | ||
| 197 | _voiceRecordState = event.voiceRecordState; | 222 | _voiceRecordState = event.voiceRecordState; |
| 198 | emitter(VoiceRecordStateChange()); | 223 | emitter(VoiceRecordStateChange()); |
| 199 | } | 224 | } |
| 200 | 225 | ||
| 201 | - | ||
| 202 | - _initVoiceSdk(XSVoiceInitEvent event,Emitter<RepeatAfterContentState> emitter) async { | ||
| 203 | - methodChannel.invokeMethod('initVoiceSdk',event.data); | ||
| 204 | - } | ||
| 205 | - | ||
| 206 | - ///先声测试 | ||
| 207 | - void _voiceXsTest(XSVoiceTestEvent event,Emitter<RepeatAfterContentState> emitter) async { | 226 | + ///驰声测试 |
| 227 | + void _voiceXsTest( | ||
| 228 | + XSVoiceTestEvent event, Emitter<RepeatAfterContentState> emitter) async { | ||
| 208 | _recordNumber += 1; | 229 | _recordNumber += 1; |
| 209 | _xSCheckState = XSVoiceCheckState.start; | 230 | _xSCheckState = XSVoiceCheckState.start; |
| 210 | emitter(XSVoiceTestState()); | 231 | emitter(XSVoiceTestState()); |
| 211 | - await methodChannel.invokeMethod( | ||
| 212 | - 'startLocalVoice', | ||
| 213 | - { | ||
| 214 | - 'type':event.type, | ||
| 215 | - 'word':event.testWord, | ||
| 216 | - 'voicePath':_path, | ||
| 217 | - 'userId':event.userId.toString() | ||
| 218 | - } | ||
| 219 | - ); | 232 | + await methodChannel.invokeMethod('startLocalVoice', { |
| 233 | + 'type': event.type, | ||
| 234 | + 'word': event.testWord, | ||
| 235 | + 'voicePath': _path, | ||
| 236 | + 'userId': event.userId.toString() | ||
| 237 | + }); | ||
| 220 | } | 238 | } |
| 221 | 239 | ||
| 222 | ///终止评测 | 240 | ///终止评测 |
| 223 | - void _voiceXsStop(XSVoiceStopEvent event,Emitter<RepeatAfterContentState> emitter) async { | ||
| 224 | - methodChannel.invokeMethod('stopVoice'); | 241 | + void _voiceXsStop( |
| 242 | + XSVoiceStopEvent event, Emitter<RepeatAfterContentState> emitter) async { | ||
| 243 | + await methodChannel.invokeMethod('stopVoice'); | ||
| 225 | } | 244 | } |
| 226 | 245 | ||
| 227 | ///取消评测(用于处理退出页面后录音未停止等异常情况的保护操作) | 246 | ///取消评测(用于处理退出页面后录音未停止等异常情况的保护操作) |
| 228 | - void _voiceXsCancel() { | ||
| 229 | - methodChannel.invokeMethod('cancelVoice'); | 247 | + Future<void> _voiceXsCancel() async { |
| 248 | + await methodChannel.invokeMethod('cancelVoice'); | ||
| 230 | } | 249 | } |
| 231 | 250 | ||
| 232 | - ///先声评测结果 | ||
| 233 | - void _voiceXsResult(XSVoiceResultEvent event,Emitter<RepeatAfterContentState> emitter) async { | 251 | + ///驰声评测结果 |
| 252 | + void _voiceXsResult(XSVoiceResultEvent event, | ||
| 253 | + Emitter<RepeatAfterContentState> emitter) async { | ||
| 234 | final Map args = event.message as Map; | 254 | final Map args = event.message as Map; |
| 235 | final result = args['result'] as Map; | 255 | final result = args['result'] as Map; |
| 236 | final overall = result['overall'].toString(); | 256 | final overall = result['overall'].toString(); |
| 237 | - _voiceTestResult = {'overall':overall}; | 257 | + _voiceTestResult = {'overall': overall}; |
| 238 | _xSCheckState = XSVoiceCheckState.result; | 258 | _xSCheckState = XSVoiceCheckState.result; |
| 239 | emitter(XSVoiceTestState()); | 259 | emitter(XSVoiceTestState()); |
| 240 | } | 260 | } |
| 241 | 261 | ||
| 242 | ///播放声音 | 262 | ///播放声音 |
| 243 | - void _recordeVoicePlay(RecordeVoicePlayEvent event,Emitter<RepeatAfterContentState> emitter) async { | 263 | + void _recordeVoicePlay(RecordeVoicePlayEvent event, |
| 264 | + Emitter<RepeatAfterContentState> emitter) async { | ||
| 244 | if (await _fileExists(_path)) { | 265 | if (await _fileExists(_path)) { |
| 245 | if (_soundPlayer.isPlaying) { | 266 | if (_soundPlayer.isPlaying) { |
| 246 | _soundPlayer.stopPlayer(); | 267 | _soundPlayer.stopPlayer(); |
| 247 | } | 268 | } |
| 248 | 269 | ||
| 249 | await _soundPlayer.startPlayer( | 270 | await _soundPlayer.startPlayer( |
| 250 | - fromURI: path, | ||
| 251 | - codec: Codec.pcm16WAV, | ||
| 252 | - whenFinished: (){ | ||
| 253 | - | ||
| 254 | - } | ||
| 255 | - ); | 271 | + fromURI: path, codec: Codec.pcm16WAV, whenFinished: () {}); |
| 256 | } | 272 | } |
| 257 | } | 273 | } |
| 258 | 274 | ||
| 259 | ///更改播放的视频 | 275 | ///更改播放的视频 |
| 260 | - void _changeVideoPlayIndex(ChangeVideoPlayIndexEvent event,Emitter<RepeatAfterContentState> emitter) async { | 276 | + void _changeVideoPlayIndex(ChangeVideoPlayIndexEvent event, |
| 277 | + Emitter<RepeatAfterContentState> emitter) async { | ||
| 261 | if (_entityList == null || _entityList!.isEmpty) { | 278 | if (_entityList == null || _entityList!.isEmpty) { |
| 262 | return; | 279 | return; |
| 263 | } | 280 | } |
| 264 | if (event.isNext) { | 281 | if (event.isNext) { |
| 265 | - if (_currentPlayIndex < _entityList!.length-1) { | 282 | + if (_currentPlayIndex < _entityList!.length - 1) { |
| 266 | _currentPlayIndex++; | 283 | _currentPlayIndex++; |
| 267 | } | 284 | } |
| 268 | } else { | 285 | } else { |
| 269 | - if (_currentPlayIndex >0) { | 286 | + if (_currentPlayIndex > 0) { |
| 270 | _currentPlayIndex--; | 287 | _currentPlayIndex--; |
| 271 | } | 288 | } |
| 272 | } | 289 | } |
| @@ -274,7 +291,8 @@ class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterCo | @@ -274,7 +291,8 @@ class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterCo | ||
| 274 | } | 291 | } |
| 275 | 292 | ||
| 276 | ///开始录音 | 293 | ///开始录音 |
| 277 | - void _starRecordVoice(StarRecordVoiceEvent event,Emitter<RepeatAfterContentState> emitter) async { | 294 | + void _starRecordVoice(StarRecordVoiceEvent event, |
| 295 | + Emitter<RepeatAfterContentState> emitter) async { | ||
| 278 | try { | 296 | try { |
| 279 | await getPermissionStatus().then((value) async { | 297 | await getPermissionStatus().then((value) async { |
| 280 | if (!value) { | 298 | if (!value) { |
| @@ -302,7 +320,8 @@ class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterCo | @@ -302,7 +320,8 @@ class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterCo | ||
| 302 | } | 320 | } |
| 303 | 321 | ||
| 304 | ///停止录音 | 322 | ///停止录音 |
| 305 | - void _stopRecordVoice(StopRecordVoiceEvent event,Emitter<RepeatAfterContentState> emitter) async { | 323 | + void _stopRecordVoice(StopRecordVoiceEvent event, |
| 324 | + Emitter<RepeatAfterContentState> emitter) async { | ||
| 306 | debugPrint('=====> 停止录音'); | 325 | debugPrint('=====> 停止录音'); |
| 307 | await _soundRecorder.stopRecorder(); | 326 | await _soundRecorder.stopRecorder(); |
| 308 | _voiceRecordState = VoiceRecordState.voiceRecordEnd; | 327 | _voiceRecordState = VoiceRecordState.voiceRecordEnd; |
| @@ -326,9 +345,7 @@ class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterCo | @@ -326,9 +345,7 @@ class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterCo | ||
| 326 | showDialog(); | 345 | showDialog(); |
| 327 | } else if (status.isRestricted) { | 346 | } else if (status.isRestricted) { |
| 328 | requestPermission(permission); | 347 | requestPermission(permission); |
| 329 | - } else { | ||
| 330 | - | ||
| 331 | - } | 348 | + } else {} |
| 332 | return false; | 349 | return false; |
| 333 | } | 350 | } |
| 334 | 351 | ||
| @@ -346,9 +363,9 @@ class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterCo | @@ -346,9 +363,9 @@ class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterCo | ||
| 346 | } | 363 | } |
| 347 | 364 | ||
| 348 | void showDialog() async { | 365 | void showDialog() async { |
| 349 | - showTwoActionDialog('提示', '取消', '去设置', '请进入设置页打开麦克风权限', leftTap: (){ | 366 | + showTwoActionDialog('提示', '取消', '去设置', '请进入设置页打开麦克风权限', leftTap: () { |
| 350 | popPage(); | 367 | popPage(); |
| 351 | - },rightTap: (){ | 368 | + }, rightTap: () { |
| 352 | popPage(); | 369 | popPage(); |
| 353 | openAppSettings(); | 370 | openAppSettings(); |
| 354 | }); | 371 | }); |
lib/pages/repeataftercontent/bloc/repeat_after_content_event.dart
| @@ -6,8 +6,10 @@ abstract class RepeatAfterContentEvent {} | @@ -6,8 +6,10 @@ abstract class RepeatAfterContentEvent {} | ||
| 6 | class InitBlocEvent extends RepeatAfterContentEvent {} | 6 | class InitBlocEvent extends RepeatAfterContentEvent {} |
| 7 | 7 | ||
| 8 | class VideoPlayChangeEvent extends RepeatAfterContentEvent {} | 8 | class VideoPlayChangeEvent extends RepeatAfterContentEvent {} |
| 9 | + | ||
| 9 | ///切换录音状态 | 10 | ///切换录音状态 |
| 10 | class VoiceRecordEvent extends RepeatAfterContentEvent {} | 11 | class VoiceRecordEvent extends RepeatAfterContentEvent {} |
| 12 | + | ||
| 11 | ///请求数据 | 13 | ///请求数据 |
| 12 | class RequestDataEvent extends RepeatAfterContentEvent {} | 14 | class RequestDataEvent extends RepeatAfterContentEvent {} |
| 13 | 15 | ||
| @@ -16,18 +18,12 @@ class VoiceRecordStateChangeEvent extends RepeatAfterContentEvent { | @@ -16,18 +18,12 @@ class VoiceRecordStateChangeEvent extends RepeatAfterContentEvent { | ||
| 16 | VoiceRecordStateChangeEvent(this.voiceRecordState); | 18 | VoiceRecordStateChangeEvent(this.voiceRecordState); |
| 17 | } | 19 | } |
| 18 | 20 | ||
| 19 | -///初始化先声SDK | ||
| 20 | -class XSVoiceInitEvent extends RepeatAfterContentEvent { | ||
| 21 | - final Map data; | ||
| 22 | - XSVoiceInitEvent(this.data); | ||
| 23 | -} | ||
| 24 | - | ||
| 25 | ///开始评测 | 21 | ///开始评测 |
| 26 | class XSVoiceTestEvent extends RepeatAfterContentEvent { | 22 | class XSVoiceTestEvent extends RepeatAfterContentEvent { |
| 27 | final String testWord; | 23 | final String testWord; |
| 28 | final String type; | 24 | final String type; |
| 29 | final String userId; | 25 | final String userId; |
| 30 | - XSVoiceTestEvent(this.testWord,this.type,this.userId); | 26 | + XSVoiceTestEvent(this.testWord, this.type, this.userId); |
| 31 | } | 27 | } |
| 32 | 28 | ||
| 33 | ///终止评测 | 29 | ///终止评测 |
| @@ -55,5 +51,3 @@ class ChangeVideoPlayIndexEvent extends RepeatAfterContentEvent { | @@ -55,5 +51,3 @@ class ChangeVideoPlayIndexEvent extends RepeatAfterContentEvent { | ||
| 55 | final bool isNext; | 51 | final bool isNext; |
| 56 | ChangeVideoPlayIndexEvent(this.isNext); | 52 | ChangeVideoPlayIndexEvent(this.isNext); |
| 57 | } | 53 | } |
| 58 | - | ||
| 59 | - |
lib/pages/repeataftercontent/repeat_after_content_page.dart
| @@ -6,7 +6,6 @@ import 'package:wow_english/pages/repeataftercontent/bloc/repeat_after_content_b | @@ -6,7 +6,6 @@ import 'package:wow_english/pages/repeataftercontent/bloc/repeat_after_content_b | ||
| 6 | import 'package:wow_english/pages/repeataftercontent/widgets/repeat_after_content_dialog.dart'; | 6 | import 'package:wow_english/pages/repeataftercontent/widgets/repeat_after_content_dialog.dart'; |
| 7 | import 'package:wow_english/route/route.dart'; | 7 | import 'package:wow_english/route/route.dart'; |
| 8 | 8 | ||
| 9 | -import '../../common/core/app_consts.dart'; | ||
| 10 | import '../../common/core/user_util.dart'; | 9 | import '../../common/core/user_util.dart'; |
| 11 | import '../../models/read_content_entity.dart'; | 10 | import '../../models/read_content_entity.dart'; |
| 12 | import '../../utils/toast_util.dart'; | 11 | import '../../utils/toast_util.dart'; |
| @@ -20,16 +19,9 @@ class RepeatAfterContentPage extends StatelessWidget { | @@ -20,16 +19,9 @@ class RepeatAfterContentPage extends StatelessWidget { | ||
| 20 | @override | 19 | @override |
| 21 | Widget build(BuildContext context) { | 20 | Widget build(BuildContext context) { |
| 22 | return BlocProvider( | 21 | return BlocProvider( |
| 23 | - create: (context) => RepeatAfterContentBloc(videoFollowReadId ??'') | 22 | + create: (context) => RepeatAfterContentBloc(videoFollowReadId ?? '') |
| 24 | ..add(InitBlocEvent()) | 23 | ..add(InitBlocEvent()) |
| 25 | - ..add(RequestDataEvent()) | ||
| 26 | - ..add(XSVoiceInitEvent( | ||
| 27 | - { | ||
| 28 | - 'appKey':AppConsts.xsAppKey, | ||
| 29 | - 'service':AppConsts.xsAppService, | ||
| 30 | - 'secretKey':AppConsts.xsAppSecretKey, | ||
| 31 | - } | ||
| 32 | - )), | 24 | + ..add(RequestDataEvent()), |
| 33 | child: _RepeatAfterContentPage(), | 25 | child: _RepeatAfterContentPage(), |
| 34 | ); | 26 | ); |
| 35 | } | 27 | } |
| @@ -38,13 +30,17 @@ class RepeatAfterContentPage extends StatelessWidget { | @@ -38,13 +30,17 @@ class RepeatAfterContentPage extends StatelessWidget { | ||
| 38 | class _RepeatAfterContentPage extends StatelessWidget { | 30 | class _RepeatAfterContentPage extends StatelessWidget { |
| 39 | @override | 31 | @override |
| 40 | Widget build(BuildContext context) { | 32 | Widget build(BuildContext context) { |
| 41 | - return BlocListener<RepeatAfterContentBloc,RepeatAfterContentState>( | ||
| 42 | - listener: (context,state){ | 33 | + return BlocListener<RepeatAfterContentBloc, RepeatAfterContentState>( |
| 34 | + listener: (context, state) { | ||
| 43 | final bloc = BlocProvider.of<RepeatAfterContentBloc>(context); | 35 | final bloc = BlocProvider.of<RepeatAfterContentBloc>(context); |
| 44 | - if (state is VoiceRecordStateChange) {//录音状态回调 | ||
| 45 | - if (bloc.voiceRecordState == VoiceRecordState.voiceRecordEnd) {//声音录制结束 | ||
| 46 | - ReadContentEntity? readContentEntity = bloc.entityList?[bloc.currentPlayIndex]; | ||
| 47 | - bloc.add(XSVoiceTestEvent(readContentEntity?.word??'','0',UserUtil.getUser()!.id.toString())); | 36 | + if (state is VoiceRecordStateChange) { |
| 37 | + //录音状态回调 | ||
| 38 | + if (bloc.voiceRecordState == VoiceRecordState.voiceRecordEnd) { | ||
| 39 | + //声音录制结束 | ||
| 40 | + ReadContentEntity? readContentEntity = | ||
| 41 | + bloc.entityList?[bloc.currentPlayIndex]; | ||
| 42 | + bloc.add(XSVoiceTestEvent(readContentEntity?.word ?? '', '0', | ||
| 43 | + UserUtil.getUser()!.id.toString())); | ||
| 48 | } | 44 | } |
| 49 | return; | 45 | return; |
| 50 | } | 46 | } |
| @@ -53,224 +49,223 @@ class _RepeatAfterContentPage extends StatelessWidget { | @@ -53,224 +49,223 @@ class _RepeatAfterContentPage extends StatelessWidget { | ||
| 53 | ); | 49 | ); |
| 54 | } | 50 | } |
| 55 | 51 | ||
| 56 | - | ||
| 57 | - Widget _repeatAfterContentView() => BlocBuilder<RepeatAfterContentBloc,RepeatAfterContentState>(builder: (context,state){ | ||
| 58 | - final bloc = BlocProvider.of<RepeatAfterContentBloc>(context); | ||
| 59 | - final String videoUrl = bloc.entityList?.first?.videoUrl??''; | ||
| 60 | - return Container( | ||
| 61 | - color: Colors.white, | ||
| 62 | - child: SafeArea( | ||
| 63 | - child: Stack( | ||
| 64 | - children: [ | ||
| 65 | - ///返回 | ||
| 66 | - Positioned( | ||
| 67 | - child: GestureDetector( | ||
| 68 | - onTap: () { | ||
| 69 | - showDialog<RepeatAfterContentDialog>( | ||
| 70 | - context: context, | ||
| 71 | - builder: (context){ | ||
| 72 | - return RepeatAfterContentDialog( (){ | ||
| 73 | - popPage(); | ||
| 74 | - }); | ||
| 75 | - }); | ||
| 76 | - // popPage(); | ||
| 77 | - }, | ||
| 78 | - child: Image.asset( | ||
| 79 | - 'back_around'.assetPng, | ||
| 80 | - height: 40.h, | ||
| 81 | - width: 40.w, | ||
| 82 | - ), | ||
| 83 | - ), | ||
| 84 | - ), | ||
| 85 | - ///左侧视频区 | ||
| 86 | - Positioned( | ||
| 87 | - top: 40.h, | ||
| 88 | - left: 20.w, | ||
| 89 | - child: Container( | ||
| 90 | - width: 285.w, | ||
| 91 | - height: 299.h, | ||
| 92 | - padding: EdgeInsets.symmetric(horizontal: 50.w,vertical: 50.h), | ||
| 93 | - decoration: BoxDecoration( | ||
| 94 | - image: DecorationImage( | ||
| 95 | - image: AssetImage('video_background'.assetPng), | ||
| 96 | - fit: BoxFit.fill | 52 | + Widget _repeatAfterContentView() => |
| 53 | + BlocBuilder<RepeatAfterContentBloc, RepeatAfterContentState>( | ||
| 54 | + builder: (context, state) { | ||
| 55 | + final bloc = BlocProvider.of<RepeatAfterContentBloc>(context); | ||
| 56 | + final String videoUrl = bloc.entityList?.first?.videoUrl ?? ''; | ||
| 57 | + return Container( | ||
| 58 | + color: Colors.white, | ||
| 59 | + child: SafeArea( | ||
| 60 | + child: Stack( | ||
| 61 | + children: [ | ||
| 62 | + ///返回 | ||
| 63 | + Positioned( | ||
| 64 | + child: GestureDetector( | ||
| 65 | + onTap: () { | ||
| 66 | + showDialog<RepeatAfterContentDialog>( | ||
| 67 | + context: context, | ||
| 68 | + builder: (context) { | ||
| 69 | + return RepeatAfterContentDialog(() { | ||
| 70 | + popPage(); | ||
| 71 | + }); | ||
| 72 | + }); | ||
| 73 | + // popPage(); | ||
| 74 | + }, | ||
| 75 | + child: Image.asset( | ||
| 76 | + 'back_around'.assetPng, | ||
| 77 | + height: 40.h, | ||
| 78 | + width: 40.w, | ||
| 79 | + ), | ||
| 97 | ), | 80 | ), |
| 98 | ), | 81 | ), |
| 99 | - child: videoUrl.isEmpty?Container(): RepeatVideoWidget(videoUrl: bloc.entityList?.first?.videoUrl,videoUrls: bloc.entityList??[],), | ||
| 100 | - ), | ||
| 101 | - ), | ||
| 102 | - ///右侧操作区 | ||
| 103 | - Positioned( | ||
| 104 | - top: 40.h, | ||
| 105 | - left: 331.w, | ||
| 106 | - child: Container( | ||
| 107 | - width: 240.w, | ||
| 108 | - height: 299.h, | ||
| 109 | - padding: EdgeInsets.only( | ||
| 110 | - left: 67.w, | ||
| 111 | - bottom: 40.h | ||
| 112 | - ), | ||
| 113 | - decoration: BoxDecoration( | ||
| 114 | - image: DecorationImage( | ||
| 115 | - image: AssetImage('light_ground'.assetPng), | ||
| 116 | - fit: BoxFit.fill | 82 | + |
| 83 | + ///左侧视频区 | ||
| 84 | + Positioned( | ||
| 85 | + top: 40.h, | ||
| 86 | + left: 20.w, | ||
| 87 | + child: Container( | ||
| 88 | + width: 285.w, | ||
| 89 | + height: 299.h, | ||
| 90 | + padding: | ||
| 91 | + EdgeInsets.symmetric(horizontal: 50.w, vertical: 50.h), | ||
| 92 | + decoration: BoxDecoration( | ||
| 93 | + image: DecorationImage( | ||
| 94 | + image: AssetImage('video_background'.assetPng), | ||
| 95 | + fit: BoxFit.fill), | ||
| 96 | + ), | ||
| 97 | + child: videoUrl.isEmpty | ||
| 98 | + ? Container() | ||
| 99 | + : RepeatVideoWidget( | ||
| 100 | + videoUrl: bloc.entityList?.first?.videoUrl, | ||
| 101 | + videoUrls: bloc.entityList ?? [], | ||
| 102 | + ), | ||
| 117 | ), | 103 | ), |
| 118 | ), | 104 | ), |
| 119 | - child: bloc.isRecord?_buildLongPressWidget():_buildPlayVideoWidget(), | ||
| 120 | - ), | ||
| 121 | - ), | ||
| 122 | - ///连接 | ||
| 123 | - Positioned( | ||
| 124 | - top: 59.h, | ||
| 125 | - left: 274.w, | ||
| 126 | - child: Container( | ||
| 127 | - width: 87.w, | ||
| 128 | - height: 240.h, | ||
| 129 | - decoration: BoxDecoration( | ||
| 130 | - image: DecorationImage( | ||
| 131 | - image: AssetImage('and_book'.assetPng), | ||
| 132 | - fit: BoxFit.fill | 105 | + |
| 106 | + ///右侧操作区 | ||
| 107 | + Positioned( | ||
| 108 | + top: 40.h, | ||
| 109 | + left: 331.w, | ||
| 110 | + child: Container( | ||
| 111 | + width: 240.w, | ||
| 112 | + height: 299.h, | ||
| 113 | + padding: EdgeInsets.only(left: 67.w, bottom: 40.h), | ||
| 114 | + decoration: BoxDecoration( | ||
| 115 | + image: DecorationImage( | ||
| 116 | + image: AssetImage('light_ground'.assetPng), | ||
| 117 | + fit: BoxFit.fill), | ||
| 118 | + ), | ||
| 119 | + child: bloc.isRecord | ||
| 120 | + ? _buildLongPressWidget() | ||
| 121 | + : _buildPlayVideoWidget(), | ||
| 133 | ), | 122 | ), |
| 134 | ), | 123 | ), |
| 135 | - ), | ||
| 136 | - ), | ||
| 137 | - ///跟读 | ||
| 138 | - Positioned( | ||
| 139 | - top: 16.h, | ||
| 140 | - left: 65.w, | ||
| 141 | - child: Container( | ||
| 142 | - width: 185.w, | ||
| 143 | - height: 48.h, | ||
| 144 | - decoration: BoxDecoration( | ||
| 145 | - image: DecorationImage( | ||
| 146 | - image: AssetImage('title_ground'.assetPng), | ||
| 147 | - fit: BoxFit.fill | 124 | + |
| 125 | + ///连接 | ||
| 126 | + Positioned( | ||
| 127 | + top: 59.h, | ||
| 128 | + left: 274.w, | ||
| 129 | + child: Container( | ||
| 130 | + width: 87.w, | ||
| 131 | + height: 240.h, | ||
| 132 | + decoration: BoxDecoration( | ||
| 133 | + image: DecorationImage( | ||
| 134 | + image: AssetImage('and_book'.assetPng), | ||
| 135 | + fit: BoxFit.fill), | ||
| 136 | + ), | ||
| 148 | ), | 137 | ), |
| 149 | ), | 138 | ), |
| 150 | - alignment: Alignment.center, | ||
| 151 | - child: Text( | ||
| 152 | - 'read title', | ||
| 153 | - textAlign: TextAlign.center, | ||
| 154 | - style: TextStyle( | ||
| 155 | - color: Colors.white, | ||
| 156 | - fontSize: 21.sp | 139 | + |
| 140 | + ///跟读 | ||
| 141 | + Positioned( | ||
| 142 | + top: 16.h, | ||
| 143 | + left: 65.w, | ||
| 144 | + child: Container( | ||
| 145 | + width: 185.w, | ||
| 146 | + height: 48.h, | ||
| 147 | + decoration: BoxDecoration( | ||
| 148 | + image: DecorationImage( | ||
| 149 | + image: AssetImage('title_ground'.assetPng), | ||
| 150 | + fit: BoxFit.fill), | ||
| 151 | + ), | ||
| 152 | + alignment: Alignment.center, | ||
| 153 | + child: Text( | ||
| 154 | + 'read title', | ||
| 155 | + textAlign: TextAlign.center, | ||
| 156 | + style: TextStyle(color: Colors.white, fontSize: 21.sp), | ||
| 157 | + ), | ||
| 157 | ), | 158 | ), |
| 158 | ), | 159 | ), |
| 159 | - ), | 160 | + ], |
| 160 | ), | 161 | ), |
| 161 | - ], | ||
| 162 | - ), | ||
| 163 | - ), | ||
| 164 | - ); | ||
| 165 | - }); | 162 | + ), |
| 163 | + ); | ||
| 164 | + }); | ||
| 166 | 165 | ||
| 167 | ///播放中 | 166 | ///播放中 |
| 168 | Widget _buildPlayVideoWidget() { | 167 | Widget _buildPlayVideoWidget() { |
| 169 | - return BlocBuilder<RepeatAfterContentBloc,RepeatAfterContentState>( | 168 | + return BlocBuilder<RepeatAfterContentBloc, RepeatAfterContentState>( |
| 170 | buildWhen: (previous, current) { | 169 | buildWhen: (previous, current) { |
| 171 | - if (current is ChangeVideoPlayIndexEvent) { | ||
| 172 | - return true; | ||
| 173 | - } | ||
| 174 | - return false; | ||
| 175 | - }, | ||
| 176 | - builder: (context,state){ | ||
| 177 | - final bloc = BlocProvider.of<RepeatAfterContentBloc>(context); | ||
| 178 | - return Column( | ||
| 179 | - mainAxisAlignment: MainAxisAlignment.end, | 170 | + if (current is ChangeVideoPlayIndexEvent) { |
| 171 | + return true; | ||
| 172 | + } | ||
| 173 | + return false; | ||
| 174 | + }, builder: (context, state) { | ||
| 175 | + final bloc = BlocProvider.of<RepeatAfterContentBloc>(context); | ||
| 176 | + return Column( | ||
| 177 | + mainAxisAlignment: MainAxisAlignment.end, | ||
| 178 | + children: [ | ||
| 179 | + Row( | ||
| 180 | + children: [ | ||
| 181 | + IconButton( | ||
| 182 | + onPressed: () => bloc.add(ChangeVideoPlayIndexEvent(false)), | ||
| 183 | + icon: Image.asset( | ||
| 184 | + 'previous'.assetPng, | ||
| 185 | + height: 23.h, | ||
| 186 | + width: 23.w, | ||
| 187 | + )), | ||
| 188 | + IconButton( | ||
| 189 | + onPressed: () => bloc.add(VideoPlayChangeEvent()), | ||
| 190 | + icon: Image.asset( | ||
| 191 | + 'video_pause'.assetPng, | ||
| 192 | + height: 50.h, | ||
| 193 | + width: 50.h, | ||
| 194 | + )), | ||
| 195 | + IconButton( | ||
| 196 | + onPressed: () => bloc.add(ChangeVideoPlayIndexEvent(true)), | ||
| 197 | + icon: Image.asset( | ||
| 198 | + 'next'.assetPng, | ||
| 199 | + height: 23.h, | ||
| 200 | + width: 23.w, | ||
| 201 | + )) | ||
| 202 | + ], | ||
| 203 | + ), | ||
| 204 | + Row( | ||
| 180 | children: [ | 205 | children: [ |
| 181 | - Row( | 206 | + SizedBox( |
| 207 | + height: 23.h, | ||
| 208 | + width: 23.w, | ||
| 209 | + ), | ||
| 210 | + 10.horizontalSpace, | ||
| 211 | + Column( | ||
| 182 | children: [ | 212 | children: [ |
| 213 | + 20.verticalSpace, | ||
| 183 | IconButton( | 214 | IconButton( |
| 184 | - onPressed: () => bloc.add(ChangeVideoPlayIndexEvent(false)), | ||
| 185 | - icon: Image.asset( | ||
| 186 | - 'previous'.assetPng, | ||
| 187 | - height: 23.h, | ||
| 188 | - width: 23.w, | ||
| 189 | - ) | ||
| 190 | - ), | ||
| 191 | - IconButton( | ||
| 192 | - onPressed:() => bloc.add(VideoPlayChangeEvent()), | ||
| 193 | - icon: Image.asset( | ||
| 194 | - 'video_pause'.assetPng, | ||
| 195 | - height: 50.h, | ||
| 196 | - width: 50.h, | ||
| 197 | - ) | ||
| 198 | - ), | ||
| 199 | - IconButton( | ||
| 200 | - onPressed: () => bloc.add(ChangeVideoPlayIndexEvent(true)), | 215 | + onPressed: () { |
| 216 | + if (bloc.videoPlaying) { | ||
| 217 | + showToast('视频正在播放中'); | ||
| 218 | + return; | ||
| 219 | + } | ||
| 220 | + bloc.add(VoiceRecordEvent()); | ||
| 221 | + }, | ||
| 201 | icon: Image.asset( | 222 | icon: Image.asset( |
| 202 | - 'next'.assetPng, | ||
| 203 | - height: 23.h, | ||
| 204 | - width: 23.w, | ||
| 205 | - ) | 223 | + 'video_record'.assetPng, |
| 224 | + height: 53.h, | ||
| 225 | + width: 53.w, | ||
| 226 | + )), | ||
| 227 | + Text( | ||
| 228 | + '录音', | ||
| 229 | + style: TextStyle( | ||
| 230 | + color: const Color(0xFF333333), fontSize: 14.sp), | ||
| 206 | ) | 231 | ) |
| 207 | ], | 232 | ], |
| 208 | ), | 233 | ), |
| 209 | - Row( | ||
| 210 | - children: [ | ||
| 211 | - SizedBox( | ||
| 212 | - height: 23.h, | ||
| 213 | - width: 23.w, | ||
| 214 | - ), | ||
| 215 | - 10.horizontalSpace, | ||
| 216 | - Column( | ||
| 217 | - children: [ | ||
| 218 | - 20.verticalSpace, | ||
| 219 | - IconButton( | ||
| 220 | - onPressed: () { | ||
| 221 | - if (bloc.videoPlaying) { | ||
| 222 | - showToast('视频正在播放中'); | ||
| 223 | - return; | ||
| 224 | - } | ||
| 225 | - bloc.add(VoiceRecordEvent()); | ||
| 226 | - }, | ||
| 227 | - icon: Image.asset( | ||
| 228 | - 'video_record'.assetPng, | ||
| 229 | - height: 53.h, | ||
| 230 | - width: 53.w, | ||
| 231 | - ) | ||
| 232 | - ), | ||
| 233 | - Text( | ||
| 234 | - '录音', | ||
| 235 | - style: TextStyle( | ||
| 236 | - color: const Color(0xFF333333), | ||
| 237 | - fontSize: 14.sp | ||
| 238 | - ), | ||
| 239 | - ) | ||
| 240 | - ], | ||
| 241 | - ), | ||
| 242 | - // Container( | ||
| 243 | - // height: 22.h, | ||
| 244 | - // width: 37.w, | ||
| 245 | - // decoration: BoxDecoration( | ||
| 246 | - // color: const Color(0xFF56CE5F), | ||
| 247 | - // borderRadius: BorderRadius.circular(10.r) | ||
| 248 | - // ), | ||
| 249 | - // child: Text( | ||
| 250 | - // '1.0x', | ||
| 251 | - // textAlign: TextAlign.center, | ||
| 252 | - // style: TextStyle( | ||
| 253 | - // color: Colors.white, | ||
| 254 | - // fontSize: 12.sp | ||
| 255 | - // ), | ||
| 256 | - // ), | ||
| 257 | - // ) | ||
| 258 | - ], | ||
| 259 | - ) | 234 | + // Container( |
| 235 | + // height: 22.h, | ||
| 236 | + // width: 37.w, | ||
| 237 | + // decoration: BoxDecoration( | ||
| 238 | + // color: const Color(0xFF56CE5F), | ||
| 239 | + // borderRadius: BorderRadius.circular(10.r) | ||
| 240 | + // ), | ||
| 241 | + // child: Text( | ||
| 242 | + // '1.0x', | ||
| 243 | + // textAlign: TextAlign.center, | ||
| 244 | + // style: TextStyle( | ||
| 245 | + // color: Colors.white, | ||
| 246 | + // fontSize: 12.sp | ||
| 247 | + // ), | ||
| 248 | + // ), | ||
| 249 | + // ) | ||
| 260 | ], | 250 | ], |
| 261 | - ); | ||
| 262 | - }); | 251 | + ) |
| 252 | + ], | ||
| 253 | + ); | ||
| 254 | + }); | ||
| 263 | } | 255 | } |
| 264 | 256 | ||
| 265 | ///长按录音 | 257 | ///长按录音 |
| 266 | - Widget _buildLongPressWidget() => BlocBuilder<RepeatAfterContentBloc,RepeatAfterContentState>( | ||
| 267 | - builder: (context,state){ | 258 | + Widget _buildLongPressWidget() => |
| 259 | + BlocBuilder<RepeatAfterContentBloc, RepeatAfterContentState>( | ||
| 260 | + builder: (context, state) { | ||
| 268 | final bloc = BlocProvider.of<RepeatAfterContentBloc>(context); | 261 | final bloc = BlocProvider.of<RepeatAfterContentBloc>(context); |
| 269 | final voiceResult = bloc.voiceTestResult; | 262 | final voiceResult = bloc.voiceTestResult; |
| 270 | Color color; | 263 | Color color; |
| 271 | - if (int.parse(voiceResult?['overall'].toString()??'0') >= 60 || int.parse(voiceResult?['overall'].toString()??'0') <= 75) { | 264 | + if (int.parse(voiceResult?['overall'].toString() ?? '0') >= 60 || |
| 265 | + int.parse(voiceResult?['overall'].toString() ?? '0') <= 75) { | ||
| 272 | color = const Color(0xFFFF0000); | 266 | color = const Color(0xFFFF0000); |
| 273 | - } else if (int.parse(voiceResult?['overall'].toString()??'0') > 75 || int.parse(voiceResult?['overall'].toString()??'0') <= 85) { | 267 | + } else if (int.parse(voiceResult?['overall'].toString() ?? '0') > 75 || |
| 268 | + int.parse(voiceResult?['overall'].toString() ?? '0') <= 85) { | ||
| 274 | color = const Color(0xFFFFCC00); | 269 | color = const Color(0xFFFFCC00); |
| 275 | } else { | 270 | } else { |
| 276 | color = const Color(0xFF40E04B); | 271 | color = const Color(0xFF40E04B); |
| @@ -279,11 +274,15 @@ class _RepeatAfterContentPage extends StatelessWidget { | @@ -279,11 +274,15 @@ class _RepeatAfterContentPage extends StatelessWidget { | ||
| 279 | mainAxisAlignment: MainAxisAlignment.end, | 274 | mainAxisAlignment: MainAxisAlignment.end, |
| 280 | children: [ | 275 | children: [ |
| 281 | Offstage( | 276 | Offstage( |
| 282 | - offstage:!(bloc.voiceRecordState == VoiceRecordState.voiceRecordEnd && bloc.xSCheckState == XSVoiceCheckState.result), | 277 | + offstage: |
| 278 | + !(bloc.voiceRecordState == VoiceRecordState.voiceRecordEnd && | ||
| 279 | + bloc.xSCheckState == XSVoiceCheckState.result), | ||
| 283 | child: Column( | 280 | child: Column( |
| 284 | children: [ | 281 | children: [ |
| 285 | Offstage( | 282 | Offstage( |
| 286 | - offstage:int.parse(voiceResult?['overall'].toString()??'0') > 60, | 283 | + offstage: |
| 284 | + int.parse(voiceResult?['overall'].toString() ?? '0') > | ||
| 285 | + 60, | ||
| 287 | child: Image.asset( | 286 | child: Image.asset( |
| 288 | 'sorrow_face'.assetPng, | 287 | 'sorrow_face'.assetPng, |
| 289 | height: 46.h, | 288 | height: 46.h, |
| @@ -291,57 +290,52 @@ class _RepeatAfterContentPage extends StatelessWidget { | @@ -291,57 +290,52 @@ class _RepeatAfterContentPage extends StatelessWidget { | ||
| 291 | ), | 290 | ), |
| 292 | ), | 291 | ), |
| 293 | Offstage( | 292 | Offstage( |
| 294 | - offstage: int.parse(voiceResult?['overall'].toString()??'0') < 60, | 293 | + offstage: |
| 294 | + int.parse(voiceResult?['overall'].toString() ?? '0') < | ||
| 295 | + 60, | ||
| 295 | child: Container( | 296 | child: Container( |
| 296 | height: 45.h, | 297 | height: 45.h, |
| 297 | width: 45.h, | 298 | width: 45.h, |
| 298 | alignment: Alignment.center, | 299 | alignment: Alignment.center, |
| 299 | decoration: BoxDecoration( | 300 | decoration: BoxDecoration( |
| 300 | color: color, | 301 | color: color, |
| 301 | - borderRadius: BorderRadius.circular(22.5.r) | ||
| 302 | - ), | 302 | + borderRadius: BorderRadius.circular(22.5.r)), |
| 303 | child: Text( | 303 | child: Text( |
| 304 | - voiceResult?['overall'].toString()??'0', | 304 | + voiceResult?['overall'].toString() ?? '0', |
| 305 | textAlign: TextAlign.center, | 305 | textAlign: TextAlign.center, |
| 306 | - style: TextStyle( | ||
| 307 | - color: Colors.white, | ||
| 308 | - fontSize: 17.sp | ||
| 309 | - ), | 306 | + style: TextStyle(color: Colors.white, fontSize: 17.sp), |
| 310 | ), | 307 | ), |
| 311 | ), | 308 | ), |
| 312 | ), | 309 | ), |
| 313 | IconButton( | 310 | IconButton( |
| 314 | - onPressed: (){ | 311 | + onPressed: () { |
| 315 | bloc.add(RecordeVoicePlayEvent()); | 312 | bloc.add(RecordeVoicePlayEvent()); |
| 316 | }, | 313 | }, |
| 317 | icon: Image.asset( | 314 | icon: Image.asset( |
| 318 | 'voice_record_play'.assetPng, | 315 | 'voice_record_play'.assetPng, |
| 319 | height: 30.h, | 316 | height: 30.h, |
| 320 | width: 30.w, | 317 | width: 30.w, |
| 321 | - ) | ||
| 322 | - ), | 318 | + )), |
| 323 | Text( | 319 | Text( |
| 324 | '录音', | 320 | '录音', |
| 325 | textAlign: TextAlign.center, | 321 | textAlign: TextAlign.center, |
| 326 | style: TextStyle( | 322 | style: TextStyle( |
| 327 | - color: const Color(0xFF666666), | ||
| 328 | - fontSize: 11.sp | ||
| 329 | - ), | 323 | + color: const Color(0xFF666666), fontSize: 11.sp), |
| 330 | ), | 324 | ), |
| 331 | ], | 325 | ], |
| 332 | ), | 326 | ), |
| 333 | ), | 327 | ), |
| 334 | Offstage( | 328 | Offstage( |
| 335 | - offstage: bloc.voiceRecordState == VoiceRecordState.voiceRecordUnkonw || bloc.xSCheckState != XSVoiceCheckState.unKnow, | 329 | + offstage: |
| 330 | + bloc.voiceRecordState == VoiceRecordState.voiceRecordUnkonw || | ||
| 331 | + bloc.xSCheckState != XSVoiceCheckState.unKnow, | ||
| 336 | child: Container( | 332 | child: Container( |
| 337 | color: Colors.grey, | 333 | color: Colors.grey, |
| 338 | - padding: EdgeInsets.symmetric( | ||
| 339 | - vertical: 50.h, | ||
| 340 | - horizontal: 50.w | ||
| 341 | - ), | 334 | + padding: EdgeInsets.symmetric(vertical: 50.h, horizontal: 50.w), |
| 342 | child: Text( | 335 | child: Text( |
| 343 | - bloc.voiceRecordState == VoiceRecordState.voiceRecording?'正在录音':'录音结束' | ||
| 344 | - ), | 336 | + bloc.voiceRecordState == VoiceRecordState.voiceRecording |
| 337 | + ? '正在录音' | ||
| 338 | + : '录音结束'), | ||
| 345 | ), | 339 | ), |
| 346 | ), | 340 | ), |
| 347 | 10.verticalSpace, | 341 | 10.verticalSpace, |
| @@ -364,10 +358,7 @@ class _RepeatAfterContentPage extends StatelessWidget { | @@ -364,10 +358,7 @@ class _RepeatAfterContentPage extends StatelessWidget { | ||
| 364 | Text( | 358 | Text( |
| 365 | '按住录音', | 359 | '按住录音', |
| 366 | textAlign: TextAlign.center, | 360 | textAlign: TextAlign.center, |
| 367 | - style: TextStyle( | ||
| 368 | - color: const Color(0xFF333333), | ||
| 369 | - fontSize: 16.sp | ||
| 370 | - ), | 361 | + style: TextStyle(color: const Color(0xFF333333), fontSize: 16.sp), |
| 371 | ), | 362 | ), |
| 372 | ], | 363 | ], |
| 373 | ); | 364 | ); |
packages/chivox_aiengine/LICENSE
0 → 100644
| 1 | +TODO: Add your license here. |
packages/chivox_aiengine/README.md
0 → 100644
| 1 | +# chivox_aiengine | ||
| 2 | + | ||
| 3 | +A new Flutter plugin project. | ||
| 4 | + | ||
| 5 | +## 应用层如何接入 | ||
| 6 | + | ||
| 7 | +1. 应用层通过本地插件的方式引用本插件: | ||
| 8 | + ``` | ||
| 9 | + # pubspec.yaml | ||
| 10 | + dependencies: | ||
| 11 | + chivox_aiengine: | ||
| 12 | + path: *** # 此处填写本插件在磁盘上的存放路径 | ||
| 13 | + ``` | ||
| 14 | + | ||
| 15 | +2. 拷贝驰声评测sdk的相关库文件至插件的相应目录中 | ||
| 16 | + + ios | ||
| 17 | + 应用层使用本插件之前先把iOS-SDK和通用SDK的静态库文件拷贝至本插件的`ios/Classes/Lib/`目录下:libCAIEngine.a, libaiengine.a | ||
| 18 | + + android | ||
| 19 | + 应用层使用之前先把android-SDK和通用SDK的相关库文件添加至本插件的android目录下的项目中:jar包和so库 | ||
| 20 | + | ||
| 21 | +## 插件接口文档 | ||
| 22 | +请查看`lib/chivox_aiengine.dart`中的文档注释。 |
packages/chivox_aiengine/analysis_options.yaml
0 → 100644
packages/chivox_aiengine/android/build.gradle
0 → 100644
| 1 | +group 'com.example.chivox_aiengine' | ||
| 2 | +version '1.0' | ||
| 3 | + | ||
| 4 | +buildscript { | ||
| 5 | + repositories { | ||
| 6 | + google() | ||
| 7 | + mavenCentral() | ||
| 8 | + } | ||
| 9 | + | ||
| 10 | + dependencies { | ||
| 11 | + classpath 'com.android.tools.build:gradle:7.3.0' | ||
| 12 | + } | ||
| 13 | +} | ||
| 14 | + | ||
| 15 | +rootProject.allprojects { | ||
| 16 | + repositories { | ||
| 17 | + google() | ||
| 18 | + mavenCentral() | ||
| 19 | + } | ||
| 20 | +} | ||
| 21 | + | ||
| 22 | +apply plugin: 'com.android.library' | ||
| 23 | + | ||
| 24 | +android { | ||
| 25 | + compileSdkVersion 31 | ||
| 26 | + | ||
| 27 | + compileOptions { | ||
| 28 | + sourceCompatibility JavaVersion.VERSION_1_8 | ||
| 29 | + targetCompatibility JavaVersion.VERSION_1_8 | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + defaultConfig { | ||
| 33 | + minSdkVersion 16 | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + dependencies { | ||
| 37 | + testImplementation 'junit:junit:4.13.2' | ||
| 38 | + testImplementation 'org.mockito:mockito-core:5.0.0' | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + testOptions { | ||
| 42 | + unitTests.all { | ||
| 43 | + testLogging { | ||
| 44 | + events "passed", "skipped", "failed", "standardOut", "standardError" | ||
| 45 | + outputs.upToDateWhen {false} | ||
| 46 | + showStandardStreams = true | ||
| 47 | + } | ||
| 48 | + } | ||
| 49 | + } | ||
| 50 | +} | ||
| 51 | + | ||
| 52 | +dependencies { | ||
| 53 | + implementation files('libs/chivox_android_sdk_release.jar') | ||
| 54 | +} |
packages/chivox_aiengine/android/libs/chivox_android_sdk_release.jar
0 → 100644
No preview for this file type
packages/chivox_aiengine/android/settings.gradle
0 → 100644
| 1 | +rootProject.name = 'chivox_aiengine' |
packages/chivox_aiengine/android/src/main/AndroidManifest.xml
0 → 100644
packages/chivox_aiengine/android/src/main/assets/vad.0.13.bin
0 → 100644
No preview for this file type