MakabakaGoodnight 2025-09-09 11:08 采纳率: 0%
浏览 9

audioplayers中如何边接受边播放mp3的音频流?

由于我的音频文件数据是接口返回的uint8List(addStreamChunk方法),需要拼接,但要求立即播放,所以我设置了一个缓冲区,边接收边播放。但是现在有新的音频片段时,只能在_safePlay方法中重新调用_audioPlayer.resume()才能播放。否则,只添加_audioPlayer.setSourceBytes(audioData)是不会播放新添加的语音片段的,会导致播放卡顿。那么该如何修改才能保证每次添加新的音频片段时不用调用resume呢?

import 'dart:async';
import 'dart:typed_data';
import 'package:audioplayers/audioplayers.dart' as audio;
import 'package:synchronized/synchronized.dart';

class AudioManager {
  static AudioManager? _instance;
  static final Lock _instanceLock = Lock();

  factory AudioManager() {
    return _instance ??= AudioManager._internal();
  }

  static Future<AudioManager> get instance async {
    return await _instanceLock.synchronized(() async {
      _instance ??= AudioManager._internal();
      return _instance!;
    });
  }

  AudioManager._internal() {
    _init();
  }

  final audio.AudioPlayer _audioPlayer = audio.AudioPlayer();
  final Lock _playLock = Lock();
  final List<int> _audioBuffer = [];
  bool _isStreamMode = false;
  bool _isPlaying = false;
  bool _isPaused = false;
  Completer<void>? _streamCompleter;
  Timer? _streamCompletionTimer;
  bool _streamFinished = false; // 标记流是否已结束
  bool _hasStartedPlaying = false; // 标记是否已开始播放,用于快速启动

  static const int _minBufferSize = 16384; // 16KB - 减少初始缓冲减少卡顿
  static const int _maxBufferSize = 32768; // 32KB - 减少最大缓冲提高响应性
  static const Duration _streamCompletionDelay = Duration(milliseconds: 200); // 进一步优化延迟
  static const Duration _playerResetDelay = Duration(milliseconds: 30); // 进一步减少重置延迟
  static const Duration _stateStabilizationDelay = Duration(milliseconds: 20); // 减少状态稳定延迟

  // ========== 公共属性 ==========
  bool get isStreamMode => _isStreamMode;
  bool get isPlaying => _isPlaying;
  bool get isPaused => _isPaused;
  int get bufferSize => _audioBuffer.length;
  audio.PlayerState get playerState => _audioPlayer.state;
  int get minBufferSize => _minBufferSize;
  int get maxBufferSize => _maxBufferSize;

  // ========== 初始化 ==========
  Future<void> _init() async {
    await _audioPlayer.setPlayerMode(audio.PlayerMode.mediaPlayer); // 使用mediaPlayer模式支持字节源
    await _audioPlayer.setReleaseMode(audio.ReleaseMode.stop);
    await _audioPlayer.setVolume(0.8); // 设置适当音量消除电流麦
    print('🎵 AudioManager 初始化完成 - 媒体播放器模式,音量0.8');
  }

  // ========== 公共方法 ==========

  /// 添加音频流数据块
  Future<void> addStreamChunk(Uint8List chunk) async {
    await _playLock.synchronized(() async {
      if (!_isStreamMode) {
        await _startStreamPlaybackInternal();
      }

      _audioBuffer.addAll(chunk);
      print(
          '📦 添加音频块: ${chunk.length} bytes, 总缓冲: ${_audioBuffer.length} bytes');

      _startStreamCompletionTimer();

      // 快速启动机制:第一次收到数据且有足够音频时立即播放
      if (!_hasStartedPlaying && !_isPlaying && _audioBuffer.length >= 8192) { // 8KB快速启动
        print('🚀 快速启动播放: ${_audioBuffer.length} bytes');
        _hasStartedPlaying = true;
        await _playBufferedAudio();
      }
      // 优先检查是否达到最大缓冲
      else if (!_isPlaying && _audioBuffer.length >= _maxBufferSize) {
        print('📊 缓冲区达到最大值 ($_maxBufferSize bytes),开始播放');
        await _playBufferedAudio();
      }
      // 其次检查是否达到最小缓冲
      else if (!_isPlaying && _audioBuffer.length >= _minBufferSize) {
        print('📊 缓冲区达到最小值 ($_minBufferSize bytes),开始播放');
        await _playBufferedAudio();
      }
    });
  }

