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 101 }
102 102  
103 103 dependencies {
104   - // sing sound
105   - implementation 'com.singsound.library:evaluating:2.1.9'
106 104 implementation "com.google.code.gson:gson:2.10"
107 105 // 基础依赖包,必须要依赖
108 106 implementation 'com.geyifeng.immersionbar:immersionbar:3.2.2'
... ...
android/app/proguard-rules.pro
... ... @@ -20,12 +20,6 @@
20 20 # hide the original source file name.
21 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 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 9 import com.gyf.immersionbar.BarHide
10 10 import com.gyf.immersionbar.ImmersionBar
11 11 import com.kouyuxingqiu.wow_english.methodChannels.GameMethodChannel
12   -import com.kouyuxingqiu.wow_english.methodChannels.SingSoungMethodChannel
13 12 import com.umeng.commonsdk.UMConfigure
14 13 import com.umeng.umcrash.UMCrash
15 14 import io.flutter.embedding.android.FlutterActivity
... ... @@ -25,7 +24,6 @@ class MainActivity : FlutterActivity() {
25 24 //隐藏状态栏和导航栏
26 25 ImmersionBar.with(this).hideBar(BarHide.FLAG_HIDE_BAR).init()
27 26 flutterEngine?.let {
28   - SingSoungMethodChannel(this, it)
29 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 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 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 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 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 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 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 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 26 // maven { url 'https://maven.aliyun.com/nexus/content/groups/public' }
27 27 google()
28 28 mavenCentral()
29   - maven { url 'https://repo.singsound.com/repository/singsound_ginger_android_sdk/' }
30 29 maven { url 'https://maven.zjzxsl.com/repository/android-public/' }
31 30 }
32 31 }
... ...
assets/chivox/aiengine.provision 0 → 100644
  1 +ÊÌÊÎËÌÌÍÌÏÌÏÌÏÌÎÉËÉÊÆ™ËÉÍÆËËÊš›Ï™ÌÏÇËÆÆÌš›Ìš›™ÈÊÍÊÍÏÏÎÍΚÇÏËÉÌÉÊÉÏÌËžžÆÊ™›ÎÈÎÆœ›šÏÍÉË™šžžÎžÉžÏÉÇËÎÌÆÎÆÈÇÏ™œÌ›ž
0 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 3 platform :ios, '12.0'
5 4  
... ... @@ -32,7 +31,6 @@ flutter_ios_podfile_setup
32 31 target 'Runner' do
33 32 use_frameworks!
34 33 use_modular_headers!
35   - pod 'SingSoundSDK'
36 34 pod 'DMProgressHUD'
37 35  
38 36 flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
... ...
ios/Runner.xcodeproj/project.pbxproj
... ... @@ -10,8 +10,6 @@
10 10 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
11 11 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
12 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 13 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
16 14 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
17 15 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
... ... @@ -450,9 +448,7 @@
450 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 449 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
452 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 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 452 6DEBBC1D861BE053F3ECE0B9 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
457 453 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
458 454 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
... ... @@ -1027,8 +1023,6 @@
1027 1023 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1028 1024 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
1029 1025 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
1030   - 52450AF02A4C415B007B3E4B /* XSMessageMehtodChannel.swift */,
1031   - 525E17192A4BD03900104CDF /* VoiceXSMessageChannel.swift */,
1032 1026 B852C1342BCABB5E00A53FC4 /* GameMessageChannel.swift */,
1033 1027 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
1034 1028 );
... ... @@ -1955,7 +1949,6 @@
1955 1949 B891A85B2BD24EFB006CB06E /* AniSimpleButton.cpp in Sources */,
1956 1950 B891A8282BD24EFB006CB06E /* TwoStateButton.cpp in Sources */,
1957 1951 B891A8B42BD24EFB006CB06E /* ToyTouchableSprite.cpp in Sources */,
1958   - 525E171A2A4BD03900104CDF /* VoiceXSMessageChannel.swift in Sources */,
1959 1952 B891A8842BD24EFB006CB06E /* ToyDragAndDropHandler.cpp in Sources */,
1960 1953 B891A88C2BD24EFB006CB06E /* ToyLayoutObject.cpp in Sources */,
1961 1954 B891A81D2BD24EFB006CB06E /* SimpleLevelPickerView.cpp in Sources */,
... ... @@ -2024,7 +2017,6 @@
2024 2017 B891A8102BD24EFB006CB06E /* ParentalGateShowInterface.cpp in Sources */,
2025 2018 B891A82F2BD24EFB006CB06E /* AniBasicSteveMapCharacterController.cpp in Sources */,
2026 2019 B891A80A2BD24EFA006CB06E /* LayoutParser.cpp in Sources */,
2027   - 52450AF12A4C415B007B3E4B /* XSMessageMehtodChannel.swift in Sources */,
2028 2020 B891A88A2BD24EFB006CB06E /* ToyGeometryUtils.cpp in Sources */,
2029 2021 B891A8A32BD24EFB006CB06E /* ToyScenarioHandler.cpp in Sources */,
2030 2022 B891A84A2BD24EFB006CB06E /* AniMathUtils.cpp in Sources */,
... ... @@ -2420,7 +2412,6 @@
2420 2412 "-framework",
2421 2413 "\"Reachability\"",
2422 2414 "-framework",
2423   - "\"SingSound\"",
2424 2415 "-framework",
2425 2416 "\"SystemConfiguration\"",
2426 2417 "-framework",
... ... @@ -2779,7 +2770,6 @@
2779 2770 "-framework",
2780 2771 "\"Reachability\"",
2781 2772 "-framework",
2782   - "\"SingSound\"",
2783 2773 "-framework",
2784 2774 "\"SystemConfiguration\"",
2785 2775 "-framework",
... ... @@ -2965,7 +2955,6 @@
2965 2955 "-framework",
2966 2956 "\"Reachability\"",
2967 2957 "-framework",
2968   - "\"SingSound\"",
2969 2958 "-framework",
2970 2959 "\"SystemConfiguration\"",
2971 2960 "-framework",
... ...
ios/Runner/AppDelegate.swift
... ... @@ -14,8 +14,6 @@ import Flutter
14 14  
15 15 GeneratedPluginRegistrant.register(with: self)
16 16 let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
17   - _ = VoiceXSMessageChannel(messager: controller.binaryMessenger)
18   - _ = XSMessageMehtodChannel(message: controller.binaryMessenger);
19 17 _ = GameMessageChannel(message: controller.binaryMessenger);
20 18  
21 19 return super.application(application, didFinishLaunchingWithOptions: launchOptions)
... ...
ios/Runner/Runner-Bridging-Header.h
... ... @@ -3,9 +3,6 @@
3 3 #ifndef Runner_Bridging_Header_h
4 4 #define Runner_Bridging_Header_h
5 5  
6   -// SingSound
7   -#import <SingSound/SSOralEvaluatingManager.h>
8   -
9 6 // UMCommon
10 7 #import <UMCommon/UMCommon.h>
11 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 1 class AppConsts {
4 2 /// 隐私协议
5 3 static const String userPrivacyPolicyUrl =
... ... @@ -16,12 +14,7 @@ class AppConsts {
16 14 static const String userTermSdkUrl =
17 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 3 import 'package:audioplayers/audioplayers.dart';
4 4 import 'package:flutter/cupertino.dart';
5 5 import 'package:flutter/foundation.dart';
6   -import 'package:flutter/services.dart';
7 6 import 'package:flutter_bloc/flutter_bloc.dart';
8 7 import 'package:flutter_easyloading/flutter_easyloading.dart';
9 8 import 'package:permission_handler/permission_handler.dart';
10 9 import 'package:wow_english/common/request/dao/listen_dao.dart';
11 10 import 'package:wow_english/common/request/exception.dart';
  11 +import 'package:wow_english/common/speech/chivox_evaluation_channel.dart';
12 12 import 'package:wow_english/models/course_process_entity.dart';
13 13 import 'package:wow_english/pages/section/subsection/base_section/bloc.dart';
14 14 import 'package:wow_english/pages/section/subsection/base_section/event.dart';
... ... @@ -27,7 +27,8 @@ part &#39;topic_picture_event.dart&#39;;
27 27 part 'topic_picture_state.dart';
28 28  
29 29 class TopicPictureBloc
30   - extends BaseSectionBloc<TopicPictureEvent, TopicPictureState> with WidgetsBindingObserver {
  30 + extends BaseSectionBloc<TopicPictureEvent, TopicPictureState>
  31 + with WidgetsBindingObserver {
31 32 final PageController pageController;
32 33  
33 34 final String courseLessonId;
... ... @@ -50,7 +51,7 @@ class TopicPictureBloc
50 51  
51 52 bool get isRecording => _isRecording;
52 53  
53   - late MethodChannel methodChannel;
  54 + final ChivoxEvaluationChannel methodChannel = ChivoxEvaluationChannel();
54 55  
55 56 late AudioPlayer audioPlayer;
56 57  
... ... @@ -66,7 +67,6 @@ class TopicPictureBloc
66 67 on<CurrentPageIndexChangeEvent>(_pageControllerChange);
67 68 on<VoicePlayStateChangeEvent>(_voicePlayStateChange);
68 69 on<XSVoiceResultEvent>(_voiceXsResult);
69   - on<XSVoiceInitEvent>(_initVoiceSdk);
70 70 on<SelectItemEvent>(_selectItemLoad);
71 71 on<SelectItemResetEvent>(_selectItemReset);
72 72 on<RequestDataEvent>(_requestData);
... ... @@ -74,7 +74,7 @@ class TopicPictureBloc
74 74 on<XSVoiceStopEvent>(_voiceXsStop);
75 75 on<VoicePlayEvent>(_questionVoicePlay);
76 76 on<OnXSVoiceStateChangeEvent>(_onVoiceXsStateChange);
77   - on<InitBlocEvent>((event, emit) {
  77 + on<InitBlocEvent>((event, emit) async {
78 78 //音频播放器
79 79 audioPlayer = AudioPlayer();
80 80 audioPlayer.onPlayerStateChanged.listen((event) async {
... ... @@ -85,8 +85,6 @@ class TopicPictureBloc
85 85 add(VoicePlayStateChangeEvent());
86 86 });
87 87  
88   - methodChannel =
89   - const MethodChannel('wow_english/sing_sound_method_channel');
90 88 methodChannel.setMethodCallHandler((call) async {
91 89 if (call.method == 'voiceResult') {
92 90 //评测结果
... ... @@ -130,6 +128,8 @@ class TopicPictureBloc
130 128 }
131 129 });
132 130  
  131 + await methodChannel.invokeMethod('initVoiceSdk', {});
  132 +
133 133 WidgetsBinding.instance.addObserver(this);
134 134 });
135 135 }
... ... @@ -153,11 +153,12 @@ class TopicPictureBloc
153 153 }
154 154  
155 155 @override
156   - Future<void> close() {
  156 + Future<void> close() async {
157 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 162 WidgetsBinding.instance.removeObserver(this);
162 163 return super.close();
163 164 }
... ... @@ -239,13 +240,7 @@ class TopicPictureBloc
239 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 244 void _voiceXsStart(
250 245 XSVoiceStartEvent event, Emitter<TopicPictureState> emitter) async {
251 246 await audioPlayer.stop();
... ... @@ -253,7 +248,7 @@ class TopicPictureBloc
253 248 bool result = await requestPermission(
254 249 context, Permission.microphone, "录音", "用于开启录音,识别您的开口作答并给出反馈");
255 250 if (result) {
256   - methodChannel.invokeMethod('startVoice', {
  251 + await methodChannel.invokeMethod('startVoice', {
257 252 'word': event.testWord,
258 253 'type': event.type,
259 254 'userId': event.userId.toString()
... ... @@ -264,17 +259,17 @@ class TopicPictureBloc
264 259 ///终止评测
265 260 Future<void> _voiceXsStop(
266 261 XSVoiceStopEvent event, Emitter<TopicPictureState> emitter) async {
267   - methodChannel.invokeMethod('stopVoice');
  262 + await methodChannel.invokeMethod('stopVoice');
268 263 }
269 264  
270 265 ///取消评测(用于处理退出页面后录音未停止等异常情况的保护操作)
271 266 Future<void> _voiceXsCancel({bool force = false}) async {
272 267 if (_isRecording || force) {
273   - methodChannel.invokeMethod('cancelVoice');
  268 + await methodChannel.invokeMethod('cancelVoice');
274 269 }
275 270 }
276 271  
277   - ///声评测结果
  272 + ///声评测结果
278 273 void _voiceXsResult(
279 274 XSVoiceResultEvent event, Emitter<TopicPictureState> emitter) async {
280 275 _isRecording = false;
... ... @@ -286,7 +281,8 @@ class TopicPictureBloc
286 281 final voiceResult = VoiceResultType.fromScore(score);
287 282 if (voiceResult.lottieFilePath != null) {
288 283 AudioPlayerUtil.getInstance().playAudio(voiceResult.audioType);
289   - await showCheerRewardDialog(context, lottieFile: voiceResult.lottieFilePath!, onDismiss: () {
  284 + await showCheerRewardDialog(context,
  285 + lottieFile: voiceResult.lottieFilePath!, onDismiss: () {
290 286 autoFlipPageByVoice(score);
291 287 });
292 288 } else {
... ...
lib/pages/practice/bloc/topic_picture_event.dart
... ... @@ -7,18 +7,12 @@ class InitBlocEvent extends TopicPictureEvent {}
7 7  
8 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 11 class XSVoiceStartEvent extends TopicPictureEvent {
18 12 final String testWord;
19 13 final String type;
20 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 24 XSVoiceResultEvent(this.message);
31 25 }
32 26  
33   -///声评测状态
  27 +///声评测状态
34 28 class OnXSVoiceStateChangeEvent extends TopicPictureEvent {}
35 29  
36 30 ///音频播放状态变化
... ...
lib/pages/practice/topic_picture_page.dart
1 1 import 'package:flutter/material.dart';
2 2 import 'package:flutter_bloc/flutter_bloc.dart';
3 3 import 'package:flutter_screenutil/flutter_screenutil.dart';
4   -import 'package:wow_english/common/core/app_consts.dart';
5 4 import 'package:wow_english/common/core/user_util.dart';
6 5 import 'package:wow_english/common/extension/string_extension.dart';
7 6 import 'package:wow_english/common/widgets/ow_image_widget.dart';
... ... @@ -28,13 +27,7 @@ class TopicPicturePage extends StatelessWidget {
28 27 create: (context) => TopicPictureBloc(
29 28 context, PageController(), courseLessonId ?? '', moduleColor)
30 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 31 child: _TopicPicturePage(),
39 32 );
40 33 }
... ... @@ -310,8 +303,7 @@ class _TopicPicturePage extends StatelessWidget {
310 303 mainAxisAlignment: MainAxisAlignment.center,
311 304 children: [
312 305 SpeakerWidget(
313   - isPlaying: isCurrentPage &&
314   - bloc.isAudioPlaying(),
  306 + isPlaying: isCurrentPage && bloc.isAudioPlaying(),
315 307 // 控制动画播放
316 308 width: 32.w,
317 309 height: 32.w,
... ... @@ -391,8 +383,7 @@ class _TopicPicturePage extends StatelessWidget {
391 383 child: Column(
392 384 children: [
393 385 SpeakerWidget(
394   - isPlaying: isCurrentPage &&
395   - bloc.isAudioPlaying(),
  386 + isPlaying: isCurrentPage && bloc.isAudioPlaying(),
396 387 width: 32.w,
397 388 height: 32.w,
398 389 onTap: () {
... ... @@ -504,8 +495,7 @@ class _TopicPicturePage extends StatelessWidget {
504 495 Row(
505 496 children: [
506 497 SpeakerWidget(
507   - isPlaying: isCurrentPage &&
508   - bloc.isAudioPlaying(),
  498 + isPlaying: isCurrentPage && bloc.isAudioPlaying(),
509 499 // 控制动画播放
510 500 isClickable: !bloc.isRecording,
511 501 // 控制是否可点击
... ...
lib/pages/reading/bloc/reading_bloc.dart
1 1 import 'package:audioplayers/audioplayers.dart';
2 2 import 'package:flutter/cupertino.dart';
3 3 import 'package:flutter/foundation.dart';
4   -import 'package:flutter/services.dart';
5 4 import 'package:flutter_bloc/flutter_bloc.dart';
6 5 import 'package:flutter_easyloading/flutter_easyloading.dart';
7 6 import 'package:flutter_screenutil/flutter_screenutil.dart';
... ... @@ -15,6 +14,7 @@ import &#39;../../../common/core/user_util.dart&#39;;
15 14 import '../../../common/permission/permissionRequester.dart';
16 15 import '../../../common/request/dao/listen_dao.dart';
17 16 import '../../../common/request/exception.dart';
  17 +import '../../../common/speech/chivox_evaluation_channel.dart';
18 18 import '../../../common/utils/show_star_reward_dialog.dart';
19 19 import '../../../models/course_process_entity.dart';
20 20 import '../../../models/singsound_result_detail_entity.dart';
... ... @@ -83,7 +83,7 @@ class ReadingPageBloc
83 83  
84 84 VoicePlayState get voicePlayState => _voicePlayState;
85 85  
86   - late MethodChannel methodChannel;
  86 + final ChivoxEvaluationChannel methodChannel = ChivoxEvaluationChannel();
87 87  
88 88 late AudioPlayer audioPlayer;
89 89  
... ... @@ -91,7 +91,8 @@ class ReadingPageBloc
91 91  
92 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 96 : super(ReadingPageInitial()) {
96 97 on<CurrentPageIndexChangeEvent>(_pageControllerChange);
97 98 on<CurrentModeChangeEvent>(_playModeChange);
... ... @@ -99,7 +100,7 @@ class ReadingPageBloc
99 100 // _currentPage = pageController.page!.round();
100 101 // });
101 102 on<RequestDataEvent>(_requestData);
102   - on<InitBlocEvent>((event, emit) {
  103 + on<InitBlocEvent>((event, emit) async {
103 104 //音频播放器
104 105 audioPlayer = AudioPlayer();
105 106 audioPlayer.onPlayerStateChanged.listen((event) async {
... ... @@ -130,9 +131,6 @@ class ReadingPageBloc
130 131 add(VoicePlayStateChangeEvent());
131 132 });
132 133  
133   - methodChannel =
134   - const MethodChannel('wow_english/sing_sound_method_channel');
135   - methodChannel.invokeMethod('initVoiceSdk', {}); //初始化评测
136 134 methodChannel.setMethodCallHandler((call) async {
137 135 Log.d(
138 136 "setMethodCallHandler method=${call.method} arguments=${call.arguments}");
... ... @@ -179,10 +177,10 @@ class ReadingPageBloc
179 177 return;
180 178 }
181 179 });
  180 + await methodChannel.invokeMethod('initVoiceSdk', {}); //初始化评测
182 181 });
183 182 on<VoicePlayStateChangeEvent>(_voicePlayStateChange);
184 183 on<PlayOriginalAudioEvent>(_playOriginalAudio);
185   - on<XSVoiceInitEvent>(_initVoiceSdk);
186 184 on<XSVoiceStartEvent>(_voiceXsStart);
187 185 on<XSVoiceStopEvent>(_voiceXsStop);
188 186 on<XSVoiceResultEvent>(_voiceXsResult);
... ... @@ -192,11 +190,12 @@ class ReadingPageBloc
192 190 }
193 191  
194 192 @override
195   - Future<void> close() {
  193 + Future<void> close() async {
196 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 199 return super.close();
201 200 }
202 201  
... ... @@ -295,8 +294,11 @@ class ReadingPageBloc
295 294 Future<void> _playAudio(String? audioUrl) async {
296 295 if (audioUrl != null && audioUrl.isNotEmpty) {
297 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 302 } catch (e) {
301 303 Log.d('_playAudio error: $e');
302 304 }
... ... @@ -329,8 +331,7 @@ class ReadingPageBloc
329 331 } else {
330 332 List<String>? wordList = currentPageData()?.word?.split(RegExp(r'\s+'));
331 333 resultDetails = (wordList ?? [])
332   - .map(
333   - (word) => SingsoundResultDetailEntity.withCharAndScore(word, 0))
  334 + .map((word) => SingsoundResultDetailEntity.withCharAndScore(word, 0))
334 335 .toList();
335 336 }
336 337 List<TextSpan> textSpans = resultDetails.asMap().entries.map((entry) {
... ... @@ -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 377 void _voiceXsStart(
383 378 XSVoiceStartEvent event, Emitter<ReadingPageState> emitter) async {
384 379 await _stopAudio();
... ... @@ -391,7 +386,7 @@ class ReadingPageBloc
391 386 bool result = await requestPermission(
392 387 context, Permission.microphone, "录音", "用于开启录音,识别您的开口作答并给出反馈");
393 388 if (result) {
394   - methodChannel.invokeMethod('startVoice', {
  389 + await methodChannel.invokeMethod('startVoice', {
395 390 'word': content,
396 391 'type': '0',
397 392 'userId': UserUtil.getUser()?.id.toString()
... ... @@ -409,17 +404,17 @@ class ReadingPageBloc
409 404 // 提取 score 和 char 字段
410 405 List<SingsoundResultDetailEntity> detailEntities = [];
411 406 for (var detail in resultDetailsJsons) {
412   - int score = detail['score'] as int;
  407 + int score = (detail['score'] as num?)?.round() ?? 0;
413 408 String char = detail['char'] as String;
414 409 detailEntities
415 410 .add(SingsoundResultDetailEntity.withCharAndScore(char, score));
416 411 }
417 412  
418 413 ///todo 后面可以考虑要不要传自己的服务器
419   - final recordFileUrl = args['audioUrl'].toString();
  414 + final recordFileUrl = args['audioUrl']?.toString() ?? '';
420 415 int score = int.parse(overall);
421 416 currentPageData()?.recordScore = overall;
422   - currentPageData()?.recordUrl = args['audioUrl'] + '.mp3';
  417 + currentPageData()?.recordUrl = recordFileUrl;
423 418 currentPageData()?.resultDetails = detailEntities;
424 419 add(OnXSVoiceStateChangeEvent());
425 420  
... ... @@ -427,7 +422,8 @@ class ReadingPageBloc
427 422  
428 423 if (voiceResult.lottieFilePath != null) {
429 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 427 await actionAfterRecord();
432 428 });
433 429 } else {
... ... @@ -458,14 +454,14 @@ class ReadingPageBloc
458 454 ///终止评测
459 455 void _voiceXsStop(
460 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 462 Log.d("取消评测 _voiceXsCancel _isRecording=$_isRecording");
467 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 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 26 class XSVoiceResultEvent extends ReadingPageEvent {
33 27 final dynamic message;
34 28 XSVoiceResultEvent(this.message);
35 29 }
36 30  
37   -///声测试
  31 +///声测试
38 32 class XSVoiceStartEvent extends ReadingPageEvent {
39 33 final String content;
40 34 final String type;
... ... @@ -42,10 +36,10 @@ class XSVoiceStartEvent extends ReadingPageEvent {
42 36 XSVoiceStartEvent(this.content, this.type, this.userId);
43 37 }
44 38  
45   -///声评测停止
  39 +///声评测停止
46 40 class XSVoiceStopEvent extends ReadingPageEvent {}
47 41  
48   -///声评测状态
  42 +///声评测状态
49 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 import 'package:wow_english/pages/reading/widgets/reading_dialog_widget.dart';
8 8 import 'package:wow_english/route/route.dart';
9 9  
10   -import '../../common/core/app_consts.dart';
11 10 import '../../common/core/user_util.dart';
12 11 import '../../common/widgets/recorder_widget.dart';
13 12 import '../../common/widgets/speaker_widget.dart';
... ... @@ -29,13 +28,7 @@ class ReadingPage extends StatelessWidget {
29 28 create: (_) => ReadingPageBloc(
30 29 context, PageController(), courseLessonId ?? '', moduleColor)
31 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 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 3  
4 4 import 'package:audio_session/audio_session.dart';
5 5 import 'package:flutter/cupertino.dart';
6   -import 'package:flutter/services.dart';
7 6 import 'package:flutter_bloc/flutter_bloc.dart';
8 7 import 'package:flutter_sound/flutter_sound.dart';
9 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 11 import 'package:wow_english/route/route.dart';
13 12 import '../../../common/dialogs/show_dialog.dart';
14 13 import '../../../common/request/exception.dart';
  14 +import '../../../common/speech/chivox_evaluation_channel.dart';
15 15 import '../../../models/read_content_entity.dart';
16 16 import '../../../utils/loading.dart';
17 17 import '../../../utils/toast_util.dart';
18 18  
19   -
20 19 part 'repeat_after_content_event.dart';
21 20 part 'repeat_after_content_state.dart';
22 21  
23 22 enum VoiceRecordState {
24 23 ///未知
25 24 voiceRecordUnkonw,
  25 +
26 26 ///开始录音
27 27 voiceRecordStat,
  28 +
28 29 ///正在录音
29 30 voiceRecording,
  31 +
30 32 ///录音结束
31 33 voiceRecordEnd
32 34 }
33 35  
34   -///声测评状态
  36 +///声测评状态
35 37 enum XSVoiceCheckState {
36 38 ///未知
37 39 unKnow,
  40 +
38 41 ///测评开始
39 42 start,
  43 +
40 44 ///评测结果
41 45 result,
  46 +
42 47 ///测评结束
43 48 stop,
44 49 }
45 50  
46   -class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterContentState> {
47   -
  51 +class RepeatAfterContentBloc
  52 + extends Bloc<RepeatAfterContentEvent, RepeatAfterContentState> {
48 53 final String courseLessonId;
49 54  
50 55 /// 是否正在播放视频
51 56 bool _videoPlaying = true;
52 57 bool get videoPlaying => _videoPlaying;
  58 +
53 59 /// 是否正在录音
54 60 bool _isRecord = false;
55 61 bool get isRecord => _isRecord;
56   - /// 先声评测状态
  62 +
  63 + /// 驰声评测状态
57 64 XSVoiceCheckState _xSCheckState = XSVoiceCheckState.unKnow;
58 65 XSVoiceCheckState get xSCheckState => _xSCheckState;
  66 +
59 67 /// 评测结果
60 68 Map? _voiceTestResult;
61 69 Map? get voiceTestResult => _voiceTestResult;
  70 +
62 71 /// 录音的次数
63 72 int _recordNumber = 0;
  73 +
64 74 /// 录音文件地址
65 75 String _path = '';
66 76 String get path => _path;
  77 +
67 78 /// 当前播放的视频位置
68 79 int _currentPlayIndex = 0;
69 80 int get currentPlayIndex => _currentPlayIndex;
... ... @@ -74,17 +85,18 @@ class RepeatAfterContentBloc extends Bloc&lt;RepeatAfterContentEvent, RepeatAfterCo
74 85  
75 86 /// 跟读内容数字
76 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 94 late FlutterSoundRecorder _soundRecorder;
84 95 late FlutterSoundPlayer _soundPlayer;
85 96 // StreamSubscription? _soundPlayerListen;
86 97  
87   - RepeatAfterContentBloc(this.courseLessonId) : super(RepeatAfterContentInitial()) {
  98 + RepeatAfterContentBloc(this.courseLessonId)
  99 + : super(RepeatAfterContentInitial()) {
88 100 on<VoiceRecordStateChangeEvent>(_voiceRecordStateChange);
89 101 on<PostFollowReadContentEvent>(_postFollowReadContent);
90 102 on<ChangeVideoPlayIndexEvent>(_changeVideoPlayIndex);
... ... @@ -93,7 +105,6 @@ class RepeatAfterContentBloc extends Bloc&lt;RepeatAfterContentEvent, RepeatAfterCo
93 105 on<StarRecordVoiceEvent>(_starRecordVoice);
94 106 on<StopRecordVoiceEvent>(_stopRecordVoice);
95 107 on<XSVoiceResultEvent>(_voiceXsResult);
96   - on<XSVoiceInitEvent>(_initVoiceSdk);
97 108 on<RequestDataEvent>(_requestData);
98 109 on<XSVoiceTestEvent>(_voiceXsTest);
99 110 on<XSVoiceStopEvent>(_voiceXsStop);
... ... @@ -102,17 +113,19 @@ class RepeatAfterContentBloc extends Bloc&lt;RepeatAfterContentEvent, RepeatAfterCo
102 113 }
103 114  
104 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 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 126 methodChannel.setMethodCallHandler((call) async {
115   - if (call.method == 'voiceResult') {//评测结果
  127 + if (call.method == 'voiceResult') {
  128 + //评测结果
116 129 add(XSVoiceResultEvent(call.arguments));
117 130 add(PostFollowReadContentEvent());
118 131 return;
... ... @@ -121,28 +134,36 @@ class RepeatAfterContentBloc extends Bloc&lt;RepeatAfterContentEvent, RepeatAfterCo
121 134 if (call.method == 'voiceEnd') {
122 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 146 _soundRecorder = FlutterSoundRecorder();
128 147 //音屏
129 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 154 await _soundRecorder.openRecorder();
135   - await _soundRecorder.setSubscriptionDuration(const Duration(milliseconds: 10));
  155 + await _soundRecorder
  156 + .setSubscriptionDuration(const Duration(milliseconds: 10));
136 157 //设置音频
137 158 final session = await AudioSession.instance;
138 159 await session.configure(AudioSessionConfiguration(
139 160 avAudioSessionCategory: AVAudioSessionCategory.playAndRecord,
140 161 avAudioSessionCategoryOptions:
141   - AVAudioSessionCategoryOptions.allowBluetooth |
142   - AVAudioSessionCategoryOptions.defaultToSpeaker,
  162 + AVAudioSessionCategoryOptions.allowBluetooth |
  163 + AVAudioSessionCategoryOptions.defaultToSpeaker,
143 164 avAudioSessionMode: AVAudioSessionMode.spokenAudio,
144 165 avAudioSessionRouteSharingPolicy:
145   - AVAudioSessionRouteSharingPolicy.defaultPolicy,
  166 + AVAudioSessionRouteSharingPolicy.defaultPolicy,
146 167 avAudioSessionSetActiveOptions: AVAudioSessionSetActiveOptions.none,
147 168 androidAudioAttributes: const AndroidAudioAttributes(
148 169 contentType: AndroidAudioContentType.speech,
... ... @@ -154,11 +175,13 @@ class RepeatAfterContentBloc extends Bloc&lt;RepeatAfterContentEvent, RepeatAfterCo
154 175 ));
155 176 await _soundPlayer.closePlayer();
156 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 185 try {
163 186 await loading(() async {
164 187 _entityList = await ListenDao.readContent(courseLessonId);
... ... @@ -166,107 +189,101 @@ class RepeatAfterContentBloc extends Bloc&lt;RepeatAfterContentEvent, RepeatAfterCo
166 189 });
167 190 } catch (e) {
168 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 200 try {
177 201 ReadContentEntity entity = _entityList![_currentPlayIndex]!;
178   - await ListenDao.followResult(_recordNumber.toString(),entity.id);
  202 + await ListenDao.followResult(_recordNumber.toString(), entity.id);
179 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 210 _videoPlaying = !_videoPlaying;
188 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 216 _isRecord = !_isRecord;
193 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 222 _voiceRecordState = event.voiceRecordState;
198 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 229 _recordNumber += 1;
209 230 _xSCheckState = XSVoiceCheckState.start;
210 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 254 final Map args = event.message as Map;
235 255 final result = args['result'] as Map;
236 256 final overall = result['overall'].toString();
237   - _voiceTestResult = {'overall':overall};
  257 + _voiceTestResult = {'overall': overall};
238 258 _xSCheckState = XSVoiceCheckState.result;
239 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 265 if (await _fileExists(_path)) {
245 266 if (_soundPlayer.isPlaying) {
246 267 _soundPlayer.stopPlayer();
247 268 }
248 269  
249 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 278 if (_entityList == null || _entityList!.isEmpty) {
262 279 return;
263 280 }
264 281 if (event.isNext) {
265   - if (_currentPlayIndex < _entityList!.length-1) {
  282 + if (_currentPlayIndex < _entityList!.length - 1) {
266 283 _currentPlayIndex++;
267 284 }
268 285 } else {
269   - if (_currentPlayIndex >0) {
  286 + if (_currentPlayIndex > 0) {
270 287 _currentPlayIndex--;
271 288 }
272 289 }
... ... @@ -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 296 try {
279 297 await getPermissionStatus().then((value) async {
280 298 if (!value) {
... ... @@ -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 325 debugPrint('=====> 停止录音');
307 326 await _soundRecorder.stopRecorder();
308 327 _voiceRecordState = VoiceRecordState.voiceRecordEnd;
... ... @@ -326,9 +345,7 @@ class RepeatAfterContentBloc extends Bloc&lt;RepeatAfterContentEvent, RepeatAfterCo
326 345 showDialog();
327 346 } else if (status.isRestricted) {
328 347 requestPermission(permission);
329   - } else {
330   -
331   - }
  348 + } else {}
332 349 return false;
333 350 }
334 351  
... ... @@ -346,9 +363,9 @@ class RepeatAfterContentBloc extends Bloc&lt;RepeatAfterContentEvent, RepeatAfterCo
346 363 }
347 364  
348 365 void showDialog() async {
349   - showTwoActionDialog('提示', '取消', '去设置', '请进入设置页打开麦克风权限', leftTap: (){
  366 + showTwoActionDialog('提示', '取消', '去设置', '请进入设置页打开麦克风权限', leftTap: () {
350 367 popPage();
351   - },rightTap: (){
  368 + }, rightTap: () {
352 369 popPage();
353 370 openAppSettings();
354 371 });
... ...
lib/pages/repeataftercontent/bloc/repeat_after_content_event.dart
... ... @@ -6,8 +6,10 @@ abstract class RepeatAfterContentEvent {}
6 6 class InitBlocEvent extends RepeatAfterContentEvent {}
7 7  
8 8 class VideoPlayChangeEvent extends RepeatAfterContentEvent {}
  9 +
9 10 ///切换录音状态
10 11 class VoiceRecordEvent extends RepeatAfterContentEvent {}
  12 +
11 13 ///请求数据
12 14 class RequestDataEvent extends RepeatAfterContentEvent {}
13 15  
... ... @@ -16,18 +18,12 @@ class VoiceRecordStateChangeEvent extends RepeatAfterContentEvent {
16 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 22 class XSVoiceTestEvent extends RepeatAfterContentEvent {
27 23 final String testWord;
28 24 final String type;
29 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 51 final bool isNext;
56 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 6 import 'package:wow_english/pages/repeataftercontent/widgets/repeat_after_content_dialog.dart';
7 7 import 'package:wow_english/route/route.dart';
8 8  
9   -import '../../common/core/app_consts.dart';
10 9 import '../../common/core/user_util.dart';
11 10 import '../../models/read_content_entity.dart';
12 11 import '../../utils/toast_util.dart';
... ... @@ -20,16 +19,9 @@ class RepeatAfterContentPage extends StatelessWidget {
20 19 @override
21 20 Widget build(BuildContext context) {
22 21 return BlocProvider(
23   - create: (context) => RepeatAfterContentBloc(videoFollowReadId ??'')
  22 + create: (context) => RepeatAfterContentBloc(videoFollowReadId ?? '')
24 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 25 child: _RepeatAfterContentPage(),
34 26 );
35 27 }
... ... @@ -38,13 +30,17 @@ class RepeatAfterContentPage extends StatelessWidget {
38 30 class _RepeatAfterContentPage extends StatelessWidget {
39 31 @override
40 32 Widget build(BuildContext context) {
41   - return BlocListener<RepeatAfterContentBloc,RepeatAfterContentState>(
42   - listener: (context,state){
  33 + return BlocListener<RepeatAfterContentBloc, RepeatAfterContentState>(
  34 + listener: (context, state) {
43 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 45 return;
50 46 }
... ... @@ -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 167 Widget _buildPlayVideoWidget() {
169   - return BlocBuilder<RepeatAfterContentBloc,RepeatAfterContentState>(
  168 + return BlocBuilder<RepeatAfterContentBloc, RepeatAfterContentState>(
170 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 205 children: [
181   - Row(
  206 + SizedBox(
  207 + height: 23.h,
  208 + width: 23.w,
  209 + ),
  210 + 10.horizontalSpace,
  211 + Column(
182 212 children: [
  213 + 20.verticalSpace,
183 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 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 261 final bloc = BlocProvider.of<RepeatAfterContentBloc>(context);
269 262 final voiceResult = bloc.voiceTestResult;
270 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 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 269 color = const Color(0xFFFFCC00);
275 270 } else {
276 271 color = const Color(0xFF40E04B);
... ... @@ -279,11 +274,15 @@ class _RepeatAfterContentPage extends StatelessWidget {
279 274 mainAxisAlignment: MainAxisAlignment.end,
280 275 children: [
281 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 280 child: Column(
284 281 children: [
285 282 Offstage(
286   - offstage:int.parse(voiceResult?['overall'].toString()??'0') > 60,
  283 + offstage:
  284 + int.parse(voiceResult?['overall'].toString() ?? '0') >
  285 + 60,
287 286 child: Image.asset(
288 287 'sorrow_face'.assetPng,
289 288 height: 46.h,
... ... @@ -291,57 +290,52 @@ class _RepeatAfterContentPage extends StatelessWidget {
291 290 ),
292 291 ),
293 292 Offstage(
294   - offstage: int.parse(voiceResult?['overall'].toString()??'0') < 60,
  293 + offstage:
  294 + int.parse(voiceResult?['overall'].toString() ?? '0') <
  295 + 60,
295 296 child: Container(
296 297 height: 45.h,
297 298 width: 45.h,
298 299 alignment: Alignment.center,
299 300 decoration: BoxDecoration(
300 301 color: color,
301   - borderRadius: BorderRadius.circular(22.5.r)
302   - ),
  302 + borderRadius: BorderRadius.circular(22.5.r)),
303 303 child: Text(
304   - voiceResult?['overall'].toString()??'0',
  304 + voiceResult?['overall'].toString() ?? '0',
305 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 310 IconButton(
314   - onPressed: (){
  311 + onPressed: () {
315 312 bloc.add(RecordeVoicePlayEvent());
316 313 },
317 314 icon: Image.asset(
318 315 'voice_record_play'.assetPng,
319 316 height: 30.h,
320 317 width: 30.w,
321   - )
322   - ),
  318 + )),
323 319 Text(
324 320 '录音',
325 321 textAlign: TextAlign.center,
326 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 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 332 child: Container(
337 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 335 child: Text(
343   - bloc.voiceRecordState == VoiceRecordState.voiceRecording?'正在录音':'录音结束'
344   - ),
  336 + bloc.voiceRecordState == VoiceRecordState.voiceRecording
  337 + ? '正在录音'
  338 + : '录音结束'),
345 339 ),
346 340 ),
347 341 10.verticalSpace,
... ... @@ -364,10 +358,7 @@ class _RepeatAfterContentPage extends StatelessWidget {
364 358 Text(
365 359 '按住录音',
366 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