Since my audio file data is returned by the interface as a uint8List (addStreamChunk method), I need to splice it, but the requirement is to play it immediately, so I set up a buffer to play it while receiving. However, now when there is a new audio clip, it can only be played by re-calling _audioPlayer.resume() in the _safePlay method. Otherwise, only adding _audioPlayer.setSourceBytes(audioData) will not play the newly added voice clip, which will cause playback to be stuck. So how can I modify it to ensure that I don't have to call resume every time a new clip is added?
`
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 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 _audioBuffer = [];
bool _isStreamMode = false;
bool _isPlaying = false;
bool _isPaused = false;
Completer? _streamCompleter;
Timer? _streamCompletionTimer;
bool _streamFinished = false; // Flag to mark if stream has ended
bool _hasStartedPlaying = false; // Flag to mark if playback has started for quick start
static const int _minBufferSize = 16384; // 16KB - Reduced initial buffer to minimize stuttering
static const int _maxBufferSize = 32768; // 32KB - Reduced max buffer for better responsiveness
static const Duration _streamCompletionDelay = Duration(milliseconds: 200); // Further optimized delay
static const Duration _playerResetDelay = Duration(milliseconds: 30); // Further reduced reset delay
static const Duration _stateStabilizationDelay = Duration(milliseconds: 20); // Reduced state stabilization delay
// ========== Public Properties ==========
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;
// ========== Initialization ==========
Future _init() async {
await _audioPlayer.setPlayerMode(audio.PlayerMode.mediaPlayer); // Use mediaPlayer mode for byte source
await _audioPlayer.setReleaseMode(audio.ReleaseMode.stop);
await _audioPlayer.setVolume(0.8); // Set appropriate volume to eliminate static noise
print('🎵 AudioManager initialized - MediaPlayer mode, Volume 0.8');
}
// ========== Public Methods ==========
/// Add audio stream chunk
Future addStreamChunk(Uint8List chunk) async {
await _playLock.synchronized(() async {
if (!_isStreamMode) {
await _startStreamPlaybackInternal();
}
_audioBuffer.addAll(chunk);
print(
'📦 Added audio chunk: ${chunk.length} bytes, Total buffer: ${_audioBuffer.length} bytes');
_startStreamCompletionTimer();
// Quick start mechanism: Start playing immediately on first data with sufficient audio
if (!_hasStartedPlaying && !_isPlaying && _audioBuffer.length >= 8192) { // 8KB quick start
print('🚀 Quick start playback: ${_audioBuffer.length} bytes');
_hasStartedPlaying = true;
await _playBufferedAudio();
}
// Priority check: if max buffer is reached
else if (!_isPlaying && _audioBuffer.length >= _maxBufferSize) {
print('📊 Buffer reached maximum ($_maxBufferSize bytes), starting playback');
await _playBufferedAudio();
}
// Secondary check: if min buffer is reached
else if (!_isPlaying && _audioBuffer.length >= _minBufferSize) {
print('📊 Buffer reached minimum ($_minBufferSize bytes), starting playback');
await _playBufferedAudio();
}
});
}
/// Mark stream playback as finished (no more data will be received)
Future finishStreamPlayback() async {
await _playLock.synchronized(() async {
if (!_isStreamMode || _streamFinished) return;
print('🏁 Marking stream playback as finished, waiting for remaining audio to play...');
_streamFinished = true;
_streamCompletionTimer?.cancel();
_streamCompletionTimer = null;
// If not playing and buffer is empty, stop immediately
if (!_isPlaying && _audioBuffer.isEmpty) {
await _stopStreamPlaybackInternal();
print('✅ Stream playback completed');
}
// If there is data but not playing, play the remaining data
else if (!_isPlaying && _audioBuffer.isNotEmpty) {
await _playBufferedAudio();
}
// If currently playing, wait for completion callback to handle remaining data
});
}
/// Pause playback
Future pause() async {
await _playLock.synchronized(() async {
if (_isPlaying && !_isPaused) {
await _audioPlayer.pause();
_isPaused = true;
print('⏸️ Audio paused');
}
});
}
/// Resume playback
Future resume() async {
await _playLock.synchronized(() async {
if (_isPlaying && _isPaused) {
await _audioPlayer.resume();
_isPaused = false;
print('▶️ Audio resumed');
}
});
}
/// Immediately stop current stream playback
Future forceStopStream() async {
await _playLock.synchronized(() async {
await _forceStopInternal();
});
}
/// Release resources
Future dispose() async {
await _playLock.synchronized(() async {
await _forceStopInternal();
await _audioPlayer.dispose();
print('🗑️ AudioManager resources released');
});
}
// ========== Internal Methods ==========
Future _startStreamPlaybackInternal() async {
if (_isStreamMode) {
print('⚠️ Detected ongoing stream playback, stopping old stream first');
await _forceStopInternal();
await Future.delayed(_playerResetDelay);
// State stabilization delay ensures old stream is fully stopped
await Future.delayed(_stateStabilizationDelay);
}
print('🎵 Starting new stream playback session');
_isStreamMode = true;
_isPlaying = false;
_isPaused = false;
_streamFinished = false;
_hasStartedPlaying = false; // Reset quick start flag
_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('⏰ Stream completion timer triggered, playing remaining data (${_audioBuffer.length} bytes)');
await _playBufferedAudio();
}
});
});
}
Future _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(
'▶️ Playing ${audioData.length} bytes, Remaining buffer: ${_audioBuffer.length} bytes');
await _safePlay(audioData);
_setupPlaybackCompletionListener();
} catch (e) {
print('❌ Error playing audio: $e');
await _handlePlaybackError();
}
}
void _setupPlaybackCompletionListener() {
audioPlayer.onPlayerComplete.first.then(() async {
await _playLock.synchronized(() async {
// State stabilization delay ensures state consistency
await Future.delayed(_stateStabilizationDelay);
_isPlaying = false;
_isPaused = false;
if (_audioBuffer.isNotEmpty) {
print('🔄 Playback completed, checking for new data: ${_audioBuffer.length} bytes pending');
// Play next segment immediately to maintain smoothness
await _playBufferedAudio();
} else if (_streamFinished) {
// All data played, finish stream playback
await _stopStreamPlaybackInternal();
print('✅ All audio data playback completed');
}
});
}).catchError((e) async {
print('❌ Playback completion listener error: $e');
await _playLock.synchronized(() async {
_isPlaying = false;
_isPaused = false;
// State stabilization delay needed on error as well
await Future.delayed(_stateStabilizationDelay);
});
});
}
Future _safePlay(Uint8List audioData) async {
try {
// Ensure player state consistency
await _audioPlayer.stop();
await Future.delayed(_playerResetDelay);
// State stabilization delay to eliminate static noise
await Future.delayed(_stateStabilizationDelay);
await _audioPlayer.setSourceBytes(audioData);
// Ensure state stability again
await Future.delayed(_stateStabilizationDelay);
await _audioPlayer.resume();
_isPaused = false;
} catch (e) {
print('❌ Safe play failed: $e');
await _handlePlaybackError();
rethrow;
}
}
Future _stopStreamPlaybackInternal() async {
if (!_isStreamMode) return;
print('⏹️ Stopping stream playback');
_isStreamMode = false;
_isPlaying = false;
_isPaused = false;
_streamFinished = false;
_hasStartedPlaying = false; // Reset quick start flag
_audioBuffer.clear();
_streamCompletionTimer?.cancel();
_streamCompletionTimer = null;
try {
await _audioPlayer.stop();
// State stabilization delay ensures player is fully stopped
await Future.delayed(_stateStabilizationDelay);
} catch (e) {
print('Error stopping player: $e');
}
if (_streamCompleter != null && !_streamCompleter!.isCompleted) {
_streamCompleter!.complete();
}
_streamCompleter = null;
}
Future _forceStopInternal() async {
print('🛑 Forcibly stopping current audio stream');
_isStreamMode = false;
_isPlaying = false;
_isPaused = false;
_streamFinished = false;
_hasStartedPlaying = false; // Reset quick start flag
_audioBuffer.clear();
_streamCompletionTimer?.cancel();
_streamCompletionTimer = null;
try {
await _audioPlayer.stop();
// State stabilization delay ensures force stop is fully effective
await Future.delayed(_stateStabilizationDelay);
} catch (e) {
print('Error force stopping player: $e');
}
if (_streamCompleter != null && !_streamCompleter!.isCompleted) {
_streamCompleter!.complete();
}
_streamCompleter = null;
}
Future _resetPlayer() async {
try {
await _audioPlayer.stop();
await Future.delayed(_playerResetDelay);
// State stabilization delay ensures player is fully reset
await Future.delayed(_stateStabilizationDelay);
} catch (e) {
print('Error resetting player: $e');
}
}
Future _handlePlaybackError() async {
_isPlaying = false;
_isPaused = false;
try {
await _resetPlayer();
// State stabilization delay after error handling
await Future.delayed(_stateStabilizationDelay);
} catch (e) {
print('Failed to reset player: $e');
}
}
Future 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('Failed to get audio duration: $e');
return 0;
} finally {
await _audioPlayer.stop();
}
}
}
`
Since my audio file data is returned by the interface as a uint8List (addStreamChunk method), I need to splice it, but the requirement is to play it immediately, so I set up a buffer to play it while receiving. However, now when there is a new audio clip, it can only be played by re-calling _audioPlayer.resume() in the _safePlay method. Otherwise, only adding _audioPlayer.setSourceBytes(audioData) will not play the newly added voice clip, which will cause playback to be stuck. So how can I modify it to ensure that I don't have to call resume every time a new clip is added?
`
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 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 _audioBuffer = [];
bool _isStreamMode = false;
bool _isPlaying = false;
bool _isPaused = false;
Completer? _streamCompleter;
Timer? _streamCompletionTimer;
bool _streamFinished = false; // Flag to mark if stream has ended
bool _hasStartedPlaying = false; // Flag to mark if playback has started for quick start
static const int _minBufferSize = 16384; // 16KB - Reduced initial buffer to minimize stuttering
static const int _maxBufferSize = 32768; // 32KB - Reduced max buffer for better responsiveness
static const Duration _streamCompletionDelay = Duration(milliseconds: 200); // Further optimized delay
static const Duration _playerResetDelay = Duration(milliseconds: 30); // Further reduced reset delay
static const Duration _stateStabilizationDelay = Duration(milliseconds: 20); // Reduced state stabilization delay
// ========== Public Properties ==========
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;
// ========== Initialization ==========
Future _init() async {
await _audioPlayer.setPlayerMode(audio.PlayerMode.mediaPlayer); // Use mediaPlayer mode for byte source
await _audioPlayer.setReleaseMode(audio.ReleaseMode.stop);
await _audioPlayer.setVolume(0.8); // Set appropriate volume to eliminate static noise
print('🎵 AudioManager initialized - MediaPlayer mode, Volume 0.8');
}
// ========== Public Methods ==========
/// Add audio stream chunk
Future addStreamChunk(Uint8List chunk) async {
await _playLock.synchronized(() async {
if (!_isStreamMode) {
await _startStreamPlaybackInternal();
}
}
/// Mark stream playback as finished (no more data will be received)
Future finishStreamPlayback() async {
await _playLock.synchronized(() async {
if (!_isStreamMode || _streamFinished) return;
}
/// Pause playback
Future pause() async {
await _playLock.synchronized(() async {
if (_isPlaying && !_isPaused) {
await _audioPlayer.pause();
_isPaused = true;
print('⏸️ Audio paused');
}
});
}
/// Resume playback▶️ Audio resumed');
Future resume() async {
await _playLock.synchronized(() async {
if (_isPlaying && _isPaused) {
await _audioPlayer.resume();
_isPaused = false;
print('
}
});
}
/// Immediately stop current stream playback
Future forceStopStream() async {
await _playLock.synchronized(() async {
await _forceStopInternal();
});
}
/// Release resources
Future dispose() async {
await _playLock.synchronized(() async {
await _forceStopInternal();
await _audioPlayer.dispose();
print('🗑️ AudioManager resources released');
});
}
// ========== Internal Methods ==========
Future _startStreamPlaybackInternal() async {⚠️ Detected ongoing stream playback, stopping old stream first');
if (_isStreamMode) {
print('
await _forceStopInternal();
await Future.delayed(_playerResetDelay);
// State stabilization delay ensures old stream is fully stopped
await Future.delayed(_stateStabilizationDelay);
}
}
void _startStreamCompletionTimer() {
_streamCompletionTimer?.cancel();
_streamCompletionTimer = Timer(_streamCompletionDelay, () async {
await _playLock.synchronized(() async {
if (_isStreamMode && _audioBuffer.isNotEmpty && !_isPlaying) {
print('⏰ Stream completion timer triggered, playing remaining data (${_audioBuffer.length} bytes)');
await _playBufferedAudio();
}
});
});
}
Future _playBufferedAudio() async {
if (_isPlaying || _audioBuffer.isEmpty) return;
}
void _setupPlaybackCompletionListener() {
audioPlayer.onPlayerComplete.first.then(() async {
await _playLock.synchronized(() async {
// State stabilization delay ensures state consistency
await Future.delayed(_stateStabilizationDelay);
}
Future _safePlay(Uint8List audioData) async {
try {
// Ensure player state consistency
await _audioPlayer.stop();
await Future.delayed(_playerResetDelay);
}
Future _stopStreamPlaybackInternal() async {
if (!_isStreamMode) return;
}
Future _forceStopInternal() async {
print('🛑 Forcibly stopping current audio stream');
_isStreamMode = false;
_isPlaying = false;
_isPaused = false;
_streamFinished = false;
_hasStartedPlaying = false; // Reset quick start flag
_audioBuffer.clear();
}
Future _resetPlayer() async {
try {
await _audioPlayer.stop();
await Future.delayed(_playerResetDelay);
// State stabilization delay ensures player is fully reset
await Future.delayed(_stateStabilizationDelay);
} catch (e) {
print('Error resetting player: $e');
}
}
Future _handlePlaybackError() async {
_isPlaying = false;
_isPaused = false;
try {
await _resetPlayer();
// State stabilization delay after error handling
await Future.delayed(_stateStabilizationDelay);
} catch (e) {
print('Failed to reset player: $e');
}
}
Future 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('Failed to get audio duration: $e');
return 0;
} finally {
await _audioPlayer.stop();
}
}
}
`