  /// 标记流式播放完成(不再接收新数据)
  Future<void> finishStreamPlayback() async {
    await _playLock.synchronized(() async {
      if (!_isStreamMode || _streamFinished) return;

      print('🏁 标记流式播放完成,等待剩余音频播放完毕...');
      _streamFinished = true;
      _streamCompletionTimer?.cancel();
      _streamCompletionTimer = null;

      // 如果没有正在播放且缓冲区为空,立即停止
      if (!_isPlaying && _audioBuffer.isEmpty) {
        await _stopStreamPlaybackInternal();
        print('✅ 流式播放完成');
      }
      // 如果有数据但没在播放,播放剩余数据
      else if (!_isPlaying && _audioBuffer.isNotEmpty) {
        await _playBufferedAudio();
      }
      // 如果正在播放,等待播放完成回调处理剩余数据
    });
  }

  /// 暂停播放
  Future<void> pause() async {
    await _playLock.synchronized(() async {
      if (_isPlaying && !_isPaused) {
        await _audioPlayer.pause();
        _isPaused = true;
        print('⏸️ 音频已暂停');
      }
    });
  }

  /// 恢复播放
  Future<void> resume() async {
    await _playLock.synchronized(() async {
      if (_isPlaying && _isPaused) {
        await _audioPlayer.resume();
        _isPaused = false;
        print('▶️ 音频已恢复');
      }
    });
  }

  /// 立即停止当前流播放
  Future<void> forceStopStream() async {
    await _playLock.synchronized(() async {
      await _forceStopInternal();
    });
  }

  /// 释放资源
  Future<void> dispose() async {
    await _playLock.synchronized(() async {
      await _forceStopInternal();
      await _audioPlayer.dispose();
      print('🗑️ AudioManager 资源已释放');
    });
  }

  // ========== 内部方法 ==========

  Future<void> _startStreamPlaybackInternal() async {
    if (_isStreamMode) {
      print('⚠️ 检测到正在进行的流式播放,先停止旧的流');
      await _forceStopInternal();
      await Future.delayed(_playerResetDelay);
      // 状态稳定延迟确保旧流完全停止
      await Future.delayed(_stateStabilizationDelay);
    }

    print('🎵 开始流式播放会话');
    _isStreamMode = true;
    _isPlaying = false;
    _isPaused = false;
    _streamFinished = false;
    _hasStartedPlaying = false; // 重置快速启动标志
    _audioBuffer.clear();
    _streamCompleter = Completer<void>();

    await _resetPlayer();
    _startStreamCompletionTimer();
  }

  void _startStreamCompletionTimer() {
    _streamCompletionTimer?.cancel();
    _streamCompletionTimer = Timer(_streamCompletionDelay, () async {
      await _playLock.synchronized(() async {
        if (_isStreamMode && _audioBuffer.isNotEmpty && !_isPlaying) {
          print('⏰ 流结束定时器触发,播放剩余数据 (${_audioBuffer.length} bytes)');
          await _playBufferedAudio();
        }
      });
    });
  }

  Future<void> _playBufferedAudio() async {
    if (_isPlaying || _audioBuffer.isEmpty) return;

    _isPlaying = true;
    _isPaused = false;

    try {
      final bytesToPlay = _audioBuffer.length > _maxBufferSize
          ? _audioBuffer.sublist(0, _maxBufferSize)
          : _audioBuffer;

      final audioData = Uint8List.fromList(bytesToPlay);

      if (_audioBuffer.length > _maxBufferSize) {
        _audioBuffer.removeRange(0, _maxBufferSize);
      } else {
        _audioBuffer.clear();
      }

      print(
          '▶️ 开始播放 ${audioData.length} bytes, 剩余缓冲: ${_audioBuffer.length} bytes');
      await _safePlay(audioData);
      _setupPlaybackCompletionListener();
    } catch (e) {
      print('❌ 播放音频时出错: $e');
      await _handlePlaybackError();
    }
  }

