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>
This commit is contained in:
Stanislav Dmitrenko 2023-05-15 11:17:32 +03:00 committed by GitHub
parent 0ec2468dce
commit baf3a12009
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 69 additions and 66 deletions

View file

@ -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)

View file

@ -307,7 +307,7 @@ private fun BoxScope.DeleteTextButton(composeState: MutableState<ComposeState>)
@Composable
private fun RecordVoiceView(recState: MutableState<RecordingState>, stopRecOnNextClick: MutableState<Boolean>) {
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 {

View file

@ -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(

View file

@ -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()

View file

@ -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)
}

View file

@ -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()

View file

@ -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()

View file

@ -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)

View file

@ -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 {

View file

@ -251,7 +251,6 @@ struct CIVideoView: View {
if let user = ChatModel.shared.currentUser {
await receiveFile(user, file.fileId)
}
// TODO image accepted alert?
}
}

View file

@ -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?

View file

@ -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

View file

@ -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

View file

@ -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"