Blame view

lib/pages/reading/bloc/reading_bloc.dart 11.6 KB
a506beff   吴启风   feat:先声sdk方法找不到问题...
1
  import 'package:audioplayers/audioplayers.dart';
1dc46cf7   吴启风   feat:绘本ui
2
  import 'package:flutter/cupertino.dart';
a506beff   吴启风   feat:先声sdk方法找不到问题...
3
4
  import 'package:flutter/foundation.dart';
  import 'package:flutter/services.dart';
1dc46cf7   吴启风   feat:绘本ui
5
  import 'package:flutter_bloc/flutter_bloc.dart';
a506beff   吴启风   feat:先声sdk方法找不到问题...
6
  import 'package:flutter_easyloading/flutter_easyloading.dart';
6051f850   吴启风   feat:带隐私合规权限申请描述的...
7
  import 'package:permission_handler/permission_handler.dart';
2a427e12   吴启风   feat:绘本静态ui基本完成
8
  import 'package:wow_english/pages/reading/widgets/ReadingModeType.dart';
820997e6   吴启风   feat:过渡页-绘本环节
9
10
11
  import 'package:wow_english/pages/section/subsection/base_section/bloc.dart';
  import 'package:wow_english/pages/section/subsection/base_section/event.dart';
  import 'package:wow_english/pages/section/subsection/base_section/state.dart';
1dc46cf7   吴启风   feat:绘本ui
12
  
dae7f2fd   吴启风   feat:增加android端先声...
13
  import '../../../common/core/user_util.dart';
a506beff   吴启风   feat:先声sdk方法找不到问题...
14
15
16
  import '../../../common/request/dao/listen_dao.dart';
  import '../../../common/request/exception.dart';
  import '../../../models/course_process_entity.dart';
820997e6   吴启风   feat:过渡页-绘本环节
17
  import '../../../route/route.dart';
a506beff   吴启风   feat:先声sdk方法找不到问题...
18
  import '../../../utils/loading.dart';
a506beff   吴启风   feat:先声sdk方法找不到问题...
19
  
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
20
  import '../../../utils/log_util.dart';
20345cbf   吴启风   feat:修改头像权限androi...
21
  import '../../../common/permission/permissionRequestPage.dart';
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
22
  
1dc46cf7   吴启风   feat:绘本ui
23
24
25
  part 'reading_event.dart';
  part 'reading_state.dart';
  
53e9e6db   吴启风   feat:绘本语音评测逻辑
26
27
28
29
30
31
32
33
34
35
36
37
38
39
  enum VoicePlayState {
    ///未知
    unKnow,
  
    ///播放中
    playing,
  
    ///播放完成
    completed,
  
    ///播放终止
    stop
  }
  
