import 'package:audioplayers/audioplayers.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:wow_english/pages/reading/widgets/ReadingModeType.dart'; import '../../../common/core/user_util.dart'; import '../../../common/request/dao/listen_dao.dart'; import '../../../common/request/exception.dart'; import '../../../models/course_process_entity.dart'; import '../../../utils/loading.dart'; import '../../../utils/log_util.dart'; import '../permissionRequestPage.dart'; part 'reading_event.dart'; part 'reading_state.dart'; enum VoicePlayState { ///未知 unKnow, ///播放中 playing, ///播放完成 completed, ///播放终止 stop } class ReadingPageBloc extends Bloc { final PageController pageController; final String courseLessonId; ///当前页索引 int _currentPage = 0; ///当前播放模式 ReadingModeType _currentMode = ReadingModeType.manual; int get currentPage => _currentPage + 1; ReadingModeType get currentMode => _currentMode; CourseProcessEntity? _entity; CourseProcessEntity? get entity => _entity; ///正在评测 bool _isRecording = false; bool get isRecording => _isRecording; ///原始音频是否正在播放 bool _isOriginAudioPlaying = false; bool get isOriginAudioPlaying => _isOriginAudioPlaying; ///录音音频是否正在播放 bool _isRecordAudioPlaying = false; bool get isRecordAudioPlaying => _isRecordAudioPlaying; ///正在播放音频状态 VoicePlayState _voicePlayState = VoicePlayState.unKnow; VoicePlayState get voicePlayState => _voicePlayState; late MethodChannel methodChannel; late AudioPlayer audioPlayer; final BuildContext context; ReadingPageBloc(this.context, this.pageController, this.courseLessonId) : super(ReadingPageInitial()) { on(_pageControllerChange); on(_playModeChange); // pageController.addListener(() { // _currentPage = pageController.page!.round(); // }); on(_requestData); on((event, emit) { //音频播放器 audioPlayer = AudioPlayer(); audioPlayer.onPlayerStateChanged.listen((event) async { debugPrint('播放状态变化'); if (event == PlayerState.completed) { debugPrint('播放完成'); _voicePlayState = VoicePlayState.completed; _onAudioPlayComplete(); } if (event == PlayerState.stopped) { debugPrint('播放结束'); _voicePlayState = VoicePlayState.stop; _onAudioPlayComplete(); } if (event == PlayerState.playing) { debugPrint('正在播放中'); _voicePlayState = VoicePlayState.playing; } if (isClosed) { return; } add(VoicePlayStateChangeEvent()); }); methodChannel = const MethodChannel('wow_english/sing_sound_method_channel'); methodChannel.invokeMethod('initVoiceSdk', {}); //初始化评测 methodChannel.setMethodCallHandler((call) async { Log.d( "setMethodCallHandler method=${call.method} arguments=${call.arguments}"); if (call.method == 'voiceResult') { //评测结果 add(XSVoiceResultEvent(call.arguments)); return; } if (call.method == 'voiceStart') { //评测开始 if (kDebugMode) { print('评测开始'); } _isRecording = true; add(OnXSVoiceStateChangeEvent()); return; } if (call.method == 'voiceEnd') { //评测结束 if (kDebugMode) { print('评测结束'); } _isRecording = false; add(OnXSVoiceStateChangeEvent()); return; } if (call.method == 'voiceFail') { //评测失败 EasyLoading.showToast('评测失败'); return; } }); }); on(_voicePlayStateChange); on(_playOriginalAudio); on(_initVoiceSdk); on(_voiceXsStart); on(_voiceXsStop); on(_voiceXsResult); on(_onVoiceXsStateChange); on(_playRecordAudio); } @override Future close() { pageController.dispose(); audioPlayer.release(); audioPlayer.dispose(); _voiceXsCancel(); return super.close(); } void _pageControllerChange(CurrentPageIndexChangeEvent event, Emitter emitter) async { _currentPage = event.pageIndex; _playOriginalAudioInner(null); emitter(CurrentPageIndexState()); } void _playModeChange( CurrentModeChangeEvent event, Emitter emitter) async { if (_currentMode == ReadingModeType.auto) { _currentMode = ReadingModeType.manual; } else { _currentMode = ReadingModeType.auto; } emitter(CurrentModeState()); } ///请求数据 void _requestData( RequestDataEvent event, Emitter emitter) async { try { await loading(() async { _entity = await ListenDao.process(courseLessonId); Log.d("reading page entity: ${_entity!.toJson()}"); emitter(RequestDataState()); }); } catch (e) { if (e is ApiException) { EasyLoading.showToast(e.message ?? '请求失败,请检查网络连接'); } } } /// 播放绘本原音 void _playOriginalAudio( PlayOriginalAudioEvent event, Emitter emitter) async { _playOriginalAudioInner(event.url); } ///播放原音音频 Future _playOriginalAudioInner(String? audioUrl) async { if (_isRecordAudioPlaying) { _isRecordAudioPlaying = false; } Log.d( "_playOriginalAudio _isRecordAudioPlaying=$_isRecordAudioPlaying _isOriginAudioPlaying=$_isOriginAudioPlaying url=$audioUrl"); if (_isOriginAudioPlaying) { _isOriginAudioPlaying = false; await audioPlayer.stop(); } else { _isOriginAudioPlaying = true; audioUrl ??= currentPageData()?.audioUrl ?? ''; _playAudio(audioUrl); } } /// 播放录音 void _playRecordAudio( PlayRecordAudioEvent event, Emitter emitter) async { _playRecordAudioInner(); } Future _playRecordAudioInner() async { if (_isOriginAudioPlaying) { _isOriginAudioPlaying = false; } Log.d( "_playRecordAudioInner _isRecordAudioPlaying=$_isRecordAudioPlaying url=${currentPageData()?.recordUrl}"); if (_isRecordAudioPlaying) { _isRecordAudioPlaying = false; await audioPlayer.stop(); } else { _isRecordAudioPlaying = true; final recordAudioUrl = currentPageData()?.recordUrl; _playAudio(recordAudioUrl); } // emit(VoicePlayStateChange()); } void _playAudio(String? audioUrl) async { if (audioUrl!.isNotEmpty) { await audioPlayer.play(UrlSource(audioUrl)); } } int dataCount() { return _entity?.readings?.length ?? 0; } CourseProcessReadings? currentPageData() { return _entity?.readings?[_currentPage]; } void nextPage() { if (_currentPage >= dataCount() - 1) { ///todo 最后一页了 } else { _currentPage += 1; pageController.nextPage( duration: const Duration(milliseconds: 500), curve: Curves.ease, ); } } ///初始化SDK _initVoiceSdk( XSVoiceInitEvent event, Emitter emitter) async { methodChannel.invokeMethod('initVoiceSdk', event.data); } ///先声测试 void _voiceXsStart( XSVoiceStartEvent event, Emitter emitter) async { _stopAudio(); startRecord(event.content); } void startRecord(String content) async { // 调用封装好的权限检查和请求方法 bool result = await permissionCheckAndRequest( context, Permission.microphone, "录音" ); if (result) { methodChannel.invokeMethod( 'startVoice', {'word': content, 'type': '0', 'userId': UserUtil.getUser()?.id.toString()}); } } void _voiceXsResult( XSVoiceResultEvent event, Emitter emitter) async { 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', duration: const Duration(seconds: 10)); currentPageData()?.recordScore = overall; currentPageData()?.recordUrl = args['audioUrl'] + '.mp3'; ///完成录音后紧接着播放录音 _playRecordAudioInner(); emitter(FeedbackState()); } ///终止评测 void _voiceXsStop( XSVoiceStopEvent event, Emitter emitter) async { methodChannel.invokeMethod('stopVoice'); } ///取消评测(用于处理退出页面后录音未停止等异常情况的保护操作) void _voiceXsCancel() { methodChannel.invokeMethod('cancelVoice'); } void _voicePlayStateChange(VoicePlayStateChangeEvent event, Emitter emitter) async { emitter(VoicePlayStateChange()); } void _onAudioPlayComplete() { if (_isRecordAudioPlaying && _currentMode == ReadingModeType.auto) { nextPage(); } Log.d("_onAudioPlayComplete _isOriginAudioPlaying=${_isOriginAudioPlaying} _voicePlayState=$_voicePlayState recordUrl=${currentPageData()?.recordUrl?.isNotEmpty}"); if (_isOriginAudioPlaying && _voicePlayState == VoicePlayState.completed && currentPageData()?.recordUrl?.isNotEmpty != true) { ///如果刚刚完成原音播放&&录音为空,则开始录音 startRecord(currentPageData()?.word ?? ''); } _isOriginAudioPlaying = false; _isRecordAudioPlaying = false; } void _stopAudio() async { await audioPlayer.stop(); _isOriginAudioPlaying = false; _isRecordAudioPlaying = false; } void _onVoiceXsStateChange( OnXSVoiceStateChangeEvent event, Emitter emitter ) async { emit(XSVoiceTestState()); } }