Browse Source

Convert mx waveform to floats as early as possible in the chain (#1652)

This way we're sure that internally we always deal with [0;1] float samples. the [0;1024] int range is used only at the rust sdk boundary.
pull/1654/head
Marco Romano 11 months ago committed by GitHub
parent
commit
0457e5915c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVoiceView.kt
  2. 3
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt
  3. 2
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContent.kt
  4. 6
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContentProvider.kt
  5. 23
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/WaveformUtils.kt
  6. 50
      libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/Waveform.kt
  7. 38
      libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/WaveformPlaybackView.kt
  8. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVoiceView-D-40_40_null_5,NEXUS_5,1.0,en].png
  9. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVoiceView-D-40_40_null_6,NEXUS_5,1.0,en].png
  10. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVoiceView-D-40_40_null_7,NEXUS_5,1.0,en].png
  11. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVoiceView-D-40_40_null_8,NEXUS_5,1.0,en].png
  12. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVoiceView-D-40_40_null_9,NEXUS_5,1.0,en].png
  13. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVoiceView-N-40_41_null_5,NEXUS_5,1.0,en].png
  14. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVoiceView-N-40_41_null_6,NEXUS_5,1.0,en].png
  15. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVoiceView-N-40_41_null_7,NEXUS_5,1.0,en].png
  16. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVoiceView-N-40_41_null_8,NEXUS_5,1.0,en].png
  17. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVoiceView-N-40_41_null_9,NEXUS_5,1.0,en].png
  18. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVoiceViewUnified-D-41_41_null,NEXUS_5,1.0,en].png
  19. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVoiceViewUnified-N-41_42_null,NEXUS_5,1.0,en].png
  20. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.media_null_WaveformPlaybackView-D_0_null,NEXUS_5,1.0,en].png
  21. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.media_null_WaveformPlaybackView-N_1_null,NEXUS_5,1.0,en].png

3
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVoiceView.kt

