Commit cd32eb01c2cc308d80db020aa05f8860259410cb
1 parent
6baa39bd
feat:播放音乐防抖处理(课程环节页)
Showing
3 changed files
with
118 additions
and
75 deletions
lib/common/utils/action_with_music_controller.dart
0 → 100644
| 1 | +import 'package:flutter/material.dart'; | |
| 2 | + | |
| 3 | +import '../../utils/audio_player_util.dart'; | |
| 4 | + | |
| 5 | +/// action前播放音乐控制类,维护状态做防抖处理 | |
| 6 | +/// todo 需要结合生命周期,尤其是在声明周期结束后及时中断,避免action泄漏 | |
| 7 | +class ActionWithMusicController { | |
| 8 | + ActionWithMusicController._privateConstructor(); | |
| 9 | + | |
| 10 | + static final ActionWithMusicController _instance = ActionWithMusicController._privateConstructor(); | |
| 11 | + | |
| 12 | + factory ActionWithMusicController() { | |
| 13 | + return _instance; | |
| 14 | + } | |
| 15 | + | |
| 16 | + bool _isPlaying = false; | |
| 17 | + | |
| 18 | + Future<void> playMusicAndPerformAction(BuildContext context, | |
| 19 | + AudioPlayerUtilType audioType, Function action) async { | |
| 20 | + if (_isPlaying) return; | |
| 21 | + | |
| 22 | + _isPlaying = true; | |
| 23 | + | |
| 24 | + // Play the music | |
| 25 | + await AudioPlayerUtil.getInstance() | |
| 26 | + .playAudio(audioType); | |
| 27 | + | |
| 28 | + action(); | |
| 29 | + | |
| 30 | + _isPlaying = false; | |
| 31 | + } | |
| 32 | + | |
| 33 | + // void dispose() { | |
| 34 | + // _audioPlayer.dispose(); | |
| 35 | + // } | |
| 36 | +} | |
| 0 | 37 | \ No newline at end of file | ... | ... |
lib/pages/section/bloc/section_bloc.dart
| ... | ... | @@ -13,7 +13,6 @@ import 'package:wow_english/utils/toast_util.dart'; |
| 13 | 13 | import '../../../models/course_section_entity.dart'; |
| 14 | 14 | import '../../../models/course_unit_entity.dart'; |
| 15 | 15 | import '../../../utils/list_ext.dart'; |
| 16 | -import '../../../utils/log_util.dart'; | |
| 17 | 16 | |
| 18 | 17 | part 'section_event.dart'; |
| 19 | 18 | |
| ... | ... | @@ -50,15 +49,12 @@ class SectionBloc extends Bloc<SectionEvent, SectionState> { |
| 50 | 49 | Map<int, List<CourseSectionEntity>?> get courseSectionDatasMap => |
| 51 | 50 | _courseSectionDatasMap; |
| 52 | 51 | |
| 53 | - ///点击环节后先请求数据再进入,标志位避免频繁点击多次请求(以及多次进入) | |
| 54 | - bool _isRequesting = false; | |
| 55 | - | |
| 56 | 52 | SectionBloc(this._courseUnitEntity, this._currentPage, this._pageController, |
| 57 | 53 | this._listController, this._indicatorSrollController) |
| 58 | 54 | : super(LessonInitial()) { |
| 59 | 55 | on<RequestDataEvent>(_requestSectionsData); |
| 60 | 56 | on<RequestEndClassEvent>(_requestEndClass); |
| 61 | - on<RequestEnterClassEvent>(_requestEnterClass); | |
| 57 | + on<RequestEnterClassEvent>(_onEnterClass); | |
| 62 | 58 | on<CurrentUnitIndexChangeEvent>(_pageControllerChange); |
| 63 | 59 | on<InitEvent>((event, emit) async { |
| 64 | 60 | await AudioPlayerUtil.getInstance().playAudio(AudioPlayerUtilType.countWithMe); |
| ... | ... | @@ -84,25 +80,30 @@ class SectionBloc extends Bloc<SectionEvent, SectionState> { |
| 84 | 80 | } |
| 85 | 81 | } |
| 86 | 82 | |
| 87 | - void _requestEnterClass( | |
| 83 | + void _onEnterClass( | |
| 88 | 84 | RequestEnterClassEvent event, Emitter<SectionState> emitter) async { |
| 89 | - try { | |
| 90 | - Log.d("WQF _requestVideoLesson _isRequesting=$_isRequesting"); | |
| 91 | - if (_isRequesting) { | |
| 92 | - return; | |
| 93 | - } | |
| 94 | - _isRequesting = true; | |
| 95 | - await loading(() async { | |
| 96 | - await ListenDao.enterClass(event.courseLessonId); | |
| 97 | - emitter(RequestEnterClassState(event.courseLessonId, event.courseType)); | |
| 98 | - }); | |
| 99 | - } catch (e) { | |
| 100 | - if (e is ApiException) { | |
| 101 | - showToast(e.message ?? '请求失败,请检查网络连接'); | |
| 85 | + emitter(RequestEnterClassState(event.courseLessonId, event.courseType)); | |
| 86 | + } | |
| 87 | + | |
| 88 | + ///进入课程接口请求函数封装 | |
| 89 | + ///onRequestEnterSuccess 接口请求成功后行为函数,比如跳转课程页面 | |
| 90 | + Future<void> requestEnterClass(String courseLessonId, Function onRequestEnterSuccess, | |
| 91 | + {Function? onRequestEnterFailed}) async { | |
| 92 | + await loading(() async { | |
| 93 | + try { | |
| 94 | + await ListenDao.enterClass(courseLessonId); | |
| 95 | + onRequestEnterSuccess(); | |
| 96 | + } catch (e) { | |
| 97 | + if (e is ApiException) { | |
| 98 | + showToast(e.message ?? '请求失败,请检查网络连接'); | |
| 99 | + } else { | |
| 100 | + showToast('$e'); | |
| 101 | + } | |
| 102 | + if (onRequestEnterFailed != null) { | |
| 103 | + onRequestEnterFailed(e); | |
| 104 | + } | |
| 102 | 105 | } |
| 103 | - } finally { | |
| 104 | - _isRequesting = false; | |
| 105 | - } | |
| 106 | + }); | |
| 106 | 107 | } |
| 107 | 108 | |
| 108 | 109 | void _requestEndClass( | ... | ... |
lib/pages/section/section_page.dart
| 1 | -import 'package:audioplayers/audioplayers.dart'; | |
| 2 | 1 | import 'package:flutter/cupertino.dart'; |
| 3 | 2 | import 'package:flutter/material.dart'; |
| 4 | 3 | import 'package:flutter_bloc/flutter_bloc.dart'; |
| 5 | 4 | import 'package:flutter_screenutil/flutter_screenutil.dart'; |
| 6 | 5 | import 'package:nested_scroll_views/material.dart'; |
| 7 | 6 | import 'package:wow_english/common/core/user_util.dart'; |
| 8 | -import 'package:wow_english/common/extension/string_extension.dart'; | |
| 7 | +import 'package:wow_english/common/utils/action_with_music_controller.dart'; | |
| 9 | 8 | import 'package:wow_english/models/course_unit_entity.dart'; |
| 10 | 9 | import 'package:wow_english/pages/section/section_type.dart'; |
| 11 | 10 | import 'package:wow_english/pages/section/widgets/section_item.dart'; |
| ... | ... | @@ -13,7 +12,6 @@ import 'package:wow_english/pages/section/widgets/section_bouns_item.dart'; |
| 13 | 12 | import 'package:wow_english/pages/section/widgets/section_header_widget.dart'; |
| 14 | 13 | import 'package:wow_english/route/route.dart'; |
| 15 | 14 | import 'package:wow_english/utils/audio_player_util.dart'; |
| 16 | -import 'package:wow_english/utils/log_util.dart'; | |
| 17 | 15 | import 'package:wow_english/utils/toast_util.dart'; |
| 18 | 16 | |
| 19 | 17 | import '../../models/course_section_entity.dart'; |
| ... | ... | @@ -58,6 +56,7 @@ class _SectionPageView extends StatelessWidget { |
| 58 | 56 | @override |
| 59 | 57 | Widget build(BuildContext context) { |
| 60 | 58 | final bloc = BlocProvider.of<SectionBloc>(context); |
| 59 | + final controller = ActionWithMusicController(); | |
| 61 | 60 | return BlocListener<SectionBloc, SectionState>( |
| 62 | 61 | listener: (context, state) async { |
| 63 | 62 | if (state is RequestEnterClassState) { |
| ... | ... | @@ -66,72 +65,79 @@ class _SectionPageView extends StatelessWidget { |
| 66 | 65 | var title = |
| 67 | 66 | state.courseType == SectionType.song.value ? 'song' : 'video'; |
| 68 | 67 | |
| 68 | + AudioPlayerUtilType audioType; | |
| 69 | 69 | ///儿歌/视频类型 |
| 70 | 70 | if (state.courseType == SectionType.song.value) { |
| 71 | - await AudioPlayerUtil.getInstance() | |
| 72 | - .playAudio(AudioPlayerUtilType.musicTime); | |
| 71 | + audioType = AudioPlayerUtilType.musicTime; | |
| 73 | 72 | } else { |
| 74 | - await AudioPlayerUtil.getInstance() | |
| 75 | - .playAudio(AudioPlayerUtilType.videoTime); | |
| 73 | + audioType = AudioPlayerUtilType.videoTime; | |
| 76 | 74 | } |
| 77 | - pushNamed(AppRouteName.lookVideo, arguments: { | |
| 78 | - 'videoUrl': null, | |
| 79 | - 'title': title, | |
| 80 | - 'courseLessonId': state.courseLessonId, | |
| 81 | - 'isTopic': true | |
| 82 | - }).then((value) { | |
| 83 | - if (value != null) { | |
| 84 | - Map<String, dynamic> dataMap = value as Map<String, dynamic>; | |
| 85 | - bloc.add(RequestEndClassEvent( | |
| 86 | - dataMap['courseLessonId']!, dataMap['isCompleted'], | |
| 87 | - currentTime: dataMap['currentTime'], | |
| 88 | - autoNextSection: dataMap['nextSection'])); | |
| 89 | - } | |
| 90 | - AudioPlayerUtil.getInstance() | |
| 91 | - .playAudio(AudioPlayerUtilType.countWithMe); | |
| 75 | + | |
| 76 | + controller.playMusicAndPerformAction(context, audioType, () async { | |
| 77 | + ///播放音乐->调进入课程接口->跳转课程页面 | |
| 78 | + bloc.requestEnterClass(state.courseLessonId, () { | |
| 79 | + pushNamed(AppRouteName.lookVideo, arguments: { | |
| 80 | + 'videoUrl': null, | |
| 81 | + 'title': title, | |
| 82 | + 'courseLessonId': state.courseLessonId, | |
| 83 | + 'isTopic': true | |
| 84 | + }).then((value) { | |
| 85 | + if (value != null) { | |
| 86 | + Map<String, dynamic> dataMap = | |
| 87 | + value as Map<String, dynamic>; | |
| 88 | + bloc.add(RequestEndClassEvent( | |
| 89 | + dataMap['courseLessonId']!, dataMap['isCompleted'], | |
| 90 | + currentTime: dataMap['currentTime'], | |
| 91 | + autoNextSection: dataMap['nextSection'])); | |
| 92 | + } | |
| 93 | + AudioPlayerUtil.getInstance() | |
| 94 | + .playAudio(AudioPlayerUtilType.countWithMe); | |
| 95 | + }); | |
| 96 | + }); | |
| 92 | 97 | }); |
| 93 | 98 | return; |
| 94 | 99 | } |
| 95 | 100 | |
| 96 | 101 | if (state.courseType == SectionType.pictureBook.value) { |
| 97 | - await AudioPlayerUtil.getInstance() | |
| 98 | - .playAudio(AudioPlayerUtilType.readingTime); | |
| 99 | 102 | //绘本 |
| 100 | - pushNamed(AppRouteName.reading, | |
| 101 | - arguments: {'courseLessonId': state.courseLessonId}) | |
| 102 | - .then((value) { | |
| 103 | - if (value != null) { | |
| 104 | - Map<String, dynamic> dataMap = value as Map<String, dynamic>; | |
| 105 | - bloc.add(RequestEndClassEvent( | |
| 106 | - dataMap['courseLessonId']!, | |
| 107 | - dataMap['isCompleted'], | |
| 108 | - currentStep: dataMap['currentStep'], | |
| 109 | - autoNextSection: dataMap['nextSection'], | |
| 110 | - )); | |
| 111 | - AudioPlayerUtil.getInstance() | |
| 112 | - .playAudio(AudioPlayerUtilType.countWithMe); | |
| 113 | - } | |
| 103 | + controller.playMusicAndPerformAction( | |
| 104 | + context, AudioPlayerUtilType.readingTime, () async { | |
| 105 | + pushNamed(AppRouteName.reading, | |
| 106 | + arguments: {'courseLessonId': state.courseLessonId}) | |
| 107 | + .then((value) { | |
| 108 | + if (value != null) { | |
| 109 | + Map<String, dynamic> dataMap = value as Map<String, dynamic>; | |
| 110 | + bloc.add(RequestEndClassEvent( | |
| 111 | + dataMap['courseLessonId']!, | |
| 112 | + dataMap['isCompleted'], | |
| 113 | + currentStep: dataMap['currentStep'], | |
| 114 | + autoNextSection: dataMap['nextSection'], | |
| 115 | + )); | |
| 116 | + AudioPlayerUtil.getInstance() | |
| 117 | + .playAudio(AudioPlayerUtilType.countWithMe); | |
| 118 | + } | |
| 119 | + }); | |
| 114 | 120 | }); |
| 115 | - | |
| 116 | 121 | return; |
| 117 | 122 | } |
| 118 | 123 | |
| 119 | 124 | if (state.courseType == SectionType.practice.value) { |
| 120 | 125 | //练习 |
| 121 | - await AudioPlayerUtil.getInstance() | |
| 122 | - .playAudio(AudioPlayerUtilType.quizTime); | |
| 123 | - pushNamed(AppRouteName.topicPic, | |
| 124 | - arguments: {'courseLessonId': state.courseLessonId}) | |
| 125 | - .then((value) { | |
| 126 | - if (value != null) { | |
| 127 | - Map<String, dynamic> dataMap = value as Map<String, dynamic>; | |
| 128 | - bloc.add(RequestEndClassEvent( | |
| 129 | - dataMap['courseLessonId']!, dataMap['isCompleted'], | |
| 130 | - currentStep: dataMap['currentStep'], | |
| 131 | - autoNextSection: dataMap['nextSection'])); | |
| 132 | - } | |
| 133 | - AudioPlayerUtil.getInstance() | |
| 134 | - .playAudio(AudioPlayerUtilType.countWithMe); | |
| 126 | + controller.playMusicAndPerformAction( | |
| 127 | + context, AudioPlayerUtilType.quizTime, () async { | |
| 128 | + pushNamed(AppRouteName.topicPic, | |
| 129 | + arguments: {'courseLessonId': state.courseLessonId}) | |
| 130 | + .then((value) { | |
| 131 | + if (value != null) { | |
| 132 | + Map<String, dynamic> dataMap = value as Map<String, dynamic>; | |
| 133 | + bloc.add(RequestEndClassEvent( | |
| 134 | + dataMap['courseLessonId']!, dataMap['isCompleted'], | |
| 135 | + currentStep: dataMap['currentStep'], | |
| 136 | + autoNextSection: dataMap['nextSection'])); | |
| 137 | + } | |
| 138 | + AudioPlayerUtil.getInstance() | |
| 139 | + .playAudio(AudioPlayerUtilType.countWithMe); | |
| 140 | + }); | |
| 135 | 141 | }); |
| 136 | 142 | return; |
| 137 | 143 | } | ... | ... |