  void _setupPlaybackCompletionListener() {
    _audioPlayer.onPlayerComplete.first.then((_) async {
      await _playLock.synchronized(() async {
        // 状态稳定延迟确保状态一致性
        await Future.delayed(_stateStabilizationDelay);

        _isPlaying = false;
        _isPaused = false;

        if (_audioBuffer.isNotEmpty) {
          print('🔄 播放完成,检查新数据: ${_audioBuffer.length} bytes 待播放');
          // 立即播放下一段,保持流畅性
          await _playBufferedAudio();
        } else if (_streamFinished) {
          // 所有数据已播放完毕,完成流式播放
          await _stopStreamPlaybackInternal();
          print('✅ 所有音频数据播放完成');
        }
      });
    }).catchError((e) async {
      print('❌ 播放完成监听错误: $e');
      await _playLock.synchronized(() async {
        _isPlaying = false;
        _isPaused = false;
        // 错误时也需要状态稳定延迟
        await Future.delayed(_stateStabilizationDelay);
      });
    });
  }

  Future<void> _safePlay(Uint8List audioData) async {
    try {
      // 确保播放器状态一致性
      await _audioPlayer.stop();
      await Future.delayed(_playerResetDelay);

      // 状态稳定延迟消除电流麦
      await Future.delayed(_stateStabilizationDelay);

      await _audioPlayer.setSourceBytes(audioData);

      // 再次确保状态稳定
      await Future.delayed(_stateStabilizationDelay);

      await _audioPlayer.resume();
      _isPaused = false;
    } catch (e) {
      print('❌ 安全播放失败: $e');
      await _handlePlaybackError();
      rethrow;
    }
  }



  Future<void> _stopStreamPlaybackInternal() async {
    if (!_isStreamMode) return;

    print('⏹️ 停止流式播放');
    _isStreamMode = false;
    _isPlaying = false;
    _isPaused = false;
    _streamFinished = false;
    _hasStartedPlaying = false; // 重置快速启动标志
    _audioBuffer.clear();

    _streamCompletionTimer?.cancel();
    _streamCompletionTimer = null;

    try {
      await _audioPlayer.stop();
      // 状态稳定延迟确保播放器完全停止
      await Future.delayed(_stateStabilizationDelay);
    } catch (e) {
      print('停止播放器时出错: $e');
    }

    if (_streamCompleter != null && !_streamCompleter!.isCompleted) {
      _streamCompleter!.complete();
    }
    _streamCompleter = null;
  }

  Future<void> _forceStopInternal() async {
    print('🛑 强制停止当前音频流');
    _isStreamMode = false;
    _isPlaying = false;
    _isPaused = false;
    _streamFinished = false;
    _hasStartedPlaying = false; // 重置快速启动标志
    _audioBuffer.clear();

    _streamCompletionTimer?.cancel();
    _streamCompletionTimer = null;

    try {
      await _audioPlayer.stop();
      // 状态稳定延迟确保强制停止完全生效
      await Future.delayed(_stateStabilizationDelay);
    } catch (e) {
      print('强制停止播放器时出错: $e');
    }

    if (_streamCompleter != null && !_streamCompleter!.isCompleted) {
      _streamCompleter!.complete();
    }
    _streamCompleter = null;
  }

  Future<void> _resetPlayer() async {
    try {
      await _audioPlayer.stop();
      await Future.delayed(_playerResetDelay);
      // 状态稳定延迟确保播放器完全重置
      await Future.delayed(_stateStabilizationDelay);
    } catch (e) {
      print('重置播放器时出错: $e');
    }
  }

  Future<void> _handlePlaybackError() async {
    _isPlaying = false;
    _isPaused = false;
    try {
      await _resetPlayer();
      // 错误处理后的状态稳定延迟
      await Future.delayed(_stateStabilizationDelay);
    } catch (e) {
      print('重置播放器失败: $e');
    }
  }

