fix: audio waveforms decoded on the attachment download

This commit is contained in:
jubb 2021-07-01 17:06:42 +10:00
parent 9566120d66
commit b329402faf
10 changed files with 84 additions and 49 deletions

View File

@ -194,8 +194,8 @@ android {
versionCode canonicalVersionCode * postFixSize versionCode canonicalVersionCode * postFixSize
versionName canonicalVersionName versionName canonicalVersionName
minSdkVersion 23 minSdkVersion androidMinimumSdkVersion
targetSdkVersion 30 targetSdkVersion androidCompileSdkVersion
multiDexEnabled = true multiDexEnabled = true

View File

@ -97,6 +97,14 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
attachmentDatabase.insertAttachmentsForPlaceholder(messageId, attachmentId, stream) attachmentDatabase.insertAttachmentsForPlaceholder(messageId, attachmentId, stream)
} }
override fun updateAudioAttachmentDuration(attachmentId: AttachmentId, durationMs: Long) {
DatabaseFactory.getAttachmentDatabase(context).setAttachmentAudioExtras(DatabaseAttachmentAudioExtras(
attachmentId = attachmentId,
visualSamples = byteArrayOf(),
durationMs = durationMs
))
}
override fun isOutgoingMessage(timestamp: Long): Boolean { override fun isOutgoingMessage(timestamp: Long): Boolean {
val smsDatabase = DatabaseFactory.getSmsDatabase(context) val smsDatabase = DatabaseFactory.getSmsDatabase(context)
val mmsDatabase = DatabaseFactory.getMmsDatabase(context) val mmsDatabase = DatabaseFactory.getMmsDatabase(context)

View File

@ -10,9 +10,11 @@ import android.widget.RelativeLayout
import androidx.core.view.isVisible import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.view_voice_message.view.* import kotlinx.android.synthetic.main.view_voice_message.view.*
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
import org.thoughtcrime.securesms.audio.AudioSlidePlayer import org.thoughtcrime.securesms.audio.AudioSlidePlayer
import org.thoughtcrime.securesms.components.CornerMask import org.thoughtcrime.securesms.components.CornerMask
import org.thoughtcrime.securesms.conversation.v2.utilities.MessageBubbleUtilities import org.thoughtcrime.securesms.conversation.v2.utilities.MessageBubbleUtilities
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -44,27 +46,29 @@ class VoiceMessageView : LinearLayout, AudioSlidePlayer.Listener {
val audio = message.slideDeck.audioSlide!! val audio = message.slideDeck.audioSlide!!
val player = AudioSlidePlayer.createFor(context, audio, this) val player = AudioSlidePlayer.createFor(context, audio, this)
this.player = player this.player = player
isPreparing = true
if (!audio.isPendingDownload && !audio.isInProgress) {
player.play(0.0)
}
voiceMessageViewLoader.isVisible = audio.isPendingDownload voiceMessageViewLoader.isVisible = audio.isPendingDownload
val cornerRadii = MessageBubbleUtilities.calculateRadii(context, isStartOfMessageCluster, isEndOfMessageCluster, message.isOutgoing) val cornerRadii = MessageBubbleUtilities.calculateRadii(context, isStartOfMessageCluster, isEndOfMessageCluster, message.isOutgoing)
cornerMask.setTopLeftRadius(cornerRadii[0]) cornerMask.setTopLeftRadius(cornerRadii[0])
cornerMask.setTopRightRadius(cornerRadii[1]) cornerMask.setTopRightRadius(cornerRadii[1])
cornerMask.setBottomRightRadius(cornerRadii[2]) cornerMask.setBottomRightRadius(cornerRadii[2])
cornerMask.setBottomLeftRadius(cornerRadii[3]) cornerMask.setBottomLeftRadius(cornerRadii[3])
// only process audio if downloaded
if (audio.isPendingDownload || audio.isInProgress) return
(audio.asAttachment() as? DatabaseAttachment)?.let { attachment ->
DatabaseFactory.getAttachmentDatabase(context).getAttachmentAudioExtras(attachment.attachmentId)?.let { audioExtras ->
if (audioExtras.durationMs > 0) {
voiceMessageViewDurationTextView.visibility = View.VISIBLE
voiceMessageViewDurationTextView.text = String.format("%02d:%02d",
TimeUnit.MILLISECONDS.toMinutes(audioExtras.durationMs),
TimeUnit.MILLISECONDS.toSeconds(audioExtras.durationMs))
}
}
}
} }
override fun onPlayerStart(player: AudioSlidePlayer) { override fun onPlayerStart(player: AudioSlidePlayer) {}
if (!isPreparing) { return }
isPreparing = false
duration = player.duration
voiceMessageViewDurationTextView.text = String.format("%01d:%02d",
TimeUnit.MILLISECONDS.toMinutes(duration),
TimeUnit.MILLISECONDS.toSeconds(duration))
player.stop()
}
override fun onPlayerProgress(player: AudioSlidePlayer, progress: Double, unused: Long) { override fun onPlayerProgress(player: AudioSlidePlayer, progress: Double, unused: Long) {
if (progress == 1.0) { if (progress == 1.0) {

View File

@ -9,10 +9,11 @@ import org.session.libsession.messaging.utilities.Data
import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.messaging.sending_receiving.attachments.Attachment
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachmentAudioExtras import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachmentAudioExtras
import org.session.libsession.utilities.DecodedAudio
import org.session.libsession.utilities.InputStreamMediaDataSource
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.jobmanager.Job import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.jobs.BaseJob import org.thoughtcrime.securesms.jobs.BaseJob
import org.thoughtcrime.securesms.loki.utilities.DecodedAudio
import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.mms.PartAuthority
import java.io.InputStream import java.io.InputStream
import java.lang.IllegalStateException import java.lang.IllegalStateException
@ -133,35 +134,4 @@ class PrepareAttachmentAudioExtrasJob : BaseJob {
/** Gets dispatched once the audio extras have been updated. */ /** Gets dispatched once the audio extras have been updated. */
data class AudioExtrasUpdatedEvent(val attachmentId: AttachmentId) data class AudioExtrasUpdatedEvent(val attachmentId: AttachmentId)
@RequiresApi(Build.VERSION_CODES.M)
private class InputStreamMediaDataSource: MediaDataSource {
private val data: ByteArray
constructor(inputStream: InputStream): super() {
this.data = inputStream.readBytes()
}
override fun readAt(position: Long, buffer: ByteArray, offset: Int, size: Int): Int {
val length: Int = data.size
if (position >= length) {
return -1 // -1 indicates EOF
}
var actualSize = size
if (position + size > length) {
actualSize -= (position + size - length).toInt()
}
System.arraycopy(data, position.toInt(), buffer, offset, actualSize)
return actualSize
}
override fun getSize(): Long {
return data.size.toLong()
}
override fun close() {
// We don't need to close the wrapped stream.
}
}
} }

View File

@ -14,7 +14,7 @@ import android.view.ViewConfiguration
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import androidx.core.math.MathUtils import androidx.core.math.MathUtils
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.loki.utilities.byteToNormalizedFloat import org.session.libsession.utilities.byteToNormalizedFloat
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min

View File

@ -50,6 +50,7 @@ allprojects {
} }
project.ext { project.ext {
androidMinimumSdkVersion = 23
androidCompileSdkVersion = 30 androidCompileSdkVersion = 30
} }
} }

View File

@ -6,6 +6,10 @@ plugins {
android { android {
compileSdkVersion androidCompileSdkVersion compileSdkVersion androidCompileSdkVersion
defaultConfig {
minSdkVersion androidMinimumSdkVersion
}
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8

View File

@ -20,6 +20,7 @@ interface MessageDataProvider {
fun getSignalAttachmentPointer(attachmentId: Long): SignalServiceAttachmentPointer? fun getSignalAttachmentPointer(attachmentId: Long): SignalServiceAttachmentPointer?
fun setAttachmentState(attachmentState: AttachmentState, attachmentId: Long, messageID: Long) fun setAttachmentState(attachmentState: AttachmentState, attachmentId: Long, messageID: Long)
fun insertAttachment(messageId: Long, attachmentId: AttachmentId, stream : InputStream) fun insertAttachment(messageId: Long, attachmentId: AttachmentId, stream : InputStream)
fun updateAudioAttachmentDuration(attachmentId: AttachmentId, durationMs: Long)
fun isOutgoingMessage(timestamp: Long): Boolean fun isOutgoingMessage(timestamp: Long): Boolean
fun handleSuccessfulAttachmentUpload(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult) fun handleSuccessfulAttachmentUpload(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult)
fun handleFailedAttachmentUpload(attachmentId: Long) fun handleFailedAttachmentUpload(attachmentId: Long)

View File

@ -1,15 +1,22 @@
package org.session.libsession.messaging.jobs package org.session.libsession.messaging.jobs
import android.content.ContentResolver
import android.media.MediaDataSource
import android.media.MediaExtractor
import okhttp3.HttpUrl import okhttp3.HttpUrl
import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2 import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState
import org.session.libsession.messaging.utilities.Data import org.session.libsession.messaging.utilities.Data
import org.session.libsession.utilities.DecodedAudio
import org.session.libsession.utilities.DownloadUtilities import org.session.libsession.utilities.DownloadUtilities
import org.session.libsession.utilities.FileUtils
import org.session.libsession.utilities.InputStreamMediaDataSource
import org.session.libsignal.streams.AttachmentCipherInputStream import org.session.libsignal.streams.AttachmentCipherInputStream
import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import java.io.File import java.io.File
import java.io.FileDescriptor
import java.io.FileInputStream import java.io.FileInputStream
class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) : Job { class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) : Job {
@ -67,6 +74,15 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long)
} }
FileInputStream(tempFile) FileInputStream(tempFile)
} }
if (attachment.contentType.startsWith("audio/")) {
// process the duration
InputStreamMediaDataSource(inputStream).use { mediaDataSource ->
val durationMs = (DecodedAudio.create(mediaDataSource).totalDuration / 1000.0).toLong()
messageDataProvider.updateAudioAttachmentDuration(attachment.attachmentId, durationMs)
}
}
messageDataProvider.insertAttachment(databaseMessageID, attachment.attachmentId, inputStream) messageDataProvider.insertAttachment(databaseMessageID, attachment.attachmentId, inputStream)
tempFile.delete() tempFile.delete()
handleSuccess() handleSuccess()

