Skip to content

Commit ba2787c

Browse files
Copilotxsahil03x
andauthored
fix(ui): await audio recorder feedback callbacks to prevent sound bleeding into recordings (#2488)
Co-authored-by: xsahil03x <25670178+xsahil03x@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Sahil Kumar <xdsahil@gmail.com> Co-authored-by: Sahil Kumar <sahil@getstream.io>
1 parent f51835f commit ba2787c

File tree

3 files changed

+42
-31
lines changed

3 files changed

+42
-31
lines changed

packages/stream_chat_flutter/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
🐞 Fixed
44

55
- Fixed `StreamChannelAvatar` crashing with `RangeError` when user/channel name is empty.
6+
- Fixed audio tone bleeding into recorded voice message when playing custom feedback sound on recording start.
67

78
## 9.22.0
89

packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_feedback.dart

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:async';
2+
13
import 'package:flutter/widgets.dart';
24

35
/// A feedback handler for audio recorder interactions.
@@ -17,13 +19,21 @@ import 'package:flutter/widgets.dart';
1719
/// ```
1820
///
1921
/// Custom feedback (haptic or system sounds):
22+
///
23+
/// **Note:** Generally, you should not await feedback to avoid blocking the
24+
/// recorder. However, if you play sound-based feedback (e.g., custom tones or
25+
/// system sounds) and notice audio bleeding into the recorded voice message,
26+
/// try awaiting it to ensure the sound completes before recording begins.
27+
///
2028
/// ```dart
2129
/// class CustomFeedback extends AudioRecorderFeedback {
2230
/// @override
2331
/// Future<void> onRecordStart(BuildContext context) async {
24-
/// // Haptic feedback
25-
/// await HapticFeedback.heavyImpact();
26-
/// // Or system sound
32+
/// // Haptic feedback - no need to await (doesn't produce sound)
33+
/// HapticFeedback.heavyImpact();
34+
///
35+
/// // System sound - await only if it bleeds into the recording
36+
/// SystemSound.play(SystemSoundType.click);
2737
/// // await SystemSound.play(SystemSoundType.click);
2838
/// }
2939
/// }
@@ -55,23 +65,23 @@ class AudioRecorderFeedback {
5565
/// recording actually begins.
5666
Future<void> onRecordStart(BuildContext context) async {
5767
if (!enableFeedback) return;
58-
return Feedback.forLongPress(context);
68+
return unawaited(Feedback.forLongPress(context));
5969
}
6070

6171
/// Provides platform-specific feedback when recording is paused.
6272
///
6373
/// This is called when the user pauses the ongoing recording.
6474
Future<void> onRecordPause(BuildContext context) async {
6575
if (!enableFeedback) return;
66-
return Feedback.forTap(context);
76+
return unawaited(Feedback.forTap(context));
6777
}
6878

6979
/// Provides platform-specific feedback when recording is finished.
7080
///
7181
/// This is called when the user finishes the recording.
7282
Future<void> onRecordFinish(BuildContext context) async {
7383
if (!enableFeedback) return;
74-
return Feedback.forTap(context);
84+
return unawaited(Feedback.forTap(context));
7585
}
7686

7787
/// Provides platform-specific feedback when the recording is locked.
@@ -80,15 +90,15 @@ class AudioRecorderFeedback {
8090
/// holding the record button.
8191
Future<void> onRecordLock(BuildContext context) async {
8292
if (!enableFeedback) return;
83-
return Feedback.forLongPress(context);
93+
return unawaited(Feedback.forLongPress(context));
8494
}
8595

8696
/// Provides platform-specific feedback when recording is canceled.
8797
///
8898
/// This is called when the user cancels an ongoing recording.
8999
Future<void> onRecordCancel(BuildContext context) async {
90100
if (!enableFeedback) return;
91-
return Feedback.forTap(context);
101+
return unawaited(Feedback.forTap(context));
92102
}
93103

94104
/// Provides platform-specific feedback when recording is canceled before
@@ -98,7 +108,7 @@ class AudioRecorderFeedback {
98108
/// the recording actually starts.
99109
Future<void> onRecordStartCancel(BuildContext context) async {
100110
if (!enableFeedback) return;
101-
return Feedback.forTap(context);
111+
return unawaited(Feedback.forTap(context));
102112
}
103113

104114
/// Provides platform-specific feedback when recording stops.
@@ -107,7 +117,7 @@ class AudioRecorderFeedback {
107117
/// is now ready to be used.
108118
Future<void> onRecordStop(BuildContext context) async {
109119
if (!enableFeedback) return;
110-
return Feedback.forTap(context);
120+
return unawaited(Feedback.forTap(context));
111121
}
112122
}
113123

packages/stream_chat_flutter/lib/src/message_input/audio_recorder/stream_audio_recorder.dart

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -127,41 +127,41 @@ class StreamAudioRecorderButton extends StatelessWidget {
127127
final isLocked = isRecording && recordState is! RecordStateRecordingHold;
128128

129129
return GestureDetector(
130-
onLongPressStart: (_) {
130+
onLongPressStart: (_) async {
131131
// Return if the recording is already started.
132132
if (isRecording) return;
133133

134-
feedback.onRecordStart(context);
134+
await feedback.onRecordStart(context);
135135
return onRecordStart?.call();
136136
},
137-
onLongPressEnd: (_) {
137+
onLongPressEnd: (_) async {
138138
// Return if the recording not yet started or already locked.
139139
if (!isRecording || isLocked) return;
140140

141-
feedback.onRecordFinish(context);
141+
await feedback.onRecordFinish(context);
142142
return onRecordFinish?.call();
143143
},
144-
onLongPressCancel: () {
144+
onLongPressCancel: () async {
145145
// Return if the recording is already started.
146146
if (isRecording) return;
147147

148148
// Notify the parent that the recorder is canceled before it starts.
149-
feedback.onRecordStartCancel(context);
149+
await feedback.onRecordStartCancel(context);
150150
return onRecordStartCancel?.call();
151151
},
152-
onLongPressMoveUpdate: (details) {
152+
onLongPressMoveUpdate: (details) async {
153153
// Return if the recording not yet started or already locked.
154154
if (!isRecording || isLocked) return;
155155
final dragOffset = details.offsetFromOrigin;
156156

157157
// Lock recording if the drag offset is greater than the threshold.
158158
if (dragOffset.dy <= -lockRecordThreshold) {
159-
feedback.onRecordLock(context);
159+
await feedback.onRecordLock(context);
160160
return onRecordLock?.call();
161161
}
162162
// Cancel recording if the drag offset is greater than the threshold.
163163
if (dragOffset.dx <= -cancelRecordThreshold) {
164-
feedback.onRecordCancel(context);
164+
await feedback.onRecordCancel(context);
165165
return onRecordCancel?.call();
166166
}
167167

@@ -187,31 +187,31 @@ class StreamAudioRecorderButton extends StatelessWidget {
187187
),
188188
RecordStateRecordingLocked() => RecordStateLockedRecordingContent(
189189
state: state,
190-
onRecordEnd: () {
191-
feedback.onRecordFinish(context);
190+
onRecordEnd: () async {
191+
await feedback.onRecordFinish(context);
192192
return onRecordFinish?.call();
193193
},
194-
onRecordPause: () {
195-
feedback.onRecordPause(context);
194+
onRecordPause: () async {
195+
await feedback.onRecordPause(context);
196196
return onRecordPause?.call();
197197
},
198-
onRecordCancel: () {
199-
feedback.onRecordCancel(context);
198+
onRecordCancel: () async {
199+
await feedback.onRecordCancel(context);
200200
return onRecordCancel?.call();
201201
},
202-
onRecordStop: () {
203-
feedback.onRecordStop(context);
202+
onRecordStop: () async {
203+
await feedback.onRecordStop(context);
204204
return onRecordStop?.call();
205205
},
206206
),
207207
RecordStateStopped() => RecordStateStoppedContent(
208208
state: state,
209-
onRecordCancel: () {
210-
feedback.onRecordCancel(context);
209+
onRecordCancel: () async {
210+
await feedback.onRecordCancel(context);
211211
return onRecordCancel?.call();
212212
},
213-
onRecordFinish: () {
214-
feedback.onRecordFinish(context);
213+
onRecordFinish: () async {
214+
await feedback.onRecordFinish(context);
215215
return onRecordFinish?.call();
216216
},
217217
),

0 commit comments

Comments
 (0)