assets_audio_player如何加载JSON音频数据实现动态播放列表
assets_audio_player 动态JSON音频列表加载异常修复
核心问题点
- 封面图资源类型使用错误:网络URL的封面调用了仅支持本地文件的
MetasImage.file()构造方法,会导致元数据解析失败,播放器初始化中断 - 列表变量未做初始化与清空逻辑:重复进入页面时会重复追加音频项,未初始化的列表直接调用
add会触发空指针错误 - 生命周期顺序错误:
super.initState()未放在方法最前,不符合Flutter生命周期规范 - 缺少边界兜底:未处理API返回字段为空、startIndex越界、多播放器实例冲突的场景,动态数据下极易触发运行时异常
- 静态列表可运行是因为写死数据时不存在字段缺失、资源类型错误的问题,动态API返回数据时上述问题会直接暴露。
修复后完整代码
// 状态变量提前初始化,避免空指针 late int currentPlayIndex; late List<dynamic> songListFromApi; List<Audio> audioPlaylist = []; final AssetsAudioPlayer audioPlayer = AssetsAudioPlayer(); @override void initState() { // 生命周期方法必须先调用super super.initState(); currentPlayIndex = widget.data['index']; songListFromApi = widget.data['songs']; // 先构建音频列表,再初始化播放器 _buildAudioListFromJson(); _initPlayer(); } @override void dispose() { audioPlayer.dispose(); // 用dispose替代stop,彻底释放播放器资源,避免内存泄漏 super.dispose(); } /// 从接口返回的JSON数据构建音频列表 void _buildAudioListFromJson() { // 进入页面先清空旧列表,避免重复追加音频 audioPlaylist.clear(); for (var i = 0; i < songListFromApi.length; i++) { final songItem = songListFromApi[i]; final fields = songItem['fields']; // 字段空值兜底,跳过结构异常的条目 if (fields == null) continue; final audioRes = fields['audio']; final coverRes = fields['cover']; if (audioRes == null || audioRes.isEmpty || coverRes == null || coverRes.isEmpty) { continue; } final audioUrl = audioRes[0]['url'].toString(); final songTitle = fields['name']?.toString() ?? '未知音频'; final songSubtitle = fields['type']?.toString() ?? ''; final coverUrl = coverRes[0]['url'].toString(); audioPlaylist.add( Audio.network( audioUrl, metas: Metas( title: songTitle, artist: songSubtitle, // 网络图片必须使用MetasImage.network构造,禁止用file构造加载网络URL image: MetasImage.network(coverUrl) ), ), ); } } /// 初始化播放器,加载播放列表 void _initPlayer() async { if (audioPlaylist.isEmpty) return; // 先释放之前的播放实例,避免多实例冲突 await audioPlayer.stop(); await audioPlayer.open( Playlist( audios: audioPlaylist, // 对起始索引做边界 clamp,避免传入索引超出列表长度触发崩溃 startIndex: currentPlayIndex.clamp(0, audioPlaylist.length - 1), ), autoStart: false, showNotification: true, loopMode: LoopMode.none, ); } // 上一首/下一首切换直接调用对应方法即可,播放器会自动按列表顺序切换 // 切上一首: audioPlayer.previous() // 切下一首: audioPlayer.next()
适配异步拉取接口场景的补充说明
如果歌曲列表不是从上一个页面通过widget传递,而是进入播放器页后才异步请求接口获取,需要将接口请求逻辑放在async方法中,await请求完成、列表构建结束后再调用_initPlayer()方法,请求过程中可加loading态避免UI异常。轮播组件的初始索引需要和传入的currentPlayIndex保持一致,避免UI显示位置和实际播放位置错位。
内容的提问来源于stack exchange,提问作者Saturn
相关产品推荐
相关产品推荐

