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 | \ No newline at end of file | 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,7 +13,6 @@ import 'package:wow_english/utils/toast_util.dart'; | ||
13 | import '../../../models/course_section_entity.dart'; | 13 | import '../../../models/course_section_entity.dart'; |
14 | import '../../../models/course_unit_entity.dart'; | 14 | import '../../../models/course_unit_entity.dart'; |
15 | import '../../../utils/list_ext.dart'; | 15 | import '../../../utils/list_ext.dart'; |
16 | -import '../../../utils/log_util.dart'; | ||
17 | 16 | ||
18 | part 'section_event.dart'; | 17 | part 'section_event.dart'; |
19 | 18 | ||
@@ -50,15 +49,12 @@ class SectionBloc extends Bloc<SectionEvent, SectionState> { | @@ -50,15 +49,12 @@ class SectionBloc extends Bloc<SectionEvent, SectionState> { | ||
50 | Map<int, List<CourseSectionEntity>?> get courseSectionDatasMap => | 49 | Map<int, List<CourseSectionEntity>?> get courseSectionDatasMap => |
51 | _courseSectionDatasMap; | 50 | _courseSectionDatasMap; |
52 | 51 | ||
53 | - ///点击环节后先请求数据再进入,标志位避免频繁点击多次请求(以及多次进入) | ||
54 | - bool _isRequesting = false; | ||
55 | - | ||
56 | SectionBloc(this._courseUnitEntity, this._currentPage, this._pageController, | 52 | SectionBloc(this._courseUnitEntity, this._currentPage, this._pageController, |
57 | this._listController, this._indicatorSrollController) | 53 | this._listController, this._indicatorSrollController) |
58 | : super(LessonInitial()) { | 54 | : super(LessonInitial()) { |
59 | on<RequestDataEvent>(_requestSectionsData); | 55 | on<RequestDataEvent>(_requestSectionsData); |
60 | on<RequestEndClassEvent>(_requestEndClass); | 56 | on<RequestEndClassEvent>(_requestEndClass); |
61 | - on<RequestEnterClassEvent>(_requestEnterClass); | 57 | + on<RequestEnterClassEvent>(_onEnterClass); |
62 | on<CurrentUnitIndexChangeEvent>(_pageControllerChange); | 58 | on<CurrentUnitIndexChangeEvent>(_pageControllerChange); |
63 | on<InitEvent>((event, emit) async { | 59 | on<InitEvent>((event, emit) async { |
64 | await AudioPlayerUtil.getInstance().playAudio(AudioPlayerUtilType.countWithMe); | 60 | await AudioPlayerUtil.getInstance().playAudio(AudioPlayerUtilType.countWithMe); |
@@ -84,25 +80,30 @@ class SectionBloc extends Bloc<SectionEvent, SectionState> { | @@ -84,25 +80,30 @@ class SectionBloc extends Bloc<SectionEvent, SectionState> { | ||
84 | } | 80 | } |
85 | } | 81 | } |
86 | 82 | ||
87 | - void _requestEnterClass( | 83 | + void _onEnterClass( |
88 | RequestEnterClassEvent event, Emitter<SectionState> emitter) async { | 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 | void _requestEndClass( | 109 | void _requestEndClass( |
lib/pages/section/section_page.dart
1 | -import 'package:audioplayers/audioplayers.dart'; | ||
2 | import 'package:flutter/cupertino.dart'; | 1 | import 'package:flutter/cupertino.dart'; |
3 | import 'package:flutter/material.dart'; | 2 | import 'package:flutter/material.dart'; |
4 | import 'package:flutter_bloc/flutter_bloc.dart'; | 3 | import 'package:flutter_bloc/flutter_bloc.dart'; |
5 | import 'package:flutter_screenutil/flutter_screenutil.dart'; | 4 | import 'package:flutter_screenutil/flutter_screenutil.dart'; |
6 | import 'package:nested_scroll_views/material.dart'; | 5 | import 'package:nested_scroll_views/material.dart'; |
7 | import 'package:wow_english/common/core/user_util.dart'; | 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 | import 'package:wow_english/models/course_unit_entity.dart'; | 8 | import 'package:wow_english/models/course_unit_entity.dart'; |
10 | import 'package:wow_english/pages/section/section_type.dart'; | 9 | import 'package:wow_english/pages/section/section_type.dart'; |
11 | import 'package:wow_english/pages/section/widgets/section_item.dart'; | 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,7 +12,6 @@ import 'package:wow_english/pages/section/widgets/section_bouns_item.dart'; | ||
13 | import 'package:wow_english/pages/section/widgets/section_header_widget.dart'; | 12 | import 'package:wow_english/pages/section/widgets/section_header_widget.dart'; |
14 | import 'package:wow_english/route/route.dart'; | 13 | import 'package:wow_english/route/route.dart'; |
15 | import 'package:wow_english/utils/audio_player_util.dart'; | 14 | import 'package:wow_english/utils/audio_player_util.dart'; |
16 | -import 'package:wow_english/utils/log_util.dart'; | ||
17 | import 'package:wow_english/utils/toast_util.dart'; | 15 | import 'package:wow_english/utils/toast_util.dart'; |
18 | 16 | ||
19 | import '../../models/course_section_entity.dart'; | 17 | import '../../models/course_section_entity.dart'; |
@@ -58,6 +56,7 @@ class _SectionPageView extends StatelessWidget { | @@ -58,6 +56,7 @@ class _SectionPageView extends StatelessWidget { | ||
58 | @override | 56 | @override |
59 | Widget build(BuildContext context) { | 57 | Widget build(BuildContext context) { |
60 | final bloc = BlocProvider.of<SectionBloc>(context); | 58 | final bloc = BlocProvider.of<SectionBloc>(context); |
59 | + final controller = ActionWithMusicController(); | ||
61 | return BlocListener<SectionBloc, SectionState>( | 60 | return BlocListener<SectionBloc, SectionState>( |
62 | listener: (context, state) async { | 61 | listener: (context, state) async { |
63 | if (state is RequestEnterClassState) { | 62 | if (state is RequestEnterClassState) { |
@@ -66,72 +65,79 @@ class _SectionPageView extends StatelessWidget { | @@ -66,72 +65,79 @@ class _SectionPageView extends StatelessWidget { | ||
66 | var title = | 65 | var title = |
67 | state.courseType == SectionType.song.value ? 'song' : 'video'; | 66 | state.courseType == SectionType.song.value ? 'song' : 'video'; |
68 | 67 | ||
68 | + AudioPlayerUtilType audioType; | ||
69 | ///儿歌/视频类型 | 69 | ///儿歌/视频类型 |
70 | if (state.courseType == SectionType.song.value) { | 70 | if (state.courseType == SectionType.song.value) { |
71 | - await AudioPlayerUtil.getInstance() | ||
72 | - .playAudio(AudioPlayerUtilType.musicTime); | 71 | + audioType = AudioPlayerUtilType.musicTime; |
73 | } else { | 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 | return; | 98 | return; |
94 | } | 99 | } |
95 | 100 | ||
96 | if (state.courseType == SectionType.pictureBook.value) { | 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 | return; | 121 | return; |
117 | } | 122 | } |
118 | 123 | ||
119 | if (state.courseType == SectionType.practice.value) { | 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 | return; | 142 | return; |
137 | } | 143 | } |