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

如何在Flutter应用终止状态下通过FCM通知触发音频播放并支持APP内暂停?

问题分析与解决方案

核心问题说明

你的代码无法正常工作的原因主要有三点:

  1. 应用终止状态下,onBackgroundMessage运行在Dart隔离环境中,普通AudioPlayer实例无法在这个受限环境里完成音频初始化与播放——该环境没有完整的Flutter引擎上下文支持多媒体操作。
  2. 若发送的是带notification字段的FCM通知,应用终止时系统会直接显示系统通知,不会触发onBackgroundMessage;只有纯数据消息或**通知+数据且配置了click_action: FLUTTER_NOTIFICATION_CLICK(Android)**的消息,才会触发后台回调。
  3. 后台播放的音频实例没有全局持久化,应用打开后无法关联到后台的播放进程,自然无法实现暂停操作。

实现步骤

1. 调整FCM消息格式

发送FCM时必须使用纯数据消息,确保应用终止时能触发后台回调。示例消息结构:

{
  "to": "你的FCM_TOKEN",
  "data": {
    "audio_path": "assets/audio/alert.mp3",
    "title": "触发音频播放"
  }
}

2. 原生层处理后台音频播放(关键)

Flutter隔离环境无法可靠处理后台音频,必须通过原生层实现:

  • Android:在FirebaseMessagingService的onMessageReceived中启动前台服务播放音频,保证后台/终止状态下的播放权限。
  • iOS:配置音频会话为后台模式,在AppDelegate的didReceiveRemoteNotification中启动音频播放。

3. Flutter层全局管理音频实例

使用全局单例AudioPlayer,确保应用打开后能关联到后台播放进程:

import 'package:audioplayers/audioplayers.dart';

// 全局单例音频播放器
final AudioPlayer globalAudioPlayer = AudioPlayer();

class AudioManager {
  // 播放本地音频
  static Future<void> playLocalAudio(String path) async {
    await globalAudioPlayer.play(DeviceFileSource(path));
  }

  // 暂停音频
  static Future<void> pauseAudio() async {
    await globalAudioPlayer.pause();
  }
}

4. 重构FCM初始化逻辑

通过平台通道调用原生层播放音频,同时监听应用打开事件同步状态:

import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/services.dart';

// 平台通道,用于调用原生音频播放方法
const platform = MethodChannel('com.your.app/audio_channel');

Future<void> backgroundHandler(RemoteMessage message) async {
  try {
    // 通过通道通知原生层播放音频
    await platform.invokeMethod('playBackgroundAudio', {
      'audioPath': message.data['audio_path'],
    });
  } on PlatformException catch (e) {
    print('音频播放失败: ${e.message}');
  }
}

class FcmNotifications {
  static Future<void> initialize() async {
    NotificationSettings settings = await FirebaseMessaging.instance.requestPermission();
    if (settings.authorizationStatus == AuthorizationStatus.authorized) {
      String? fcmToken = await FirebaseMessaging.instance.getToken();
      print('FCM Token: $fcmToken');
    }

    FirebaseMessaging.onBackgroundMessage(backgroundHandler);
    
    // 应用从通知打开时,同步播放状态
    FirebaseMessaging.onMessageOpenedApp.listen((message) {
      // 可在此初始化全局播放器,准备接受暂停操作
    });
  }
}

5. 添加暂停按钮UI

在应用首页添加按钮,调用全局音频管理器的暂停方法:

ElevatedButton(
  onPressed: () => AudioManager.pauseAudio(),
  child: const Text('暂停音频'),
)

原生层关键配置示例

Android(AndroidManifest.xml)

<!-- 声明前台服务权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />

<!-- 自定义Firebase消息服务 -->
<service
    android:name=".MyFirebaseMessagingService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

MyFirebaseMessagingService.java

public class MyFirebaseMessagingService extends FirebaseMessagingService {
    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        String audioPath = remoteMessage.getData().get("audio_path");
        // 启动前台音频播放服务
        Intent serviceIntent = new Intent(this, AudioPlaybackService.class);
        serviceIntent.putExtra("audio_path", audioPath);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForegroundService(serviceIntent);
        } else {
            startService(serviceIntent);
        }
    }
}

iOS(Info.plist)

<!-- 开启后台音频与远程通知权限 -->
<key>UIBackgroundModes</key>
<array>
    <string>audio</string>
    <string>remote-notification</string>
</array>

注意事项

  • 必须申请后台音频播放权限:Android需声明前台服务权限,iOS需配置UIBackgroundModes。
  • 纯数据消息不会触发系统通知,若需要显示通知,需在原生层手动创建。
  • 全局音频播放器需与原生层播放实例保持状态同步,避免出现控制失效的情况。

内容的提问来源于stack exchange,提问作者Asif

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 20:53:16