From baf3a12009e90a01aba03ae0cad624b33e5e5fc9 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Mon, 15 May 2023 11:17:32 +0300 Subject: [PATCH] mobile: increased size of voice messages (#2150) * android: increased size of voice messages * ios: increased size of voice messages * size changes * increase audio quality to 16kHz/32Kbps, refactor auto-accept logic --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- .../java/chat/simplex/app/model/SimpleXAPI.kt | 12 +++++------ .../simplex/app/views/chat/SendMsgView.kt | 2 +- .../app/views/chat/item/CIVoiceView.kt | 21 +++++++++++-------- .../app/views/chat/item/ChatItemView.kt | 2 +- .../app/views/chat/item/FramedItemView.kt | 2 +- .../simplex/app/views/helpers/RecAndPlay.kt | 5 ++--- .../chat/simplex/app/views/helpers/Util.kt | 10 ++++----- apps/ios/Shared/Model/AudioRecPlay.swift | 5 +++-- apps/ios/Shared/Model/SimpleXAPI.swift | 12 +++-------- .../Views/Chat/ChatItem/CIVideoView.swift | 1 - .../Views/Chat/ChatItem/CIVoiceView.swift | 14 ++++++++++++- .../ios/SimpleX NSE/NotificationService.swift | 21 ++----------------- apps/ios/SimpleXChat/ChatTypes.swift | 14 ++++++++++++- apps/ios/SimpleXChat/FileUtils.swift | 14 ++++++------- 14 files changed, 69 insertions(+), 66 deletions(-) diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt index 9f0af623e2..fd2157898e 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt @@ -1355,12 +1355,12 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a } val file = cItem.file val mc = cItem.content.msgContent - if (file != null && file.fileSize <= MAX_IMAGE_SIZE_AUTO_RCV) { - val acceptImages = appPrefs.privacyAcceptImages.get() - if ((mc is MsgContent.MCImage && acceptImages) - || (mc is MsgContent.MCVoice && ((file.fileSize > MAX_VOICE_SIZE_FOR_SENDING && acceptImages) || cInfo is ChatInfo.Group))) { - withApi { receiveFile(r.user, file.fileId) } // TODO check inlineFileMode != IFMSent - } + if (file != null && + appPrefs.privacyAcceptImages.get() && + ((mc is MsgContent.MCImage && file.fileSize <= MAX_IMAGE_SIZE_AUTO_RCV) + || (mc is MsgContent.MCVideo && file.fileSize <= MAX_VIDEO_SIZE_AUTO_RCV) + || (mc is MsgContent.MCVoice && file.fileSize <= MAX_VOICE_SIZE_AUTO_RCV && file.fileStatus !is CIFileStatus.RcvAccepted))) { + withApi { receiveFile(r.user, file.fileId) } } if (cItem.showNotification && (!SimplexApp.context.isAppOnForeground || chatModel.chatId.value != cInfo.id)) { ntfManager.notifyMessageReceived(r.user, cInfo, cItem) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt index 366f79c1f3..831badf7fa 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt @@ -307,7 +307,7 @@ private fun BoxScope.DeleteTextButton(composeState: MutableState) @Composable private fun RecordVoiceView(recState: MutableState, stopRecOnNextClick: MutableState) { - val rec: Recorder = remember { RecorderNative(MAX_VOICE_SIZE_FOR_SENDING) } + val rec: Recorder = remember { RecorderNative() } DisposableEffect(Unit) { onDispose { rec.stop() } } val stopRecordingAndAddAudio: () -> Unit = { recState.value.filePathNullable?.let { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIVoiceView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIVoiceView.kt index f5ca6d7596..fa94e00c74 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIVoiceView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIVoiceView.kt @@ -36,6 +36,7 @@ fun CIVoiceView( ci: ChatItem, timedMessagesTTL: Int?, longClick: () -> Unit, + receiveFile: (Long) -> Unit, ) { Row( Modifier.padding(top = if (hasText) 14.dp else 4.dp, bottom = if (hasText) 14.dp else 6.dp, start = if (hasText) 6.dp else 0.dp, end = if (hasText) 6.dp else 0.dp), @@ -64,11 +65,11 @@ fun CIVoiceView( durationText(time / 1000) } } - VoiceLayout(file, ci, text, audioPlaying, progress, duration, brokenAudio, sent, hasText, timedMessagesTTL, play, pause, longClick) { + VoiceLayout(file, ci, text, audioPlaying, progress, duration, brokenAudio, sent, hasText, timedMessagesTTL, play, pause, longClick, receiveFile) { AudioPlayer.seekTo(it, progress, filePath) } } else { - VoiceMsgIndicator(null, false, sent, hasText, null, null, false, {}, {}, longClick) + VoiceMsgIndicator(null, false, sent, hasText, null, null, false, {}, {}, longClick, receiveFile) val metaReserve = if (edited) " " else @@ -93,8 +94,8 @@ private fun VoiceLayout( play: () -> Unit, pause: () -> Unit, longClick: () -> Unit, + receiveFile: (Long) -> Unit, onProgressChanged: (Int) -> Unit, - ) { @Composable fun RowScope.Slider(backgroundColor: Color, padding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING_HALF)) { @@ -142,7 +143,7 @@ private fun VoiceLayout( val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage Spacer(Modifier.width(6.dp)) - VoiceMsgIndicator(file, audioPlaying.value, sent, hasText, progress, duration, brokenAudio, play, pause, longClick) + VoiceMsgIndicator(file, audioPlaying.value, sent, hasText, progress, duration, brokenAudio, play, pause, longClick, receiveFile) Row(verticalAlignment = Alignment.CenterVertically) { DurationText(text, PaddingValues(start = 12.dp)) Slider(if (ci.chatDir.sent) sentColor else receivedColor) @@ -156,7 +157,7 @@ private fun VoiceLayout( DurationText(text, PaddingValues(end = 12.dp)) } Column { - VoiceMsgIndicator(file, audioPlaying.value, sent, hasText, progress, duration, brokenAudio, play, pause, longClick) + VoiceMsgIndicator(file, audioPlaying.value, sent, hasText, progress, duration, brokenAudio, play, pause, longClick, receiveFile) Box(Modifier.align(Alignment.CenterHorizontally).padding(top = 6.dp)) { CIMetaView(ci, timedMessagesTTL) } @@ -166,7 +167,7 @@ private fun VoiceLayout( else -> { Row { Column { - VoiceMsgIndicator(file, audioPlaying.value, sent, hasText, progress, duration, brokenAudio, play, pause, longClick) + VoiceMsgIndicator(file, audioPlaying.value, sent, hasText, progress, duration, brokenAudio, play, pause, longClick, receiveFile) Box(Modifier.align(Alignment.CenterHorizontally).padding(top = 6.dp)) { CIMetaView(ci, timedMessagesTTL) } @@ -245,7 +246,8 @@ private fun VoiceMsgIndicator( error: Boolean, play: () -> Unit, pause: () -> Unit, - longClick: () -> Unit + longClick: () -> Unit, + receiveFile: (Long) -> Unit, ) { val strokeWidth = with(LocalDensity.current) { 3.dp.toPx() } val strokeColor = MaterialTheme.colors.primary @@ -264,8 +266,9 @@ private fun VoiceMsgIndicator( PlayPauseButton(audioPlaying, sent, angle, strokeWidth, strokeColor, true, error, play, pause, longClick = longClick) } } else { - if (file?.fileStatus is CIFileStatus.RcvInvitation - || file?.fileStatus is CIFileStatus.RcvTransfer + if (file?.fileStatus is CIFileStatus.RcvInvitation) { + PlayPauseButton(audioPlaying, sent, 0f, strokeWidth, strokeColor, true, error, { receiveFile(file.fileId) }, {}, longClick = longClick) + } else if (file?.fileStatus is CIFileStatus.RcvTransfer || file?.fileStatus is CIFileStatus.RcvAccepted ) { Box( diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt index f5376355f4..4d68c41c76 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt @@ -203,7 +203,7 @@ fun ChatItemView( EmojiItemView(cItem, cInfo.timedMessagesTTL) MsgContentItemDropdownMenu() } else if (mc is MsgContent.MCVoice && cItem.content.text.isEmpty()) { - CIVoiceView(mc.duration, cItem.file, cItem.meta.itemEdited, cItem.chatDir.sent, hasText = false, cItem, cInfo.timedMessagesTTL, longClick = { onLinkLongClick("") }) + CIVoiceView(mc.duration, cItem.file, cItem.meta.itemEdited, cItem.chatDir.sent, hasText = false, cItem, cInfo.timedMessagesTTL, longClick = { onLinkLongClick("") }, receiveFile) MsgContentItemDropdownMenu() } else { framedItemView() diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt index 99d6ab7eae..61d8080e06 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt @@ -221,7 +221,7 @@ fun FramedItemView( } } is MsgContent.MCVoice -> { - CIVoiceView(mc.duration, ci.file, ci.meta.itemEdited, ci.chatDir.sent, hasText = true, ci, timedMessagesTTL = chatTTL, longClick = { onLinkLongClick("") }) + CIVoiceView(mc.duration, ci.file, ci.meta.itemEdited, ci.chatDir.sent, hasText = true, ci, timedMessagesTTL = chatTTL, longClick = { onLinkLongClick("") }, receiveFile) if (mc.text != "") { CIMarkdownText(ci, chatTTL, showMember, linkMode, uriHandler) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/RecAndPlay.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/RecAndPlay.kt index f3aa27aa24..63da5f5c24 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/RecAndPlay.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/RecAndPlay.kt @@ -21,7 +21,7 @@ interface Recorder { fun stop(): Int } -class RecorderNative(private val recordedBytesLimit: Long): Recorder { +class RecorderNative(): Recorder { companion object { // Allows to stop the recorder from outside without having the recorder in a variable var stopRecording: (() -> Unit)? = null @@ -48,9 +48,8 @@ class RecorderNative(private val recordedBytesLimit: Long): Recorder { rec.setAudioEncoder(MediaRecorder.AudioEncoder.AAC) rec.setAudioChannels(1) rec.setAudioSamplingRate(16000) - rec.setAudioEncodingBitRate(16000) + rec.setAudioEncodingBitRate(32000) rec.setMaxDuration(MAX_VOICE_MILLIS_FOR_SENDING) - rec.setMaxFileSize(recordedBytesLimit) val tmpDir = SimplexApp.context.getDir("temp", Application.MODE_PRIVATE) val fileToSave = File.createTempFile(generateNewFileName(SimplexApp.context, "voice", "${extension}_"), ".tmp", tmpDir) fileToSave.deleteOnExit() diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt index f001719b1f..1f6d3e7f0b 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt @@ -236,16 +236,16 @@ private fun spannableStringToAnnotatedString( } // maximum image file size to be auto-accepted -const val MAX_IMAGE_SIZE: Long = 236700 +const val MAX_IMAGE_SIZE: Long = 261_120 // 255KB const val MAX_IMAGE_SIZE_AUTO_RCV: Long = MAX_IMAGE_SIZE * 2 -const val MAX_VOICE_SIZE_AUTO_RCV: Long = MAX_IMAGE_SIZE +const val MAX_VOICE_SIZE_AUTO_RCV: Long = MAX_IMAGE_SIZE * 2 +const val MAX_VIDEO_SIZE_AUTO_RCV: Long = 1_047_552 // 1023KB -const val MAX_VOICE_SIZE_FOR_SENDING: Long = 94680 // 6 chunks * 15780 bytes per chunk -const val MAX_VOICE_MILLIS_FOR_SENDING: Int = 43_000 +const val MAX_VOICE_MILLIS_FOR_SENDING: Int = 300_000 const val MAX_FILE_SIZE_SMP: Long = 8000000 -const val MAX_FILE_SIZE_XFTP: Long = 1_073_741_824 +const val MAX_FILE_SIZE_XFTP: Long = 1_073_741_824 // 1GB fun getFilesDirectory(context: Context): String { return context.filesDir.toString() diff --git a/apps/ios/Shared/Model/AudioRecPlay.swift b/apps/ios/Shared/Model/AudioRecPlay.swift index 5d4eb41d20..698799457e 100644 --- a/apps/ios/Shared/Model/AudioRecPlay.swift +++ b/apps/ios/Shared/Model/AudioRecPlay.swift @@ -36,8 +36,9 @@ class AudioRecorder { try av.setActive(true) let settings: [String : Any] = [ AVFormatIDKey: kAudioFormatMPEG4AAC, - AVSampleRateKey: 12000, - AVEncoderBitRateKey: 12000, + AVSampleRateKey: 16000, + AVEncoderBitRateKey: 32000, + AVEncoderBitRateStrategyKey: AVAudioBitRateStrategy_VariableConstrained, AVNumberOfChannelsKey: 1 ] let url = getAppFilePath(fileName) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 97b073cb48..68df35d973 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1240,15 +1240,9 @@ func processReceivedMsg(_ res: ChatResponse) async { } else if cItem.isRcvNew && cInfo.ntfsEnabled { m.increaseUnreadCounter(user: user) } - if let file = cItem.file, - let mc = cItem.content.msgContent, - file.fileSize <= MAX_IMAGE_SIZE_AUTO_RCV { - let acceptImages = UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_ACCEPT_IMAGES) - if (mc.isImage && acceptImages) - || (mc.isVoice && ((file.fileSize > MAX_VOICE_MESSAGE_SIZE_INLINE_SEND && acceptImages) || cInfo.chatType == .group)) { - Task { - await receiveFile(user: user, fileId: file.fileId) // TODO check inlineFileMode != IFMSent - } + if let file = cItem.autoReceiveFile() { + Task { + await receiveFile(user: user, fileId: file.fileId) } } if cItem.showNotification { diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift index 6c7d023cbb..0232be700b 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift @@ -251,7 +251,6 @@ struct CIVideoView: View { if let user = ChatModel.shared.currentUser { await receiveFile(user, file.fileId) } - // TODO image accepted alert? } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift index 11f8781c53..e6ce74953b 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift @@ -138,7 +138,7 @@ struct VoiceMessagePlayer: View { case .sndComplete: playbackButton() case .sndCancelled: playbackButton() case .sndError: playbackButton() - case .rcvInvitation: loadingIcon() + case .rcvInvitation: downloadButton(recordingFile) case .rcvAccepted: loadingIcon() case .rcvTransfer: loadingIcon() case .rcvComplete: playbackButton() @@ -214,6 +214,18 @@ struct VoiceMessagePlayer: View { } } + private func downloadButton(_ recordingFile: CIFile) -> some View { + Button { + Task { + if let user = ChatModel.shared.currentUser { + await receiveFile(user: user, fileId: recordingFile.fileId) + } + } + } label: { + playPauseIcon("play.fill") + } + } + private struct ProgressCircle: View { var length: TimeInterval @Binding var progress: TimeInterval? diff --git a/apps/ios/SimpleX NSE/NotificationService.swift b/apps/ios/SimpleX NSE/NotificationService.swift index b4a6bbd974..24f5bc5144 100644 --- a/apps/ios/SimpleX NSE/NotificationService.swift +++ b/apps/ios/SimpleX NSE/NotificationService.swift @@ -271,25 +271,8 @@ func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? { if !cInfo.ntfsEnabled { ntfBadgeCountGroupDefault.set(max(0, ntfBadgeCountGroupDefault.get() - 1)) } - if case .image = cItem.content.msgContent { - if let file = cItem.file, - file.fileSize <= MAX_IMAGE_SIZE_AUTO_RCV, - privacyAcceptImagesGroupDefault.get() { - cItem = autoReceiveFile(file) ?? cItem - } - } else if case .video = cItem.content.msgContent { - if let file = cItem.file, - file.fileSize <= MAX_VIDEO_SIZE_AUTO_RCV, - privacyAcceptImagesGroupDefault.get() { - cItem = autoReceiveFile(file) ?? cItem - } - } else if case .voice = cItem.content.msgContent { // TODO check inlineFileMode != IFMSent - if let file = cItem.file, - file.fileSize <= MAX_IMAGE_SIZE, - file.fileSize > MAX_VOICE_MESSAGE_SIZE_INLINE_SEND, - privacyAcceptImagesGroupDefault.get() { - cItem = autoReceiveFile(file) ?? cItem - } + if let file = cItem.autoReceiveFile() { + cItem = autoReceiveFile(file) ?? cItem } let ntf: NSENotification = cInfo.ntfsEnabled ? .nse(notification: createMessageReceivedNtf(user, cInfo, cItem)) : .empty return cItem.showMutableNotification ? (aChatItem.chatId, ntf) : nil diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 492758fa04..23a1d95565 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -1851,6 +1851,18 @@ public struct ChatItem: Identifiable, Decodable { } } + public func autoReceiveFile() -> CIFile? { + if let file = file, + let mc = content.msgContent, + privacyAcceptImagesGroupDefault.get(), + (mc.isImage && file.fileSize <= MAX_IMAGE_SIZE_AUTO_RCV) + || (mc.isVideo && file.fileSize <= MAX_VIDEO_SIZE_AUTO_RCV) + || (mc.isVoice && file.fileSize <= MAX_VOICE_SIZE_AUTO_RCV && file.fileStatus != .rcvAccepted) { + return file + } + return nil + } + public var showMutableNotification: Bool { switch content { case .rcvCall: return false @@ -2383,7 +2395,7 @@ public enum FileProtocol: String, Decodable { case xftp = "xftp" } -public enum CIFileStatus: Decodable { +public enum CIFileStatus: Decodable, Equatable { case sndStored case sndTransfer(sndProgress: Int64, sndTotal: Int64) case sndComplete diff --git a/apps/ios/SimpleXChat/FileUtils.swift b/apps/ios/SimpleXChat/FileUtils.swift index dd6cdbf178..148ab12e29 100644 --- a/apps/ios/SimpleXChat/FileUtils.swift +++ b/apps/ios/SimpleXChat/FileUtils.swift @@ -11,20 +11,20 @@ import OSLog let logger = Logger() -// maximum image file size to be auto-accepted -public let MAX_IMAGE_SIZE: Int64 = 236700 +// image file size for complession +public let MAX_IMAGE_SIZE: Int64 = 261_120 // 255KB public let MAX_IMAGE_SIZE_AUTO_RCV: Int64 = MAX_IMAGE_SIZE * 2 -public let MAX_VIDEO_SIZE_AUTO_RCV: Int64 = 8000000 +public let MAX_VOICE_SIZE_AUTO_RCV: Int64 = MAX_IMAGE_SIZE * 2 -public let MAX_FILE_SIZE_XFTP: Int64 = 1_073_741_824 +public let MAX_VIDEO_SIZE_AUTO_RCV: Int64 = 1_047_552 // 1023KB + +public let MAX_FILE_SIZE_XFTP: Int64 = 1_073_741_824 // 1GB public let MAX_FILE_SIZE_SMP: Int64 = 8000000 -public let MAX_VOICE_MESSAGE_LENGTH = TimeInterval(30) - -public let MAX_VOICE_MESSAGE_SIZE_INLINE_SEND: Int64 = 94680 +public let MAX_VOICE_MESSAGE_LENGTH = TimeInterval(300) private let CHAT_DB: String = "_chat.db"