Browse Source

Pin Android recording targets and stop capture outside the main thread

master
evgeny 2 weeks 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 val VIDEO_EXTENSIONS = setOf("mp4", "m4v", "mov", "webm", "3gp", "mkv", "avi", "ts", "mpg", "mpeg")
private fun isVideoMessage(message: Message): Boolean { private fun isVideoMessage(message: Message): Boolean {
if (message.contentType.startsWith("video/")) return true return message.contentType.startsWith("video/")
if (message.contentType == "application/octet-stream") {
val ext = message.text.substringAfterLast('.', "").lowercase()
return ext in VIDEO_EXTENSIONS
}
return false
} }
private fun isImageMessage(message: Message): Boolean = message.contentType.startsWith("image/") 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) r.joinChannel(channelId, nodeId, joinKey, pubkey, addrs, addrCount)
} }
fun startVoiceRecording() { private var recordingStopping = false
val ch = _currentChannel.value ?: return private var recordingTarget: ConversationTarget? = null
if (!audioRecorder.startRecording(ch.id)) {
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") LogManager.addLog("ERROR", "VM", "voice recording failed to start")
return return
} }
_isRecording.value = true _isRecording.value = true
recordingTarget = target
_isRecordingLocked.value = false _isRecordingLocked.value = false
recordingStartTimeMs = System.currentTimeMillis() recordingStartTimeMs = System.currentTimeMillis()
_recordingDurationMs.value = 0 _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() { fun lockVoiceRecording() {
@ -964,42 +968,44 @@ class ChatViewModel : ViewModel() {
LogManager.addLog("DEBUG", "VM", "voice recording locked") LogManager.addLog("DEBUG", "VM", "voice recording locked")
} }
fun stopVoiceRecording(): Int { fun stopVoiceRecording() {
if (!_isRecording.value) return 0 if (!_isRecording.value || recordingStopping) return
val duration = audioRecorder.stopRecording() recordingStopping = true
_isRecording.value = false _isRecording.value = false
_isRecordingLocked.value = false _isRecordingLocked.value = false
_recordingDurationMs.value = 0 _recordingDurationMs.value = 0
val ch = _currentChannel.value
if (ch != null && duration >= 500) {
viewModelScope.launch { viewModelScope.launch {
delay(500) try {
refreshMessages(ch.id) val duration = withContext(Dispatchers.IO) { audioRecorder.stopRecording() }
refreshChannels() 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() { fun cancelVoiceRecording() {
audioRecorder.cancelRecording() if (recordingStopping) return
recordingStopping = true
_isRecording.value = false _isRecording.value = false
_isRecordingLocked.value = false _isRecordingLocked.value = false
_recordingDurationMs.value = 0 _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() { fun sendVoiceMessage() {
val duration = stopVoiceRecording() stopVoiceRecording()
if (duration < 1000) {
LogManager.addLog("INFO", "VM", "voice too short (${duration}ms), discarded")
}
} }
fun sendAttachment(filePath: String) { fun sendAttachment(filePath: String, target: ConversationTarget, name: String = File(filePath).name) {
val r = repo ?: return 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() val ext = filePath.substringAfterLast('.', "").lowercase()
if (ext in IMAGE_EXTENSIONS) { if (ext in IMAGE_EXTENSIONS) {
viewModelScope.launch { viewModelScope.launch {
@ -1010,29 +1016,28 @@ class ChatViewModel : ViewModel() {
} }
val (w, h) = dims val (w, h) = dims
if (w > 0 && h > 0) { if (w > 0 && h > 0) {
LogManager.addLog("INFO", "VM", "sendAttachment(image) ch=${ch.id} ext=$ext ${w}x${h}") LogManager.addLog("INFO", "VM", "sendAttachment(image) ch=$chId ext=$ext ${w}x${h}")
r.sendPhoto(ch.id, filePath, w, h) r.sendPhoto(chId, filePath, w, h)
} else { } else {
LogManager.addLog("WARN", "VM", "image decode failed, sent as file: $filePath") LogManager.addLog("WARN", "VM", "image decode failed, sent as file: $filePath")
r.sendAttachment(ch.id, filePath) r.sendAttachment(chId, filePath)
} }
delay(500) delay(500)
refreshMessages(ch.id) refreshMessages(chId)
refreshChannels() refreshChannels()
} }
} else { } else {
r.sendAttachment(ch.id, filePath) r.sendAttachment(chId, filePath)
viewModelScope.launch { viewModelScope.launch {
delay(500) delay(500)
refreshMessages(ch.id) refreshMessages(chId)
refreshChannels() refreshChannels()
} }
} }
} }
fun sendVideoMessage(filePath: String) { fun sendVideoMessage(filePath: String, target: ConversationTarget) {
val r = repo ?: return val r = repo ?: return
val ch = _currentChannel.value ?: return
viewModelScope.launch { viewModelScope.launch {
val meta = withContext(Dispatchers.IO) { val meta = withContext(Dispatchers.IO) {
var dur = 0; var w = 0; var h = 0 var dur = 0; var w = 0; var h = 0
@ -1049,11 +1054,8 @@ class ChatViewModel : ViewModel() {
} }
Triple(dur, w, h) Triple(dur, w, h)
} }
LogManager.addLog("INFO", "VM", "sendVideoMessage ch=${ch.id} dur=${meta.first} ${meta.second}x${meta.third}") LogManager.addLog("INFO", "VM", "sendVideoMessage target=${target.nativeId} dur=${meta.first} ${meta.second}x${meta.third}")
r.sendVideo(ch.id, filePath, meta.first, meta.second, meta.third) r.sendVideo(target.nativeId, filePath, meta.first, meta.second, meta.third)
delay(500)
refreshMessages(ch.id)
refreshChannels()
} }
} }

Loading…
Cancel
Save