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

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),
        ],
      ),
    ),
  ));
}

针对你之前踩坑的说明

  1. just_audio多实例问题:用audioplayers的单实例复用方案,避免维护多个Player实例的复杂度,也减少资源占用。
  2. audio_waveforms路径错误:assets内的音频不需要用getApplicationDocumentsDirectory(),因为assets是打包在App安装包内的,不是存在文档目录里的,正确的方式是直接用AssetSource或者rootBundle获取路径,而audio_waveforms的路径参数如果是assets,需要用asset://前缀(比如asset://assets/audio/1.wav),但audioplayers的AssetSource已经帮你处理了这些细节。

内容的提问来源于stack exchange,提问作者EgenSolve

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 08:30:25