Commit 53e9e6db47d8f13736fd21d0c7007ec822bd5615
1 parent
08a0f5a8
feat:绘本语音评测逻辑
Showing
14 changed files
with
279 additions
and
69 deletions
android/app/src/main/assets/vad.0.1.bin
100755 → 100644
No preview for this file type
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/methodChannels/SingSoungMethodChannel.kt
... | ... | @@ -2,9 +2,13 @@ package com.kouyuxingqiu.wow_english.methodChannels |
2 | 2 | |
3 | 3 | import android.util.Log |
4 | 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 | |
5 | 8 | import io.flutter.embedding.android.FlutterActivity |
6 | 9 | import io.flutter.embedding.engine.FlutterEngine |
7 | 10 | import io.flutter.plugin.common.MethodChannel |
11 | +import org.json.JSONObject | |
8 | 12 | import java.lang.ref.WeakReference |
9 | 13 | |
10 | 14 | /** |
... | ... | @@ -12,13 +16,13 @@ import java.lang.ref.WeakReference |
12 | 16 | * @date: 2023/6/27 00:32 |
13 | 17 | * @description: |
14 | 18 | */ |
15 | -class SingSoungMethodChannel(val activity: FlutterActivity, val flutterEngine: FlutterEngine) { | |
19 | +class SingSoungMethodChannel(activity: FlutterActivity, flutterEngine: FlutterEngine): SingEngineLifecycles.OnSingEngineAdapter() { | |
16 | 20 | private var methodChannel: MethodChannel? = null |
17 | 21 | |
18 | 22 | companion object { |
19 | 23 | var channel: WeakReference<SingSoungMethodChannel>? = null |
20 | 24 | |
21 | - fun invokeMethod(method: String, arguments: Any) { | |
25 | + fun invokeMethod(method: String, arguments: Any?) { | |
22 | 26 | channel?.get()?.methodChannel?.invokeMethod(method, arguments) |
23 | 27 | } |
24 | 28 | } |
... | ... | @@ -30,10 +34,11 @@ class SingSoungMethodChannel(val activity: FlutterActivity, val flutterEngine: F |
30 | 34 | flutterEngine.dartExecutor.binaryMessenger, |
31 | 35 | "wow_english/sing_sound_method_channel" |
32 | 36 | ) |
37 | + init(activity) | |
33 | 38 | methodChannel?.setMethodCallHandler { call, result -> |
34 | 39 | when (call.method) { |
35 | 40 | "initVoiceSdk" -> { |
36 | - SingEngineHelper.init(activity) | |
41 | + | |
37 | 42 | } |
38 | 43 | "startVoice" -> { |
39 | 44 | val paramMap = call.arguments as HashMap<String, String> |
... | ... | @@ -43,6 +48,7 @@ class SingSoungMethodChannel(val activity: FlutterActivity, val flutterEngine: F |
43 | 48 | } |
44 | 49 | "stopVoice" -> { |
45 | 50 | Log.d("WQF", "SingSoungMethodChannel stopVoice") |
51 | + SingEngineHelper.stopRecord() | |
46 | 52 | } |
47 | 53 | else -> { |
48 | 54 | result.notImplemented() |
... | ... | @@ -51,5 +57,26 @@ class SingSoungMethodChannel(val activity: FlutterActivity, val flutterEngine: F |
51 | 57 | |
52 | 58 | } |
53 | 59 | channel = WeakReference(this) |
60 | + | |
61 | + SingEngineHelper.addOnResultListener(this) | |
62 | + } | |
63 | + | |
64 | + override fun onResult(jsonObject: JSONObject, evalType: Int?) { | |
65 | + //先声回调在子线程,需要切换到主线程 | |
66 | + GlobalHandler.runOnMainThread { | |
67 | + invokeMethod("voiceResult", jsonObject.toString()) | |
68 | + } | |
69 | + } | |
70 | + | |
71 | + override fun onRecordBegin() { | |
72 | + GlobalHandler.runOnMainThread { | |
73 | + invokeMethod("voiceStart", null) | |
74 | + } | |
75 | + } | |
76 | + | |
77 | + override fun onRecordStop() { | |
78 | + GlobalHandler.runOnMainThread { | |
79 | + invokeMethod("voiceEnd", null) | |
80 | + } | |
54 | 81 | } |
55 | 82 | } |
56 | 83 | \ No newline at end of file | ... | ... |
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/SingEngineHelper.kt
... | ... | @@ -73,7 +73,7 @@ object SingEngineHelper : |
73 | 73 | // 设置录音音频路径 |
74 | 74 | wavPath = AiUtil.getFilesDir(context).path + "/userdata/sound_record/" |
75 | 75 | // 设置是否开启 VAD 功能 |
76 | - setOpenVad(true, "vad.0.1.bin") | |
76 | +// setOpenVad(true, "vad.0.1.bin") | |
77 | 77 | //setOpenVad(false, null); |
78 | 78 | // 设置 VAD 前置超时时间 |
79 | 79 | setFrontVadTime(3000) |
... | ... | @@ -383,6 +383,7 @@ object SingEngineHelper : |
383 | 383 | } |
384 | 384 | |
385 | 385 | fun addOnResultListener(listener: SingEngineLifecycles) { |
386 | + Log.i(TAG, "addOnResultListener") | |
386 | 387 | if (mListeners?.contains(listener) == true) { |
387 | 388 | return |
388 | 389 | } |
... | ... | @@ -390,6 +391,7 @@ object SingEngineHelper : |
390 | 391 | } |
391 | 392 | |
392 | 393 | fun removeOnResultListener(listener: SingEngineLifecycles) { |
394 | + Log.i(TAG, "removeOnResultListener") | |
393 | 395 | if (mListeners?.contains(listener) == true) { |
394 | 396 | mListeners?.remove(listener) |
395 | 397 | } | ... | ... |
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/singsound/bean/BaseResultModel.kt
... | ... | @@ -19,7 +19,9 @@ class BaseResultModel( |
19 | 19 | constructor(parcel: Parcel) : this( |
20 | 20 | parcel.readString(), |
21 | 21 | parcel.readDouble(), |
22 | - TODO("scores"), | |
22 | + mutableListOf<SingleResultModel>().apply { | |
23 | + parcel.readTypedList(this, SingleResultModel.CREATOR) | |
24 | + }, | |
23 | 25 | parcel.readDouble(), |
24 | 26 | parcel.readDouble() |
25 | 27 | ) { |
... | ... | @@ -59,6 +61,7 @@ class BaseResultModel( |
59 | 61 | override fun writeToParcel(parcel: Parcel, flags: Int) { |
60 | 62 | parcel.writeString(originText) |
61 | 63 | parcel.writeDouble(score) |
64 | + parcel.writeTypedList(scores) | |
62 | 65 | parcel.writeDouble(pronounce) |
63 | 66 | parcel.writeDouble(fluency) |
64 | 67 | } | ... | ... |
android/app/src/main/kotlin/com/kouyuxingqiu/wow_english/util/GlobalHandler.kt
0 → 100644
1 | +package com.kouyuxingqiu.wow_english.util | |
2 | + | |
3 | +import android.os.Handler | |
4 | +import android.os.Looper | |
5 | + | |
6 | +/** | |
7 | + * @author: stay | |
8 | + * @date: 2023/7/1 16:55 | |
9 | + * @description: 全局Handler, 用于切线程等操作 | |
10 | + */ | |
11 | +object GlobalHandler { | |
12 | + private var handler: Handler? = null | |
13 | + | |
14 | + init { | |
15 | + handler = Handler(Looper.getMainLooper()) | |
16 | + } | |
17 | + | |
18 | + fun runOnMainThread(runnable: Runnable?) { | |
19 | + runnable?.let { | |
20 | + handler?.post(it) | |
21 | + } | |
22 | + } | |
23 | +} | |
0 | 24 | \ No newline at end of file | ... | ... |
ios/Runner/XSMessageMehtodChannel.swift
... | ... | @@ -13,7 +13,7 @@ class XSMessageMehtodChannel: NSObject,SSOralEvaluatingManagerDelegate { |
13 | 13 | init(message:FlutterBinaryMessenger) { |
14 | 14 | super.init() |
15 | 15 | resultData = Dictionary() |
16 | - messageChannel = FlutterMethodChannel.init(name: "wow_english/sing_sound_method_channely", binaryMessenger: message) | |
16 | + messageChannel = FlutterMethodChannel.init(name: "wow_english/sing_sound_method_channel", binaryMessenger: message) | |
17 | 17 | messageChannel!.setMethodCallHandler { call, result in |
18 | 18 | self.handle(call, result) |
19 | 19 | } | ... | ... |
lib/models/course_process_entity.dart
lib/pages/home/home_page.dart
lib/pages/practice/bloc/topic_picture_bloc.dart
... | ... | @@ -90,7 +90,7 @@ class TopicPictureBloc extends Bloc<TopicPictureEvent, TopicPictureState> { |
90 | 90 | add(VoicePlayStateChangeEvent()); |
91 | 91 | }); |
92 | 92 | |
93 | - methodChannel = const MethodChannel('wow_english/sing_sound_method_channely'); | |
93 | + methodChannel = const MethodChannel('wow_english/sing_sound_method_channel'); | |
94 | 94 | methodChannel.setMethodCallHandler((call) async { |
95 | 95 | if (call.method == 'voiceResult') {//评测结果 |
96 | 96 | add(XSVoiceResultEvent(call.arguments)); |
... | ... | @@ -181,7 +181,7 @@ class TopicPictureBloc extends Bloc<TopicPictureEvent, TopicPictureState> { |
181 | 181 | |
182 | 182 | ///先声测试 |
183 | 183 | void _voiceXsTest(XSVoiceTestEvent event,Emitter<TopicPictureState> emitter) async { |
184 | - audioPlayer.stop(); | |
184 | + await audioPlayer.stop(); | |
185 | 185 | methodChannel.invokeMethod( |
186 | 186 | 'startVoice', |
187 | 187 | {'word':event.testWord,'type':event.type,'userId':event.userId.toString()} | ... | ... |
lib/pages/reading/bloc/reading_bloc.dart
... | ... | @@ -10,13 +10,30 @@ import '../../../common/request/dao/listen_dao.dart'; |
10 | 10 | import '../../../common/request/exception.dart'; |
11 | 11 | import '../../../models/course_process_entity.dart'; |
12 | 12 | import '../../../utils/loading.dart'; |
13 | +import 'dart:convert'; | |
13 | 14 | |
14 | 15 | part 'reading_event.dart'; |
15 | 16 | part 'reading_state.dart'; |
16 | 17 | |
18 | +enum VoicePlayState { | |
19 | + ///未知 | |
20 | + unKnow, | |
21 | + | |
22 | + ///播放中 | |
23 | + playing, | |
24 | + | |
25 | + ///播放完成 | |
26 | + completed, | |
27 | + | |
28 | + ///播放终止 | |
29 | + stop | |
30 | +} | |
31 | + | |
17 | 32 | class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { |
18 | 33 | final PageController pageController; |
19 | 34 | |
35 | + final String courseLessonId; | |
36 | + | |
20 | 37 | ///当前页索引 |
21 | 38 | int _currentPage = 0; |
22 | 39 | |
... | ... | @@ -36,40 +53,98 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { |
36 | 53 | |
37 | 54 | bool get isRecording => _isRecording; |
38 | 55 | |
56 | + ///原始音频是否正在播放 | |
57 | + bool _isOriginAudioPlaying = false; | |
58 | + | |
59 | + bool get isOriginAudioPlaying => _isOriginAudioPlaying; | |
60 | + | |
61 | + ///录音音频是否正在播放 | |
62 | + bool _isRecordAudioPlaying = false; | |
63 | + | |
64 | + bool get isRecordAudioPlaying => _isRecordAudioPlaying; | |
65 | + | |
66 | + ///正在播放音频状态 | |
67 | + VoicePlayState _voicePlayState = VoicePlayState.unKnow; | |
68 | + | |
69 | + VoicePlayState get voicePlayState => _voicePlayState; | |
70 | + | |
39 | 71 | late MethodChannel methodChannel; |
40 | 72 | |
41 | 73 | late AudioPlayer audioPlayer; |
42 | 74 | |
43 | - ReadingPageBloc(this.pageController) : super(ReadingPageInitial()) { | |
75 | + ReadingPageBloc(this.pageController, this.courseLessonId) | |
76 | + : super(ReadingPageInitial()) { | |
44 | 77 | on<CurrentPageIndexChangeEvent>(_pageControllerChange); |
45 | - on<CurrentModeChangeEvent>(_selectItemLoad); | |
78 | + on<CurrentModeChangeEvent>(_playModeChange); | |
46 | 79 | // pageController.addListener(() { |
47 | 80 | // _currentPage = pageController.page!.round(); |
48 | 81 | // }); |
49 | 82 | on<RequestDataEvent>(_requestData); |
50 | - on<ReadingPageEvent>((event, emit) { | |
83 | + on<InitBlocEvent>((event, emit) { | |
51 | 84 | //音频播放器 |
52 | 85 | audioPlayer = AudioPlayer(); |
53 | - audioPlayer.onPlayerStateChanged.listen((event) { | |
86 | + audioPlayer.onPlayerStateChanged.listen((event) async { | |
87 | + debugPrint('播放状态变化'); | |
54 | 88 | if (event == PlayerState.completed) { |
55 | - if (kDebugMode) { | |
56 | - print('绘本播放完成'); | |
89 | + debugPrint('播放完成'); | |
90 | + _voicePlayState = VoicePlayState.completed; | |
91 | + } | |
92 | + if (event == PlayerState.stopped) { | |
93 | + debugPrint('播放结束'); | |
94 | + _voicePlayState = VoicePlayState.stop; | |
95 | + } | |
57 | 96 | |
58 | - } | |
97 | + if (event == PlayerState.playing) { | |
98 | + debugPrint('正在播放中'); | |
99 | + _voicePlayState = VoicePlayState.playing; | |
100 | + } | |
101 | + if (isClosed) { | |
102 | + return; | |
59 | 103 | } |
104 | + add(VoicePlayStateChangeEvent()); | |
60 | 105 | }); |
61 | 106 | |
62 | - methodChannel = const MethodChannel('sing_sound_method_channel'); | |
63 | - methodChannel.invokeMethod('initVoiceSdk',{}); | |
107 | + methodChannel = | |
108 | + const MethodChannel('wow_english/sing_sound_method_channel'); | |
109 | + methodChannel.invokeMethod('initVoiceSdk', {}); //初始化评测 | |
64 | 110 | methodChannel.setMethodCallHandler((call) async { |
65 | - if (call.method == 'voiceResult') {//评测结束 | |
66 | - // add(XSVoiceResultEvent(call.arguments)); | |
111 | + if (call.method == 'voiceResult') { | |
112 | + //评测结果 | |
113 | + add(XSVoiceResultEvent(call.arguments)); | |
114 | + return; | |
115 | + } | |
116 | + | |
117 | + if (call.method == 'voiceStart') { | |
118 | + //评测开始 | |
119 | + if (kDebugMode) { | |
120 | + print('评测开始'); | |
121 | + } | |
122 | + return; | |
123 | + } | |
124 | + | |
125 | + if (call.method == 'voiceEnd') { | |
126 | + //评测结束 | |
127 | + if (kDebugMode) { | |
128 | + print('评测结束'); | |
129 | + } | |
130 | + return; | |
131 | + } | |
132 | + | |
133 | + if (call.method == 'voiceFail') { | |
134 | + //评测失败 | |
135 | + EasyLoading.showToast('评测失败'); | |
136 | + return; | |
67 | 137 | } |
68 | 138 | }); |
69 | 139 | }); |
140 | + on<VoicePlayStateChangeEvent>(_voicePlayStateChange); | |
70 | 141 | on<PlayOriginalAudioEvent>(_playOriginalAudio); |
71 | - on<XSVoiceTestEvent>(_voiceXsTest); | |
142 | + on<XSVoiceInitEvent>(_initVoiceSdk); | |
143 | + on<XSVoiceStartEvent>(_voiceXsStart); | |
144 | + on<XSVoiceStopEvent>(_voiceXsStop); | |
72 | 145 | on<XSVoiceResultEvent>(_voiceXsResult); |
146 | + | |
147 | + on<PlayRecordAudioEvent>(_playRecordAudio); | |
73 | 148 | } |
74 | 149 | |
75 | 150 | @override |
... | ... | @@ -83,11 +158,11 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { |
83 | 158 | void _pageControllerChange(CurrentPageIndexChangeEvent event, |
84 | 159 | Emitter<ReadingPageState> emitter) async { |
85 | 160 | _currentPage = event.pageIndex; |
86 | - _playOriginVoice(null); | |
161 | + _playOriginalAudioInner(null); | |
87 | 162 | emitter(CurrentPageIndexState()); |
88 | 163 | } |
89 | 164 | |
90 | - void _selectItemLoad( | |
165 | + void _playModeChange( | |
91 | 166 | CurrentModeChangeEvent event, Emitter<ReadingPageState> emitter) async { |
92 | 167 | if (_currentMode == ReadingModeType.auto) { |
93 | 168 | _currentMode = ReadingModeType.manual; |
... | ... | @@ -102,7 +177,7 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { |
102 | 177 | RequestDataEvent event, Emitter<ReadingPageState> emitter) async { |
103 | 178 | try { |
104 | 179 | await loading(() async { |
105 | - _entity = await ListenDao.process('1'); | |
180 | + _entity = await ListenDao.process(courseLessonId); | |
106 | 181 | print("reading page entity: ${_entity!.toJson()}"); |
107 | 182 | emitter(RequestDataState()); |
108 | 183 | }); |
... | ... | @@ -113,20 +188,36 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { |
113 | 188 | } |
114 | 189 | } |
115 | 190 | |
116 | - void _playOriginalAudio(PlayOriginalAudioEvent event, Emitter<ReadingPageState> emitter) async { | |
117 | - print("_playOriginalAudio"); | |
118 | - _playOriginVoice(event.url); | |
191 | + /// 播放绘本原音 | |
192 | + void _playOriginalAudio( | |
193 | + PlayOriginalAudioEvent event, Emitter<ReadingPageState> emitter) async { | |
194 | + _playOriginalAudioInner(event.url); | |
195 | + } | |
196 | + | |
197 | + void _playOriginalAudioInner(String? audioUrl) { | |
198 | + print("_playOriginalAudio url=$audioUrl"); | |
199 | + audioUrl ??= currentPageData()?.audioUrl ?? ''; | |
200 | + _playAudio(audioUrl); | |
201 | + _isOriginAudioPlaying = true; | |
119 | 202 | } |
120 | 203 | |
121 | - /// 播放绘本原音 | |
122 | - void _playOriginVoice(String? audioUrl) async { | |
123 | - audioPlayer.stop(); | |
124 | - final readingData = currentPageData(); | |
125 | - if (readingData?.audioUrl != null) { | |
126 | - final urlStr = audioUrl ?? readingData?.audioUrl ?? ''; | |
127 | - if (urlStr.isNotEmpty) { | |
128 | - audioPlayer.play(UrlSource(urlStr)); | |
129 | - } | |
204 | + /// 播放录音 | |
205 | + void _playRecordAudio( | |
206 | + PlayRecordAudioEvent event, Emitter<ReadingPageState> emitter) async { | |
207 | + _playRecordAudioInner(); | |
208 | + } | |
209 | + | |
210 | + void _playRecordAudioInner() { | |
211 | + final recordAudioUrl = currentPageData()?.recordUrl; | |
212 | + print("_playRecordAudioInner url=${currentPageData()?.recordUrl}"); | |
213 | + _playAudio(recordAudioUrl); | |
214 | + _isRecordAudioPlaying = true; | |
215 | + } | |
216 | + | |
217 | + void _playAudio(String? audioUrl) async { | |
218 | + await audioPlayer.stop(); | |
219 | + if (audioUrl!.isNotEmpty) { | |
220 | + await audioPlayer.play(UrlSource(audioUrl)); | |
130 | 221 | } |
131 | 222 | } |
132 | 223 | |
... | ... | @@ -139,8 +230,16 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { |
139 | 230 | return _entity?.readings?[_currentPage]; |
140 | 231 | } |
141 | 232 | |
233 | + ///初始化SDK | |
234 | + _initVoiceSdk( | |
235 | + XSVoiceInitEvent event, Emitter<ReadingPageState> emitter) async { | |
236 | + methodChannel.invokeMethod('initVoiceSdk', event.data); | |
237 | + } | |
238 | + | |
142 | 239 | ///先声测试 |
143 | - void _voiceXsTest(XSVoiceTestEvent event, Emitter<ReadingPageState> emitter) async { | |
240 | + void _voiceXsStart( | |
241 | + XSVoiceStartEvent event, Emitter<ReadingPageState> emitter) async { | |
242 | + _stopAudio(); | |
144 | 243 | startRecord(event.content); |
145 | 244 | emitter(XSVoiceTestState()); |
146 | 245 | } |
... | ... | @@ -149,24 +248,44 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { |
149 | 248 | if (_isRecording == true) { |
150 | 249 | return; |
151 | 250 | } |
152 | - EasyLoading.show(status: '录音中....'); | |
153 | 251 | methodChannel.invokeMethod( |
154 | - 'startVoice', | |
155 | - {'word':'how old are you','type':'0','userId':'1'} | |
156 | - ); | |
252 | + 'startVoice', {'word': 'how old are you', 'type': '0', 'userId': '1'}); | |
157 | 253 | _isRecording = true; |
158 | 254 | } |
159 | 255 | |
160 | - void _voiceXsResult(XSVoiceResultEvent event,Emitter<ReadingPageState> emitter) async { | |
161 | - final Map args = event.message as Map; | |
162 | - final result = args['result'] as String; | |
163 | - if (result == '1') { | |
164 | - final overall = args['overall'].toString(); | |
165 | - EasyLoading.showToast('测评成功,分数是$overall',duration: const Duration(seconds: 10)); | |
256 | + void _voiceXsResult( | |
257 | + XSVoiceResultEvent event, Emitter<ReadingPageState> emitter) async { | |
258 | + final Map args = json.decode(event.message); | |
259 | + final result = args['result']; | |
260 | + print("_voiceXsResult result=${result}"); | |
261 | + if (result != null) { | |
262 | + final overall = result['overall'].toString(); | |
263 | + EasyLoading.showToast('测评成功,分数是$overall', | |
264 | + duration: const Duration(seconds: 10)); | |
265 | + currentPageData()?.recordScore = overall; | |
266 | + currentPageData()?.recordUrl = args['audioUrl'] + '.mp3'; | |
267 | + _playRecordAudioInner(); | |
166 | 268 | } else { |
167 | - EasyLoading.showToast('测评失败',duration: const Duration(seconds: 10)); | |
269 | + EasyLoading.showToast('测评失败', duration: const Duration(seconds: 10)); | |
168 | 270 | } |
169 | 271 | _isRecording = false; |
170 | 272 | emitter(XSVoiceTestState()); |
171 | 273 | } |
274 | + | |
275 | + ///终止评测 | |
276 | + void _voiceXsStop( | |
277 | + XSVoiceStopEvent event, Emitter<ReadingPageState> emitter) async { | |
278 | + methodChannel.invokeMethod('stopVoice'); | |
279 | + } | |
280 | + | |
281 | + void _voicePlayStateChange(VoicePlayStateChangeEvent event, | |
282 | + Emitter<ReadingPageState> emitter) async { | |
283 | + emitter(VoicePlayStateChange()); | |
284 | + } | |
285 | + | |
286 | + void _stopAudio() async { | |
287 | + await audioPlayer.stop(); | |
288 | + _isOriginAudioPlaying = false; | |
289 | + _isRecordAudioPlaying = false; | |
290 | + } | |
172 | 291 | } | ... | ... |
lib/pages/reading/bloc/reading_event.dart
... | ... | @@ -3,6 +3,9 @@ part of 'reading_bloc.dart'; |
3 | 3 | @immutable |
4 | 4 | abstract class ReadingPageEvent {} |
5 | 5 | |
6 | +///页面初始化 | |
7 | +class InitBlocEvent extends ReadingPageEvent {} | |
8 | + | |
6 | 9 | class CurrentPageIndexChangeEvent extends ReadingPageEvent { |
7 | 10 | final int pageIndex; |
8 | 11 | CurrentPageIndexChangeEvent(this.pageIndex); |
... | ... | @@ -32,9 +35,18 @@ class XSVoiceResultEvent extends ReadingPageEvent { |
32 | 35 | } |
33 | 36 | |
34 | 37 | ///先声测试 |
35 | -class XSVoiceTestEvent extends ReadingPageEvent { | |
38 | +class XSVoiceStartEvent extends ReadingPageEvent { | |
36 | 39 | final String content; |
37 | 40 | final String type; |
38 | 41 | final String userId; |
39 | - XSVoiceTestEvent(this.content,this.type,this.userId); | |
40 | -} | |
41 | 42 | \ No newline at end of file |
43 | + XSVoiceStartEvent(this.content,this.type,this.userId); | |
44 | +} | |
45 | + | |
46 | +///先声评测停止 | |
47 | +class XSVoiceStopEvent extends ReadingPageEvent {} | |
48 | + | |
49 | +///音频播放状态 | |
50 | +class VoicePlayStateChangeEvent extends ReadingPageEvent {} | |
51 | + | |
52 | +///录音播放 | |
53 | +class PlayRecordAudioEvent extends ReadingPageEvent {} | |
42 | 54 | \ No newline at end of file | ... | ... |
lib/pages/reading/bloc/reading_state.dart
lib/pages/reading/reading_page.dart
... | ... | @@ -9,12 +9,16 @@ import '../../models/course_process_entity.dart'; |
9 | 9 | import 'bloc/reading_bloc.dart'; |
10 | 10 | |
11 | 11 | class ReadingPage extends StatelessWidget { |
12 | - const ReadingPage({super.key}); | |
12 | + const ReadingPage({super.key, this.courseLessonId}); | |
13 | + | |
14 | + final String? courseLessonId; | |
13 | 15 | |
14 | 16 | @override |
15 | 17 | Widget build(BuildContext context) { |
16 | 18 | return BlocProvider( |
17 | - create: (_) => ReadingPageBloc(PageController())..add(RequestDataEvent()), | |
19 | + create: (_) => ReadingPageBloc(PageController(), courseLessonId ?? '') | |
20 | + ..add(InitBlocEvent()) | |
21 | + ..add(RequestDataEvent()), | |
18 | 22 | child: _ReadingPage(), |
19 | 23 | ); |
20 | 24 | } |
... | ... | @@ -135,11 +139,13 @@ class _ReadingPage extends StatelessWidget { |
135 | 139 | if (bloc.isRecording) { |
136 | 140 | return; |
137 | 141 | } |
138 | - print("voice tap"); | |
139 | 142 | bloc.add(PlayOriginalAudioEvent(null)); |
140 | 143 | }, |
141 | 144 | child: Image.asset( |
142 | - 'voice'.assetPng, | |
145 | + bloc.voicePlayState == VoicePlayState.playing && | |
146 | + bloc.isOriginAudioPlaying | |
147 | + ? 'reade_answer'.assetGif | |
148 | + : 'voice'.assetPng, | |
143 | 149 | height: 40.h, |
144 | 150 | width: 45.w, |
145 | 151 | ), |
... | ... | @@ -161,9 +167,13 @@ class _ReadingPage extends StatelessWidget { |
161 | 167 | GestureDetector( |
162 | 168 | onTap: () { |
163 | 169 | if (bloc.isRecording) { |
164 | - return; | |
170 | + bloc.add(XSVoiceStopEvent()); | |
171 | + } else { | |
172 | + bloc.add(XSVoiceStartEvent( | |
173 | + bloc.currentPageData()?.word ?? '', | |
174 | + '0', | |
175 | + UserUtil.getUser()!.id.toString())); | |
165 | 176 | } |
166 | - bloc.add(XSVoiceTestEvent(bloc.currentPageData()?.word??'', '0',UserUtil.getUser()!.id.toString())); | |
167 | 177 | }, |
168 | 178 | child: Image.asset( |
169 | 179 | 'micro_phone'.assetPng, |
... | ... | @@ -173,18 +183,23 @@ class _ReadingPage extends StatelessWidget { |
173 | 183 | SizedBox( |
174 | 184 | width: 10.w, |
175 | 185 | ), |
176 | - Visibility( | |
177 | - visible: false, | |
178 | - | |
179 | - ///todo 依据是否录过音 | |
180 | - child: Image.asset( | |
181 | - 'record_pause'.assetWebp, | |
182 | - | |
183 | - ///todo 根据播放状态切换图片 | |
184 | - height: 33.h, | |
185 | - width: 33.w, | |
186 | - ), | |
187 | - ) | |
186 | + GestureDetector( | |
187 | + onTap: () { | |
188 | + if (bloc.isRecording) { | |
189 | + return; | |
190 | + } | |
191 | + bloc.add(PlayRecordAudioEvent()); | |
192 | + }, | |
193 | + child: Visibility( | |
194 | + visible: bloc.currentPageData()?.recordUrl != null, | |
195 | + child: Image.asset( | |
196 | + bloc.isRecordAudioPlaying | |
197 | + ? 'record_pause'.assetWebp | |
198 | + : 'record_play'.assetWebp, | |
199 | + height: 33.h, | |
200 | + width: 33.w, | |
201 | + ), | |
202 | + )), | |
188 | 203 | ], |
189 | 204 | ), |
190 | 205 | ), | ... | ... |
lib/route/route.dart
... | ... | @@ -139,7 +139,11 @@ class AppRouter { |
139 | 139 | pageBuilder: (_, __, ___) => const TabPage(), |
140 | 140 | transitionsBuilder: (_, __, ___, child) => child); |
141 | 141 | case AppRouteName.reading: |
142 | - return CupertinoPageRoute(builder: (_) => const ReadingPage()); | |
142 | + var courseLessonId = ''; | |
143 | + if (settings.arguments != null) { | |
144 | + courseLessonId = (settings.arguments as Map)['courseLessonId'] as String??''; | |
145 | + } | |
146 | + return CupertinoPageRoute(builder: (_) => ReadingPage(courseLessonId: courseLessonId)); | |
143 | 147 | default: |
144 | 148 | return CupertinoPageRoute( |
145 | 149 | builder: (_) => Scaffold(body: Center(child: Text('No route defined for ${settings.name}')))); | ... | ... |