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 '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 '../../../common/core/user_util.dart'; import '../../../common/request/dao/listen_dao.dart'; import '../../../common/request/exception.dart'; import '../../../models/course_process_entity.dart'; import '../../../route/route.dart'; import '../../../utils/loading.dart'; import '../../../utils/log_util.dart'; import '../../../common/permission/permissionRequestPage.dart'; part 'reading_event.dart'; part 'reading_state.dart'; enum VoicePlayState { ///未知 unKnow, ///播放中 playing, ///播放完成 completed, ///播放终止 stop } class ReadingPageBloc extends BaseSectionBloc { 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 { Log.d("播放状态变化 event=$event"); if (event == PlayerState.completed) { debugPrint('播放完成'); _voicePlayState = VoicePlayState.completed; _onAudioPlayComplete(); } if (event == PlayerState.stopped) { debugPrint('播放结束'); _voicePlayState = VoicePlayState.stop; } if (event == PlayerState.playing) { debugPrint('正在播放中'); _voicePlayState = VoicePlayState.playing; } if (event == PlayerState.disposed) { debugPrint('播放器释放'); _voicePlayState = VoicePlayState.unKnow; } 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' || call.method == 'voiceCancel') { //评测结束or评测取消 if (kDebugMode) { print(call.method == 'voiceEnd' ? '评测结束' : '评测取消'); } _isRecording = false; add(OnXSVoiceStateChangeEvent()); return; } if (call.method == 'voiceFail') { //评测失败 _isRecording = false; 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; _voiceXsCancel(); _playOriginalAudioInner(null, forcePlay: true); 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); } ///播放原音音频 /// - [force]: 是否强制播放(true:不管当前状态如何,都会播放目标原音音频,比如翻页场景) /// (false:如果正在播放,暂停播放,比如点击播放按钮场景) void _playOriginalAudioInner(String? audioUrl, {bool forcePlay = false}) async { if (_isRecordAudioPlaying) { await audioPlayer.stop(); _isRecordAudioPlaying = false; } Log.d( "_playOriginalAudio _isRecordAudioPlaying=$_isRecordAudioPlaying _isOriginAudioPlaying=$_isOriginAudioPlaying url=$audioUrl"); if (_isOriginAudioPlaying) { await audioPlayer.stop(); _isOriginAudioPlaying = false; if (forcePlay) { audioUrl ??= currentPageData()?.audioUrl ?? ''; await _playAudio(audioUrl); _isOriginAudioPlaying = true; } } else { audioUrl ??= currentPageData()?.audioUrl ?? ''; await _playAudio(audioUrl); _isOriginAudioPlaying = true; } } /// 播放录音 void _playRecordAudio( PlayRecordAudioEvent event, Emitter emitter) async { _playRecordAudioInner(); } Future _playRecordAudioInner() async { Log.d( "_playRecordAudioInner _isOriginAudioPlaying=$_isOriginAudioPlaying _isRecordAudioPlaying=$_isRecordAudioPlaying url=${currentPageData() ?.recordUrl}"); if (_isOriginAudioPlaying) { ///如果正在播放原音,暂停 await audioPlayer.stop(); _isOriginAudioPlaying = false; } if (_isRecordAudioPlaying) { await audioPlayer.stop(); _isRecordAudioPlaying = false; } else { final recordAudioUrl = currentPageData()?.recordUrl; await _playAudio(recordAudioUrl); _isRecordAudioPlaying = true; } // emitter(VoicePlayStateChange()); } Future _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()) { sectionComplete(() { popPage(data: { 'currentStep': currentPage, 'courseLessonId': courseLessonId, 'isCompleted': true, 'nextSection': true }); }, againSectionTap: () { pageController.jumpToPage(0); }); } else { _currentPage += 1; pageController.nextPage( duration: const Duration(milliseconds: 250), curve: Curves.ease, ); } } ///初始化SDK _initVoiceSdk( XSVoiceInitEvent event, Emitter emitter) async { methodChannel.invokeMethod('initVoiceSdk', event.data); } ///先声测试 void _voiceXsStart( XSVoiceStartEvent event, Emitter emitter) async { await _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: 2)); currentPageData()?.recordScore = overall; currentPageData()?.recordUrl = args['audioUrl'] + '.mp3'; ///完成录音后紧接着播放录音 await _playRecordAudioInner(); if (isLastPage()) { sectionComplete(() { popPage(data: { 'currentStep': currentPage, 'courseLessonId': courseLessonId, 'isCompleted': true, 'nextSection': true }); }, againSectionTap: () { _resetLocalResult(); pageController.jumpToPage(0); }); } // emitter(FeedbackState()); } ///终止评测 void _voiceXsStop( XSVoiceStopEvent event, Emitter emitter) async { methodChannel.invokeMethod('stopVoice'); } ///取消评测(用于处理退出页面后录音未停止等异常情况的保护操作) void _voiceXsCancel() { Log.d("取消评测 _voiceXsCancel _isRecording=$_isRecording"); if (_isRecording) { 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 && currentPageData()?.recordUrl?.isNotEmpty != true) { ///如果刚刚完成原音播放&&录音为空,则开始录音 startRecord(currentPageData()?.word ?? ''); } _isOriginAudioPlaying = false; _isRecordAudioPlaying = false; } Future _stopAudio() async { await audioPlayer.stop(); _isOriginAudioPlaying = false; _isRecordAudioPlaying = false; } void _onVoiceXsStateChange(OnXSVoiceStateChangeEvent event, Emitter emitter) async { emitter(XSVoiceTestState()); } ///是否是最后一页 bool isLastPage() { return currentPage == dataCount(); } ///重置数据 void _resetLocalResult() { _entity?.readings?.forEach((element) { element.recordScore = null; element.recordUrl = null; }); } }