Skip to content

Commit

Permalink
feat: Audio messages new design #WPB-11723 (#3718)
Browse files Browse the repository at this point in the history
  • Loading branch information
borichellow authored Dec 27, 2024
1 parent f1c158b commit 7741fa7
Show file tree
Hide file tree
Showing 29 changed files with 861 additions and 154 deletions.
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ dependencies {
implementation(libs.aboutLibraries.core)
implementation(libs.aboutLibraries.ui)
implementation(libs.compose.qr.code)
implementation(libs.audio.amplituda)

// screenshot testing
screenshotTestImplementation(libs.compose.ui.tooling)
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/kotlin/com/wire/android/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import linc.com.amplituda.Amplituda
import javax.inject.Qualifier
import javax.inject.Singleton

Expand Down Expand Up @@ -84,6 +85,9 @@ object AppModule {
}
}

@Provides
fun provideAmplituda(appContext: Context): Amplituda = Amplituda(appContext)

@Singleton
@Provides
fun provideCurrentTimestampProvider(): CurrentTimestampProvider = { System.currentTimeMillis() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import com.wire.kalium.logic.feature.message.GetNotificationsUseCase
import com.wire.kalium.logic.feature.message.GetPaginatedFlowOfMessagesByConversationUseCase
import com.wire.kalium.logic.feature.message.GetPaginatedFlowOfMessagesBySearchQueryAndConversationIdUseCase
import com.wire.kalium.logic.feature.message.GetSearchedConversationMessagePositionUseCase
import com.wire.kalium.logic.feature.message.GetSenderNameByMessageIdUseCase
import com.wire.kalium.logic.feature.message.MarkMessagesAsNotifiedUseCase
import com.wire.kalium.logic.feature.message.MessageScope
import com.wire.kalium.logic.feature.message.ObserveMessageReactionsUseCase
Expand Down Expand Up @@ -222,4 +223,9 @@ class MessageModule {
@Provides
fun provideSendInCallReactionUseCase(messageScope: MessageScope): SendInCallReactionUseCase =
messageScope.sendInCallReactionUseCase

@ViewModelScoped
@Provides
fun provideGetSenderNameByMessageIdUseCase(messageScope: MessageScope): GetSenderNameByMessageIdUseCase =
messageScope.getSenderNameByMessageId
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,24 @@
*/
package com.wire.android.media.audiomessage

import androidx.annotation.StringRes
import com.wire.android.R

data class AudioState(
val audioMediaPlayingState: AudioMediaPlayingState,
val currentPositionInMs: Int,
val totalTimeInMs: TotalTimeInMs
val totalTimeInMs: TotalTimeInMs,
val wavesMask: List<Int>
) {
companion object {
val DEFAULT = AudioState(AudioMediaPlayingState.Stopped, 0, TotalTimeInMs.NotKnown)
val DEFAULT = AudioState(AudioMediaPlayingState.Stopped, 0, TotalTimeInMs.NotKnown, listOf())
}

// if the back-end returned the total time, we use that, in case it didn't we use what we get from
// the [ConversationAudioMessagePlayer.kt] which will emit the time once the users play the audio.
fun sanitizeTotalTime(otherClientTotalTime: Int): TotalTimeInMs {
if (otherClientTotalTime != 0) {
return TotalTimeInMs.Known(otherClientTotalTime)
return TotalTimeInMs.Known(otherClientTotalTime)
}

return totalTimeInMs
Expand All @@ -43,6 +47,27 @@ data class AudioState(
}
}

@Suppress("MagicNumber")
enum class AudioSpeed(val value: Float, @StringRes val titleRes: Int) {
NORMAL(1f, R.string.audio_speed_1),
FAST(1.5f, R.string.audio_speed_1_5),
MAX(2f, R.string.audio_speed_2);

fun toggle(): AudioSpeed = when (this) {
NORMAL -> FAST
FAST -> MAX
MAX -> NORMAL
}

companion object {
fun fromFloat(speed: Float): AudioSpeed = when {
(speed < FAST.value) -> NORMAL
(speed < MAX.value) -> FAST
else -> MAX
}
}
}

sealed class AudioMediaPlayingState {
object Playing : AudioMediaPlayingState()
object Stopped : AudioMediaPlayingState()
Expand Down Expand Up @@ -75,6 +100,11 @@ sealed class AudioMediaPlayerStateUpdate(
override val messageId: String,
val totalTimeInMs: Int
) : AudioMediaPlayerStateUpdate(messageId)

data class WaveMaskUpdate(
override val messageId: String,
val waveMask: List<Int>
) : AudioMediaPlayerStateUpdate(messageId)
}

sealed class RecordAudioMediaPlayerStateUpdate {
Expand All @@ -89,4 +119,8 @@ sealed class RecordAudioMediaPlayerStateUpdate {
data class TotalTimeUpdate(
val totalTimeInMs: Int
) : RecordAudioMediaPlayerStateUpdate()

data class WaveMaskUpdate(
val waveMask: List<Int>
) : RecordAudioMediaPlayerStateUpdate()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.media.audiomessage

import linc.com.amplituda.Amplituda
import linc.com.amplituda.Cache
import okio.Path
import java.io.File
import javax.inject.Inject
import kotlin.math.roundToInt

class AudioWavesMaskHelper @Inject constructor(
private val amplituda: Amplituda
) {

companion object {
private const val WAVES_AMOUNT = 75
private const val WAVE_MAX = 32
}

fun getWaveMask(decodedAssetPath: Path): List<Int> = getWaveMask(File(decodedAssetPath.toString()))

fun getWaveMask(file: File): List<Int> = amplituda
.processAudio(file, Cache.withParams(Cache.REUSE))
.get()
.amplitudesAsList()
.averageWavesMask()
.equalizeWavesMask()

private fun List<Double>.equalizeWavesMask(): List<Int> {
if (this.isEmpty()) return listOf()

val divider = max() / (WAVE_MAX - 1)
return map { (it / divider).roundToInt() + 1 }
}

private fun List<Int>.averageWavesMask(): List<Double> {
val wavesSize = size
val sectionSize = (wavesSize.toFloat() / WAVES_AMOUNT).roundToInt()

if (wavesSize < WAVES_AMOUNT || sectionSize == 1) return map { it.toDouble() }

val averagedWaves = mutableListOf<Double>()
for (i in 0..<(wavesSize / sectionSize)) {
val startIndex = (i * sectionSize)
if (startIndex >= wavesSize) continue
val endIndex = (startIndex + sectionSize).coerceAtMost(wavesSize - 1)
averagedWaves.add(subList(startIndex, endIndex).averageInt())
}
return averagedWaves
}

private fun List<Int>.averageInt(): Double {
if (isEmpty()) return 0.0
return sum().toDouble() / size
}

fun clear() {
amplituda.clearCache()
}
}
Loading

0 comments on commit 7741fa7

Please sign in to comment.