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

Flutter本地通知自定义音效下拉通知栏后停止,如何让音效持续播放?

解决Flutter本地通知下拉栏后音效停止的问题

问题根源是通知自带的sound属性设计用于播放短提示音,当用户下拉通知栏交互时,系统会自动终止通知的音频播放。要实现持续播放,必须脱离通知的音频机制,改用独立的后台音频服务。

具体解决方案

1. 引入必要依赖

在pubspec.yaml中添加后台音频相关插件:

dependencies:
  flutter_local_notifications: ^16.1.0 # 替换为最新版本
  audio_service: ^0.18.11 # 处理后台音频播放
  just_audio: ^0.9.34 # 音频播放核心
  timezone: ^0.9.2

2. 实现后台音频服务

创建音频服务处理类,确保音频在后台持续播放:

import 'package:audio_service/audio_service.dart';
import 'package:just_audio/just_audio.dart';

class AdhanAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
  final _player = AudioPlayer();

  AdhanAudioHandler() {
    // 将播放器状态同步到音频服务
    _player.playbackEventStream.map(_convertToPlaybackState).pipe(playbackState);
  }

  // 播放指定音效
  Future<void> playAdhan(String soundAssetPath) async {
    await _player.setAsset('assets/$soundAssetPath'); // 假设音效放在assets目录
    await _player.play();
  }

  // 转换播放器状态为音频服务需要的格式
  PlaybackState _convertToPlaybackState(PlaybackEvent event) {
    return PlaybackState(
      controls: [MediaControl.stop],
      systemActions: const {MediaAction.stop},
      androidCompactActionIndices: const [0],
      processingState: {
        ProcessingState.idle: AudioProcessingState.idle,
        ProcessingState.loading: AudioProcessingState.loading,
        ProcessingState.buffering: AudioProcessingState.buffering,
        ProcessingState.ready: AudioProcessingState.ready,
        ProcessingState.completed: AudioProcessingState.completed,
      }[_player.processingState]!,
      playing: _player.playing,
      updatePosition: _player.position,
      bufferedPosition: _player.bufferedPosition,
      speed: _player.speed,
    );
  }

  @override
  Future<void> stop() async {
    await _player.stop();
    return super.stop();
  }
}

3. 修改通知调度逻辑

移除通知的sound配置,改为触发通知时启动后台音频服务:

import 'package:audio_service/audio_service.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:timezone/timezone.dart' as tz;

late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
late AdhanAudioHandler _audioHandler;

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // 初始化音频服务
  _audioHandler = await AudioService.init(
    builder: () => AdhanAudioHandler(),
    config: const AudioServiceConfig(
      androidNotificationChannelId: 'adhan_audio_channel',
      androidNotificationChannelName: 'Adhan Audio',
      androidNotificationOngoing: true, // 保持音频通知在状态栏持续显示
    ),
  );

  // 初始化本地通知插件
  flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
  await flutterLocalNotificationsPlugin.initialize(
    InitializationSettings(
      android: AndroidInitializationSettings('@mipmap/ic_launcher'),
      iOS: DarwinInitializationSettings(),
    ),
    // 前台点击通知时触发音频播放
    onDidReceiveNotificationResponse: (response) async {
      if (response.payload != null) {
        await _audioHandler.playAdhan(response.payload!);
      }
    },
  );

  runApp(const MyApp());
}

// 调度定时通知的方法
Future<void> scheduleAdhanNotification(DateTime scheduledTime, String soundFile) async {
  const int insistentFlag = 4;

  await flutterLocalNotificationsPlugin.zonedSchedule(
    1,
    'Adhan Sound',
    '',
    tz.TZDateTime.from(scheduledTime, tz.local),
    NotificationDetails(
      android: AndroidNotificationDetails(
        'adhan channel id',
        'adhan channel name',
        channelDescription: 'adhan channel description',
        importance: Importance.max,
        priority: Priority.high,
        audioAttributesUsage: AudioAttributesUsage.alarm,
        additionalFlags: Int32List.fromList([insistentFlag]),
        // 移除sound配置,改用后台音频服务
      ),
      iOS: DarwinNotificationDetails(sound: null),
    ),
    androidScheduleMode: AndroidScheduleMode.alarmClock,
    uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime,
    matchDateTimeComponents: DateTimeComponents.time,
    payload: soundFile, // 传递音效文件名到回调
  );
}

4. 配置平台权限

Android端(AndroidManifest.xml)

添加后台服务和权限:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

<application ...>
  <service android:name="com.ryanheise.audioservice.AudioService">
    <intent-filter>
      <action android:name="android.media.browse.MediaBrowserService" />
    </intent-filter>
  </service>
</application>

iOS端(Info.plist)

添加后台音频权限:

<key>UIBackgroundModes</key>
<array>
  <string>audio</string>
</array>

5. 后台触发处理(Android)

添加后台通知回调入口,确保定时通知触发时能启动音频:

@pragma('vm:entry-point')
Future<void> onDidReceiveBackgroundNotificationResponse(NotificationResponse response) async {
  WidgetsFlutterBinding.ensureInitialized();
  
  final audioHandler = await AudioService.init(
    builder: () => AdhanAudioHandler(),
    config: const AudioServiceConfig(
      androidNotificationChannelId: 'adhan_audio_channel',
      androidNotificationChannelName: 'Adhan Audio',
      androidNotificationOngoing: true,
    ),
  );

  if (response.payload != null) {
    await audioHandler.playAdhan(response.payload!);
  }
}

关键说明

  • 通知自带的sound仅适用于短提示音,系统会在通知交互时终止其播放,无法通过修改通知参数绕过这个限制。
  • 后台音频服务独立于通知生命周期,即使下拉通知栏或切换应用,音频仍会持续播放。
  • 音频服务会在状态栏生成一个持续的通知(可通过配置修改样式),这是Android后台播放音频的强制要求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 19:14:52