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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 21:15:48