@ -44,7 +44,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt @@ -44,7 +44,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageEvents
import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageState
import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageStateProvider
import io.element.android.libraries.designsystem.components.media.Waveform
import io.element.android.libraries.designsystem.components.media.WaveformPlaybackView
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
@ -88,7 +87,7 @@ fun TimelineItemVoiceView( @@ -88,7 +87,7 @@ fun TimelineItemVoiceView(
WaveformPlaybackView(
showCursor = state.button == VoiceMessageState.Button.Pause,
playbackProgress = state.progress,
waveform = Waveform(data = content.waveform),
waveform = content.waveform,
modifier = Modifier
.height(34.dp)
.weight(1f),

3
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt

@ -29,6 +29,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt @@ -29,6 +29,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
import io.element.android.features.messages.impl.timeline.util.FileExtensionExtractor
import io.element.android.features.messages.impl.timeline.util.toHtmlDocument
import io.element.android.features.messages.impl.voicemessages.fromMSC3246range
import io.element.android.libraries.androidutils.filesize.FileSizeFormatter
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.featureflag.api.FeatureFlagService
@ -117,7 +118,7 @@ class TimelineItemContentMessageFactory @Inject constructor( @@ -117,7 +118,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
mediaSource = messageType.source,
duration = messageType.info?.duration ?: Duration.ZERO,
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
waveform = messageType.details?.waveform?.toImmutableList() ?: persistentListOf(),
waveform = messageType.details?.waveform?.fromMSC3246range()?.toImmutableList() ?: persistentListOf(),
)
else -> TimelineItemAudioContent(
body = messageType.body,

2
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContent.kt

@ -27,7 +27,7 @@ data class TimelineItemVoiceContent( @@ -27,7 +27,7 @@ data class TimelineItemVoiceContent(
val duration: Duration,
val mediaSource: MediaSource,
val mimeType: String,
val waveform: ImmutableList<Int>,
val waveform: ImmutableList<Float>,
) : TimelineItemEventContent {
override val type: String = "TimelineItemAudioContent"
}

6
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContentProvider.kt

@ -32,11 +32,11 @@ open class TimelineItemVoiceContentProvider : PreviewParameterProvider<TimelineI @@ -32,11 +32,11 @@ open class TimelineItemVoiceContentProvider : PreviewParameterProvider<TimelineI
),
aTimelineItemVoiceContent(
durationMs = 10_000,
waveform = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0),
waveform = listOf(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 8f, 7f, 6f, 5f, 4f, 3f, 2f, 1f, 0f),
),
aTimelineItemVoiceContent(
durationMs = 1_800_000, // 30 minutes
waveform = List(1024) { it },
waveform = List(1024) { it / 1024f },
),
)
}
@ -47,7 +47,7 @@ fun aTimelineItemVoiceContent( @@ -47,7 +47,7 @@ fun aTimelineItemVoiceContent(
durationMs: Long = 61_000,
contentUri: String = "mxc://matrix.org/1234567890abcdefg",
mimeType: String = MimeTypes.Ogg,
waveform: List<Int> = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0),
waveform: List<Float> = listOf(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 8f, 7f, 6f, 5f, 4f, 3f, 2f, 1f, 0f),
) = TimelineItemVoiceContent(
eventId = eventId?.let { EventId(it) },
body = body,

23
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/WaveformUtils.kt

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.messages.impl.voicemessages
/**
* Resizes the given [0;1024] int list as per unstable MSC3246 spec
* to a [0;1] range float list to be used for waveform rendering.
*/
fun List<Int>.fromMSC3246range(): List<Float> = map { it / 1024f }

50
libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/Waveform.kt

@ -1,50 +0,0 @@ @@ -1,50 +0,0 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.designsystem.components.media
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlin.math.roundToInt
data class Waveform (
val data: ImmutableList<Int>
) {
companion object {
private val dataRange = 0..1024
}
fun normalisedData(maxSamplesCount: Int): ImmutableList<Float> {
if(maxSamplesCount <= 0) {
return persistentListOf()
}
// Filter the data to keep only the expected number of samples
val result = if (data.size > maxSamplesCount) {
(0..<maxSamplesCount)
.map { index ->
val targetIndex = (index.toDouble() * (data.count().toDouble() / maxSamplesCount.toDouble())).roundToInt()
data[targetIndex]
}
} else {
data
}
// Normalize the sample in the allowed range
return result.map { it.toFloat() / dataRange.last.toFloat() }.toPersistentList()
}
}

38
libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/WaveformPlaybackView.kt

@ -46,22 +46,25 @@ import androidx.compose.ui.unit.dp @@ -46,22 +46,25 @@ import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.theme.ElementTheme
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlin.math.max
import kotlin.math.roundToInt
private const val DEFAULT_GRAPHICS_LAYER_ALPHA: Float = 0.99F
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun WaveformPlaybackView(
playbackProgress: Float,
showCursor: Boolean,
waveform: Waveform,
waveform: ImmutableList<Float>,
modifier: Modifier = Modifier,
onSeek: (progress: Float) -> Unit = {},
brush: Brush = SolidColor(ElementTheme.colors.iconQuaternary),
progressBrush: Brush = SolidColor(ElementTheme.colors.iconSecondary),
cursorBrush: Brush = SolidColor(ElementTheme.colors.iconAccentTertiary),
progressBrush: Brush = SolidColor(ElementTheme.colors.iconSecondary),
cursorBrush: Brush = SolidColor(ElementTheme.colors.iconAccentTertiary),
lineWidth: Dp = 2.dp,
linePadding: Dp = 2.dp,
minimumGraphAmplitude: Float = 2F,
@ -145,7 +148,7 @@ fun WaveformPlaybackView( @@ -145,7 +148,7 @@ fun WaveformPlaybackView(
),
blendMode = BlendMode.SrcAtop
)
if(showCursor || seekProgress.value != null) {
if (showCursor || seekProgress.value != null) {
drawRoundRect(
brush = cursorBrush,
topLeft = Offset(
@ -166,24 +169,43 @@ fun WaveformPlaybackView( @@ -166,24 +169,43 @@ fun WaveformPlaybackView(
@PreviewsDayNight
@Composable
internal fun WaveformPlaybackViewPreview() = ElementPreview {
Column{
Column {
WaveformPlaybackView(
modifier = Modifier.height(34.dp),
showCursor = false,
playbackProgress = 0.5f,
waveform = Waveform(persistentListOf()),
waveform = persistentListOf(),
)
WaveformPlaybackView(
modifier = Modifier.height(34.dp),
showCursor = false,
playbackProgress = 0.5f,
waveform = Waveform(persistentListOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)),
waveform = persistentListOf(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 8f, 7f, 6f, 5f, 4f, 3f, 2f, 1f, 0f),
)
WaveformPlaybackView(
modifier = Modifier.height(34.dp),
showCursor = true,
playbackProgress = 0.5f,
waveform = Waveform(List(1024) { it }.toPersistentList()),
waveform = List(1024) { it / 1024f }.toPersistentList(),
)
}
}
private fun ImmutableList<Float>.normalisedData(maxSamplesCount: Int): ImmutableList<Float> {
if (maxSamplesCount <= 0) {
return persistentListOf()
}
// Filter the data to keep only the expected number of samples
val result = if (this.size > maxSamplesCount) {
(0..<maxSamplesCount)
.map { index ->
val targetIndex = (index.toDouble() * (this.count().toDouble() / maxSamplesCount.toDouble())).roundToInt()
this[targetIndex]
}
} else {
this
}
return result.toPersistentList()
}

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVoiceView-D-40_40_null_5,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVoiceView-D-40_40_null_6,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVoiceView-D-40_40_null_7,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVoiceView-D-40_40_null_8,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVoiceView-D-40_40_null_9,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVoiceView-N-40_41_null_5,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVoiceView-N-40_41_null_6,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVoiceView-N-40_41_null_7,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVoiceView-N-40_41_null_8,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVoiceView-N-40_41_null_9,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVoiceViewUnified-D-41_41_null,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVoiceViewUnified-N-41_42_null,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.media_null_WaveformPlaybackView-D_0_null,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.media_null_WaveformPlaybackView-N_1_null,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.
Loading…
Cancel
Save