repeat_after_content_bloc.dart 10.2 KB
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 '../../../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,
  ///测评结束
  stop,
}

class RepeatAfterContentBloc extends Bloc<RepeatAfterContentEvent, RepeatAfterContentState> {

  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<ReadContentEntity?>? _entityList;
  List<ReadContentEntity?>? get entityList => _entityList ;

  /// 方法
  late MethodChannel methodChannel;

  ///录音
  late FlutterSoundRecorder _soundRecorder;
  late FlutterSoundPlayer _soundPlayer;
  StreamSubscription? _soundPlayerListen;

  RepeatAfterContentBloc(this.courseLessonId) : super(RepeatAfterContentInitial()) {
    on<VoiceRecordStateChangeEvent>(_voiceRecordStateChange);
    on<PostFollowReadContentEvent>(_postFollowReadContent);
    on<ChangeVideoPlayIndexEvent>(_changeVideoPlayIndex);
    on<VideoPlayChangeEvent>(_videoPlayStateChange);
    on<RecordeVoicePlayEvent>(_recordeVoicePlay);
    on<StarRecordVoiceEvent>(_starRecordVoice);
    on<StopRecordVoiceEvent>(_stopRecordVoice);
    on<XSVoiceResultEvent>(_voiceXsResult);
    on<XSVoiceInitEvent>(_initVoiceSdk);
    on<RequestDataEvent>(_requestData);
    on<XSVoiceTestEvent>(_voiceXsTest);
    on<XSVoiceStopEvent>(_voiceXsStop);
    on<VoiceRecordEvent>(_voiceRecord);
    on<InitBlocEvent>(_initBlocData);
  }

  @override
  Future<void> close() {
    _releaseFlauto();
    _voiceXsCancel();
    return super.close();
  }

  ///初始化功能
  void _initBlocData(InitBlocEvent event, Emitter<RepeatAfterContentState> 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());
      }
    });

    //录音
    _soundRecorder = FlutterSoundRecorder();


    await _soundRecorder.openRecorder();
    // await _soundRecorder.setSubscriptionDuration(const Duration(milliseconds: 10));

    //音屏
    _soundPlayer = FlutterSoundPlayer();
    //设置音频
    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<RepeatAfterContentState> 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<RepeatAfterContentState> emitter) async {
    try {
      await loading(() async {
        _entityList = await ListenDao.followResult(_recordNumber.toString(),courseLessonId);

      });
    } catch (e) {
      if (e is ApiException) {

      }
    }
  }

  void _videoPlayStateChange(VideoPlayChangeEvent event,Emitter<RepeatAfterContentState> emitter) async {
    _videoPlaying = !_videoPlaying;
    emitter(VideoPlayChangeState());
  }

  void _voiceRecord(VoiceRecordEvent event,Emitter<RepeatAfterContentState> emitter) async {
    _isRecord = !_isRecord;
    emitter(VoiceRecordChangeState());
  }

  void _voiceRecordStateChange(VoiceRecordStateChangeEvent event,Emitter<RepeatAfterContentState> emitter) async {
    _voiceRecordState = event.voiceRecordState;
    emitter(VoiceRecordStateChange());
  }


  _initVoiceSdk(XSVoiceInitEvent event,Emitter<RepeatAfterContentState> emitter) async {
    methodChannel.invokeMethod('initVoiceSdk',event.data);
  }

  ///先声测试
  void _voiceXsTest(XSVoiceTestEvent event,Emitter<RepeatAfterContentState> emitter) async {
    await methodChannel.invokeMethod(
        'startLocalVoice',
        {
          'type':event.type,
          'word':event.testWord,
          'voicePath':_path,
          'userId':event.userId.toString()
        }
    );
    _recordNumber++;
    _xSCheckState = XSVoiceCheckState.start;
    emitter(XSVoiceTestState());
  }

  ///终止评测
  void _voiceXsStop(XSVoiceStopEvent event,Emitter<RepeatAfterContentState> emitter) async {
    methodChannel.invokeMethod('stopVoice');
  }

  ///取消评测(用于处理退出页面后录音未停止等异常情况的保护操作)
  void _voiceXsCancel() {
    methodChannel.invokeMethod('cancelVoice');
  }

  ///先声评测结果
  void _voiceXsResult(XSVoiceResultEvent event,Emitter<RepeatAfterContentState> emitter) async {
    final Map args = event.message as Map;
    final result = args['result'] as Map;
    final overall = result['overall'].toString();
    _voiceTestResult = {'overall':overall};
    emitter(XSVoiceTestState());
  }

  ///播放声音
  void _recordeVoicePlay(RecordeVoicePlayEvent event,Emitter<RepeatAfterContentState> emitter) async {
    if (await _fileExists(_path)) {
      if (_soundPlayer.isPlaying) {
        _soundPlayer.stopPlayer();
      }

      await _soundPlayer.startPlayer(
        fromURI: path,
        codec: Codec.aacADTS,
        sampleRate: 44000,
        whenFinished: (){

        }
      );
    }
  }

  ///更改播放的视频
  void _changeVideoPlayIndex(ChangeVideoPlayIndexEvent event,Emitter<RepeatAfterContentState> 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<RepeatAfterContentState> 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.aacADTS.index]}';

        _path = path;
        debugPrint('=====> 准备开始录音');
        await _soundRecorder.startRecorder(
          toFile: path,
          codec: Codec.aacADTS,
          bitRate: 8000,
          numChannels: 1,
          sampleRate: 8000,
        );
        debugPrint('=====> 开始录音');
        _voiceRecordState = VoiceRecordState.voiceRecording;
        emitter(VoiceRecordStateChange());
      });
    } catch (error) {
      await _soundRecorder.stopRecorder();
    }
  }

  ///停止录音
  void _stopRecordVoice(StopRecordVoiceEvent event,Emitter<RepeatAfterContentState> emitter) async {
    debugPrint('=====> 停止录音');
    await _soundRecorder.stopRecorder();
    _voiceRecordState = VoiceRecordState.voiceRecordEnd;
    emitter(VoiceRecordStateChange());
  }

  /// 判断文件是否存在
  Future<bool> _fileExists(String path) async {
    return await File(path).exists();
  }

  ///获取权限
  Future<bool> 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) {
      openAppSettings();
    } else if (status.isRestricted) {
      requestPermission(permission);
    } else {

    }
    return false;
  }

  /// 释放录音
  Future<void> _releaseFlauto() async {
    await _soundRecorder.closeRecorder();
  }

  ///申请权限
  void requestPermission(Permission permission) async {
    PermissionStatus status = await permission.request();
    if (status.isPermanentlyDenied) {
      openAppSettings();
    }
  }
}