topic_picture_bloc.dart 10.7 KB
import 'dart:async';

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/common/extension/string_extension.dart';
import 'package:wow_english/common/request/dao/listen_dao.dart';
import 'package:wow_english/common/request/exception.dart';
import 'package:wow_english/models/course_process_entity.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 'package:wow_english/utils/audio_player_util.dart';
import 'package:wow_english/utils/toast_util.dart';

import '../../../common/permission/permissionRequester.dart';
import '../../../common/utils/click_with_music_controller.dart';
import '../../../common/utils/show_star_reward_dialog.dart';
import '../../../models/voice_result_type.dart';
import '../../../route/route.dart';

part 'topic_picture_event.dart';

part 'topic_picture_state.dart';

enum VoicePlayState {
  ///未知
  unKnow,

  ///播放中
  playing,

  ///播放完成
  completed,

  ///播放终止
  stop
}

class TopicPictureBloc
    extends BaseSectionBloc<TopicPictureEvent, TopicPictureState> {
  final PageController pageController;

  final String courseLessonId;

  int _currentPage = 0;

  int _optionSelectItem = -1;

  CourseProcessEntity? _entity;

  CourseProcessEntity? get entity => _entity;

  ///正在评测
  bool _isRecording = false;

  ///正在播放音频
  VoicePlayState _voicePlayState = VoicePlayState.unKnow;

  int get currentPage => _currentPage + 1;

  /// 选择题选中项
  int get optionSelectItem => _optionSelectItem;

  bool get isRecording => _isRecording;

  VoicePlayState get voicePlayState => _voicePlayState;

  late MethodChannel methodChannel;

  late AudioPlayer audioPlayer;

  final BuildContext context;

  final Color? moduleColor;

  TopicPictureBloc(
      this.context, this.pageController, this.courseLessonId, this.moduleColor)
      : super(TopicPictureInitial()) {
    on<CurrentPageIndexChangeEvent>(_pageControllerChange);
    on<VoicePlayStateChangeEvent>(_voicePlayStateChange);
    on<XSVoiceResultEvent>(_voiceXsResult);
    on<XSVoiceInitEvent>(_initVoiceSdk);
    on<SelectItemEvent>(_selectItemLoad);
    on<RequestDataEvent>(_requestData);
    on<XSVoiceStartEvent>(_voiceXsStart);
    on<XSVoiceStopEvent>(_voiceXsStop);
    on<VoicePlayEvent>(_questionVoicePlay);
    on<InitBlocEvent>((event, emit) {
      //音频播放器
      audioPlayer = AudioPlayer();
      audioPlayer.onPlayerStateChanged.listen((event) async {
        debugPrint('播放状态变化 _voicePlayState=$_voicePlayState event=$event');
        if (event == PlayerState.completed) {
          debugPrint('播放完成');
          _voicePlayState = VoicePlayState.completed;
        }
        if (event == PlayerState.stopped) {
          debugPrint('播放结束');
          _voicePlayState = VoicePlayState.stop;
        }

        if (event == PlayerState.playing) {
          debugPrint('正在播放中');
          _voicePlayState = VoicePlayState.playing;
        }
        if (isClosed) {
          return;
        }
        add(VoicePlayStateChangeEvent());
      });

      methodChannel =
          const MethodChannel('wow_english/sing_sound_method_channel');
      methodChannel.setMethodCallHandler((call) async {
        if (call.method == 'voiceResult') {
          //评测结果
          await audioPlayer.setAudioContext(AudioContext());
          await audioPlayer.setBalance(0.0);
          add(XSVoiceResultEvent(call.arguments));
          return;
        }

        if (call.method == 'voiceStart') {
          //评测开始
          if (kDebugMode) {
            print('评测开始');
          }
          return;
        }

        if (call.method == 'voiceEnd') {
          await audioPlayer.setAudioContext(AudioContext());
          await audioPlayer.setBalance(0.0);
          //评测结束
          if (kDebugMode) {
            print('评测结束');
          }
          return;
        }

        if (call.method == 'voiceFail') {
          //评测失败
          await audioPlayer.setAudioContext(AudioContext());
          await audioPlayer.setBalance(0.0);

          EasyLoading.showToast('评测失败');
          return;
        }
      });
    });
  }

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

  ///请求数据
  void _requestData(
      RequestDataEvent event, Emitter<TopicPictureState> emitter) async {
    try {
      _entity = await ListenDao.process(courseLessonId);
      emitter(RequestDataState());
    } catch (e) {
      if (e is ApiException) {
        showToast(e.message ?? '请求失败,请检查网络连接');
      }
    }
  }

  ///页面切换
  void _pageControllerChange(CurrentPageIndexChangeEvent event,
      Emitter<TopicPictureState> emitter) async {
    await pageResetIfNeed();
    debugPrint('翻页 $_currentPage->${event.pageIndex}');
    if (_currentPage == _entity?.topics?.length) {
      return;
    }
    _currentPage = event.pageIndex;
    final topics = _entity?.topics?[_currentPage];
    if (topics?.type != 3 && topics?.type != 4) {
      if (topics?.audioUrl != null) {
        final urlStr = topics?.audioUrl ?? '';
        if (urlStr.isNotEmpty) {
          debugPrint(urlStr);
          await audioPlayer.play(UrlSource(urlStr));
        }
      }
    }
    emitter(CurrentPageIndexState());
  }

  ///选择
  void _selectItemLoad(
      SelectItemEvent event, Emitter<TopicPictureState> emitter) async {
    _optionSelectItem = event.selectIndex;
    emitter(SelectItemChangeState());
    if (checkAnswerRight(_optionSelectItem) == true) {
      /// 如果选择题答(选)对后题目没播完,则暂停播放题目。答错的话继续播放体验也不错
      await closePlayerResource();
      showStarRewardDialog(context);
      await _playResultSound(true);
    } else {
      await _playResultSound(false);
    }
  }

  ///为空则数据异常,用于是否晃动时需要
  bool? checkAnswerRight(int selectIndex) {
    CourseProcessTopics? topics = _entity?.topics?[_currentPage];
    if (topics == null ||
        topics.topicAnswerList == null ||
        selectIndex < 0 ||
        selectIndex >= topics.topicAnswerList!.length) {
      return null;
    }
    CourseProcessTopicsTopicAnswerList? answerList =
        topics.topicAnswerList?[selectIndex];
    return answerList?.correct != 0;
  }

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

  ///先声测试
  void _voiceXsStart(
      XSVoiceStartEvent event, Emitter<TopicPictureState> emitter) async {
    await audioPlayer.stop();
    // 调用封装好的权限检查和请求方法
    bool result = await requestPermission(
        context, Permission.microphone, "录音", "用于开启录音,识别您的开口作答并给出反馈");
    if (result) {
      methodChannel.invokeMethod('startVoice', {
        'word': event.testWord,
        'type': event.type,
        'userId': event.userId.toString()
      });
      _isRecording = true;
      emitter(XSVoiceTestState());
    }
  }

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

  ///取消评测(用于处理退出页面后录音未停止等异常情况的保护操作)
  Future<void> _voiceXsCancel({bool force = false}) async {
    if (_isRecording || force) {
      methodChannel.invokeMethod('cancelVoice');
    }
  }

  ///先声评测结果
  void _voiceXsResult(
      XSVoiceResultEvent event, Emitter<TopicPictureState> emitter) async {
    _isRecording = false;
    emitter(XSVoiceTestState());
    final Map args = event.message as Map;
    final result = args['result'] as Map;
    final overall = result['overall'].toString();
    int score = int.parse(overall);
    final voiceResult = VoiceResultType.fromScore(score);
    if (voiceResult.lottieFilePath != null) {
      showCheerRewardDialog(context, lottieFile: voiceResult.lottieFilePath!);
    }
    await ClickWithMusicController.instance.playMusicAndPerformAction(
        context,
        voiceResult.audioType,
        () {
              if (isLastPage()) {showStepPage();};
            });
  }

  // 暂时没用上
  void _voicePlayStateChange(VoicePlayStateChangeEvent event,
      Emitter<TopicPictureState> emitter) async {
    emitter(VoicePlayStateChange());
  }

  // 题目音频播放
  void _questionVoicePlay(
      VoicePlayEvent event, Emitter<TopicPictureState> emitter) async {
    await pageResetIfNeed();
    final topics = _entity?.topics?[_currentPage];
    final urlStr = topics?.audioUrl ?? '';
    await audioPlayer.play(UrlSource(urlStr),
        balance: 0.0, ctx: AudioContext());
  }

  /// 重置状态,音频播放、录音以及一些变量等。用于翻页,打断等场景
  Future<void> pageResetIfNeed() async {
    _optionSelectItem = -1;
    _isRecording = false;
    _voicePlayState = VoicePlayState.stop;

    await closePlayerResource();
    await _voiceXsCancel();
  }

  Future<void> closePlayerResource() async {
    if (voicePlayState == VoicePlayState.playing) {
      await audioPlayer.stop();
    }
    await ClickWithMusicController.instance.reset();
  }

  ///播放选择结果音效
  Future<void> _playResultSound(bool isCorrect) async {
    await ClickWithMusicController.instance.playMusicAndPerformAction(context,
        isCorrect ? AudioPlayerUtilType.right : AudioPlayerUtilType.wrong, () {
      if (isCorrect) {
        if (isLastPage()) {
          showStepPage();
        } else {
          // 答对后且播放完自动翻页
          pageController.nextPage(
            duration: const Duration(milliseconds: 250),
            curve: Curves.ease,
          );
        }
      }
    });
  }

  ///是否是最后一页
  bool isLastPage() {
    return currentPage == _entity?.topics?.length;
  }

  ///展示过渡页
  void showStepPage() {
    ///如果最后一页是语音问答题,评测完后自动翻页
    sectionComplete(() {
      popPage(data: {
        'currentStep': currentPage,
        'courseLessonId': courseLessonId,
        'isCompleted': true,
        'nextSection': true
      });
    }, againSectionTap: () {
      pageController.jumpToPage(0);
    });
  }
}