Flutter应用后台意外播放音乐问题求助
问题分析
你的问题根源有两处:
- 仅依赖App生命周期状态,未处理音频焦点变化:当Instagram Reels占用音频焦点时,你的应用音频会被系统暂停,但焦点释放后(比如退出Reels),音频播放器可能自动恢复播放,此时你的App仍处于后台,
AppLifecycleState并未切换到resumed,现有逻辑无法拦截这种场景。 - HomeAppState的build方法存在误触发逻辑:App在后台时,若Bloc状态发生重建(比如某些状态更新),build方法会重新执行,此时如果播放器状态不是
paused,会再次触发_playAudio,导致后台播放。
解决方案
1. 引入音频焦点管理
通过audio_session包管理音频焦点,确保只有应用获得焦点且处于前台时才播放音频,失去焦点时自动暂停。
2. 简化生命周期监听逻辑
移除复杂的appStates列表记录,直接根据当前状态处理,避免数组越界或状态判断错误。
3. 优化Home页面播放触发逻辑
将自动播放逻辑从build方法中移出,放到Bloc事件或页面初始化生命周期中,避免build重建时误触发。
修改后的代码
void main() { WidgetsFlutterBinding.ensureInitialized(); runApp( LayoutBuilder( builder: ((context, constraints) { Responsive().init(constraints: constraints); return const MaterialApp( debugShowCheckedModeBanner: false, home: AppRouter(), ); }), ), ); } class AppRouter extends StatefulWidget { const AppRouter({super.key}); @override State<AppRouter> createState() => _AppRouterState(); } class _AppRouterState extends State<AppRouter> with WidgetsBindingObserver { bool wasAudioPlaying = false; Future<void> _playAudio() async { // 先获取音频焦点再播放 final session = await AudioSession.instance; await session.configure(AudioSessionConfiguration.speech()); await session.setActive(true); await QUser().audioPlayer.setReleaseMode(ReleaseMode.loop); await QUser().audioPlayer.play(AssetSource(QUser().audioSource)); } Future<void> _pauseAudio() async { await QUser().audioPlayer.pause(); // 释放音频焦点 final session = await AudioSession.instance; await session.setActive(false); } @override void didChangeAppLifecycleState(AppLifecycleState state) async { super.didChangeAppLifecycleState(state); log(state.toString()); switch (state) { case AppLifecycleState.resumed: if (wasAudioPlaying) { await _playAudio(); } break; case AppLifecycleState.paused: case AppLifecycleState.inactive: case AppLifecycleState.detached: wasAudioPlaying = QUser().audioPlayer.state == PlayerState.playing; await _pauseAudio(); break; } } @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); _initAudioSession(); } Future<void> _initAudioSession() async { final session = await AudioSession.instance; // 监听音频焦点变化 session.becomingActiveNotification.listen((_) { // 仅当应用处于前台时,才恢复播放 if (wasAudioPlaying && WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) { _playAudio(); } }); session.becomingInactiveNotification.listen((_) { wasAudioPlaying = QUser().audioPlayer.state == PlayerState.playing; _pauseAudio(); }); session.interruptedNotification.listen((_) { wasAudioPlaying = QUser().audioPlayer.state == PlayerState.playing; _pauseAudio(); }); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override Widget build(BuildContext context) { return BlocProvider<AppBloc>( create: (context) => AppBloc(), child: BlocConsumer<AppBloc, AppState>( listener: (context, state) async { if (state.isLoading) { LoadingScreen().show( context: context, text: "منتظر بمانید", ); } else { LoadingScreen().hide(); } if (state.exception is InternetConnectionException) { await showErrorDialog( context: context, text: "ارتباط خود را با اینترنت بررسی نمیایید", ); } else if (state.exception is GenericException) { await showErrorDialog( context: context, text: "مشکلی پیش آمده، لطفا دوباره تلاش کنید", ); } }, builder: (context, state) { if (state is OnboardingAppState) { return const OnBoardingView(); } else if (state is RegisterAppState) { return const RegisterView(); } else if (state is AppRulesAppState) { return const AppRulesView(); } else if (state is LoginAppState) { return const LoginView(); } else if (state is ChooseProfileIconAppState) { return ChooseProfileIconView( isRegistered: state.isRegistered, ); } else if (state is ConfirmNumberAppState) { return ConfirmNumberView( isRegistered: state.isRegistered, ); } else if (state is ProfileAppState) { return const ProfileView(); } else if (state is HomeAppState) { // 移除build中的自动播放逻辑,改为在Bloc进入Home状态时触发 return HomeView( runAnimation: state.runAnimation, ); } else if (state is RulesAppState) { return const RulesView(); } else if (state is CommentAppState) { return const CommentView(); } else if (state is CategoryAppState) { return const CategoryView(); } else if (state is PickPlayerAppState) { return const PickPlayerView(); } else if (state is SelectTimeAppState) { return const SelectTimeView(); } else if (state is GameAppState) { return GameView( question: state.question, player: state.player, seconds: state.seconds, ); } else if (state is GameEndAppState) { return const GameEndView(); } else if (state is InitialAppState) { return const InitialApp(); } else { context.read<AppBloc>().add(const InitialAppEvent()); return const Scaffold( body: Center( child: CircularProgressIndicator(), ), ); } }, ), ); } }
额外说明
- 需要在
pubspec.yaml中添加依赖:audio_session: ^0.1.16 - 将Home页面的自动播放逻辑移到
AppBloc的HomeAppEvent处理方法中,确保只有用户主动进入Home页面且应用处于前台时才触发播放。
内容的提问来源于stack exchange,提问作者samirhossein__
相关产品推荐
相关产品推荐

