import 'dart:async'; 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/common/request/dao/listen_dao.dart'; import 'package:wow_english/common/request/exception.dart'; import 'package:wow_english/models/course_process_entity.dart'; 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'; import 'package:wow_english/utils/audio_player_util.dart'; import 'package:wow_english/utils/toast_util.dart'; import '../../../common/permission/permissionRequester.dart'; import '../../../common/utils/show_star_reward_dialog.dart'; import '../../../models/voice_result_type.dart'; import '../../../route/route.dart'; part 'topic_picture_event.dart'; part 'topic_picture_state.dart'; enum VoicePlayState { ///未知 unKnow, ///播放中 playing, ///播放完成 completed, ///播放终止 stop } class TopicPictureBloc extends BaseSectionBloc { final PageController pageController; final String courseLessonId; int _currentPage = 0; int _optionSelectItem = -1; CourseProcessEntity? _entity; CourseProcessEntity? get entity => _entity; ///正在评测 bool _isRecording = false; ///正在播放音频 VoicePlayState _voicePlayState = VoicePlayState.unKnow; int get currentPage => _currentPage + 1; /// 选择题选中项 int get optionSelectItem => _optionSelectItem; bool get isRecording => _isRecording; VoicePlayState get voicePlayState => _voicePlayState; late MethodChannel methodChannel; late AudioPlayer audioPlayer; final BuildContext context; final Color? moduleColor; TopicPictureBloc( this.context, this.pageController, this.courseLessonId, this.moduleColor) : super(TopicPictureInitial()) { on(_pageControllerChange); on(_voicePlayStateChange); on(_voiceXsResult); on(_initVoiceSdk); on(_selectItemLoad); on(_selectItemReset); on(_requestData); on(_voiceXsStart); on(_voiceXsStop); on(_questionVoicePlay); on((event, emit) { //音频播放器 audioPlayer = AudioPlayer(); audioPlayer.onPlayerStateChanged.listen((event) async { debugPrint('播放状态变化 _voicePlayState=$_voicePlayState event=$event'); if (event == PlayerState.completed) { debugPrint('播放完成'); _voicePlayState = VoicePlayState.completed; } if (event == PlayerState.stopped) { debugPrint('播放结束'); _voicePlayState = VoicePlayState.stop; } if (event == PlayerState.playing) { debugPrint('正在播放中'); _voicePlayState = VoicePlayState.playing; } if (isClosed) { return; } add(VoicePlayStateChangeEvent()); }); methodChannel = const MethodChannel('wow_english/sing_sound_method_channel'); methodChannel.setMethodCallHandler((call) async { if (call.method == 'voiceResult') { //评测结果 await audioPlayer.setAudioContext(AudioContext()); await audioPlayer.setBalance(0.0); add(XSVoiceResultEvent(call.arguments)); return; } if (call.method == 'voiceStart') { //评测开始 if (kDebugMode) { print('评测开始'); } return; } if (call.method == 'voiceEnd') { await audioPlayer.setAudioContext(AudioContext()); await audioPlayer.setBalance(0.0); //评测结束 if (kDebugMode) { print('评测结束'); } return; } if (call.method == 'voiceFail') { //评测失败 await audioPlayer.setAudioContext(AudioContext()); await audioPlayer.setBalance(0.0); EasyLoading.showToast('评测失败'); return; } }); }); } @override Future close() { pageController.dispose(); audioPlayer.release(); audioPlayer.dispose(); _voiceXsCancel(); return super.close(); } ///请求数据 void _requestData( RequestDataEvent event, Emitter emitter) async { try { _entity = await ListenDao.process(courseLessonId); emitter(RequestDataState()); } catch (e) { if (e is ApiException) { showToast(e.message ?? '请求失败,请检查网络连接'); } } } ///页面切换 void _pageControllerChange(CurrentPageIndexChangeEvent event, Emitter emitter) async { await pageResetIfNeed(); debugPrint('翻页 $_currentPage->${event.pageIndex}'); if (_currentPage == _entity?.topics?.length) { return; } _currentPage = event.pageIndex; final topics = _entity?.topics?[_currentPage]; if (topics?.type != 3 && topics?.type != 4) { if (topics?.audioUrl != null) { final urlStr = topics?.audioUrl ?? ''; if (urlStr.isNotEmpty) { debugPrint(urlStr); await audioPlayer.play(UrlSource(urlStr)); } } } emitter(CurrentPageIndexState()); } ///选择 void _selectItemLoad( SelectItemEvent event, Emitter emitter) async { _optionSelectItem = event.selectIndex; emitter(SelectItemChangeState()); if (checkAnswerRight(_optionSelectItem) == true) { /// 如果选择题答(选)对后题目没播完,则暂停播放题目。答错的话继续播放体验也不错 await closePlayerResource(); /// right音频长度比动效短,所以等动效完了再翻页 AudioPlayerUtil.getInstance().playAudio(AudioPlayerUtilType.right); await showStarRewardDialog(context, onDismiss: () { _resetSelectItem(); autoFlipPage(); }); } else { await AudioPlayerUtil.getInstance().playAudio(AudioPlayerUtilType.wrong); _resetSelectItem(); } } ///重置选择 void _selectItemReset( SelectItemResetEvent event, Emitter emitter) async { ///用于一次完整的选择后重置视图 emitter(SelectItemResetState()); } ///为空则数据异常,用于是否晃动时需要 bool? checkAnswerRight(int selectIndex) { CourseProcessTopics? topics = _entity?.topics?[_currentPage]; if (topics == null || topics.topicAnswerList == null || selectIndex < 0 || selectIndex >= topics.topicAnswerList!.length) { return null; } CourseProcessTopicsTopicAnswerList? answerList = topics.topicAnswerList?[selectIndex]; return answerList?.correct != 0; } ///初始化SDK _initVoiceSdk( XSVoiceInitEvent event, Emitter emitter) async { methodChannel.invokeMethod('initVoiceSdk', event.data); } ///先声测试 void _voiceXsStart( XSVoiceStartEvent event, Emitter emitter) async { await audioPlayer.stop(); // 调用封装好的权限检查和请求方法 bool result = await requestPermission( context, Permission.microphone, "录音", "用于开启录音,识别您的开口作答并给出反馈"); if (result) { methodChannel.invokeMethod('startVoice', { 'word': event.testWord, 'type': event.type, 'userId': event.userId.toString() }); _isRecording = true; emitter(XSVoiceTestState()); } } ///终止评测 Future _voiceXsStop( XSVoiceStopEvent event, Emitter emitter) async { methodChannel.invokeMethod('stopVoice'); } ///取消评测(用于处理退出页面后录音未停止等异常情况的保护操作) Future _voiceXsCancel({bool force = false}) async { if (_isRecording || force) { methodChannel.invokeMethod('cancelVoice'); } } ///先声评测结果 void _voiceXsResult( XSVoiceResultEvent event, Emitter emitter) async { _isRecording = false; emitter(XSVoiceTestState()); final Map args = event.message as Map; final result = args['result'] as Map; final overall = result['overall'].toString(); int score = int.parse(overall); final voiceResult = VoiceResultType.fromScore(score); if (voiceResult.lottieFilePath != null) { AudioPlayerUtil.getInstance().playAudio(voiceResult.audioType); await showCheerRewardDialog(context, lottieFile: voiceResult.lottieFilePath!, onDismiss: () { autoFlipPageByVoice(score); }); } else { await AudioPlayerUtil.getInstance().playAudio(voiceResult.audioType); autoFlipPageByVoice(score); } } // 暂时没用上 void _voicePlayStateChange(VoicePlayStateChangeEvent event, Emitter emitter) async { emitter(VoicePlayStateChange()); } // 题目音频播放 void _questionVoicePlay( VoicePlayEvent event, Emitter emitter) async { await pageResetIfNeed(); final topics = _entity?.topics?[_currentPage]; final urlStr = topics?.audioUrl ?? ''; await audioPlayer.play(UrlSource(urlStr), balance: 0.0, ctx: AudioContext()); } /// 重置状态,音频播放、录音以及一些变量等。用于翻页,打断等场景 Future pageResetIfNeed() async { _optionSelectItem = -1; _isRecording = false; _voicePlayState = VoicePlayState.stop; await closePlayerResource(); await _voiceXsCancel(); } void _resetSelectItem() { _optionSelectItem = -1; add(SelectItemResetEvent()); } Future closePlayerResource() async { if (voicePlayState == VoicePlayState.playing) { await audioPlayer.stop(); } } ///是否是最后一页 bool isLastPage() { return currentPage == _entity?.topics?.length; } ///自动翻页 void autoFlipPage() { if (isLastPage()) { showStepPage(); } else { // 答对后且播放完自动翻页 pageController.nextPage( duration: const Duration(milliseconds: 250), curve: Curves.ease, ); } } ///语音题自动翻页 ///90分以上自动翻页 void autoFlipPageByVoice(int score) { if (score >= 90) { autoFlipPage(); } else { if (isLastPage()) { showStepPage(); } } } ///展示过渡页 void showStepPage() { ///如果最后一页是语音问答题,评测完后自动翻页 sectionComplete(() { popPage(data: { 'currentStep': currentPage, 'courseLessonId': courseLessonId, 'isCompleted': true, 'nextSection': true }); }, againSectionTap: () { pageController.jumpToPage(0); }); } }