  Future<double> getAudioDuration(Uint8List audioData) async {
    try {
      await _audioPlayer.setSourceBytes(audioData);
      final duration = await _audioPlayer.getDuration();
      return duration != null ? duration.inMilliseconds / 1000.0 : 0;
    } catch (e) {
      print('获取音频时长失败: $e');
      return 0;
    } finally {
      await _audioPlayer.stop();
    }
  }
}


  • 写回答

4条回答 默认 最新

  • 紫雾凌寒 新星创作者: 人工智能技术领域 2025-09-09 11:09
    关注

    让 “紫雾凌寒” 来帮你回答,本回答结合 DeepSeek

    如果还有问题,请评论或点击头像私信,如果有帮助到您, 请关注、采纳、点赞

    1. 关键点分析:
      - 要在audioplayers中实现边接收接口返回的uint8List音频流数据并拼接,同时立即播放。
      - 遇到新音频片段时,只能在特定位置播放的问题,可能涉及到音频流的拼接和播放控制逻辑。
    2. 解决方案:
      - 首先,需要使用audioplayersAudioPlayer来进行播放。
      - 对于音频流的拼接,可以将每次接收到的uint8List数据追加到一个总的缓冲区中。
      - 然后,当缓冲区有足够的数据时(例如达到一定长度或者新数据接收完成),创建一个临时的音频源(如AudioSource.memory)来播放缓冲区中的数据。

    以下是一个简单的示例代码:

    ```dart
    import ‘package:audioplayers/audioplayers.dart’;

    class AudioStreamPlayer {
    final AudioPlayer _audioPlayer = AudioPlayer();
    Uint8List buffer = Uint8List(0);

     void addStreamChunk(Uint8List chunk) {
       buffer = Uint8List.fromList([...buffer, ...chunk]);
       if (buffer.length >= 1024) { // 假设达到1024字节开始播放,可根据实际情况调整
         _playBuffer();
       }
     }
    
     void _playBuffer() async {
       await _audioPlayer.play(AudioSource.memory(buffer));
       buffer = Uint8List(0); // 播放完后清空缓冲区
     }
    
     void dispose() {
       _audioPlayer.dispose();
     }
    

    }
    ```

    在使用时:

    dart void main() async { AudioStreamPlayer player = AudioStreamPlayer(); // 模拟接收音频流数据 for (int i = 0; i < 5; i++) { Uint8List chunk = Uint8List.fromList(List.generate(256, (index) => index.toByte())); player.addStreamChunk(chunk); } await Future.delayed(Duration(seconds: 5)); player.dispose(); }
    3. 多种解决方案及优缺点:
    - 方案一:每次接收数据都直接播放
    - 优点:实现简单,能及时播放新接收的数据。
    - 缺点:如果数据接收速度不稳定,可能会导致播放卡顿或者数据丢失。例如,当接收数据较慢时,可能会出现音频不连续的情况。
    - 方案二:缓冲区达到一定长度后播放
    - 优点:可以保证每次播放的数据量相对稳定,减少播放卡顿的可能性。
    - 缺点:可能会导致新接收的数据等待时间较长才播放,尤其是在数据接收较快时,缓冲区会占用较多内存。
    4. 总结:
    - 通过创建一个缓冲区来接收和拼接音频流数据,并在缓冲区达到一定条件时使用audioplayersAudioSource.memory来播放数据,可以实现边接收边播放音频流的功能。
    - 不同的缓冲区处理策略各有优缺点,可根据实际需求进行选择和调整。例如,如果对播放实时性要求极高,可考虑每次接收数据都直接播放;如果更注重播放的稳定性,缓冲区达到一定长度后播放的方案可能更合适。

    请注意,上述代码仅为示例,实际应用中可能需要根据具体的音频数据格式、网络情况等进行更细致的调整和优化。

    希望以上解答对您有所帮助。如果您有任何疑问,欢迎在评论区提出。

    评论

报告相同问题?

问题事件

  • 创建了问题 9月9日