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 | 44 | } |
| 45 | 45 | |
| 46 | 46 | void _gotoGamePage(GotoGamePageEvent event, Emitter<GamesState> emit) async { |
| 47 | - AudioPlayerUtil.getInstance().pause(); | |
| 47 | + await AudioPlayerUtil.getInstance().pause(); | |
| 48 | 48 | try { |
| 49 | 49 | _methodChannel = const MethodChannel('wow_english/game_method_channel'); |
| 50 | 50 | await _methodChannel | ... | ... |
lib/pages/section/section_page.dart
| ... | ... | @@ -59,7 +59,7 @@ class _SectionPageView extends StatelessWidget { |
| 59 | 59 | Widget build(BuildContext context) { |
| 60 | 60 | final bloc = BlocProvider.of<SectionBloc>(context); |
| 61 | 61 | return BlocListener<SectionBloc, SectionState>( |
| 62 | - listener: (context, state) { | |
| 62 | + listener: (context, state) async { | |
| 63 | 63 | if (state is RequestVideoLessonState) { |
| 64 | 64 | final videoUrl = bloc.processEntity?.videos?.videoUrl ?? ''; |
| 65 | 65 | var title = ''; |
| ... | ... | @@ -103,40 +103,36 @@ class _SectionPageView extends StatelessWidget { |
| 103 | 103 | ///视频类型 |
| 104 | 104 | ///获取视频课程内容 |
| 105 | 105 | if (state.courseType == 1) { |
| 106 | - AudioPlayerUtil.getInstance() | |
| 106 | + await AudioPlayerUtil.getInstance() | |
| 107 | 107 | .playAudio(AudioPlayerUtilType.musicTime); |
| 108 | 108 | } else { |
| 109 | - AudioPlayerUtil.getInstance() | |
| 109 | + await AudioPlayerUtil.getInstance() | |
| 110 | 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 | 115 | return; |
| 118 | 116 | } |
| 119 | 117 | |
| 120 | 118 | if (state.courseType == SectionType.pictureBook.value) { |
| 121 | - AudioPlayerUtil.getInstance() | |
| 119 | + await AudioPlayerUtil.getInstance() | |
| 122 | 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 | 138 | return; |
| ... | ... | @@ -144,22 +140,20 @@ class _SectionPageView extends StatelessWidget { |
| 144 | 140 | |
| 145 | 141 | if (state.courseType == SectionType.practice.value) { |
| 146 | 142 | //练习 |
| 147 | - AudioPlayerUtil.getInstance() | |
| 143 | + await AudioPlayerUtil.getInstance() | |
| 148 | 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 | 158 | return; |
| 165 | 159 | } | ... | ... |
lib/utils/audio_player_util.dart
| 1 | 1 | import 'package:audioplayers/audioplayers.dart'; |
| 2 | +import 'package:flutter/cupertino.dart'; | |
| 2 | 3 | import 'package:wow_english/common/extension/string_extension.dart'; |
| 3 | 4 | |
| 4 | 5 | enum AudioPlayerUtilType { |
| ... | ... | @@ -18,15 +19,18 @@ enum AudioPlayerUtilType { |
| 18 | 19 | final String path; |
| 19 | 20 | } |
| 20 | 21 | |
| 21 | -class AudioPlayerUtil { | |
| 22 | +class AudioPlayerUtil extends WidgetsBindingObserver { | |
| 22 | 23 | static AudioPlayerUtil? _instance; |
| 23 | - late AudioPlayer audioPlayer; | |
| 24 | + late AudioPlayer _audioPlayer; | |
| 24 | 25 | late AudioPlayerUtilType currentType; |
| 26 | + bool _wasPlaying = false; | |
| 25 | 27 | |
| 26 | 28 | // 私有构造函数 |
| 27 | 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 | 34 | if (event == PlayerState.completed) { |
| 31 | 35 | // 播放结束再次播放 |
| 32 | 36 | if (currentType == AudioPlayerUtilType.inMyTummy) { |
| ... | ... | @@ -56,26 +60,46 @@ class AudioPlayerUtil { |
| 56 | 60 | Future<void> playAudio(AudioPlayerUtilType type) async { |
| 57 | 61 | currentType = type; |
| 58 | 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 | 67 | // stop |
| 64 | - void stop() { | |
| 65 | - audioPlayer.stop(); | |
| 68 | + Future<void> stop() async { | |
| 69 | + _audioPlayer.stop(); | |
| 66 | 70 | } |
| 67 | 71 | |
| 68 | 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 | 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 | } | ... | ... |