Commit 0fa4e405a692ec6cb797f1ff6047158e54b72ff6

Authored by 吴启风
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
  1 +ÊÌÊÎËÌÌÍÌÏÌÏÌÏÌÎÉËÉÊÆ™ËÉÍÆËËÊš›Ï™ÌÏÇËÆÆÌš›Ìš›™ÈÊÍÊÍÏÏÎÍΚÇÏËÉÌÉÊÉÏÌËžžÆÊ™›ÎÈÎÆœ›šÏÍÉË™šžžÎžÉžÏÉÇËÎÌÆÎÆÈÇÏ™œÌ›ž
0 \ No newline at end of file 2 \ No newline at end of file
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 &#39;dart:async&#39;; @@ -3,12 +3,12 @@ import &#39;dart:async&#39;;
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 &#39;topic_picture_event.dart&#39;; @@ -27,7 +27,8 @@ part &#39;topic_picture_event.dart&#39;;
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 &#39;../../../common/core/user_util.dart&#39;; @@ -15,6 +14,7 @@ import &#39;../../../common/core/user_util.dart&#39;;
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 &#39;package:wow_english/pages/reading/widgets/ReadingModeType.dart&#39;; @@ -7,7 +7,6 @@ import &#39;package:wow_english/pages/reading/widgets/ReadingModeType.dart&#39;;
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 &#39;dart:async&#39;; @@ -3,7 +3,6 @@ import &#39;dart:async&#39;;
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 &#39;package:wow_english/common/request/dao/listen_dao.dart&#39;; @@ -12,58 +11,70 @@ import &#39;package:wow_english/common/request/dao/listen_dao.dart&#39;;
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&lt;RepeatAfterContentEvent, RepeatAfterCo @@ -74,17 +85,18 @@ class RepeatAfterContentBloc extends Bloc&lt;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&lt;RepeatAfterContentEvent, RepeatAfterCo @@ -93,7 +105,6 @@ class RepeatAfterContentBloc extends Bloc&lt;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&lt;RepeatAfterContentEvent, RepeatAfterCo @@ -102,17 +113,19 @@ class RepeatAfterContentBloc extends Bloc&lt;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&lt;RepeatAfterContentEvent, RepeatAfterCo @@ -121,28 +134,36 @@ class RepeatAfterContentBloc extends Bloc&lt;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&lt;RepeatAfterContentEvent, RepeatAfterCo @@ -154,11 +175,13 @@ class RepeatAfterContentBloc extends Bloc&lt;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&lt;RepeatAfterContentEvent, RepeatAfterCo @@ -166,107 +189,101 @@ class RepeatAfterContentBloc extends Bloc&lt;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&lt;RepeatAfterContentEvent, RepeatAfterCo @@ -274,7 +291,8 @@ class RepeatAfterContentBloc extends Bloc&lt;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&lt;RepeatAfterContentEvent, RepeatAfterCo @@ -302,7 +320,8 @@ class RepeatAfterContentBloc extends Bloc&lt;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&lt;RepeatAfterContentEvent, RepeatAfterCo @@ -326,9 +345,7 @@ class RepeatAfterContentBloc extends Bloc&lt;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&lt;RepeatAfterContentEvent, RepeatAfterCo @@ -346,9 +363,9 @@ class RepeatAfterContentBloc extends Bloc&lt;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 &#39;package:wow_english/pages/repeataftercontent/bloc/repeat_after_content_b @@ -6,7 +6,6 @@ import &#39;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
  1 +include: package:flutter_lints/flutter.yaml
  2 +
  3 +# Additional information about this file can be found at
  4 +# https://dart.dev/guides/language/analysis-options
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
  1 +<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  2 + package="com.example.chivox_aiengine">
  3 +</manifest>
packages/chivox_aiengine/android/src/main/assets/vad.0.13.bin 0 → 100644
No preview for this file type