Flutter聊天应用多音频播放器非同时播放实现方案咨询
推荐方案:使用audioplayers包
针对聊天应用非同时播放语音的场景,audioplayers是更稳定的选择——轻量、API简洁,对本地assets/本地文件/网络音频的支持都很完善,且单实例即可满足“播放一条时自动停止上一条”的需求,无需维护多个Player实例。
核心解决思路
因为不需要同时播放多音频,所以复用一个AudioPlayer实例即可:
- 点击新语音消息时,先停止当前正在播放的音频
- 再加载并播放新的音频
- 配合UI状态(比如播放状态图标切换),实现单条语音的独立控制
步骤与代码示例
1. 引入依赖
在pubspec.yaml中添加:
dependencies: audioplayers: ^4.0.1 # 请使用最新稳定版本
2. 正确配置Assets(如果音频是打包在App内的)
如果你的语音文件是放在assets/audio/目录下,必须在pubspec.yaml中声明:
flutter: assets: - assets/audio/1.wav - assets/audio/2.wav # 或者用通配符匹配所有文件:- assets/audio/
3. 核心实现代码
以下是单页面内的语音消息播放器示例,复用一个Player实例:
import 'package:audioplayers/audioplayers.dart'; import 'package:flutter/material.dart'; class ChatVoiceMessage extends StatefulWidget { final String audioPath; // 语音路径:assets路径或本地文件路径 final bool isAsset; // 是否是assets内的文件 const ChatVoiceMessage({ super.key, required this.audioPath, this.isAsset = true, }); @override State<ChatVoiceMessage> createState() => _ChatVoiceMessageState(); } class _ChatVoiceMessageState extends State<ChatVoiceMessage> { final AudioPlayer _audioPlayer = AudioPlayer(); PlayerState _playerState = PlayerState.stopped; String? _currentPlayingPath; // 记录当前正在播放的语音路径 @override void initState() { super.initState(); // 监听播放状态变化 _audioPlayer.onPlayerStateChanged.listen((state) { setState(() { _playerState = state; }); }); } Future<void> _togglePlay() async { // 如果当前播放的是这条语音,切换播放/暂停 if (_currentPlayingPath == widget.audioPath) { if (_playerState == PlayerState.playing) { await _audioPlayer.pause(); } else if (_playerState == PlayerState.paused) { await _audioPlayer.resume(); } } else { // 播放新的语音,先停止之前的 await _audioPlayer.stop(); // 根据类型加载音频 Source audioSource = widget.isAsset ? AssetSource(widget.audioPath) : DeviceFileSource(widget.audioPath); await _audioPlayer.play(audioSource); setState(() { _currentPlayingPath = widget.audioPath; }); } } @override void dispose() { _audioPlayer.dispose(); // 页面销毁时释放资源 super.dispose(); } @override Widget build(BuildContext context) { return InkWell( onTap: _togglePlay, child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( _currentPlayingPath == widget.audioPath && _playerState == PlayerState.playing ? Icons.pause_circle : Icons.play_circle, color: Colors.blue, size: 32, ), const SizedBox(width: 8), Text(_currentPlayingPath == widget.audioPath && _playerState == PlayerState.playing ? '正在播放' : '点击播放'), ], ), ); } } // 使用示例 void main() { runApp(MaterialApp( home: Scaffold( body: Column( children: [ ChatVoiceMessage(audioPath: 'assets/audio/1.wav'), ChatVoiceMessage(audioPath: 'assets/audio/2.wav'), // 如果是本地文件路径(比如下载到手机的音频): // ChatVoiceMessage(audioPath: '/storage/emulated/0/Download/voice.wav', isAsset: false), ], ), ), )); }
针对你之前踩坑的说明
- just_audio多实例问题:用audioplayers的单实例复用方案,避免维护多个Player实例的复杂度,也减少资源占用。
- audio_waveforms路径错误:assets内的音频不需要用
getApplicationDocumentsDirectory(),因为assets是打包在App安装包内的,不是存在文档目录里的,正确的方式是直接用AssetSource或者rootBundle获取路径,而audio_waveforms的路径参数如果是assets,需要用asset://前缀(比如asset://assets/audio/1.wav),但audioplayers的AssetSource已经帮你处理了这些细节。
内容的提问来源于stack exchange,提问作者EgenSolve
相关产品推荐
相关产品推荐

