Commit 53e9e6db47d8f13736fd21d0c7007ec822bd5615

Authored by 吴启风
1 parent 08a0f5a8

feat:绘本语音评测逻辑

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
... ... @@ -34,6 +34,8 @@ class CourseProcessReadings {
34 34 String? picUrl;
35 35 int? sortOrder;
36 36 String? word;
  37 + String? recordUrl;
  38 + String? recordScore;
37 39  
38 40 CourseProcessReadings();
39 41  
... ...
lib/pages/home/home_page.dart
... ... @@ -116,6 +116,7 @@ class _HomePageView extends StatelessWidget {
116 116 return;
117 117 }
118 118 if (data.courseType == 4) {//绘本
  119 + Navigator.of(context).pushNamed(AppRouteName.reading, arguments: {'courseLessonId':data.id!});
119 120 return;
120 121 }
121 122  
... ...
lib/pages/practice/bloc/topic_picture_bloc.dart
... ... @@ -90,7 +90,7 @@ class TopicPictureBloc extends Bloc&lt;TopicPictureEvent, TopicPictureState&gt; {
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&lt;TopicPictureEvent, TopicPictureState&gt; {
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 &#39;../../../common/request/dao/listen_dao.dart&#39;;
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&lt;ReadingPageEvent, ReadingPageState&gt; {
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&lt;ReadingPageEvent, ReadingPageState&gt; {
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&lt;ReadingPageEvent, ReadingPageState&gt; {
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&lt;ReadingPageEvent, ReadingPageState&gt; {
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&lt;ReadingPageEvent, ReadingPageState&gt; {
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&lt;ReadingPageEvent, ReadingPageState&gt; {
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 &#39;reading_bloc.dart&#39;;
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
... ... @@ -13,3 +13,5 @@ class CurrentModeState extends ReadingPageState {}
13 13 class RequestDataState extends ReadingPageState {}
14 14  
15 15 class XSVoiceTestState extends ReadingPageState {}
  16 +
  17 +class VoicePlayStateChange extends ReadingPageState {}
... ...
lib/pages/reading/reading_page.dart
... ... @@ -9,12 +9,16 @@ import &#39;../../models/course_process_entity.dart&#39;;
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}'))));
... ...