import 'dart:io'; import 'dart:async'; import 'package:audio_session/audio_session.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_sound/flutter_sound.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:wow_english/common/request/dao/listen_dao.dart'; import 'package:wow_english/route/route.dart'; import '../../../common/dialogs/show_dialog.dart'; import '../../../common/request/exception.dart'; import '../../../common/speech/chivox_evaluation_channel.dart'; import '../../../models/read_content_entity.dart'; import '../../../utils/loading.dart'; import '../../../utils/toast_util.dart'; part 'repeat_after_content_event.dart'; part 'repeat_after_content_state.dart'; enum VoiceRecordState { ///未知 voiceRecordUnkonw, ///开始录音 voiceRecordStat, ///正在录音 voiceRecording, ///录音结束 voiceRecordEnd } ///驰声测评状态 enum XSVoiceCheckState { ///未知 unKnow, ///测评开始 start, ///评测结果 result, ///测评结束 stop, } class RepeatAfterContentBloc extends Bloc { final String courseLessonId; /// 是否正在播放视频 bool _videoPlaying = true; bool get videoPlaying => _videoPlaying; /// 是否正在录音 bool _isRecord = false; bool get isRecord => _isRecord; /// 驰声评测状态 XSVoiceCheckState _xSCheckState = XSVoiceCheckState.unKnow; XSVoiceCheckState get xSCheckState => _xSCheckState; /// 评测结果 Map? _voiceTestResult; Map? get voiceTestResult => _voiceTestResult; /// 录音的次数 int _recordNumber = 0; /// 录音文件地址 String _path = ''; String get path => _path; /// 当前播放的视频位置 int _currentPlayIndex = 0; int get currentPlayIndex => _currentPlayIndex; /// 录音状态 VoiceRecordState _voiceRecordState = VoiceRecordState.voiceRecordUnkonw; VoiceRecordState get voiceRecordState => _voiceRecordState; /// 跟读内容数字 List? _entityList; List? get entityList => _entityList; /// 方法 final ChivoxEvaluationChannel methodChannel = ChivoxEvaluationChannel(); ///录音 late FlutterSoundRecorder _soundRecorder; late FlutterSoundPlayer _soundPlayer; // StreamSubscription? _soundPlayerListen; RepeatAfterContentBloc(this.courseLessonId) : super(RepeatAfterContentInitial()) { on(_voiceRecordStateChange); on(_postFollowReadContent); on(_changeVideoPlayIndex); on(_videoPlayStateChange); on(_recordeVoicePlay); on(_starRecordVoice); on(_stopRecordVoice); on(_voiceXsResult); on(_requestData); on(_voiceXsTest); on(_voiceXsStop); on(_voiceRecord); on(_initBlocData); } @override Future close() async { await _releaseFlauto(); await _voiceXsCancel(); await methodChannel.dispose(); return super.close(); } ///初始化功能 void _initBlocData( InitBlocEvent event, Emitter emitter) async { methodChannel.setMethodCallHandler((call) async { if (call.method == 'voiceResult') { //评测结果 add(XSVoiceResultEvent(call.arguments)); add(PostFollowReadContentEvent()); return; } if (call.method == 'voiceEnd') { return; } if (call.method == 'voiceFail') { _xSCheckState = XSVoiceCheckState.unKnow; add(VoiceRecordStateChangeEvent(_voiceRecordState)); showToast('评测失败'); } }); //录音 _soundRecorder = FlutterSoundRecorder(); //音屏 _soundPlayer = FlutterSoundPlayer(); await _init(); await methodChannel.invokeMethod('initVoiceSdk', {}); } Future _init() async { await _soundRecorder.openRecorder(); await _soundRecorder .setSubscriptionDuration(const Duration(milliseconds: 10)); //设置音频 final session = await AudioSession.instance; await session.configure(AudioSessionConfiguration( avAudioSessionCategory: AVAudioSessionCategory.playAndRecord, avAudioSessionCategoryOptions: AVAudioSessionCategoryOptions.allowBluetooth | AVAudioSessionCategoryOptions.defaultToSpeaker, avAudioSessionMode: AVAudioSessionMode.spokenAudio, avAudioSessionRouteSharingPolicy: AVAudioSessionRouteSharingPolicy.defaultPolicy, avAudioSessionSetActiveOptions: AVAudioSessionSetActiveOptions.none, androidAudioAttributes: const AndroidAudioAttributes( contentType: AndroidAudioContentType.speech, flags: AndroidAudioFlags.none, usage: AndroidAudioUsage.voiceCommunication, ), androidAudioFocusGainType: AndroidAudioFocusGainType.gain, androidWillPauseWhenDucked: true, )); await _soundPlayer.closePlayer(); await _soundPlayer.openPlayer(); await _soundPlayer .setSubscriptionDuration(const Duration(milliseconds: 10)); } ///请求数据 void _requestData( RequestDataEvent event, Emitter emitter) async { try { await loading(() async { _entityList = await ListenDao.readContent(courseLessonId); emitter(RequestDataState()); }); } catch (e) { if (e is ApiException) { showToast(e.message ?? '请求失败,请检查网络连接'); } } } ///提交跟读结果 void _postFollowReadContent(PostFollowReadContentEvent event, Emitter emitter) async { try { ReadContentEntity entity = _entityList![_currentPlayIndex]!; await ListenDao.followResult(_recordNumber.toString(), entity.id); } catch (e) { if (e is ApiException) {} } } void _videoPlayStateChange(VideoPlayChangeEvent event, Emitter emitter) async { _videoPlaying = !_videoPlaying; emitter(VideoPlayChangeState()); } void _voiceRecord( VoiceRecordEvent event, Emitter emitter) async { _isRecord = !_isRecord; emitter(VoiceRecordChangeState()); } void _voiceRecordStateChange(VoiceRecordStateChangeEvent event, Emitter emitter) async { _voiceRecordState = event.voiceRecordState; emitter(VoiceRecordStateChange()); } ///驰声测试 void _voiceXsTest( XSVoiceTestEvent event, Emitter emitter) async { _recordNumber += 1; _xSCheckState = XSVoiceCheckState.start; emitter(XSVoiceTestState()); await methodChannel.invokeMethod('startLocalVoice', { 'type': event.type, 'word': event.testWord, 'voicePath': _path, 'userId': event.userId.toString() }); } ///终止评测 void _voiceXsStop( XSVoiceStopEvent event, Emitter emitter) async { await methodChannel.invokeMethod('stopVoice'); } ///取消评测(用于处理退出页面后录音未停止等异常情况的保护操作) Future _voiceXsCancel() async { await 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(); _voiceTestResult = {'overall': overall}; _xSCheckState = XSVoiceCheckState.result; emitter(XSVoiceTestState()); } ///播放声音 void _recordeVoicePlay(RecordeVoicePlayEvent event, Emitter emitter) async { if (await _fileExists(_path)) { if (_soundPlayer.isPlaying) { _soundPlayer.stopPlayer(); } await _soundPlayer.startPlayer( fromURI: path, codec: Codec.pcm16WAV, whenFinished: () {}); } } ///更改播放的视频 void _changeVideoPlayIndex(ChangeVideoPlayIndexEvent event, Emitter emitter) async { if (_entityList == null || _entityList!.isEmpty) { return; } if (event.isNext) { if (_currentPlayIndex < _entityList!.length - 1) { _currentPlayIndex++; } } else { if (_currentPlayIndex > 0) { _currentPlayIndex--; } } emitter(ChangeVideoPlayIndexState(event.isNext)); } ///开始录音 void _starRecordVoice(StarRecordVoiceEvent event, Emitter emitter) async { try { await getPermissionStatus().then((value) async { if (!value) { debugPrint('失败$value'); return; } Directory tempDir = await getTemporaryDirectory(); var time = DateTime.now().millisecondsSinceEpoch; String path = '${tempDir.path}/$time${ext[Codec.pcm16WAV.index]}'; _path = path; debugPrint('=====> 准备开始录音'); await _soundRecorder.startRecorder( toFile: path, codec: Codec.pcm16WAV, ); debugPrint('=====> 开始录音'); _voiceRecordState = VoiceRecordState.voiceRecording; _xSCheckState = XSVoiceCheckState.unKnow; emitter(VoiceRecordStateChange()); }); } catch (error) { await _soundRecorder.stopRecorder(); } } ///停止录音 void _stopRecordVoice(StopRecordVoiceEvent event, Emitter emitter) async { debugPrint('=====> 停止录音'); await _soundRecorder.stopRecorder(); _voiceRecordState = VoiceRecordState.voiceRecordEnd; emitter(VoiceRecordStateChange()); } /// 判断文件是否存在 Future _fileExists(String path) async { return await File(path).exists(); } ///获取权限 Future getPermissionStatus() async { Permission permission = Permission.microphone; PermissionStatus status = await permission.status; if (status.isGranted) { return true; } else if (status.isDenied) { requestPermission(permission); } else if (status.isPermanentlyDenied) { showDialog(); } else if (status.isRestricted) { requestPermission(permission); } else {} return false; } /// 释放录音 Future _releaseFlauto() async { await _soundRecorder.closeRecorder(); } ///申请权限 void requestPermission(Permission permission) async { PermissionStatus status = await permission.request(); if (status.isPermanentlyDenied) { showDialog(); } } void showDialog() async { showTwoActionDialog('提示', '取消', '去设置', '请进入设置页打开麦克风权限', leftTap: () { popPage(); }, rightTap: () { popPage(); openAppSettings(); }); } }