Browse Source

Pin Android recording targets and stop capture outside the main thread

master
evgeny 1 week ago
parent
commit
dbec6e75e8
  1. 7
      tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/MessageBubble.kt
  2. 72
      tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt

7
tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/MessageBubble.kt

@ -296,12 +296,7 @@ private fun FileBubble(message: Message, textColor: Color, onDownload: (Message)
private val VIDEO_EXTENSIONS = setOf("mp4", "m4v", "mov", "webm", "3gp", "mkv", "avi", "ts", "mpg", "mpeg")
private fun isVideoMessage(message: Message): Boolean {
if (message.contentType.startsWith("video/")) return true
if (message.contentType == "application/octet-stream") {
val ext = message.text.substringAfterLast('.', "").lowercase()
return ext in VIDEO_EXTENSIONS
}
return false
return message.contentType.startsWith("video/")
}
private fun isImageMessage(message: Message): Boolean = message.contentType.startsWith("image/")

72
tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt

@ -946,17 +946,21 @@ class ChatViewModel : ViewModel() {
r.joinChannel(channelId, nodeId, joinKey, pubkey, addrs, addrCount)
}
fun startVoiceRecording() {
val ch = _currentChannel.value ?: return
if (!audioRecorder.startRecording(ch.id)) {
private var recordingStopping = false
private var recordingTarget: ConversationTarget? = null
fun startVoiceRecording(target: ConversationTarget) {
if (recordingStopping) { LogManager.addLog("WARN", "VM", "recording is still stopping"); return }
if (!audioRecorder.startRecording(target.nativeId)) {
LogManager.addLog("ERROR", "VM", "voice recording failed to start")
return
}
_isRecording.value = true
recordingTarget = target
_isRecordingLocked.value = false
recordingStartTimeMs = System.currentTimeMillis()
_recordingDurationMs.value = 0
LogManager.addLog("INFO", "VM", "voice recording started ch=${ch.id}")
LogManager.addLog("INFO", "VM", "voice recording started target=${target.nativeId}")
}
fun lockVoiceRecording() {
@ -964,42 +968,44 @@ class ChatViewModel : ViewModel() {
LogManager.addLog("DEBUG", "VM", "voice recording locked")
}
fun stopVoiceRecording(): Int {
if (!_isRecording.value) return 0
val duration = audioRecorder.stopRecording()
fun stopVoiceRecording() {
if (!_isRecording.value || recordingStopping) return
recordingStopping = true
_isRecording.value = false
_isRecordingLocked.value = false
_recordingDurationMs.value = 0
val ch = _currentChannel.value
if (ch != null && duration >= 500) {
viewModelScope.launch {
delay(500)
refreshMessages(ch.id)
refreshChannels()
try {
val duration = withContext(Dispatchers.IO) { audioRecorder.stopRecording() }
LogManager.addLog("INFO", "VM", "voice detached target=${recordingTarget?.nativeId} duration=$duration")
} finally {
recordingStopping = false
recordingTarget = null
}
}
LogManager.addLog("INFO", "VM", "voice recording stopped dur=${duration}ms")
return duration
}
fun cancelVoiceRecording() {
audioRecorder.cancelRecording()
if (recordingStopping) return
recordingStopping = true
_isRecording.value = false
_isRecordingLocked.value = false
_recordingDurationMs.value = 0
LogManager.addLog("INFO", "VM", "voice recording cancelled")
viewModelScope.launch {
try { withContext(Dispatchers.IO) { audioRecorder.cancelRecording() } }
finally { recordingStopping = false; recordingTarget = null }
}
LogManager.addLog("INFO", "VM", "voice recording cancelled target=${recordingTarget?.nativeId}")
}
fun sendVoiceMessage() {
val duration = stopVoiceRecording()
if (duration < 1000) {
LogManager.addLog("INFO", "VM", "voice too short (${duration}ms), discarded")
}
stopVoiceRecording()
}
fun sendAttachment(filePath: String) {
fun sendAttachment(filePath: String, target: ConversationTarget, name: String = File(filePath).name) {
val r = repo ?: return
val ch = _currentChannel.value ?: return
if (target is ConversationTarget.Pm) { NativeLib.dmSendFile(target.id, filePath, name); return }
val chId = target.id
val ext = filePath.substringAfterLast('.', "").lowercase()
if (ext in IMAGE_EXTENSIONS) {
viewModelScope.launch {
@ -1010,29 +1016,28 @@ class ChatViewModel : ViewModel() {
}
val (w, h) = dims
if (w > 0 && h > 0) {
LogManager.addLog("INFO", "VM", "sendAttachment(image) ch=${ch.id} ext=$ext ${w}x${h}")
r.sendPhoto(ch.id, filePath, w, h)
LogManager.addLog("INFO", "VM", "sendAttachment(image) ch=$chId ext=$ext ${w}x${h}")
r.sendPhoto(chId, filePath, w, h)
} else {
LogManager.addLog("WARN", "VM", "image decode failed, sent as file: $filePath")
r.sendAttachment(ch.id, filePath)
r.sendAttachment(chId, filePath)
}
delay(500)
refreshMessages(ch.id)
refreshMessages(chId)
refreshChannels()
}
} else {
r.sendAttachment(ch.id, filePath)
r.sendAttachment(chId, filePath)
viewModelScope.launch {
delay(500)
refreshMessages(ch.id)
refreshMessages(chId)
refreshChannels()
}
}
}
fun sendVideoMessage(filePath: String) {
fun sendVideoMessage(filePath: String, target: ConversationTarget) {
val r = repo ?: return
val ch = _currentChannel.value ?: return
viewModelScope.launch {
val meta = withContext(Dispatchers.IO) {
var dur = 0; var w = 0; var h = 0
@ -1049,11 +1054,8 @@ class ChatViewModel : ViewModel() {
}
Triple(dur, w, h)
}
LogManager.addLog("INFO", "VM", "sendVideoMessage ch=${ch.id} dur=${meta.first} ${meta.second}x${meta.third}")
r.sendVideo(ch.id, filePath, meta.first, meta.second, meta.third)
delay(500)
refreshMessages(ch.id)
refreshChannels()
LogManager.addLog("INFO", "VM", "sendVideoMessage target=${target.nativeId} dur=${meta.first} ${meta.second}x${meta.third}")
r.sendVideo(target.nativeId, filePath, meta.first, meta.second, meta.third)
}
}

Loading…
Cancel
Save