import 'dart:io'; import 'dart:async'; import 'package:audio_session/audio_session.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.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 '../../../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 ; /// 方法 late MethodChannel methodChannel; ///录音 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(_initVoiceSdk); on(_requestData); on(_voiceXsTest); on(_voiceXsStop); on(_voiceRecord); on(_initBlocData); } @override Future close() { _releaseFlauto(); _voiceXsCancel(); return super.close(); } ///初始化功能 void _initBlocData(InitBlocEvent event, Emitter emitter) async { methodChannel = const MethodChannel('wow_english/sing_sound_method_channel'); methodChannel.setMethodCallHandler((call) async { if (call.method == 'voiceResult') {//评测结果 add(XSVoiceResultEvent(call.arguments)); add(PostFollowReadContentEvent()); return; } if (call.method == 'voiceEnd') { return; } }); //录音 _soundRecorder = FlutterSoundRecorder(); //音屏 _soundPlayer = FlutterSoundPlayer(); _init(); } void _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()); } _initVoiceSdk(XSVoiceInitEvent event,Emitter emitter) async { methodChannel.invokeMethod('initVoiceSdk',event.data); } ///先声测试 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 { 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(); _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(); }); } }