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 | } | ... | ... |