Commit c272c662bd42c755c8ebf73b721a3a64bac78bed

Authored by 吴启风
1 parent 46385ad0

fix: 崩溃修复

lib/app/splash_page.dart
@@ -140,19 +140,22 @@ class _TransitionViewState extends State<TransitionView> { @@ -140,19 +140,22 @@ class _TransitionViewState extends State<TransitionView> {
140 actions: <Widget>[ 140 actions: <Widget>[
141 TextButton( 141 TextButton(
142 child: const Text('退出'), 142 child: const Text('退出'),
143 - onPressed: () => {  
144 - // 关闭对话框并重试  
145 - Navigator.of(context).pop(),  
146 - AppConfigHelper.exitApp(),  
147 - completer?.complete(),  
148 - }), 143 + onPressed: () {
  144 + // 关闭对话框并退出应用
  145 + Navigator.of(context).pop();
  146 + AppConfigHelper.exitApp();
  147 + // 只有在 completer 还没有完成时才完成它
  148 + if (completer != null && !completer.isCompleted) {
  149 + completer.complete();
  150 + }
  151 + }),
149 TextButton( 152 TextButton(
150 child: const Text('重试'), 153 child: const Text('重试'),
151 onPressed: () async { 154 onPressed: () async {
152 // 关闭对话框并重试 155 // 关闭对话框并重试
153 Navigator.of(context).pop(); 156 Navigator.of(context).pop();
154 await fetchNecessaryData(userToken, completer: completer); 157 await fetchNecessaryData(userToken, completer: completer);
155 - completer?.complete(); 158 + // 不需要再次调用 completer.complete(),因为 fetchNecessaryData 内部已经处理了
156 }, 159 },
157 ), 160 ),
158 ], 161 ],
lib/common/permission/permissionRequestPage.dart
@@ -30,6 +30,7 @@ class _PermissionRequestPageState extends State&lt;PermissionRequestPage&gt; @@ -30,6 +30,7 @@ class _PermissionRequestPageState extends State&lt;PermissionRequestPage&gt;
30 bool _isGoSetting = false; 30 bool _isGoSetting = false;
31 late final List<String> msgList; 31 late final List<String> msgList;
32 bool _isDialogShowing = false; 32 bool _isDialogShowing = false;
  33 + bool _isRequestingPermission = false;
33 34
34 @override 35 @override
35 void initState() { 36 void initState() {
@@ -107,6 +108,11 @@ class _PermissionRequestPageState extends State&lt;PermissionRequestPage&gt; @@ -107,6 +108,11 @@ class _PermissionRequestPageState extends State&lt;PermissionRequestPage&gt;
107 108
108 /// 校验权限 109 /// 校验权限
109 void _handlePermission(List<Permission> permissions) async { 110 void _handlePermission(List<Permission> permissions) async {
  111 + if (_isRequestingPermission) {
  112 + Log.d('_handlePermission already in progress, skipping');
  113 + return;
  114 + }
  115 +
110 ///一个新待申请权限列表 116 ///一个新待申请权限列表
111 List<Permission> intentPermissionList = []; 117 List<Permission> intentPermissionList = [];
112 118
@@ -129,37 +135,51 @@ class _PermissionRequestPageState extends State&lt;PermissionRequestPage&gt; @@ -129,37 +135,51 @@ class _PermissionRequestPageState extends State&lt;PermissionRequestPage&gt;
129 135
130 ///实际触发请求权限 136 ///实际触发请求权限
131 Future<void> _requestPermission(List<Permission> permissions) async { 137 Future<void> _requestPermission(List<Permission> permissions) async {
132 - Log.d('_requestPermission permissions=$permissions');  
133 - MapEntry<Permission, PermissionStatus>? statusEntry =  
134 - await requestPermissionsInner(permissions);  
135 - if (statusEntry == null) {  
136 - ///都手动同意授予了  
137 - _popPage(true); 138 + if (_isRequestingPermission) {
  139 + Log.d('_requestPermission already in progress, skipping');
138 return; 140 return;
139 } 141 }
  142 +
  143 + _isRequestingPermission = true;
  144 + Log.d('_requestPermission permissions=$permissions');
  145 +
  146 + try {
  147 + MapEntry<Permission, PermissionStatus>? statusEntry =
  148 + await requestPermissionsInner(permissions);
  149 + if (statusEntry == null) {
  150 + ///都手动同意授予了
  151 + _popPage(true);
  152 + return;
  153 + }
140 154
141 - Permission permission = statusEntry.key;  
142 - PermissionStatus status = statusEntry.value; 155 + Permission permission = statusEntry.key;
  156 + PermissionStatus status = statusEntry.value;
143 157
144 - // 还未申请权限或之前拒绝了权限(在 iOS 上为首次申请权限,拒绝后将变为 `永久拒绝权限`)  
145 - if (status.isDenied) {  
146 - showAlert(  
147 - permission, msgList[0], msgList[3], _isGoSetting ? "前往系统设置" : "继续");  
148 - }  
149 - // 权限已被永久拒绝  
150 - /// 在 Android 上:Android 11+ (API 30+):用户是否第二次拒绝权限。低于 Android 11 (API 30):用户是否拒绝访问请求的功能,并选择不再显示请求。  
151 - /// 在 iOS 上:如果用户拒绝访问所请求的功能。  
152 - if (status.isPermanentlyDenied) {  
153 - _isGoSetting = true;  
154 - showAlert(  
155 - permission, msgList[2], msgList[3], _isGoSetting ? "前往系统设置" : "继续");  
156 - }  
157 - // isLimited:拥有部分权限(受限,仅在 iOS (iOS14+) 上受支持)  
158 - // isRestricted:拥有部分权限,活动限制(例如,设置了家长///控件,仅在iOS以上受支持。(仅限 iOS)  
159 - if (status.isLimited || status.isRestricted) {  
160 - if (Platform.isIOS || Platform.isMacOS) _isGoSetting = true;  
161 - showAlert(  
162 - permission, msgList[1], msgList[3], _isGoSetting ? "前往系统设置" : "继续"); 158 + // 还未申请权限或之前拒绝了权限(在 iOS 上为首次申请权限,拒绝后将变为 `永久拒绝权限`)
  159 + if (status.isDenied) {
  160 + showAlert(
  161 + permission, msgList[0], msgList[3], _isGoSetting ? "前往系统设置" : "继续");
  162 + }
  163 + // 权限已被永久拒绝
  164 + /// 在 Android 上:Android 11+ (API 30+):用户是否第二次拒绝权限。低于 Android 11 (API 30):用户是否拒绝访问请求的功能,并选择不再显示请求。
  165 + /// 在 iOS 上:如果用户拒绝访问所请求的功能。
  166 + if (status.isPermanentlyDenied) {
  167 + _isGoSetting = true;
  168 + showAlert(
  169 + permission, msgList[2], msgList[3], _isGoSetting ? "前往系统设置" : "继续");
  170 + }
  171 + // isLimited:拥有部分权限(受限,仅在 iOS (iOS14+) 上受支持)
  172 + // isRestricted:拥有部分权限,活动限制(例如,设置了家长///控件,仅在iOS以上受支持。(仅限 iOS)
  173 + if (status.isLimited || status.isRestricted) {
  174 + if (Platform.isIOS || Platform.isMacOS) _isGoSetting = true;
  175 + showAlert(
  176 + permission, msgList[1], msgList[3], _isGoSetting ? "前往系统设置" : "继续");
  177 + }
  178 + } catch (e) {
  179 + Log.d('_requestPermission error: $e');
  180 + _popPage(false);
  181 + } finally {
  182 + _isRequestingPermission = false;
163 } 183 }
164 } 184 }
165 185
lib/common/permission/permissionRequester.dart
@@ -35,12 +35,13 @@ Future&lt;bool&gt; requestPermissions( @@ -35,12 +35,13 @@ Future&lt;bool&gt; requestPermissions(
35 if (allGranted) { 35 if (allGranted) {
36 return true; 36 return true;
37 } else { 37 } else {
38 - return await Navigator.of(context).push(PageRouteBuilder( 38 + final result = await Navigator.of(context).push(PageRouteBuilder(
39 opaque: false, 39 opaque: false,
40 pageBuilder: ((context, animation, secondaryAnimation) { 40 pageBuilder: ((context, animation, secondaryAnimation) {
41 return PermissionRequestPage(permissions, permissionNames, permissionDesc, 41 return PermissionRequestPage(permissions, permissionNames, permissionDesc,
42 isRequiredPermission: isRequiredPermission); 42 isRequiredPermission: isRequiredPermission);
43 }))); 43 })));
  44 + return result ?? false;
44 } 45 }
45 } 46 }
46 47
lib/common/request/api_response/api_response_entity.dart
1 import 'dart:convert'; 1 import 'dart:convert';
2 2
3 -import 'api_response_entity.g.dart'; 3 +import 'package:json_annotation/json_annotation.dart';
4 4
  5 +import 'api_response_entity.g.dart';
5 6
  7 +@JsonSerializable(genericArgumentFactories: true)
6 class ApiResponse<T> { 8 class ApiResponse<T> {
7 int? code; 9 int? code;
8 String? msg; 10 String? msg;
lib/pages/home/widgets/ShakeImage.dart
@@ -20,7 +20,7 @@ class _ShakeImageState extends State&lt;ShakeImage&gt; @@ -20,7 +20,7 @@ class _ShakeImageState extends State&lt;ShakeImage&gt;
20 with SingleTickerProviderStateMixin, WidgetsBindingObserver, RouteAware { 20 with SingleTickerProviderStateMixin, WidgetsBindingObserver, RouteAware {
21 late AnimationController _controller; 21 late AnimationController _controller;
22 late Animation<double> _animation; 22 late Animation<double> _animation;
23 - late Timer _timer; 23 + Timer? _timer;
24 late final AppLifecycleListener appLifecycleListener; 24 late final AppLifecycleListener appLifecycleListener;
25 static const TAG = 'ShakeImage'; 25 static const TAG = 'ShakeImage';
26 26
@@ -43,7 +43,11 @@ class _ShakeImageState extends State&lt;ShakeImage&gt; @@ -43,7 +43,11 @@ class _ShakeImageState extends State&lt;ShakeImage&gt;
43 ); 43 );
44 44
45 super.initState(); 45 super.initState();
46 - WidgetsBinding.instance.addObserver(this); 46 + try {
  47 + WidgetsBinding.instance.addObserver(this);
  48 + } catch (e) {
  49 + printLog('Add observer error: $e');
  50 + }
47 WidgetsBinding.instance.addPostFrameCallback((_) { 51 WidgetsBinding.instance.addPostFrameCallback((_) {
48 final route = ModalRoute.of(context); 52 final route = ModalRoute.of(context);
49 if (route is PageRoute) { 53 if (route is PageRoute) {
@@ -100,16 +104,32 @@ class _ShakeImageState extends State&lt;ShakeImage&gt; @@ -100,16 +104,32 @@ class _ShakeImageState extends State&lt;ShakeImage&gt;
100 104
101 void _startAnimation() { 105 void _startAnimation() {
102 Timer(const Duration(seconds: 1), () { 106 Timer(const Duration(seconds: 1), () {
103 - _controller.forward(from: 0.0);  
104 - _timer = Timer.periodic(const Duration(seconds: 4), (Timer timer) {  
105 - _controller.forward(from: 0.0);  
106 - }); 107 + // 检查widget是否还存在且controller是否有效
  108 + if (mounted) {
  109 + try {
  110 + _controller.forward(from: 0.0);
  111 + _timer = Timer.periodic(const Duration(seconds: 4), (Timer timer) {
  112 + // 每次执行前都检查widget状态和controller状态
  113 + if (mounted) {
  114 + try {
  115 + _controller.forward(from: 0.0);
  116 + } catch (e) {
  117 + printLog('Timer callback error: $e');
  118 + timer.cancel();
  119 + }
  120 + }
  121 + });
  122 + } catch (e) {
  123 + printLog('Initial animation error: $e');
  124 + }
  125 + }
107 }); 126 });
108 printLog('_startAnimation'); 127 printLog('_startAnimation');
109 } 128 }
110 129
111 void _stopAnimation() { 130 void _stopAnimation() {
112 - _timer.cancel(); 131 + _timer?.cancel();
  132 + _timer = null;
113 printLog('_stopAnimation'); 133 printLog('_stopAnimation');
114 } 134 }
115 135
@@ -162,9 +182,21 @@ class _ShakeImageState extends State&lt;ShakeImage&gt; @@ -162,9 +182,21 @@ class _ShakeImageState extends State&lt;ShakeImage&gt;
162 @override 182 @override
163 void didChangeAppLifecycleState(AppLifecycleState state) { 183 void didChangeAppLifecycleState(AppLifecycleState state) {
164 if (state == AppLifecycleState.paused) { 184 if (state == AppLifecycleState.paused) {
165 - _controller.stop(); 185 + if (mounted) {
  186 + try {
  187 + _controller.stop();
  188 + } catch (e) {
  189 + printLog('Stop animation error: $e');
  190 + }
  191 + }
166 } else if (state == AppLifecycleState.resumed) { 192 } else if (state == AppLifecycleState.resumed) {
167 - _controller.forward(); 193 + if (mounted) {
  194 + try {
  195 + _controller.forward();
  196 + } catch (e) {
  197 + printLog('Resume animation error: $e');
  198 + }
  199 + }
168 } 200 }
169 } 201 }
170 202
@@ -172,9 +204,13 @@ class _ShakeImageState extends State&lt;ShakeImage&gt; @@ -172,9 +204,13 @@ class _ShakeImageState extends State&lt;ShakeImage&gt;
172 void dispose() { 204 void dispose() {
173 printLog('dispose'); 205 printLog('dispose');
174 customerRouteObserver.unsubscribe(this); 206 customerRouteObserver.unsubscribe(this);
175 - WidgetsBinding.instance.removeObserver(this); 207 + try {
  208 + WidgetsBinding.instance.removeObserver(this);
  209 + } catch (e) {
  210 + printLog('Remove observer error: $e');
  211 + }
176 _controller.dispose(); 212 _controller.dispose();
177 - _timer.cancel(); 213 + _timer?.cancel();
178 super.dispose(); 214 super.dispose();
179 } 215 }
180 216
lib/pages/reading/bloc/reading_bloc.dart
@@ -293,9 +293,13 @@ class ReadingPageBloc @@ -293,9 +293,13 @@ class ReadingPageBloc
293 } 293 }
294 294
295 Future<void> _playAudio(String? audioUrl) async { 295 Future<void> _playAudio(String? audioUrl) async {
296 - if (audioUrl!.isNotEmpty) {  
297 - await audioPlayer.play(UrlSource(audioUrl),  
298 - balance: 0.0, ctx: AudioContext()); 296 + if (audioUrl != null && audioUrl.isNotEmpty) {
  297 + try {
  298 + await audioPlayer.play(UrlSource(audioUrl),
  299 + balance: 0.0, ctx: AudioContext());
  300 + } catch (e) {
  301 + Log.d('_playAudio error: $e');
  302 + }
299 } 303 }
300 } 304 }
301 305
@@ -321,10 +325,10 @@ class ReadingPageBloc @@ -321,10 +325,10 @@ class ReadingPageBloc
321 } 325 }
322 List<SingsoundResultDetailEntity> resultDetails; 326 List<SingsoundResultDetailEntity> resultDetails;
323 if (currentPageData()?.resultDetails != null) { 327 if (currentPageData()?.resultDetails != null) {
324 - resultDetails = currentPageData()!.resultDetails!; 328 + resultDetails = currentPageData()?.resultDetails ?? [];
325 } else { 329 } else {
326 List<String>? wordList = currentPageData()?.word?.split(RegExp(r'\s+')); 330 List<String>? wordList = currentPageData()?.word?.split(RegExp(r'\s+'));
327 - resultDetails = wordList! 331 + resultDetails = (wordList ?? [])
328 .map( 332 .map(
329 (word) => SingsoundResultDetailEntity.withCharAndScore(word, 0)) 333 (word) => SingsoundResultDetailEntity.withCharAndScore(word, 0))
330 .toList(); 334 .toList();
lib/pages/section/section_page.dart
@@ -85,13 +85,17 @@ class _SectionPageView extends StatelessWidget { @@ -85,13 +85,17 @@ class _SectionPageView extends StatelessWidget {
85 'courseLessonId': courseLessonId, 85 'courseLessonId': courseLessonId,
86 'isTopic': true 86 'isTopic': true
87 }).then((value) { 87 }).then((value) {
88 - if (value != null) { 88 + if (value != null && !bloc.isClosed) {
89 Map<String, dynamic> dataMap = 89 Map<String, dynamic> dataMap =
90 value as Map<String, dynamic>; 90 value as Map<String, dynamic>;
91 - bloc.add(RequestEndClassEvent(  
92 - dataMap['courseLessonId']!, dataMap['isCompleted'],  
93 - currentTime: dataMap['currentTime'],  
94 - autoNextSection: dataMap['nextSection'])); 91 + try {
  92 + bloc.add(RequestEndClassEvent(
  93 + dataMap['courseLessonId']!, dataMap['isCompleted'],
  94 + currentTime: dataMap['currentTime'],
  95 + autoNextSection: dataMap['nextSection']));
  96 + } catch (e) {
  97 + print('BLoC add event error: $e');
  98 + }
95 } 99 }
96 AudioPlayerUtil.getInstance() 100 AudioPlayerUtil.getInstance()
97 .playAudio(AudioPlayerUtilType.countWithMe); 101 .playAudio(AudioPlayerUtilType.countWithMe);
@@ -111,15 +115,19 @@ class _SectionPageView extends StatelessWidget { @@ -111,15 +115,19 @@ class _SectionPageView extends StatelessWidget {
111 'courseLessonId': courseLessonId, 115 'courseLessonId': courseLessonId,
112 'moduleColor': ModuleCache.instance.getCurrentThemeColor() 116 'moduleColor': ModuleCache.instance.getCurrentThemeColor()
113 }).then((value) { 117 }).then((value) {
114 - if (value != null) { 118 + if (value != null && !bloc.isClosed) {
115 Map<String, dynamic> dataMap = 119 Map<String, dynamic> dataMap =
116 value as Map<String, dynamic>; 120 value as Map<String, dynamic>;
117 - bloc.add(RequestEndClassEvent(  
118 - dataMap['courseLessonId']!,  
119 - dataMap['isCompleted'],  
120 - currentStep: dataMap['currentStep'],  
121 - autoNextSection: dataMap['nextSection'],  
122 - )); 121 + try {
  122 + bloc.add(RequestEndClassEvent(
  123 + dataMap['courseLessonId']!,
  124 + dataMap['isCompleted'],
  125 + currentStep: dataMap['currentStep'],
  126 + autoNextSection: dataMap['nextSection'],
  127 + ));
  128 + } catch (e) {
  129 + print('BLoC add event error: $e');
  130 + }
123 AudioPlayerUtil.getInstance() 131 AudioPlayerUtil.getInstance()
124 .playAudio(AudioPlayerUtilType.countWithMe); 132 .playAudio(AudioPlayerUtilType.countWithMe);
125 } 133 }
@@ -138,13 +146,17 @@ class _SectionPageView extends StatelessWidget { @@ -138,13 +146,17 @@ class _SectionPageView extends StatelessWidget {
138 'courseLessonId': courseLessonId, 146 'courseLessonId': courseLessonId,
139 'moduleColor': ModuleCache.instance.getCurrentThemeColor() 147 'moduleColor': ModuleCache.instance.getCurrentThemeColor()
140 }).then((value) { 148 }).then((value) {
141 - if (value != null) { 149 + if (value != null && !bloc.isClosed) {
142 Map<String, dynamic> dataMap = 150 Map<String, dynamic> dataMap =
143 value as Map<String, dynamic>; 151 value as Map<String, dynamic>;
144 - bloc.add(RequestEndClassEvent(  
145 - dataMap['courseLessonId']!, dataMap['isCompleted'],  
146 - currentStep: dataMap['currentStep'],  
147 - autoNextSection: dataMap['nextSection'])); 152 + try {
  153 + bloc.add(RequestEndClassEvent(
  154 + dataMap['courseLessonId']!, dataMap['isCompleted'],
  155 + currentStep: dataMap['currentStep'],
  156 + autoNextSection: dataMap['nextSection']));
  157 + } catch (e) {
  158 + print('BLoC add event error: $e');
  159 + }
148 } 160 }
149 AudioPlayerUtil.getInstance() 161 AudioPlayerUtil.getInstance()
150 .playAudio(AudioPlayerUtilType.countWithMe); 162 .playAudio(AudioPlayerUtilType.countWithMe);
@@ -183,7 +195,9 @@ class _SectionPageView extends StatelessWidget { @@ -183,7 +195,9 @@ class _SectionPageView extends StatelessWidget {
183 itemCount: bloc.unlockPageCount(), 195 itemCount: bloc.unlockPageCount(),
184 controller: bloc.pageController, 196 controller: bloc.pageController,
185 onPageChanged: (int index) { 197 onPageChanged: (int index) {
186 - bloc.add(CurrentUnitIndexChangeEvent(index)); 198 + if (!bloc.isClosed) {
  199 + bloc.add(CurrentUnitIndexChangeEvent(index));
  200 + }
187 }, 201 },
188 itemBuilder: (context, index) { 202 itemBuilder: (context, index) {
189 return ScrollConfiguration( 203 return ScrollConfiguration(
lib/pages/unit/view.dart
@@ -78,7 +78,7 @@ class UnitPage extends StatelessWidget { @@ -78,7 +78,7 @@ class UnitPage extends StatelessWidget {
78 if (value != null) { 78 if (value != null) {
79 Map<String, dynamic> dataMap = 79 Map<String, dynamic> dataMap =
80 value as Map<String, dynamic>; 80 value as Map<String, dynamic>;
81 - bool needRefresh = dataMap['needRefresh']; 81 + bool needRefresh = dataMap['needRefresh'] ?? false;
82 if (needRefresh) { 82 if (needRefresh) {
83 bloc.add(RequestUnitDataEvent( 83 bloc.add(RequestUnitDataEvent(
84 courseModuleEntity?.id)); 84 courseModuleEntity?.id));
lib/pages/video/lookvideo/widgets/video_widget.dart
@@ -81,7 +81,9 @@ class _VideoWidgetState extends State&lt;VideoWidget&gt; { @@ -81,7 +81,9 @@ class _VideoWidgetState extends State&lt;VideoWidget&gt; {
81 'nextSection': widget.isTopic 81 'nextSection': widget.isTopic
82 }); 82 });
83 } as VoidCallback, againSectionTap: (() { 83 } as VoidCallback, againSectionTap: (() {
84 - lookVideoBloc.add(SectionAgainEvent()); 84 + if (mounted && !lookVideoBloc.isClosed) {
  85 + lookVideoBloc.add(SectionAgainEvent());
  86 + }
85 }), context: context); 87 }), context: context);
86 } 88 }
87 } 89 }
lib/utils/audio_player_util.dart
@@ -30,7 +30,7 @@ enum AudioPlayerUtilType { @@ -30,7 +30,7 @@ enum AudioPlayerUtilType {
30 30
31 class AudioPlayerUtil extends WidgetsBindingObserver { 31 class AudioPlayerUtil extends WidgetsBindingObserver {
32 static AudioPlayerUtil? _instance; 32 static AudioPlayerUtil? _instance;
33 - late AudioPlayer _audioPlayer; 33 + AudioPlayer? _audioPlayer;
34 late AudioPlayerUtilType currentType; 34 late AudioPlayerUtilType currentType;
35 bool _wasPlaying = false; 35 bool _wasPlaying = false;
36 static const TAG = "AudioPlayerUtil"; 36 static const TAG = "AudioPlayerUtil";
@@ -43,7 +43,7 @@ class AudioPlayerUtil extends WidgetsBindingObserver { @@ -43,7 +43,7 @@ class AudioPlayerUtil extends WidgetsBindingObserver {
43 if (!BasicConfig.isEnvProd()) { 43 if (!BasicConfig.isEnvProd()) {
44 AudioLogger.logLevel = AudioLogLevel.info; 44 AudioLogger.logLevel = AudioLogLevel.info;
45 } 45 }
46 - _audioPlayer.onPlayerStateChanged.listen((event) async { 46 + _audioPlayer?.onPlayerStateChanged.listen((event) async {
47 Log.d("$TAG onPlayerStateChanged $event _wasPlaying=$_wasPlaying"); 47 Log.d("$TAG onPlayerStateChanged $event _wasPlaying=$_wasPlaying");
48 if (event == PlayerState.completed) { 48 if (event == PlayerState.completed) {
49 // 播放结束再次播放 49 // 播放结束再次播放
@@ -70,42 +70,66 @@ class AudioPlayerUtil extends WidgetsBindingObserver { @@ -70,42 +70,66 @@ class AudioPlayerUtil extends WidgetsBindingObserver {
70 // 播放音频 70 // 播放音频
71 Future<void> playAudio(AudioPlayerUtilType type) async { 71 Future<void> playAudio(AudioPlayerUtilType type) async {
72 Log.d('$TAG playAudio begin $type'); 72 Log.d('$TAG playAudio begin $type');
  73 + if (_audioPlayer == null) {
  74 + Log.d('$TAG playAudio _audioPlayer is null, creating new one');
  75 + _audioPlayer = AudioPlayer();
  76 + }
73 currentType = type; 77 currentType = type;
74 String path = type.path; 78 String path = type.path;
75 - await _audioPlayer.play(AssetSource(path.assetMp3), volume: 0.5);  
76 - await _audioPlayer.onPlayerComplete.first; 79 + try {
  80 + await _audioPlayer!.play(AssetSource(path.assetMp3), volume: 0.5);
  81 + await _audioPlayer!.onPlayerComplete.first;
  82 + } catch (e) {
  83 + Log.d('$TAG playAudio error: $e');
  84 + }
77 Log.d('$TAG playAudio end $type'); 85 Log.d('$TAG playAudio end $type');
78 } 86 }
79 87
80 // stop 88 // stop
81 Future<void> stop() async { 89 Future<void> stop() async {
82 - Log.d("$TAG stop _audioPlayer.state=${_audioPlayer.state}");  
83 - if (_audioPlayer.state == PlayerState.playing) {  
84 - await _audioPlayer.stop(); 90 + if (_audioPlayer == null) return;
  91 + Log.d("$TAG stop _audioPlayer.state=${_audioPlayer!.state}");
  92 + try {
  93 + if (_audioPlayer!.state == PlayerState.playing) {
  94 + await _audioPlayer!.stop();
  95 + }
  96 + } catch (e) {
  97 + Log.d("$TAG stop error: $e");
85 } 98 }
86 } 99 }
87 100
88 // pause 101 // pause
89 Future<void> pause() async { 102 Future<void> pause() async {
90 - Log.d("$TAG pause _audioPlayer.state=${_audioPlayer.state}");  
91 - if (_audioPlayer.state == PlayerState.playing) {  
92 - await _audioPlayer.pause(); 103 + if (_audioPlayer == null) return;
  104 + Log.d("$TAG pause _audioPlayer.state=${_audioPlayer!.state}");
  105 + try {
  106 + if (_audioPlayer!.state == PlayerState.playing) {
  107 + await _audioPlayer!.pause();
  108 + }
  109 + } catch (e) {
  110 + Log.d("$TAG pause error: $e");
93 } 111 }
94 } 112 }
95 113
96 // resume 114 // resume
97 Future<void> resume() async { 115 Future<void> resume() async {
98 - Log.d("$TAG resume _audioPlayer.state=${_audioPlayer.state}");  
99 - if (_audioPlayer.state == PlayerState.paused) {  
100 - await _audioPlayer.resume(); 116 + if (_audioPlayer == null) return;
  117 + Log.d("$TAG resume _audioPlayer.state=${_audioPlayer!.state}");
  118 + try {
  119 + if (_audioPlayer!.state == PlayerState.paused) {
  120 + await _audioPlayer!.resume();
  121 + }
  122 + } catch (e) {
  123 + Log.d("$TAG resume error: $e");
101 } 124 }
102 } 125 }
103 126
104 @override 127 @override
105 void didChangeAppLifecycleState(AppLifecycleState state) async { 128 void didChangeAppLifecycleState(AppLifecycleState state) async {
106 - Log.d("$TAG didChangeAppLifecycleState appState=$state _wasPlaying=$_wasPlaying _audioPlayer.state=${_audioPlayer.state}"); 129 + if (_audioPlayer == null) return;
  130 + Log.d("$TAG didChangeAppLifecycleState appState=$state _wasPlaying=$_wasPlaying _audioPlayer.state=${_audioPlayer!.state}");
107 if (state == AppLifecycleState.paused) { 131 if (state == AppLifecycleState.paused) {
108 - if (_audioPlayer.state == PlayerState.playing) { 132 + if (_audioPlayer!.state == PlayerState.playing) {
109 _wasPlaying = true; 133 _wasPlaying = true;
110 await pause(); 134 await pause();
111 }; 135 };
@@ -118,12 +142,15 @@ class AudioPlayerUtil extends WidgetsBindingObserver { @@ -118,12 +142,15 @@ class AudioPlayerUtil extends WidgetsBindingObserver {
118 } 142 }
119 143
120 bool isPlaying() { 144 bool isPlaying() {
121 - return _audioPlayer.state == PlayerState.playing; 145 + return _audioPlayer?.state == PlayerState.playing;
122 } 146 }
123 147
124 void dispose() { 148 void dispose() {
125 - Log.d("$TAG dispose _audioPlayer.state=${_audioPlayer.state}");  
126 - _audioPlayer.dispose(); 149 + if (_audioPlayer != null) {
  150 + Log.d("$TAG dispose _audioPlayer.state=${_audioPlayer!.state}");
  151 + _audioPlayer!.dispose();
  152 + _audioPlayer = null;
  153 + }
127 WidgetsBinding.instance.removeObserver(this); 154 WidgetsBinding.instance.removeObserver(this);
128 } 155 }
129 } 156 }