820997e6   吴启风   feat:过渡页-绘本环节
40
41
  class ReadingPageBloc
      extends BaseSectionBloc<ReadingPageEvent, ReadingPageState> {
2a427e12   吴启风   feat:绘本静态ui基本完成
42
43
    final PageController pageController;
  
53e9e6db   吴启风   feat:绘本语音评测逻辑
44
45
    final String courseLessonId;
  
2a427e12   吴启风   feat:绘本静态ui基本完成
46
47
48
49
    ///当前页索引
    int _currentPage = 0;
  
    ///当前播放模式
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
50
    ReadingModeType _currentMode = ReadingModeType.manual;
2a427e12   吴启风   feat:绘本静态ui基本完成
51
52
53
54
55
  
    int get currentPage => _currentPage + 1;
  
    ReadingModeType get currentMode => _currentMode;
  
a506beff   吴启风   feat:先声sdk方法找不到问题...
56
57
58
59
60
61
62
63
64
    CourseProcessEntity? _entity;
  
    CourseProcessEntity? get entity => _entity;
  
    ///正在评测
    bool _isRecording = false;
  
    bool get isRecording => _isRecording;
  
53e9e6db   吴启风   feat:绘本语音评测逻辑
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
    ///原始音频是否正在播放
    bool _isOriginAudioPlaying = false;
  
    bool get isOriginAudioPlaying => _isOriginAudioPlaying;
  
    ///录音音频是否正在播放
    bool _isRecordAudioPlaying = false;
  
    bool get isRecordAudioPlaying => _isRecordAudioPlaying;
  
    ///正在播放音频状态
    VoicePlayState _voicePlayState = VoicePlayState.unKnow;
  
    VoicePlayState get voicePlayState => _voicePlayState;
  
a506beff   吴启风   feat:先声sdk方法找不到问题...
80
81
82
83
    late MethodChannel methodChannel;
  
    late AudioPlayer audioPlayer;
  
6051f850   吴启风   feat:带隐私合规权限申请描述的...
84
85
86
    final BuildContext context;
  
    ReadingPageBloc(this.context, this.pageController, this.courseLessonId)
53e9e6db   吴启风   feat:绘本语音评测逻辑
87
        : super(ReadingPageInitial()) {
2a427e12   吴启风   feat:绘本静态ui基本完成
88
      on<CurrentPageIndexChangeEvent>(_pageControllerChange);
53e9e6db   吴启风   feat:绘本语音评测逻辑
89
      on<CurrentModeChangeEvent>(_playModeChange);
2a427e12   吴启风   feat:绘本静态ui基本完成
90
91
92
      // pageController.addListener(() {
      //   _currentPage = pageController.page!.round();
      // });
a506beff   吴启风   feat:先声sdk方法找不到问题...
93
      on<RequestDataEvent>(_requestData);
53e9e6db   吴启风   feat:绘本语音评测逻辑
94
      on<InitBlocEvent>((event, emit) {
a506beff   吴启风   feat:先声sdk方法找不到问题...
95
96
        //音频播放器
        audioPlayer = AudioPlayer();
53e9e6db   吴启风   feat:绘本语音评测逻辑
97
98
        audioPlayer.onPlayerStateChanged.listen((event) async {
          debugPrint('播放状态变化');
a506beff   吴启风   feat:先声sdk方法找不到问题...
99
          if (event == PlayerState.completed) {
53e9e6db   吴启风   feat:绘本语音评测逻辑
100
101
            debugPrint('播放完成');
            _voicePlayState = VoicePlayState.completed;
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
102
            _onAudioPlayComplete();
53e9e6db   吴启风   feat:绘本语音评测逻辑
103
104
105
106
          }
          if (event == PlayerState.stopped) {
            debugPrint('播放结束');
            _voicePlayState = VoicePlayState.stop;
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
107
            _onAudioPlayComplete();
53e9e6db   吴启风   feat:绘本语音评测逻辑
108
          }
a506beff   吴启风   feat:先声sdk方法找不到问题...
109
  
53e9e6db   吴启风   feat:绘本语音评测逻辑
110
111
112
113
114
115
          if (event == PlayerState.playing) {
            debugPrint('正在播放中');
            _voicePlayState = VoicePlayState.playing;
          }
          if (isClosed) {
            return;
a506beff   吴启风   feat:先声sdk方法找不到问题...
116
          }
53e9e6db   吴启风   feat:绘本语音评测逻辑
117
          add(VoicePlayStateChangeEvent());
a506beff   吴启风   feat:先声sdk方法找不到问题...
118
119
        });
  
53e9e6db   吴启风   feat:绘本语音评测逻辑
120
121
122
        methodChannel =
            const MethodChannel('wow_english/sing_sound_method_channel');
        methodChannel.invokeMethod('initVoiceSdk', {}); //初始化评测
a506beff   吴启风   feat:先声sdk方法找不到问题...
123
        methodChannel.setMethodCallHandler((call) async {
6051f850   吴启风   feat:带隐私合规权限申请描述的...
124
125
          Log.d(
              "setMethodCallHandler method=${call.method} arguments=${call.arguments}");
53e9e6db   吴启风   feat:绘本语音评测逻辑
126
127
128
129
130
131
132
133
134
135
136
          if (call.method == 'voiceResult') {
            //评测结果
            add(XSVoiceResultEvent(call.arguments));
            return;
          }
  
          if (call.method == 'voiceStart') {
            //评测开始
            if (kDebugMode) {
              print('评测开始');
            }
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
137
138
            _isRecording = true;
            add(OnXSVoiceStateChangeEvent());
53e9e6db   吴启风   feat:绘本语音评测逻辑
139
140
141
142
143
144
145
146
            return;
          }
  
          if (call.method == 'voiceEnd') {
            //评测结束
            if (kDebugMode) {
              print('评测结束');
            }
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
147
148
            _isRecording = false;
            add(OnXSVoiceStateChangeEvent());
53e9e6db   吴启风   feat:绘本语音评测逻辑
149
150
151
152
153
154
155
            return;
          }
  
          if (call.method == 'voiceFail') {
            //评测失败
            EasyLoading.showToast('评测失败');
            return;
a506beff   吴启风   feat:先声sdk方法找不到问题...
156
157
158
          }
        });
      });
53e9e6db   吴启风   feat:绘本语音评测逻辑
159
      on<VoicePlayStateChangeEvent>(_voicePlayStateChange);
a506beff   吴启风   feat:先声sdk方法找不到问题...
160
      on<PlayOriginalAudioEvent>(_playOriginalAudio);
53e9e6db   吴启风   feat:绘本语音评测逻辑
161
162
163
      on<XSVoiceInitEvent>(_initVoiceSdk);
      on<XSVoiceStartEvent>(_voiceXsStart);
      on<XSVoiceStopEvent>(_voiceXsStop);
a506beff   吴启风   feat:先声sdk方法找不到问题...
164
      on<XSVoiceResultEvent>(_voiceXsResult);
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
165
      on<OnXSVoiceStateChangeEvent>(_onVoiceXsStateChange);
53e9e6db   吴启风   feat:绘本语音评测逻辑
166
167
  
      on<PlayRecordAudioEvent>(_playRecordAudio);
2a427e12   吴启风   feat:绘本静态ui基本完成
168
169
170
171
172
    }
  
    @override
    Future<void> close() {
      pageController.dispose();
a506beff   吴启风   feat:先声sdk方法找不到问题...
173
174
      audioPlayer.release();
      audioPlayer.dispose();
6b0947ca   吴启风   feat:绘本增加initVoic...
175
      _voiceXsCancel();
2a427e12   吴启风   feat:绘本静态ui基本完成
176
177
178
      return super.close();
    }
  
a506beff   吴启风   feat:先声sdk方法找不到问题...
179
180
    void _pageControllerChange(CurrentPageIndexChangeEvent event,
        Emitter<ReadingPageState> emitter) async {
2a427e12   吴启风   feat:绘本静态ui基本完成
181
      _currentPage = event.pageIndex;
ae77d87f   吴启风   feat:fix语音题无法手动停止...
182
      debugPrint("WQF _playOriginalAudioInner begin");
53e9e6db   吴启风   feat:绘本语音评测逻辑
183
      _playOriginalAudioInner(null);
ae77d87f   吴启风   feat:fix语音题无法手动停止...
184
      debugPrint("WQF _playOriginalAudioInner end");
2a427e12   吴启风   feat:绘本静态ui基本完成
185
186
187
      emitter(CurrentPageIndexState());
    }
  
53e9e6db   吴启风   feat:绘本语音评测逻辑
188
    void _playModeChange(
a506beff   吴启风   feat:先声sdk方法找不到问题...
189
        CurrentModeChangeEvent event, Emitter<ReadingPageState> emitter) async {
2a427e12   吴启风   feat:绘本静态ui基本完成
190
191
192
193
194
195
      if (_currentMode == ReadingModeType.auto) {
        _currentMode = ReadingModeType.manual;
      } else {
        _currentMode = ReadingModeType.auto;
      }
      emitter(CurrentModeState());
1dc46cf7   吴启风   feat:绘本ui
196
    }
a506beff   吴启风   feat:先声sdk方法找不到问题...
197
198
199
200
201
202
  
    ///请求数据
    void _requestData(
        RequestDataEvent event, Emitter<ReadingPageState> emitter) async {
      try {
        await loading(() async {
53e9e6db   吴启风   feat:绘本语音评测逻辑
203
          _entity = await ListenDao.process(courseLessonId);
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
204
          Log.d("reading page entity: ${_entity!.toJson()}");
a506beff   吴启风   feat:先声sdk方法找不到问题...
205
206
207
208
209
210
211
212
213
          emitter(RequestDataState());
        });
      } catch (e) {
        if (e is ApiException) {
          EasyLoading.showToast(e.message ?? '请求失败,请检查网络连接');
        }
      }
    }
  
53e9e6db   吴启风   feat:绘本语音评测逻辑
214
215
216
217
218
219
    /// 播放绘本原音
    void _playOriginalAudio(
        PlayOriginalAudioEvent event, Emitter<ReadingPageState> emitter) async {
      _playOriginalAudioInner(event.url);
    }
  
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
220
    ///播放原音音频
820997e6   吴启风   feat:过渡页-绘本环节
221
    void _playOriginalAudioInner(String? audioUrl) async {
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
222
223
224
      if (_isRecordAudioPlaying) {
        _isRecordAudioPlaying = false;
      }
6051f850   吴启风   feat:带隐私合规权限申请描述的...
225
226
      Log.d(
          "_playOriginalAudio _isRecordAudioPlaying=$_isRecordAudioPlaying _isOriginAudioPlaying=$_isOriginAudioPlaying url=$audioUrl");
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
227
228
      if (_isOriginAudioPlaying) {
        _isOriginAudioPlaying = false;
820997e6   吴启风   feat:过渡页-绘本环节
229
        audioPlayer.stop();
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
230
231
      } else {
        _isOriginAudioPlaying = true;
ae77d87f   吴启风   feat:fix语音题无法手动停止...
232
        debugPrint("WQF _playOriginalAudioInner _isOriginAudioPlaying: true");
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
233
234
235
        audioUrl ??= currentPageData()?.audioUrl ?? '';
        _playAudio(audioUrl);
      }
a506beff   吴启风   feat:先声sdk方法找不到问题...
236
237
    }
  
53e9e6db   吴启风   feat:绘本语音评测逻辑
238
239
240
241
242
243
    /// 播放录音
    void _playRecordAudio(
        PlayRecordAudioEvent event, Emitter<ReadingPageState> emitter) async {
      _playRecordAudioInner();
    }
  
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
244
245
246
247
    Future<void> _playRecordAudioInner() async {
      if (_isOriginAudioPlaying) {
        _isOriginAudioPlaying = false;
      }
6051f850   吴启风   feat:带隐私合规权限申请描述的...
248
249
      Log.d(
          "_playRecordAudioInner _isRecordAudioPlaying=$_isRecordAudioPlaying url=${currentPageData()?.recordUrl}");
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
250
251
252
253
254
255
256
257
      if (_isRecordAudioPlaying) {
        _isRecordAudioPlaying = false;
        await audioPlayer.stop();
      } else {
        _isRecordAudioPlaying = true;
        final recordAudioUrl = currentPageData()?.recordUrl;
        _playAudio(recordAudioUrl);
      }
ad37b653   吴启风   feat:1、完成微信支付;2、商...
258
      // emitter(VoicePlayStateChange());
53e9e6db   吴启风   feat:绘本语音评测逻辑
259
260
261
    }
  
    void _playAudio(String? audioUrl) async {
53e9e6db   吴启风   feat:绘本语音评测逻辑
262
263
      if (audioUrl!.isNotEmpty) {
        await audioPlayer.play(UrlSource(audioUrl));
a506beff   吴启风   feat:先声sdk方法找不到问题...
264
265
266
267
      }
    }
  
    int dataCount() {
a506beff   吴启风   feat:先声sdk方法找不到问题...
268
269
270
271
272
273
274
      return _entity?.readings?.length ?? 0;
    }
  
    CourseProcessReadings? currentPageData() {
      return _entity?.readings?[_currentPage];
    }
  
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
275
    void nextPage() {
820997e6   吴启风   feat:过渡页-绘本环节
276
277
278
279
280
281
282
283
284
285
286
      if (currentPage >= dataCount()) {
        sectionComplete(() {
          popPage(data: {
            'currentStep': currentPage,
            'courseLessonId': courseLessonId,
            'isCompleted': true,
            'nextSection': true
          });
        }, againSectionTap: () {
          pageController.jumpToPage(0);
        });
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
287
288
289
290
291
292
293
294
295
      } else {
        _currentPage += 1;
        pageController.nextPage(
          duration: const Duration(milliseconds: 500),
          curve: Curves.ease,
        );
      }
    }
  
53e9e6db   吴启风   feat:绘本语音评测逻辑
296
297
298
299
300
301
    ///初始化SDK
    _initVoiceSdk(
        XSVoiceInitEvent event, Emitter<ReadingPageState> emitter) async {
      methodChannel.invokeMethod('initVoiceSdk', event.data);
    }
  
a506beff   吴启风   feat:先声sdk方法找不到问题...
302
    ///先声测试
53e9e6db   吴启风   feat:绘本语音评测逻辑
303
304
305
    void _voiceXsStart(
        XSVoiceStartEvent event, Emitter<ReadingPageState> emitter) async {
      _stopAudio();
a506beff   吴启风   feat:先声sdk方法找不到问题...
306
      startRecord(event.content);
a506beff   吴启风   feat:先声sdk方法找不到问题...
307
308
309
    }
  
    void startRecord(String content) async {
6051f850   吴启风   feat:带隐私合规权限申请描述的...
310
      // 调用封装好的权限检查和请求方法
820997e6   吴启风   feat:过渡页-绘本环节
311
312
      bool result =
          await permissionCheckAndRequest(context, Permission.microphone, "录音");
6051f850   吴启风   feat:带隐私合规权限申请描述的...
313
      if (result) {
820997e6   吴启风   feat:过渡页-绘本环节
314
315
316
317
318
        methodChannel.invokeMethod('startVoice', {
          'word': content,
          'type': '0',
          'userId': UserUtil.getUser()?.id.toString()
        });
a506beff   吴启风   feat:先声sdk方法找不到问题...
319
      }
a506beff   吴启风   feat:先声sdk方法找不到问题...
320
321
    }
  
53e9e6db   吴启风   feat:绘本语音评测逻辑
322
323
    void _voiceXsResult(
        XSVoiceResultEvent event, Emitter<ReadingPageState> emitter) async {
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
324
325
326
327
328
      final Map args = event.message as Map;
      final result = args['result'] as Map;
      Log.d("_voiceXsResult result=$result");
      final overall = result['overall'].toString();
      EasyLoading.showToast('测评成功,分数是$overall',
a6ad33b3   吴启风   feat:绘本评测结果toast时长修改
329
          duration: const Duration(seconds: 2));
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
330
331
332
      currentPageData()?.recordScore = overall;
      currentPageData()?.recordUrl = args['audioUrl'] + '.mp3';
      ///完成录音后紧接着播放录音
820997e6   吴启风   feat:过渡页-绘本环节
333
334
335
336
337
338
339
340
341
342
343
344
345
346
      await _playRecordAudioInner();
      if (isLastPage()) {
        sectionComplete(() {
          popPage(data: {
            'currentStep': currentPage,
            'courseLessonId': courseLessonId,
            'isCompleted': true,
            'nextSection': true
          });
        }, againSectionTap: () {
          _resetLocalResult();
          pageController.jumpToPage(0);
        });
      }
a6ad33b3   吴启风   feat:绘本评测结果toast时长修改
347
      // emitter(FeedbackState());
a506beff   吴启风   feat:先声sdk方法找不到问题...
348
    }
53e9e6db   吴启风   feat:绘本语音评测逻辑
349
350
351
352
353
354
355
  
    ///终止评测
    void _voiceXsStop(
        XSVoiceStopEvent event, Emitter<ReadingPageState> emitter) async {
      methodChannel.invokeMethod('stopVoice');
    }
  
6b0947ca   吴启风   feat:绘本增加initVoic...
356
357
358
359
360
    ///取消评测(用于处理退出页面后录音未停止等异常情况的保护操作)
    void _voiceXsCancel() {
      methodChannel.invokeMethod('cancelVoice');
    }
  
53e9e6db   吴启风   feat:绘本语音评测逻辑
361
362
363
364
365
    void _voicePlayStateChange(VoicePlayStateChangeEvent event,
        Emitter<ReadingPageState> emitter) async {
      emitter(VoicePlayStateChange());
    }
  
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
366
367
368
369
370
    void _onAudioPlayComplete() {
      if (_isRecordAudioPlaying && _currentMode == ReadingModeType.auto) {
        nextPage();
      }
  
820997e6   吴启风   feat:过渡页-绘本环节
371
372
373
374
375
      Log.d(
          "_onAudioPlayComplete _isOriginAudioPlaying=$_isOriginAudioPlaying _voicePlayState=$_voicePlayState recordUrl=${currentPageData()?.recordUrl?.isNotEmpty}");
      if (_isOriginAudioPlaying &&
          _voicePlayState == VoicePlayState.completed &&
          currentPageData()?.recordUrl?.isNotEmpty != true) {
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
376
377
378
379
380
381
382
383
        ///如果刚刚完成原音播放&&录音为空,则开始录音
        startRecord(currentPageData()?.word ?? '');
      }
  
      _isOriginAudioPlaying = false;
      _isRecordAudioPlaying = false;
    }
  
53e9e6db   吴启风   feat:绘本语音评测逻辑
384
385
386
387
388
    void _stopAudio() async {
      await audioPlayer.stop();
      _isOriginAudioPlaying = false;
      _isRecordAudioPlaying = false;
    }
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
389
  
820997e6   吴启风   feat:过渡页-绘本环节
390
391
    void _onVoiceXsStateChange(OnXSVoiceStateChangeEvent event,
        Emitter<ReadingPageState> emitter) async {
ad37b653   吴启风   feat:1、完成微信支付;2、商...
392
      emitter(XSVoiceTestState());
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
393
    }
065022b7   吴启风   feat:绘本评测反馈弹窗+原音播...
394
  
820997e6   吴启风   feat:过渡页-绘本环节
395
396
397
398
399
400
401
402
403
404
405
406
407
    ///是否是最后一页
    bool isLastPage() {
      return currentPage == dataCount();
    }
  
    ///重置数据
    void _resetLocalResult() {
      _entity?.readings?.forEach((element) {
        element.recordScore = null;
        element.recordUrl = null;
      });
    }
  }