Commit c272c662bd42c755c8ebf73b721a3a64bac78bed
1 parent
46385ad0
fix: 崩溃修复
Showing
10 changed files
with
199 additions
and
90 deletions
lib/app/splash_page.dart
| ... | ... | @@ -140,19 +140,22 @@ class _TransitionViewState extends State<TransitionView> { |
| 140 | 140 | actions: <Widget>[ |
| 141 | 141 | TextButton( |
| 142 | 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 | 152 | TextButton( |
| 150 | 153 | child: const Text('重试'), |
| 151 | 154 | onPressed: () async { |
| 152 | 155 | // 关闭对话框并重试 |
| 153 | 156 | Navigator.of(context).pop(); |
| 154 | 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<PermissionRequestPage> |
| 30 | 30 | bool _isGoSetting = false; |
| 31 | 31 | late final List<String> msgList; |
| 32 | 32 | bool _isDialogShowing = false; |
| 33 | + bool _isRequestingPermission = false; | |
| 33 | 34 | |
| 34 | 35 | @override |
| 35 | 36 | void initState() { |
| ... | ... | @@ -107,6 +108,11 @@ class _PermissionRequestPageState extends State<PermissionRequestPage> |
| 107 | 108 | |
| 108 | 109 | /// 校验权限 |
| 109 | 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 | 117 | List<Permission> intentPermissionList = []; |
| 112 | 118 | |
| ... | ... | @@ -129,37 +135,51 @@ class _PermissionRequestPageState extends State<PermissionRequestPage> |
| 129 | 135 | |
| 130 | 136 | ///实际触发请求权限 |
| 131 | 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 | 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<bool> requestPermissions( |
| 35 | 35 | if (allGranted) { |
| 36 | 36 | return true; |
| 37 | 37 | } else { |
| 38 | - return await Navigator.of(context).push(PageRouteBuilder( | |
| 38 | + final result = await Navigator.of(context).push(PageRouteBuilder( | |
| 39 | 39 | opaque: false, |
| 40 | 40 | pageBuilder: ((context, animation, secondaryAnimation) { |
| 41 | 41 | return PermissionRequestPage(permissions, permissionNames, permissionDesc, |
| 42 | 42 | isRequiredPermission: isRequiredPermission); |
| 43 | 43 | }))); |
| 44 | + return result ?? false; | |
| 44 | 45 | } |
| 45 | 46 | } |
| 46 | 47 | ... | ... |
lib/common/request/api_response/api_response_entity.dart
lib/pages/home/widgets/ShakeImage.dart
| ... | ... | @@ -20,7 +20,7 @@ class _ShakeImageState extends State<ShakeImage> |
| 20 | 20 | with SingleTickerProviderStateMixin, WidgetsBindingObserver, RouteAware { |
| 21 | 21 | late AnimationController _controller; |
| 22 | 22 | late Animation<double> _animation; |
| 23 | - late Timer _timer; | |
| 23 | + Timer? _timer; | |
| 24 | 24 | late final AppLifecycleListener appLifecycleListener; |
| 25 | 25 | static const TAG = 'ShakeImage'; |
| 26 | 26 | |
| ... | ... | @@ -43,7 +43,11 @@ class _ShakeImageState extends State<ShakeImage> |
| 43 | 43 | ); |
| 44 | 44 | |
| 45 | 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 | 51 | WidgetsBinding.instance.addPostFrameCallback((_) { |
| 48 | 52 | final route = ModalRoute.of(context); |
| 49 | 53 | if (route is PageRoute) { |
| ... | ... | @@ -100,16 +104,32 @@ class _ShakeImageState extends State<ShakeImage> |
| 100 | 104 | |
| 101 | 105 | void _startAnimation() { |
| 102 | 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 | 127 | printLog('_startAnimation'); |
| 109 | 128 | } |
| 110 | 129 | |
| 111 | 130 | void _stopAnimation() { |
| 112 | - _timer.cancel(); | |
| 131 | + _timer?.cancel(); | |
| 132 | + _timer = null; | |
| 113 | 133 | printLog('_stopAnimation'); |
| 114 | 134 | } |
| 115 | 135 | |
| ... | ... | @@ -162,9 +182,21 @@ class _ShakeImageState extends State<ShakeImage> |
| 162 | 182 | @override |
| 163 | 183 | void didChangeAppLifecycleState(AppLifecycleState state) { |
| 164 | 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 | 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<ShakeImage> |
| 172 | 204 | void dispose() { |
| 173 | 205 | printLog('dispose'); |
| 174 | 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 | 212 | _controller.dispose(); |
| 177 | - _timer.cancel(); | |
| 213 | + _timer?.cancel(); | |
| 178 | 214 | super.dispose(); |
| 179 | 215 | } |
| 180 | 216 | ... | ... |
lib/pages/reading/bloc/reading_bloc.dart
| ... | ... | @@ -293,9 +293,13 @@ class ReadingPageBloc |
| 293 | 293 | } |
| 294 | 294 | |
| 295 | 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 | 325 | } |
| 322 | 326 | List<SingsoundResultDetailEntity> resultDetails; |
| 323 | 327 | if (currentPageData()?.resultDetails != null) { |
| 324 | - resultDetails = currentPageData()!.resultDetails!; | |
| 328 | + resultDetails = currentPageData()?.resultDetails ?? []; | |
| 325 | 329 | } else { |
| 326 | 330 | List<String>? wordList = currentPageData()?.word?.split(RegExp(r'\s+')); |
| 327 | - resultDetails = wordList! | |
| 331 | + resultDetails = (wordList ?? []) | |
| 328 | 332 | .map( |
| 329 | 333 | (word) => SingsoundResultDetailEntity.withCharAndScore(word, 0)) |
| 330 | 334 | .toList(); | ... | ... |
lib/pages/section/section_page.dart
| ... | ... | @@ -85,13 +85,17 @@ class _SectionPageView extends StatelessWidget { |
| 85 | 85 | 'courseLessonId': courseLessonId, |
| 86 | 86 | 'isTopic': true |
| 87 | 87 | }).then((value) { |
| 88 | - if (value != null) { | |
| 88 | + if (value != null && !bloc.isClosed) { | |
| 89 | 89 | Map<String, dynamic> dataMap = |
| 90 | 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 | 100 | AudioPlayerUtil.getInstance() |
| 97 | 101 | .playAudio(AudioPlayerUtilType.countWithMe); |
| ... | ... | @@ -111,15 +115,19 @@ class _SectionPageView extends StatelessWidget { |
| 111 | 115 | 'courseLessonId': courseLessonId, |
| 112 | 116 | 'moduleColor': ModuleCache.instance.getCurrentThemeColor() |
| 113 | 117 | }).then((value) { |
| 114 | - if (value != null) { | |
| 118 | + if (value != null && !bloc.isClosed) { | |
| 115 | 119 | Map<String, dynamic> dataMap = |
| 116 | 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 | 131 | AudioPlayerUtil.getInstance() |
| 124 | 132 | .playAudio(AudioPlayerUtilType.countWithMe); |
| 125 | 133 | } |
| ... | ... | @@ -138,13 +146,17 @@ class _SectionPageView extends StatelessWidget { |
| 138 | 146 | 'courseLessonId': courseLessonId, |
| 139 | 147 | 'moduleColor': ModuleCache.instance.getCurrentThemeColor() |
| 140 | 148 | }).then((value) { |
| 141 | - if (value != null) { | |
| 149 | + if (value != null && !bloc.isClosed) { | |
| 142 | 150 | Map<String, dynamic> dataMap = |
| 143 | 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 | 161 | AudioPlayerUtil.getInstance() |
| 150 | 162 | .playAudio(AudioPlayerUtilType.countWithMe); |
| ... | ... | @@ -183,7 +195,9 @@ class _SectionPageView extends StatelessWidget { |
| 183 | 195 | itemCount: bloc.unlockPageCount(), |
| 184 | 196 | controller: bloc.pageController, |
| 185 | 197 | onPageChanged: (int index) { |
| 186 | - bloc.add(CurrentUnitIndexChangeEvent(index)); | |
| 198 | + if (!bloc.isClosed) { | |
| 199 | + bloc.add(CurrentUnitIndexChangeEvent(index)); | |
| 200 | + } | |
| 187 | 201 | }, |
| 188 | 202 | itemBuilder: (context, index) { |
| 189 | 203 | return ScrollConfiguration( | ... | ... |
lib/pages/unit/view.dart
| ... | ... | @@ -78,7 +78,7 @@ class UnitPage extends StatelessWidget { |
| 78 | 78 | if (value != null) { |
| 79 | 79 | Map<String, dynamic> dataMap = |
| 80 | 80 | value as Map<String, dynamic>; |
| 81 | - bool needRefresh = dataMap['needRefresh']; | |
| 81 | + bool needRefresh = dataMap['needRefresh'] ?? false; | |
| 82 | 82 | if (needRefresh) { |
| 83 | 83 | bloc.add(RequestUnitDataEvent( |
| 84 | 84 | courseModuleEntity?.id)); | ... | ... |
lib/pages/video/lookvideo/widgets/video_widget.dart
| ... | ... | @@ -81,7 +81,9 @@ class _VideoWidgetState extends State<VideoWidget> { |
| 81 | 81 | 'nextSection': widget.isTopic |
| 82 | 82 | }); |
| 83 | 83 | } as VoidCallback, againSectionTap: (() { |
| 84 | - lookVideoBloc.add(SectionAgainEvent()); | |
| 84 | + if (mounted && !lookVideoBloc.isClosed) { | |
| 85 | + lookVideoBloc.add(SectionAgainEvent()); | |
| 86 | + } | |
| 85 | 87 | }), context: context); |
| 86 | 88 | } |
| 87 | 89 | } | ... | ... |
lib/utils/audio_player_util.dart
| ... | ... | @@ -30,7 +30,7 @@ enum AudioPlayerUtilType { |
| 30 | 30 | |
| 31 | 31 | class AudioPlayerUtil extends WidgetsBindingObserver { |
| 32 | 32 | static AudioPlayerUtil? _instance; |
| 33 | - late AudioPlayer _audioPlayer; | |
| 33 | + AudioPlayer? _audioPlayer; | |
| 34 | 34 | late AudioPlayerUtilType currentType; |
| 35 | 35 | bool _wasPlaying = false; |
| 36 | 36 | static const TAG = "AudioPlayerUtil"; |
| ... | ... | @@ -43,7 +43,7 @@ class AudioPlayerUtil extends WidgetsBindingObserver { |
| 43 | 43 | if (!BasicConfig.isEnvProd()) { |
| 44 | 44 | AudioLogger.logLevel = AudioLogLevel.info; |
| 45 | 45 | } |
| 46 | - _audioPlayer.onPlayerStateChanged.listen((event) async { | |
| 46 | + _audioPlayer?.onPlayerStateChanged.listen((event) async { | |
| 47 | 47 | Log.d("$TAG onPlayerStateChanged $event _wasPlaying=$_wasPlaying"); |
| 48 | 48 | if (event == PlayerState.completed) { |
| 49 | 49 | // 播放结束再次播放 |
| ... | ... | @@ -70,42 +70,66 @@ class AudioPlayerUtil extends WidgetsBindingObserver { |
| 70 | 70 | // 播放音频 |
| 71 | 71 | Future<void> playAudio(AudioPlayerUtilType type) async { |
| 72 | 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 | 77 | currentType = type; |
| 74 | 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 | 85 | Log.d('$TAG playAudio end $type'); |
| 78 | 86 | } |
| 79 | 87 | |
| 80 | 88 | // stop |
| 81 | 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 | 101 | // pause |
| 89 | 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 | 114 | // resume |
| 97 | 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 | 127 | @override |
| 105 | 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 | 131 | if (state == AppLifecycleState.paused) { |
| 108 | - if (_audioPlayer.state == PlayerState.playing) { | |
| 132 | + if (_audioPlayer!.state == PlayerState.playing) { | |
| 109 | 133 | _wasPlaying = true; |
| 110 | 134 | await pause(); |
| 111 | 135 | }; |
| ... | ... | @@ -118,12 +142,15 @@ class AudioPlayerUtil extends WidgetsBindingObserver { |
| 118 | 142 | } |
| 119 | 143 | |
| 120 | 144 | bool isPlaying() { |
| 121 | - return _audioPlayer.state == PlayerState.playing; | |
| 145 | + return _audioPlayer?.state == PlayerState.playing; | |
| 122 | 146 | } |
| 123 | 147 | |
| 124 | 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 | 154 | WidgetsBinding.instance.removeObserver(this); |
| 128 | 155 | } |
| 129 | 156 | } | ... | ... |