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/extension/string_extension.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/loading.dart'; import 'package:wow_english/utils/toast_util.dart'; import '../../../common/permission/permissionRequester.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 _selectItem = -1; CourseProcessEntity? _entity; CourseProcessEntity? get entity => _entity; ///正在评测 bool _isVoicing = false; ///正在播放音频 VoicePlayState _voicePlayState = VoicePlayState.unKnow; // 是否是回答(选择)结果音效 bool _isResultSoundPlaying = false; bool get isResultSoundPlaying => _isResultSoundPlaying; // 答对播放音效时禁止任何点击(选择)操作 bool _forbiddenWhenCorrect = false; bool get forbiddenWhenCorrect => _forbiddenWhenCorrect; int get currentPage => _currentPage + 1; int get selectItem => _selectItem; bool get isVoicing => _isVoicing; VoicePlayState get voicePlayState => _voicePlayState; late MethodChannel methodChannel; late AudioPlayer audioPlayer; final BuildContext context; TopicPictureBloc(this.context, this.pageController, this.courseLessonId) : super(TopicPictureInitial()) { on(_pageControllerChange); on(_voicePlayStateChange); on(_voiceXsResult); on(_initVoiceSdk); on(_selectItemLoad); on(_requestData); on(_voiceXsStart); on(_voiceXsStop); on(_questionVoicePlay); on((event, emit) { //音频播放器 audioPlayer = AudioPlayer(); audioPlayer.onPlayerStateChanged.listen((event) async { debugPrint( '播放状态变化 _voicePlayState=$_voicePlayState event=$event _isResultSoundPlaying=$_isResultSoundPlaying _forbiddenWhenCorrect=$_forbiddenWhenCorrect'); if (_isResultSoundPlaying) { if (event != PlayerState.playing) { _isResultSoundPlaying = false; if (_forbiddenWhenCorrect) { _forbiddenWhenCorrect = false; debugPrint('播放完成后解除禁止'); if (event == PlayerState.completed) { if (isLastPage()) { showStepPage(); } else { // 答对后且播放完自动翻页 pageController.nextPage( duration: const Duration(milliseconds: 250), curve: Curves.ease, ); } } } } } else { 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(); _isResultSoundPlaying = false; _forbiddenWhenCorrect = false; _voiceXsCancel(); return super.close(); } ///请求数据 void _requestData( RequestDataEvent event, Emitter emitter) async { try { await loading(() async { _entity = await ListenDao.process(courseLessonId); emitter(RequestDataState()); }); } catch (e) { if (e is ApiException) { showToast(e.message ?? '请求失败,请检查网络连接'); } } } ///页面切换 void _pageControllerChange(CurrentPageIndexChangeEvent event, Emitter emitter) async { await closePlayerResource(); 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)); } } } _selectItem = -1; emitter(CurrentPageIndexState()); } ///选择 void _selectItemLoad( SelectItemEvent event, Emitter emitter) async { if (_forbiddenWhenCorrect) { return; } _selectItem = event.selectIndex; CourseProcessTopics? topics = _entity?.topics?[_currentPage]; CourseProcessTopicsTopicAnswerList? answerList = topics?.topicAnswerList?[_selectItem]; if (answerList?.correct == 0) { _playResultSound(false); // showToast('继续加油哦',duration: const Duration(seconds: 2)); } else { _playResultSound(true); // showToast('恭喜你,答对啦!',duration: const Duration(seconds: 2)); } emitter(SelectItemChangeState()); } ///初始化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() }); _isVoicing = true; emitter(XSVoiceTestState()); } } ///终止评测 void _voiceXsStop( XSVoiceStopEvent event, Emitter emitter) async { methodChannel.invokeMethod('stopVoice'); } ///取消评测(用于处理退出页面后录音未停止等异常情况的保护操作) void _voiceXsCancel() { methodChannel.invokeMethod('cancelVoice'); } ///先声评测结果 void _voiceXsResult( XSVoiceResultEvent event, Emitter emitter) async { final Map args = event.message as Map; final result = args['result'] as Map; final overall = result['overall'].toString(); showToast('测评成功,分数是$overall', duration: const Duration(seconds: 5)); _isVoicing = false; emitter(XSVoiceTestState()); if (isLastPage()) { showStepPage(); } } // 暂时没用上 void _voicePlayStateChange(VoicePlayStateChangeEvent event, Emitter emitter) async { emitter(VoicePlayStateChange()); } // 题目音频播放 void _questionVoicePlay( VoicePlayEvent event, Emitter emitter) async { if (_forbiddenWhenCorrect) { return; } _forbiddenWhenCorrect = false; await closePlayerResource(); final topics = _entity?.topics?[_currentPage]; final urlStr = topics?.audioUrl ?? ''; await audioPlayer.play(UrlSource(urlStr), balance: 0.0, ctx: AudioContext()); } Future closePlayerResource() async { if (voicePlayState == VoicePlayState.playing || _isResultSoundPlaying) { await audioPlayer.stop(); } } ///播放选择结果音效 void _playResultSound(bool isCorrect) async { // await audioPlayer.stop(); if (audioPlayer.state == PlayerState.playing && _isResultSoundPlaying == false) { _voicePlayState = VoicePlayState.stop; } debugPrint("_playResultSound isCorrect=$isCorrect"); _isResultSoundPlaying = true; _forbiddenWhenCorrect = isCorrect; if (isCorrect) { await audioPlayer.play(AssetSource('correct_voice'.assetMp3)); } else { await audioPlayer.play(AssetSource('incorrect_voice'.assetMp3)); } } ///是否是最后一页 bool isLastPage() { return currentPage == _entity?.topics?.length; } ///展示过渡页 void showStepPage() { ///如果最后一页是语音问答题,评测完后自动翻页 sectionComplete(() { popPage(data: { 'currentStep': currentPage, 'courseLessonId': courseLessonId, 'isCompleted': true, 'nextSection': true }); }, againSectionTap: () { pageController.jumpToPage(0); }); } }