Commit 523864dd18878775b430d258c0b8a47db684dc46
1 parent
b2af9c1c
feat:音频播放单例增加声明周期感知
Showing
3 changed files
with
73 additions
and
55 deletions
lib/pages/games/bloc.dart
@@ -44,7 +44,7 @@ class GamesBloc extends Bloc<GamesEvent, GamesState> { | @@ -44,7 +44,7 @@ class GamesBloc extends Bloc<GamesEvent, GamesState> { | ||
44 | } | 44 | } |
45 | 45 | ||
46 | void _gotoGamePage(GotoGamePageEvent event, Emitter<GamesState> emit) async { | 46 | void _gotoGamePage(GotoGamePageEvent event, Emitter<GamesState> emit) async { |
47 | - AudioPlayerUtil.getInstance().pause(); | 47 | + await AudioPlayerUtil.getInstance().pause(); |
48 | try { | 48 | try { |
49 | _methodChannel = const MethodChannel('wow_english/game_method_channel'); | 49 | _methodChannel = const MethodChannel('wow_english/game_method_channel'); |
50 | await _methodChannel | 50 | await _methodChannel |
lib/pages/section/section_page.dart
@@ -59,7 +59,7 @@ class _SectionPageView extends StatelessWidget { | @@ -59,7 +59,7 @@ class _SectionPageView extends StatelessWidget { | ||
59 | Widget build(BuildContext context) { | 59 | Widget build(BuildContext context) { |
60 | final bloc = BlocProvider.of<SectionBloc>(context); | 60 | final bloc = BlocProvider.of<SectionBloc>(context); |
61 | return BlocListener<SectionBloc, SectionState>( | 61 | return BlocListener<SectionBloc, SectionState>( |
62 | - listener: (context, state) { | 62 | + listener: (context, state) async { |
63 | if (state is RequestVideoLessonState) { | 63 | if (state is RequestVideoLessonState) { |
64 | final videoUrl = bloc.processEntity?.videos?.videoUrl ?? ''; | 64 | final videoUrl = bloc.processEntity?.videos?.videoUrl ?? ''; |
65 | var title = ''; | 65 | var title = ''; |
@@ -103,40 +103,36 @@ class _SectionPageView extends StatelessWidget { | @@ -103,40 +103,36 @@ class _SectionPageView extends StatelessWidget { | ||
103 | ///视频类型 | 103 | ///视频类型 |
104 | ///获取视频课程内容 | 104 | ///获取视频课程内容 |
105 | if (state.courseType == 1) { | 105 | if (state.courseType == 1) { |
106 | - AudioPlayerUtil.getInstance() | 106 | + await AudioPlayerUtil.getInstance() |
107 | .playAudio(AudioPlayerUtilType.musicTime); | 107 | .playAudio(AudioPlayerUtilType.musicTime); |
108 | } else { | 108 | } else { |
109 | - AudioPlayerUtil.getInstance() | 109 | + await AudioPlayerUtil.getInstance() |
110 | .playAudio(AudioPlayerUtilType.videoTime); | 110 | .playAudio(AudioPlayerUtilType.videoTime); |
111 | } | 111 | } |
112 | - Future.delayed(const Duration(seconds: 1), () { | ||
113 | - bloc.add(RequestVideoLessonEvent( | ||
114 | - state.courseLessonId, state.courseType)); | ||
115 | - }); | 112 | + bloc.add(RequestVideoLessonEvent( |
113 | + state.courseLessonId, state.courseType)); | ||
116 | 114 | ||
117 | return; | 115 | return; |
118 | } | 116 | } |
119 | 117 | ||
120 | if (state.courseType == SectionType.pictureBook.value) { | 118 | if (state.courseType == SectionType.pictureBook.value) { |
121 | - AudioPlayerUtil.getInstance() | 119 | + await AudioPlayerUtil.getInstance() |
122 | .playAudio(AudioPlayerUtilType.readingTime); | 120 | .playAudio(AudioPlayerUtilType.readingTime); |
123 | - Future.delayed(const Duration(seconds: 1), () { | ||
124 | - //绘本 | ||
125 | - pushNamed(AppRouteName.reading, | ||
126 | - arguments: {'courseLessonId': state.courseLessonId}) | ||
127 | - .then((value) { | ||
128 | - if (value != null) { | ||
129 | - Map<String, dynamic> dataMap = value as Map<String, dynamic>; | ||
130 | - bloc.add(RequestEndClassEvent( | ||
131 | - dataMap['courseLessonId']!, | ||
132 | - dataMap['isCompleted'], | ||
133 | - currentStep: dataMap['currentStep'], | ||
134 | - autoNextSection: dataMap['nextSection'], | ||
135 | - )); | ||
136 | - AudioPlayerUtil.getInstance() | ||
137 | - .playAudio(AudioPlayerUtilType.countWithMe); | ||
138 | - } | ||
139 | - }); | 121 | + //绘本 |
122 | + pushNamed(AppRouteName.reading, | ||
123 | + arguments: {'courseLessonId': state.courseLessonId}) | ||
124 | + .then((value) { | ||
125 | + if (value != null) { | ||
126 | + Map<String, dynamic> dataMap = value as Map<String, dynamic>; | ||
127 | + bloc.add(RequestEndClassEvent( | ||
128 | + dataMap['courseLessonId']!, | ||
129 | + dataMap['isCompleted'], | ||
130 | + currentStep: dataMap['currentStep'], | ||
131 | + autoNextSection: dataMap['nextSection'], | ||
132 | + )); | ||
133 | + AudioPlayerUtil.getInstance() | ||
134 | + .playAudio(AudioPlayerUtilType.countWithMe); | ||
135 | + } | ||
140 | }); | 136 | }); |
141 | 137 | ||
142 | return; | 138 | return; |
@@ -144,22 +140,20 @@ class _SectionPageView extends StatelessWidget { | @@ -144,22 +140,20 @@ class _SectionPageView extends StatelessWidget { | ||
144 | 140 | ||
145 | if (state.courseType == SectionType.practice.value) { | 141 | if (state.courseType == SectionType.practice.value) { |
146 | //练习 | 142 | //练习 |
147 | - AudioPlayerUtil.getInstance() | 143 | + await AudioPlayerUtil.getInstance() |
148 | .playAudio(AudioPlayerUtilType.quizTime); | 144 | .playAudio(AudioPlayerUtilType.quizTime); |
149 | - Future.delayed(const Duration(seconds: 1), () { | ||
150 | - pushNamed(AppRouteName.topicPic, | ||
151 | - arguments: {'courseLessonId': state.courseLessonId}) | ||
152 | - .then((value) { | ||
153 | - if (value != null) { | ||
154 | - Map<String, dynamic> dataMap = value as Map<String, dynamic>; | ||
155 | - bloc.add(RequestEndClassEvent( | ||
156 | - dataMap['courseLessonId']!, dataMap['isCompleted'], | ||
157 | - currentStep: dataMap['currentStep'], | ||
158 | - autoNextSection: dataMap['nextSection'])); | ||
159 | - } | ||
160 | - AudioPlayerUtil.getInstance() | ||
161 | - .playAudio(AudioPlayerUtilType.countWithMe); | ||
162 | - }); | 145 | + pushNamed(AppRouteName.topicPic, |
146 | + arguments: {'courseLessonId': state.courseLessonId}) | ||
147 | + .then((value) { | ||
148 | + if (value != null) { | ||
149 | + Map<String, dynamic> dataMap = value as Map<String, dynamic>; | ||
150 | + bloc.add(RequestEndClassEvent( | ||
151 | + dataMap['courseLessonId']!, dataMap['isCompleted'], | ||
152 | + currentStep: dataMap['currentStep'], | ||
153 | + autoNextSection: dataMap['nextSection'])); | ||
154 | + } | ||
155 | + AudioPlayerUtil.getInstance() | ||
156 | + .playAudio(AudioPlayerUtilType.countWithMe); | ||
163 | }); | 157 | }); |
164 | return; | 158 | return; |
165 | } | 159 | } |
lib/utils/audio_player_util.dart
1 | import 'package:audioplayers/audioplayers.dart'; | 1 | import 'package:audioplayers/audioplayers.dart'; |
2 | +import 'package:flutter/cupertino.dart'; | ||
2 | import 'package:wow_english/common/extension/string_extension.dart'; | 3 | import 'package:wow_english/common/extension/string_extension.dart'; |
3 | 4 | ||
4 | enum AudioPlayerUtilType { | 5 | enum AudioPlayerUtilType { |
@@ -18,15 +19,18 @@ enum AudioPlayerUtilType { | @@ -18,15 +19,18 @@ enum AudioPlayerUtilType { | ||
18 | final String path; | 19 | final String path; |
19 | } | 20 | } |
20 | 21 | ||
21 | -class AudioPlayerUtil { | 22 | +class AudioPlayerUtil extends WidgetsBindingObserver { |
22 | static AudioPlayerUtil? _instance; | 23 | static AudioPlayerUtil? _instance; |
23 | - late AudioPlayer audioPlayer; | 24 | + late AudioPlayer _audioPlayer; |
24 | late AudioPlayerUtilType currentType; | 25 | late AudioPlayerUtilType currentType; |
26 | + bool _wasPlaying = false; | ||
25 | 27 | ||
26 | // 私有构造函数 | 28 | // 私有构造函数 |
27 | AudioPlayerUtil._internal() { | 29 | AudioPlayerUtil._internal() { |
28 | - audioPlayer = AudioPlayer(); | ||
29 | - audioPlayer.onPlayerStateChanged.listen((event) async { | 30 | + // 监听应用生命周期 |
31 | + WidgetsBinding.instance.addObserver(this); | ||
32 | + _audioPlayer = AudioPlayer(); | ||
33 | + _audioPlayer.onPlayerStateChanged.listen((event) async { | ||
30 | if (event == PlayerState.completed) { | 34 | if (event == PlayerState.completed) { |
31 | // 播放结束再次播放 | 35 | // 播放结束再次播放 |
32 | if (currentType == AudioPlayerUtilType.inMyTummy) { | 36 | if (currentType == AudioPlayerUtilType.inMyTummy) { |
@@ -56,26 +60,46 @@ class AudioPlayerUtil { | @@ -56,26 +60,46 @@ class AudioPlayerUtil { | ||
56 | Future<void> playAudio(AudioPlayerUtilType type) async { | 60 | Future<void> playAudio(AudioPlayerUtilType type) async { |
57 | currentType = type; | 61 | currentType = type; |
58 | String path = type.path; | 62 | String path = type.path; |
59 | - await audioPlayer.play(AssetSource(path.assetMp3), volume: 0.5); | ||
60 | - await audioPlayer.onPlayerComplete.first; | 63 | + await _audioPlayer.play(AssetSource(path.assetMp3), volume: 0.5); |
64 | + await _audioPlayer.onPlayerComplete.first; | ||
61 | } | 65 | } |
62 | 66 | ||
63 | // stop | 67 | // stop |
64 | - void stop() { | ||
65 | - audioPlayer.stop(); | 68 | + Future<void> stop() async { |
69 | + _audioPlayer.stop(); | ||
66 | } | 70 | } |
67 | 71 | ||
68 | // pause | 72 | // pause |
69 | - void pause() { | ||
70 | - if (audioPlayer.state == PlayerState.playing) { | ||
71 | - audioPlayer.pause(); | 73 | + Future<void> pause() async { |
74 | + if (_audioPlayer.state == PlayerState.playing) { | ||
75 | + _audioPlayer.pause(); | ||
72 | } | 76 | } |
73 | } | 77 | } |
74 | 78 | ||
75 | // resume | 79 | // resume |
76 | - void resume() { | ||
77 | - if (audioPlayer.state == PlayerState.paused) { | ||
78 | - audioPlayer.resume(); | 80 | + Future<void> resume() async { |
81 | + if (_audioPlayer.state == PlayerState.paused) { | ||
82 | + _audioPlayer.resume(); | ||
79 | } | 83 | } |
80 | } | 84 | } |
85 | + | ||
86 | + @override | ||
87 | + void didChangeAppLifecycleState(AppLifecycleState state) async { | ||
88 | + if (state == AppLifecycleState.paused) { | ||
89 | + if (_audioPlayer.state == PlayerState.playing) { | ||
90 | + _wasPlaying = true; | ||
91 | + await pause(); | ||
92 | + }; | ||
93 | + } else if (state == AppLifecycleState.resumed) { | ||
94 | + if (_wasPlaying == true) { | ||
95 | + _wasPlaying = false; | ||
96 | + await resume(); | ||
97 | + } | ||
98 | + } | ||
99 | + } | ||
100 | + | ||
101 | + void dispose() { | ||
102 | + _audioPlayer.dispose(); | ||
103 | + WidgetsBinding.instance.removeObserver(this); | ||
104 | + } | ||
81 | } | 105 | } |