feat: add replay functionality and improve error handling in audio player widget

This commit is contained in:
Resh 2024-12-19 13:21:55 +07:00
parent 21e90e1b16
commit 2f1eef5f1a
2 changed files with 62 additions and 31 deletions

View File

@ -27,7 +27,6 @@ class AudioPlayerWidgetState extends State<AudioPlayerWidget>
Duration _position = Duration.zero;
bool _isAudioLoaded = false;
String? _errorMessage;
double _volume = 1.0;
bool _isDisposed = false;
@override
@ -88,6 +87,18 @@ class AudioPlayerWidgetState extends State<AudioPlayerWidget>
setState(() => _position = newPosition);
}
});
// Add completion listener
_audioPlayer.onPlayerComplete.listen((_) {
if (!_isDisposed) {
setState(() {
_position = Duration.zero;
_playerState = PlayerState.stopped;
});
// Reset source when playback completes
_audioPlayer.setSource(UrlSource(_getFullAudioUrl()));
}
});
}
String _getFullAudioUrl() {
@ -96,6 +107,20 @@ class AudioPlayerWidgetState extends State<AudioPlayerWidget>
: '${widget.baseUrl ?? ''}${widget.audioFileName}';
}
Future<void> _replayAudio() async {
try {
await _audioPlayer.seek(Duration.zero);
await _audioPlayer.setSource(UrlSource(_getFullAudioUrl()));
await _audioPlayer.resume();
} catch (error) {
if (!_isDisposed) {
setState(() {
_errorMessage = "Failed to replay audio: $error";
});
}
}
}
Widget _buildErrorWidget() {
return Container(
padding: const EdgeInsets.all(16),
@ -174,7 +199,7 @@ class AudioPlayerWidgetState extends State<AudioPlayerWidget>
),
),
const SizedBox(width: 12),
_buildVolumeButton(),
_buildReplayButton(),
],
),
if (_errorMessage != null)
@ -204,17 +229,36 @@ class AudioPlayerWidgetState extends State<AudioPlayerWidget>
size: 32,
),
onPressed: _isAudioLoaded
? () {
if (_playerState == PlayerState.playing) {
_audioPlayer.pause();
} else {
_audioPlayer.resume();
? () async {
try {
if (_playerState == PlayerState.playing) {
await _audioPlayer.pause();
} else {
await _audioPlayer.resume();
}
} catch (error) {
if (!_isDisposed) {
setState(() {
_errorMessage = "Failed to control playback: $error";
});
}
}
}
: null,
);
}
Widget _buildReplayButton() {
return IconButton(
icon: const Icon(
Icons.replay,
color: Colors.blue,
size: 24,
),
onPressed: _isAudioLoaded ? _replayAudio : null,
);
}
Widget _buildTimelineIndicator() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -243,9 +287,17 @@ class AudioPlayerWidgetState extends State<AudioPlayerWidget>
min: 0.0,
max: _duration.inSeconds.toDouble(),
onChanged: _isAudioLoaded
? (value) {
final position = Duration(seconds: value.toInt());
_audioPlayer.seek(position);
? (value) async {
try {
final position = Duration(seconds: value.toInt());
await _audioPlayer.seek(position);
} catch (error) {
if (!_isDisposed) {
setState(() {
_errorMessage = "Failed to seek: $error";
});
}
}
}
: null,
activeColor: Colors.blue,
@ -254,27 +306,6 @@ class AudioPlayerWidgetState extends State<AudioPlayerWidget>
);
}
Widget _buildVolumeButton() {
return IconButton(
icon: Icon(
_volume == 0.0 ? Icons.volume_off : Icons.volume_up,
color: _volume == 0.0 ? Colors.grey : Colors.blue,
size: 24,
),
onPressed: _isAudioLoaded
? () {
if (_volume == 0.0) {
_audioPlayer.setVolume(1.0);
setState(() => _volume = 1.0);
} else {
_audioPlayer.setVolume(0.0);
setState(() => _volume = 0.0);
}
}
: null,
);
}
@override
Widget build(BuildContext context) {
if (_errorMessage != null) {