reading_bloc.dart 10.3 KB
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 '../../../common/core/user_util.dart';
import '../../../common/request/dao/listen_dao.dart';
import '../../../common/request/exception.dart';
import '../../../models/course_process_entity.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 Bloc<ReadingPageEvent, ReadingPageState> {
  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<CurrentPageIndexChangeEvent>(_pageControllerChange);
    on<CurrentModeChangeEvent>(_playModeChange);
    // pageController.addListener(() {
    //   _currentPage = pageController.page!.round();
    // });
    on<RequestDataEvent>(_requestData);
    on<InitBlocEvent>((event, emit) {
      //音频播放器
      audioPlayer = AudioPlayer();
      audioPlayer.onPlayerStateChanged.listen((event) async {
        debugPrint('播放状态变化');
        if (event == PlayerState.completed) {
          debugPrint('播放完成');
          _voicePlayState = VoicePlayState.completed;
          _onAudioPlayComplete();
        }
        if (event == PlayerState.stopped) {
          debugPrint('播放结束');
          _voicePlayState = VoicePlayState.stop;
          _onAudioPlayComplete();
        }

        if (event == PlayerState.playing) {
          debugPrint('正在播放中');
          _voicePlayState = VoicePlayState.playing;
        }
        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') {
          //评测结束
          if (kDebugMode) {
            print('评测结束');
          }
          _isRecording = false;
          add(OnXSVoiceStateChangeEvent());
          return;
        }

        if (call.method == 'voiceFail') {
          //评测失败
          EasyLoading.showToast('评测失败');
          return;
        }
      });
    });
    on<VoicePlayStateChangeEvent>(_voicePlayStateChange);
    on<PlayOriginalAudioEvent>(_playOriginalAudio);
    on<XSVoiceInitEvent>(_initVoiceSdk);
    on<XSVoiceStartEvent>(_voiceXsStart);
    on<XSVoiceStopEvent>(_voiceXsStop);
    on<XSVoiceResultEvent>(_voiceXsResult);
    on<OnXSVoiceStateChangeEvent>(_onVoiceXsStateChange);

    on<PlayRecordAudioEvent>(_playRecordAudio);
  }

  @override
  Future<void> close() {
    pageController.dispose();
    audioPlayer.release();
    audioPlayer.dispose();
    _voiceXsCancel();
    return super.close();
  }

  void _pageControllerChange(CurrentPageIndexChangeEvent event,
      Emitter<ReadingPageState> emitter) async {
    _currentPage = event.pageIndex;
    _playOriginalAudioInner(null);
    emitter(CurrentPageIndexState());
  }

  void _playModeChange(
      CurrentModeChangeEvent event, Emitter<ReadingPageState> emitter) async {
    if (_currentMode == ReadingModeType.auto) {
      _currentMode = ReadingModeType.manual;
    } else {
      _currentMode = ReadingModeType.auto;
    }
    emitter(CurrentModeState());
  }

  ///请求数据
  void _requestData(
      RequestDataEvent event, Emitter<ReadingPageState> 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<ReadingPageState> emitter) async {
    _playOriginalAudioInner(event.url);
  }

  ///播放原音音频
  Future<void> _playOriginalAudioInner(String? audioUrl) async {
    if (_isRecordAudioPlaying) {
      _isRecordAudioPlaying = false;
    }
    Log.d(
        "_playOriginalAudio _isRecordAudioPlaying=$_isRecordAudioPlaying _isOriginAudioPlaying=$_isOriginAudioPlaying url=$audioUrl");
    if (_isOriginAudioPlaying) {
      _isOriginAudioPlaying = false;
      await audioPlayer.stop();
    } else {
      _isOriginAudioPlaying = true;
      audioUrl ??= currentPageData()?.audioUrl ?? '';
      _playAudio(audioUrl);
    }
  }

  /// 播放录音
  void _playRecordAudio(
      PlayRecordAudioEvent event, Emitter<ReadingPageState> emitter) async {
    _playRecordAudioInner();
  }

  Future<void> _playRecordAudioInner() async {
    if (_isOriginAudioPlaying) {
      _isOriginAudioPlaying = false;
    }
    Log.d(
        "_playRecordAudioInner _isRecordAudioPlaying=$_isRecordAudioPlaying url=${currentPageData()?.recordUrl}");
    if (_isRecordAudioPlaying) {
      _isRecordAudioPlaying = false;
      await audioPlayer.stop();
    } else {
      _isRecordAudioPlaying = true;
      final recordAudioUrl = currentPageData()?.recordUrl;
      _playAudio(recordAudioUrl);
    }
    // emit(VoicePlayStateChange());
  }

  void _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() - 1) {
      ///todo 最后一页了
    } else {
      _currentPage += 1;
      pageController.nextPage(
        duration: const Duration(milliseconds: 500),
        curve: Curves.ease,
      );
    }
  }

  ///初始化SDK
  _initVoiceSdk(
      XSVoiceInitEvent event, Emitter<ReadingPageState> emitter) async {
    methodChannel.invokeMethod('initVoiceSdk', event.data);
  }

  ///先声测试
  void _voiceXsStart(
      XSVoiceStartEvent event, Emitter<ReadingPageState> emitter) async {
    _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<ReadingPageState> 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: 10));
    currentPageData()?.recordScore = overall;
    currentPageData()?.recordUrl = args['audioUrl'] + '.mp3';
    ///完成录音后紧接着播放录音
    _playRecordAudioInner();
    emitter(FeedbackState());
  }

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

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

  void _voicePlayStateChange(VoicePlayStateChangeEvent event,
      Emitter<ReadingPageState> 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 && _voicePlayState == VoicePlayState.completed && currentPageData()?.recordUrl?.isNotEmpty != true) {
      ///如果刚刚完成原音播放&&录音为空,则开始录音
      startRecord(currentPageData()?.word ?? '');
    }

    _isOriginAudioPlaying = false;
    _isRecordAudioPlaying = false;
  }

  void _stopAudio() async {
    await audioPlayer.stop();
    _isOriginAudioPlaying = false;
    _isRecordAudioPlaying = false;
  }

  void _onVoiceXsStateChange(
    OnXSVoiceStateChangeEvent event,
      Emitter<ReadingPageState> emitter
  ) async {
      emit(XSVoiceTestState());
  }
}