fix: зависание записи ГС — race condition в startVoiceRecording + утечка isVoiceRecordTransitioning
Root cause 1: startVoiceRecording() проверял только isVoiceRecording, но isVoiceRecording=true ставился через 192ms в scope.launch. При быстром двойном тапе два MediaRecorder создавались, первый терялся (утечка). Фикс: добавлен guard на isVoiceRecordTransitioning и voiceRecorder!=null. Root cause 2: isVoiceRecordTransitioning=true ставился перед scope.launch, но если launch крашился или composable disposed, transitioning навсегда оставался true — gesture guard блокировал все записи до перезапуска. Фикс: try/catch в launch + reset в DisposableEffect. Root cause 3: DisposableEffect проверял только isVoiceRecording, но не voiceRecorder!=null — если recorder создан но isVoiceRecording ещё false, recorder не освобождался при dispose. Фикс: проверка voiceRecorder!=null в dispose. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1180,7 +1180,7 @@ fun MessageInputBar(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun startVoiceRecording() {
|
fun startVoiceRecording() {
|
||||||
if (isVoiceRecording) return
|
if (isVoiceRecording || isVoiceRecordTransitioning || voiceRecorder != null) return
|
||||||
inputJumpLog(
|
inputJumpLog(
|
||||||
"startVoiceRecording begin mode=$recordMode state=$recordUiState kb=$isKeyboardVisible emojiBox=${coordinator.isEmojiBoxVisible} " +
|
"startVoiceRecording begin mode=$recordMode state=$recordUiState kb=$isKeyboardVisible emojiBox=${coordinator.isEmojiBoxVisible} " +
|
||||||
"emojiPicker=$showEmojiPicker panelH=$inputPanelHeightPx normalH=$normalInputRowHeightPx"
|
"emojiPicker=$showEmojiPicker panelH=$inputPanelHeightPx normalH=$normalInputRowHeightPx"
|
||||||
@@ -1227,20 +1227,25 @@ fun MessageInputBar(
|
|||||||
)
|
)
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
repeat(12) {
|
try {
|
||||||
if (!isKeyboardVisible && !coordinator.isEmojiBoxVisible) return@repeat
|
repeat(12) {
|
||||||
delay(16)
|
if (!isKeyboardVisible && !coordinator.isEmojiBoxVisible) return@repeat
|
||||||
|
delay(16)
|
||||||
|
}
|
||||||
|
isVoiceRecording = true
|
||||||
|
isVoiceRecordTransitioning = false
|
||||||
|
if (recordUiState == RecordUiState.PRESSING || recordUiState == RecordUiState.IDLE) {
|
||||||
|
setRecordUiState(RecordUiState.RECORDING, "voice-recorder-started")
|
||||||
|
}
|
||||||
|
inputJumpLog(
|
||||||
|
"startVoiceRecording ui-enter mode=$recordMode state=$recordUiState voice=$isVoiceRecording kb=$isKeyboardVisible " +
|
||||||
|
"emojiBox=${coordinator.isEmojiBoxVisible} transitioning=$isVoiceRecordTransitioning " +
|
||||||
|
"panelH=$inputPanelHeightPx recH=$recordingInputRowHeightPx"
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
isVoiceRecordTransitioning = false
|
||||||
|
inputJumpLog("startVoiceRecording launch failed: ${e.message}")
|
||||||
}
|
}
|
||||||
isVoiceRecording = true
|
|
||||||
isVoiceRecordTransitioning = false
|
|
||||||
if (recordUiState == RecordUiState.PRESSING || recordUiState == RecordUiState.IDLE) {
|
|
||||||
setRecordUiState(RecordUiState.RECORDING, "voice-recorder-started")
|
|
||||||
}
|
|
||||||
inputJumpLog(
|
|
||||||
"startVoiceRecording ui-enter mode=$recordMode state=$recordUiState voice=$isVoiceRecording kb=$isKeyboardVisible " +
|
|
||||||
"emojiBox=${coordinator.isEmojiBoxVisible} transitioning=$isVoiceRecordTransitioning " +
|
|
||||||
"panelH=$inputPanelHeightPx recH=$recordingInputRowHeightPx"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
isVoiceRecordTransitioning = false
|
isVoiceRecordTransitioning = false
|
||||||
@@ -1420,8 +1425,9 @@ fun MessageInputBar(
|
|||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
onDispose {
|
onDispose {
|
||||||
pendingRecordAfterPermission = false
|
pendingRecordAfterPermission = false
|
||||||
|
isVoiceRecordTransitioning = false
|
||||||
resetGestureState()
|
resetGestureState()
|
||||||
if (isVoiceRecording) {
|
if (isVoiceRecording || voiceRecorder != null) {
|
||||||
stopVoiceRecording(send = false)
|
stopVoiceRecording(send = false)
|
||||||
} else {
|
} else {
|
||||||
setRecordUiState(RecordUiState.IDLE, "dispose")
|
setRecordUiState(RecordUiState.IDLE, "dispose")
|
||||||
|
|||||||
Reference in New Issue
Block a user