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

使用audio_service与just_audio时通知为空且无法正常关闭问题

问题

使用audio_service和just_audio实现后台音频播放时遇到两个问题:

  • 后台通知仅显示图标,无任何播放控制组件(播放/暂停/停止等按钮)
  • 应用完全退出后,通知仍在播放音频,且无法像普通通知一样滑动删除

相关代码

void main() async{
  WidgetsFlutterBinding.ensureInitialized();
  //iniciaStream();

  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  audioHandler = await AudioService.init(
    builder: () => AudioPlayerTask(),
    config: const AudioServiceConfig(
      androidNotificationChannelId: 'meuapp.channel.audio',
      androidNotificationChannelName: 'meu app',
      androidNotificationOngoing: false,
      androidStopForegroundOnPause: false,
      notificationColor: Color.fromARGB(10,255, 209, 0),

    ),
  );

  SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
runApp(
  const MaterialApp(
    home: Principal(),)
  );
}


class AudioPlayerTask extends BaseAudioHandler with SeekHandler {
  static final _item = MediaItem(
    id: urlList[currentSetStream],
    album: "Science Friday",
    title: "titulo",
    artist: "artista",
    displayTitle: "Nome da rádio",//radiosList[currentSetStream],
    duration: const Duration(milliseconds: 5739820),

    //artUri: Image.asset(imgList[currentSetStream]),
  );
  final AudioPlayer _player = AudioPlayer();



  AudioPlayerHandler() {

    _player.playbackEventStream.map(_transformEvent).pipe(playbackState);

    mediaItem.add(_item);

    _player.setAudioSource(AudioSource.uri(Uri.parse(_item.id)));

  }

  @override
  Future<void> play() async{
    await _player.setUrl(urlList[currentSetStream]);
    await _player.play();

    playbackState.add(
      playbackState.value.copyWith(
        playing: true,
        processingState: AudioProcessingState.ready,
      ),
    );
    PlaybackState(
      controls: [MediaControl.pause, MediaControl.stop], 
      processingState: AudioProcessingState.ready,
      playing: true,
      
      bufferedPosition: _player.bufferedPosition,
      speed: _player.speed,
    );
  }
  @override
  Future<void> setVolume(double volume) async {

    await _player.setVolume(volume);
    
  }



  @override
  Future<void> pause() async{
    _player.pause();
      }

  @override
  Future<void> seek(Duration position) => _player.seek(position);

  @override
  Future<void> stop() => _player.stop();

  PlaybackState _transformEvent(PlaybackEvent event) {
    return PlaybackState(
      controls: [
        MediaControl.rewind,
        if (_player.playing) MediaControl.pause else MediaControl.play,
        MediaControl.stop,
        MediaControl.fastForward,
      ],
      systemActions: const {
        MediaAction.seek,
        MediaAction.seekForward,
        MediaAction.seekBackward,
      },
      androidCompactActionIndices: const [0, 1, 3],
      processingState: const {
        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,
      queueIndex: event.currentIndex,

    );
  }

  PlaybackState updatePlaybackState(PlaybackEvent event) {
    return PlaybackState(
      controls: [
        MediaControl.rewind,
        if (_player.playing) MediaControl.pause else MediaControl.play,
        MediaControl.stop,
        MediaControl.fastForward,
      ],
      systemActions: const {
        MediaAction.seek,
        MediaAction.seekForward,
        MediaAction.seekBackward,
      },
      androidCompactActionIndices: const [0, 1, 3],
      processingState: const {
        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,
      queueIndex: event.currentIndex,
    );
  }
}

问题修复方案

1. 解决通知无控制组件问题

核心问题是状态流未正确绑定,导致通知接收不到控制按钮配置:

  • 修正构造函数名:原代码中构造函数AudioPlayerHandler()与类名AudioPlayerTask不匹配,导致初始化逻辑完全没执行。将构造函数改为AudioPlayerTask(),确保_player.playbackEventStream与playbackState流绑定。
  • 移除play方法中的无效代码:play方法最后新建的PlaybackState未添加到流中,属于无效代码;同时copyWith未保留controls配置,直接删除这部分,依赖_transformEvent生成带控制按钮的状态。
  • 避免重复设置音频源:初始化时已调用setAudioSource,play方法只需直接调用_player.play()即可。

2. 解决应用退出后音频持续播放、通知无法删除问题

需要在停止播放时正确终止音频服务:

  • 完善stop方法:不仅停止播放器,还要更新状态为idle、停止前台服务并关闭音频服务:
    @override
    Future<void> stop() async {
      await _player.stop();
      await playbackState.add(playbackState.value.copyWith(
        processingState: AudioProcessingState.idle,
        playing: false,
      ));
      await stopForeground(true); // 停止前台服务并移除通知
      await close(); // 关闭音频服务
    }
    
  • 监听应用生命周期:在主页面添加AppLifecycleListener,当应用进入后台或被销毁时,调用audioHandler.stop()确保服务终止。

修复后的关键代码片段

class AudioPlayerTask extends BaseAudioHandler with SeekHandler {
  static final _item = MediaItem(
    id: urlList[currentSetStream],
    album: "Science Friday",
    title: "titulo",
    artist: "artista",
    displayTitle: "Nome da rádio",
    duration: const Duration(milliseconds: 5739820),
  );
  final AudioPlayer _player = AudioPlayer();

  // 修正构造函数名
  AudioPlayerTask() {
    _player.playbackEventStream.map(_transformEvent).pipe(playbackState);
    mediaItem.add(_item);
    _player.setAudioSource(AudioSource.uri(Uri.parse(_item.id)));
  }

  @override
  Future<void> play() async{
    await _player.play();
  }

  @override
  Future<void> pause() async{
    await _player.pause();
  }

  @override
  Future<void> stop() async {
    await _player.stop();
    await playbackState.add(playbackState.value.copyWith(
      processingState: AudioProcessingState.idle,
      playing: false,
    ));
    await stopForeground(true);
    await close();
  }

  PlaybackState _transformEvent(PlaybackEvent event) {
    return PlaybackState(
      controls: [
        MediaControl.rewind,
        if (_player.playing) MediaControl.pause else MediaControl.play,
        MediaControl.stop,
        MediaControl.fastForward,
      ],
      systemActions: const {
        MediaAction.seek,
        MediaAction.seekForward,
        MediaAction.seekBackward,
      },
      androidCompactActionIndices: const [0, 1, 3],
      processingState: const {
        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,
      queueIndex: event.currentIndex,
    );
  }
}

额外注意事项

  • 测试前卸载旧版本应用,避免通知缓存问题
  • 确保urlList[currentSetStream]为有效音频流地址,避免初始化加载失败
  • 可在主页面添加生命周期监听,确保应用退出时终止音频服务

内容的提问来源于stack exchange,提问作者Raul Tomé

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 22:19:49