| .. | .. |
|---|
| 86 | 86 | // Listen for playback state changes to reset play button UI |
|---|
| 87 | 87 | // Use a brief delay to avoid race between queue transitions |
|---|
| 88 | 88 | AudioService.onPlaybackStateChanged = () { |
|---|
| 89 | | - if (mounted && !AudioService.isPlaying) { |
|---|
| 90 | | - Future.delayed(const Duration(milliseconds: 100), () { |
|---|
| 91 | | - if (mounted && !AudioService.isPlaying) { |
|---|
| 92 | | - setState(() => _playingMessageId = null); |
|---|
| 93 | | - } |
|---|
| 94 | | - }); |
|---|
| 89 | + if (mounted) { |
|---|
| 90 | + if (AudioService.isPlaying) { |
|---|
| 91 | + // Something started playing — keep the indicator as-is |
|---|
| 92 | + } else { |
|---|
| 93 | + // Playback stopped — clear indicator only if queue is truly empty. |
|---|
| 94 | + // Use a short delay since the queue transition has a brief gap. |
|---|
| 95 | + Future.delayed(const Duration(milliseconds: 200), () { |
|---|
| 96 | + if (mounted && !AudioService.isPlaying) { |
|---|
| 97 | + setState(() => _playingMessageId = null); |
|---|
| 98 | + } |
|---|
| 99 | + }); |
|---|
| 100 | + } |
|---|
| 95 | 101 | } |
|---|
| 96 | 102 | }; |
|---|
| 97 | 103 | |
|---|
| .. | .. |
|---|
| 519 | 525 | _scrollToBottom(); |
|---|
| 520 | 526 | |
|---|
| 521 | 527 | if (audioData != null && !AudioService.isBackgrounded && !_isCatchingUp && !_isRecording) { |
|---|
| 522 | | - // Only set playing ID if nothing is currently playing (first chunk). |
|---|
| 523 | | - // Subsequent chunks just queue audio without touching the play indicator, |
|---|
| 524 | | - // preventing the completion callback race from clearing it prematurely. |
|---|
| 525 | | - if (_playingMessageId == null) { |
|---|
| 526 | | - setState(() => _playingMessageId = storedMessage.id); |
|---|
| 527 | | - } |
|---|
| 528 | + setState(() => _playingMessageId = storedMessage.id); |
|---|
| 528 | 529 | AudioService.queueBase64(audioData); |
|---|
| 529 | 530 | } |
|---|
| 530 | 531 | } |
|---|