Flutter audio_service单流应用IcyMetadata更新通知栏元数据问题求解
解决方案
问题根因分析
- 初始化阻塞的核心原因:
- 自定义的
_item()Stream 没有通过yield关键字发射MediaItem,导致Stream无任何输出 mediaItem是BehaviorSubject类型,默认无初始值,你在构造函数中直接调用mediaItem.value!会抛出空异常,导致AudioService.init执行失败,Future永远无法完成,所以FutureBuilder一直拿不到hasData
- 自定义的
- updateMediaItem不生效的核心原因:audio_service只会监听
mediaItem流的变化同步通知栏内容,要么是更新前后MediaItem的id不一致,要么是没有通过流正确发射更新后的对象
修复步骤
1. 修正AudioPlayerHandler逻辑,实现元数据自动同步
无需修改audio_service源码,直接监听just_audio的icy元数据流,更新后同步到mediaItem流即可自动刷新通知栏:
class AudioPlayerHandler extends BaseAudioHandler { final _player = AudioPlayer(); // 提前定义固定流地址 static const _streamUrl = "http://stream.drumandbass.fm:9012"; // 初始默认MediaItem final _initialMediaItem = MediaItem( id: _streamUrl, title: "加载中...", artist: "Drum and Bass FM", genre: 'Drum and Bass', duration: const Duration(milliseconds: 5739820), ); AudioPlayerHandler() { // 先给mediaItem塞初始值,避免后续取值空异常 mediaItem.add(_initialMediaItem); // 绑定原有播放状态逻辑 _player.playbackEventStream.map(_transformEvent).pipe(playbackState); // 监听icy元数据更新,自动同步到通知栏 _player.icyMetadataStream.listen((icyMetadata) { if (icyMetadata?.info?.title != null) { final titleParts = icyMetadata!.info!.title!.split(' - '); final newTitle = titleParts.length >=2 ? titleParts[1] : icyMetadata.info!.title!; final newArtist = titleParts.length >=2 ? titleParts[0] : _initialMediaItem.artist; // 生成新MediaItem,id必须和原有一致 final newMediaItem = _initialMediaItem.copyWith( title: newTitle, artist: newArtist, artUri: icyMetadata.info?.url != null ? Uri.parse(icyMetadata.info!.url!) : _initialMediaItem.artUri, ); // 发射新MediaItem,通知栏会自动同步更新 mediaItem.add(newMediaItem); } }); // 加载音频流 _player.setAudioSource(AudioSource.uri(Uri.parse(_streamUrl))); } // 保留你原有_transformEvent、play、pause等逻辑不变 PlaybackState _transformEvent(PlaybackEvent event) { // 原有逻辑无需修改 } }
2. 修正初始化和Widget逻辑
不要把initAH放在FutureBuilder的future参数中,避免每次页面build重复初始化AudioService,在State中提前初始化即可:
class _MyAppState extends State<MyApp> { late final Future<AudioPlayerHandler> _audioHandlerFuture; bool loaded = false; late AudioPlayerHandler _audioHandler; @override void initState() { super.initState(); // 全局仅初始化一次AudioService _audioHandlerFuture = initAH(); } Future<AudioPlayerHandler> initAH() async { return await AudioService.init( builder: () => AudioPlayerHandler(), config: const AudioServiceConfig( androidNotificationChannelId: 'com.ryanheise.myapp.channel.audio', androidNotificationChannelName: 'Audio playback', androidNotificationOngoing: true, ), ); } @override Widget build(BuildContext context) { return FutureBuilder<AudioPlayerHandler>( future: _audioHandlerFuture, builder: (context, snapshot) { if (!snapshot.hasData) { return const Center(child: CircularProgressIndicator()); } _audioHandler = snapshot.data!; loaded = true; // 这里返回你实际的业务UI即可 return Container(); }, ); } }
注意事项
- 不需要额外调用
updateMediaItem,只要通过mediaItem.add()发射id一致的新MediaItem,audio_service会自动同步通知栏内容 - Icy元数据的split解析逻辑可以根据你实际的流格式调整,上述代码仅为示例
内容的提问来源于stack exchange,提问作者Tom Peak
相关产品推荐
相关产品推荐

