Follow the spec regarding waveform content

This commit is contained in:
Benoit Marty 2021-07-13 17:58:14 +02:00
parent 95bb796bad
commit 6a0ea11e7a
4 changed files with 212 additions and 1 deletions

View File

@ -19,11 +19,18 @@ package org.matrix.android.sdk.api.session.room.model.message
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* See https://github.com/matrix-org/matrix-doc/blob/travis/msc/audio-waveform/proposals/3246-audio-waveform.md
*/
@JsonClass(generateAdapter = true)
data class AudioWaveformInfo(
@Json(name = "duration")
val duration: Int? = null,
/**
* The array should have no less than 30 elements and no more than 120.
* List of integers between zero and 1024, inclusive.
*/
@Json(name = "waveform")
val waveform: List<Int>? = null
)

View File

@ -75,6 +75,7 @@ internal class LocalEchoEventFactory @Inject constructor(
private val markdownParser: MarkdownParser,
private val textPillsUtils: TextPillsUtils,
private val thumbnailExtractor: ThumbnailExtractor,
private val waveformSanitizer: WaveFormSanitizer,
private val localEchoRepository: LocalEchoRepository,
private val permalinkFactory: PermalinkFactory
) {
@ -302,7 +303,7 @@ internal class LocalEchoEventFactory @Inject constructor(
url = attachment.queryUri.toString(),
audioWaveformInfo = if (!isVoiceMessage) null else AudioWaveformInfo(
duration = attachment.duration?.toInt(),
waveform = attachment.waveform
waveform = waveformSanitizer.sanitize(attachment.waveform)
),
voiceMessageIndicator = if (!isVoiceMessage) null else emptyMap()
)

View File

@ -0,0 +1,80 @@
/*
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.session.room.send
import javax.inject.Inject
import kotlin.math.abs
import kotlin.math.ceil
internal class WaveFormSanitizer @Inject constructor() {
private companion object {
const val MIN_NUMBER_OF_VALUES = 30
const val MAX_NUMBER_OF_VALUES = 120
const val MAX_VALUE = 1024
}
/**
* The array should have no less than 30 elements and no more than 120.
* List of integers between zero and 1024, inclusive.
*/
fun sanitize(waveForm: List<Int>?): List<Int>? {
if (waveForm.isNullOrEmpty()) {
return null
}
// Limit the number of items
val result = mutableListOf<Int>()
if (waveForm.size < MIN_NUMBER_OF_VALUES) {
// Repeat the same value to have at least 30 items
val repeatTimes = ceil(MIN_NUMBER_OF_VALUES / waveForm.size.toDouble()).toInt()
waveForm.map { value ->
repeat(repeatTimes) {
result.add(value)
}
}
} else if (waveForm.size > MAX_NUMBER_OF_VALUES) {
val keepOneOf = ceil(waveForm.size.toDouble() / MAX_NUMBER_OF_VALUES).toInt()
waveForm.mapIndexed { idx, value ->
if (idx % keepOneOf == 0) {
result.add(value)
}
}
} else {
result.addAll(waveForm)
}
// OK, ensure all items are positive
val limited = result.map {
abs(it)
}
// Ensure max is not above MAX_VALUE
val max = limited.maxOrNull() ?: MAX_VALUE
val final = if (max > MAX_VALUE) {
// Reduce the range
limited.map {
it * MAX_VALUE / max
}
} else {
limited
}
return final
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.session.room.send
import org.amshove.kluent.shouldBe
import org.amshove.kluent.shouldBeInRange
import org.junit.Test
class WaveFormSanitizerTest {
private val waveFormSanitizer = WaveFormSanitizer()
@Test
fun sanitizeNull() {
waveFormSanitizer.sanitize(null) shouldBe null
}
@Test
fun sanitizeEmpty() {
waveFormSanitizer.sanitize(emptyList()) shouldBe null
}
@Test
fun sanitizeSingleton() {
val result = waveFormSanitizer.sanitize(listOf(1))!!
result.size shouldBe 30
checkResult(result)
}
@Test
fun sanitize29() {
val list = generateSequence { 1 }.take(29).toList()
val result = waveFormSanitizer.sanitize(list)!!
checkResult(result)
}
@Test
fun sanitize30() {
val list = generateSequence { 1 }.take(30).toList()
val result = waveFormSanitizer.sanitize(list)!!
result.size shouldBe 30
checkResult(result)
}
@Test
fun sanitize31() {
val list = generateSequence { 1 }.take(31).toList()
val result = waveFormSanitizer.sanitize(list)!!
checkResult(result)
}
@Test
fun sanitize119() {
val list = generateSequence { 1 }.take(119).toList()
val result = waveFormSanitizer.sanitize(list)!!
checkResult(result)
}
@Test
fun sanitize120() {
val list = generateSequence { 1 }.take(120).toList()
val result = waveFormSanitizer.sanitize(list)!!
result.size shouldBe 120
checkResult(result)
}
@Test
fun sanitize121() {
val list = generateSequence { 1 }.take(121).toList()
val result = waveFormSanitizer.sanitize(list)!!
checkResult(result)
}
@Test
fun sanitize1024() {
val list = generateSequence { 1 }.take(1024).toList()
val result = waveFormSanitizer.sanitize(list)!!
checkResult(result)
}
@Test
fun sanitizeNegative() {
val list = generateSequence { -1 }.take(30).toList()
val result = waveFormSanitizer.sanitize(list)!!
checkResult(result)
}
@Test
fun sanitizeMaxValue() {
val list = generateSequence { 1025 }.take(30).toList()
val result = waveFormSanitizer.sanitize(list)!!
checkResult(result)
}
@Test
fun sanitizeNegativeMaxValue() {
val list = generateSequence { -1025 }.take(30).toList()
val result = waveFormSanitizer.sanitize(list)!!
checkResult(result)
}
private fun checkResult(result: List<Int>) {
result.forEach {
it shouldBeInRange 0..1024
}
result.size shouldBeInRange 30..120
}
}