Flutter ListView.Builder音频播放实现:求支持空安全的插件及示例代码
Flutter 网络音频播放适配方案
推荐插件:audioplayers
该插件是目前Flutter生态中活跃度最高的音频播放组件之一,完全满足需求:
- 支持空安全
- 适配全平台(Android、iOS、Web、桌面端)
- 支持网络音频、本地音频、Assets音频播放
- 自带播放状态、进度、完成、错误等全场景监听回调
- 支持后台播放、音量调节、播放速度控制等扩展能力
集成步骤
1. 添加依赖
在项目pubspec.yaml文件的依赖区块添加配置:
dependencies: flutter: sdk: flutter audioplayers: ^4.0.1 # 可替换为当前最新稳定版本
添加完成后执行flutter pub get完成依赖安装。
2. 平台权限配置
如果仅播放HTTPS协议的网络音频,可跳过iOS配置,Android必须配置网络权限:
- Android端:在
android/app/src/main/AndroidManifest.xml的<manifest>标签下添加权限声明:<uses-permission android:name="android.permission.INTERNET" /> - iOS端:如果需要播放HTTP协议的音频,在
ios/Runner/Info.plist的<dict>标签下添加配置:<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict>
适配歌曲列表场景的完整示例代码
以下代码已经和ListView.Builder的歌曲列表逻辑打通,自带播放控制、进度展示、状态切换能力:
import 'package:audioplayers/audioplayers.dart'; import 'package:flutter/material.dart'; // 自定义歌曲数据模型,可根据业务调整 class Song { final String name; final String networkUrl; final Duration totalDuration; Song({ required this.name, required this.networkUrl, required this.totalDuration, }); } class SongPlayPage extends StatefulWidget { const SongPlayPage({super.key}); @override State<SongPlayPage> createState() => _SongPlayPageState(); } class _SongPlayPageState extends State<SongPlayPage> { final AudioPlayer _audioPlayer = AudioPlayer(); // 记录当前正在播放的歌曲下标,为null表示没有正在播放的音频 int? _currentPlayingIndex; // 记录当前播放进度 Duration _currentPlayPosition = Duration.zero; // 示例歌曲列表,替换为实际业务数据即可 final List<Song> _songList = [ Song( name: "测试歌曲1", networkUrl: "替换为你的音频网络地址", totalDuration: const Duration(minutes: 3, seconds: 18), ), Song( name: "测试歌曲2", networkUrl: "替换为你的音频网络地址", totalDuration: const Duration(minutes: 4, seconds: 02), ), ]; @override void initState() { super.initState(); // 监听播放进度更新 _audioPlayer.onPositionChanged.listen((position) { setState(() { _currentPlayPosition = position; }); }); // 监听播放完成事件,自动重置状态 _audioPlayer.onPlayerComplete.listen((_) { setState(() { _currentPlayingIndex = null; _currentPlayPosition = Duration.zero; }); }); } // 播放/暂停控制逻辑 Future<void> _togglePlay(int songIndex) async { if (_currentPlayingIndex == songIndex) { // 点击的是当前正在播放的歌曲,切换播放/暂停状态 if (_audioPlayer.state == PlayerState.playing) { await _audioPlayer.pause(); } else { await _audioPlayer.resume(); } } else { // 点击的是新歌曲,停止之前的播放后加载新音频播放 await _audioPlayer.stop(); await _audioPlayer.play(UrlSource(_songList[songIndex].networkUrl)); _currentPlayingIndex = songIndex; _currentPlayPosition = Duration.zero; } setState(() {}); } // 格式化时长为mm:ss格式 String _formatTime(Duration duration) { String twoDigits(int num) => num.toString().padLeft(2, '0'); final minute = twoDigits(duration.inMinutes.remainder(60)); final second = twoDigits(duration.inSeconds.remainder(60)); return "$minute:$second"; } @override void dispose() { // 页面销毁时释放音频资源 _audioPlayer.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("歌曲列表")), body: ListView.builder( itemCount: _songList.length, itemBuilder: (context, index) { final currentSong = _songList[index]; final isCurrentPlaying = _currentPlayingIndex == index; // 计算进度条进度 final playProgress = isCurrentPlaying && currentSong.totalDuration.inSeconds > 0 ? _currentPlayPosition.inSeconds / currentSong.totalDuration.inSeconds : 0.0; return ListTile( leading: IconButton( icon: Icon( isCurrentPlaying && _audioPlayer.state == PlayerState.playing ? Icons.pause_circle : Icons.play_circle, size: 32, ), onPressed: () => _togglePlay(index), ), title: Text(currentSong.name), subtitle: Padding( padding: const EdgeInsets.only(top: 8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ LinearProgressIndicator( value: playProgress, minHeight: 4, ), const SizedBox(height: 4), Text( "${_formatTime(isCurrentPlaying ? _currentPlayPosition : Duration.zero)} / ${_formatTime(currentSong.totalDuration)}", style: const TextStyle(fontSize: 12, color: Colors.grey), ), ], ), ), ); }, ), ); } }
注意事项
- 示例中的音频网络地址需要替换为有效音频链接,优先使用HTTPS协议地址减少适配成本
- 若出现播放失败的情况,可通过监听
_audioPlayer.onPlayerError回调获取具体错误信息排查问题 - 如果需要播放本地Assets目录下的音频,只需要将
UrlSource替换为AssetSource,传入assets路径即可
内容的提问来源于stack exchange,提问作者abhijith
相关产品推荐
相关产品推荐

