You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter应用后台意外播放音乐问题求助

问题分析

你的问题根源有两处:

  1. 仅依赖App生命周期状态,未处理音频焦点变化:当Instagram Reels占用音频焦点时,你的应用音频会被系统暂停,但焦点释放后(比如退出Reels),音频播放器可能自动恢复播放,此时你的App仍处于后台,AppLifecycleState并未切换到resumed,现有逻辑无法拦截这种场景。
  2. 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__

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 19:14:56