View File

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.loki.utilities package org.session.libsession.utilities
import android.media.AudioFormat import android.media.AudioFormat
import android.media.MediaCodec import android.media.MediaCodec
@ -11,6 +11,7 @@ import androidx.annotation.RequiresApi
import java.io.FileDescriptor import java.io.FileDescriptor
import java.io.IOException import java.io.IOException
import java.io.InputStream
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
import java.nio.ShortBuffer import java.nio.ShortBuffer
@ -365,4 +366,34 @@ inline fun byteToNormalizedFloat(value: Byte): Float {
/** Turns a [0..1] float into a signed byte. */ /** Turns a [0..1] float into a signed byte. */
inline fun normalizedFloatToByte(value: Float): Byte { inline fun normalizedFloatToByte(value: Float): Byte {
return (255f * value - 128f).roundToInt().toByte() return (255f * value - 128f).roundToInt().toByte()
}
class InputStreamMediaDataSource: MediaDataSource {
private val data: ByteArray
constructor(inputStream: InputStream): super() {
this.data = inputStream.readBytes()
}
override fun readAt(position: Long, buffer: ByteArray, offset: Int, size: Int): Int {
val length: Int = data.size
if (position >= length) {
return -1 // -1 indicates EOF
}
var actualSize = size
if (position + size > length) {
actualSize -= (position + size - length).toInt()
}
System.arraycopy(data, position.toInt(), buffer, offset, actualSize)
return actualSize
}
override fun getSize(): Long {
return data.size.toLong()
}
override fun close() {
// We don't need to close the wrapped stream.
}
} }