Add media file limit size warning and media quality selection (#5131)
* Add `VideoCompressorPreset` enum This represents the different compression presets used for processing videos before uploading them * Add `VideoCompressorHelper` util class to calculate the scaled output size of the video given an input size and its optimal bitrate Also add `MediaOptimizationConfig` which will be used to decide how to apply compression in `MediaPreProcessor` * Add `RustMatrixClient.getMaxFileUploadSize()` function and `MaxUploadSizeProvider` so we can import only this functionality into other components * Try preloading the max file upload size the first time we get network connectivity - it's a best effort This should help ensure we'll have this value available later, even if we still need to load it asynchronously. * Split the `compressMedia` preference into `compressImages` and `compressMediaPreset` * Modify the media processing parts to use the new classes and utils * Add `MediaOptimizationSelectorPresenter`, which will retrieve the compression values and the max file upload size, also estimating the compressed video file sizes if needed. * Add a feature flag to allow selecting the media upload quality per upload * Integrate the previous changes with the attachments preview screen Add strings from localazy too. * Adapt the rest of the app calls to upload media to using the media optimization configs * Allow modifying the default compression values in advanced settings, based on the feature flag value * Pass the `fileSize` in `MediaUploadInfo` too, to be able to check it against the `maxUploadSize` * Update screenshots --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
parent
ffe183c952
commit
a170d80cb3
174 changed files with 2152 additions and 340 deletions
|
|
@ -32,8 +32,10 @@ import io.element.android.libraries.matrix.api.media.AudioInfo
|
|||
import io.element.android.libraries.matrix.api.media.FileInfo
|
||||
import io.element.android.libraries.matrix.api.media.ImageInfo
|
||||
import io.element.android.libraries.matrix.api.media.VideoInfo
|
||||
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfig
|
||||
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
|
||||
import io.element.android.libraries.mediaupload.api.MediaUploadInfo
|
||||
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
|
@ -76,7 +78,7 @@ class AndroidMediaPreProcessor @Inject constructor(
|
|||
uri: Uri,
|
||||
mimeType: String,
|
||||
deleteOriginal: Boolean,
|
||||
compressIfPossible: Boolean,
|
||||
mediaOptimizationConfig: MediaOptimizationConfig,
|
||||
): Result<MediaUploadInfo> = withContext(coroutineDispatchers.computation) {
|
||||
runCatchingExceptions {
|
||||
val result = when {
|
||||
|
|
@ -85,10 +87,10 @@ class AndroidMediaPreProcessor @Inject constructor(
|
|||
processFile(uri, mimeType)
|
||||
}
|
||||
mimeType.isMimeTypeImage() -> {
|
||||
val shouldBeCompressed = compressIfPossible && mimeType !in notCompressibleImageTypes
|
||||
val shouldBeCompressed = mediaOptimizationConfig.compressImages && mimeType !in notCompressibleImageTypes
|
||||
processImage(uri, mimeType, shouldBeCompressed)
|
||||
}
|
||||
mimeType.isMimeTypeVideo() -> processVideo(uri, mimeType, compressIfPossible)
|
||||
mimeType.isMimeTypeVideo() -> processVideo(uri, mimeType, mediaOptimizationConfig.videoCompressionPreset)
|
||||
mimeType.isMimeTypeAudio() -> processAudio(uri, mimeType)
|
||||
else -> processFile(uri, mimeType)
|
||||
}
|
||||
|
|
@ -214,9 +216,9 @@ class AndroidMediaPreProcessor @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun processVideo(uri: Uri, mimeType: String?, shouldBeCompressed: Boolean): MediaUploadInfo {
|
||||
private suspend fun processVideo(uri: Uri, mimeType: String?, videoCompressionPreset: VideoCompressionPreset): MediaUploadInfo {
|
||||
val resultFile = runCatchingExceptions {
|
||||
videoCompressor.compress(uri, shouldBeCompressed)
|
||||
videoCompressor.compress(uri, videoCompressionPreset)
|
||||
.onEach {
|
||||
if (it is VideoTranscodingEvent.Progress) {
|
||||
Timber.d("Video compression progress: ${it.value}%")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.mediaupload.impl
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfig
|
||||
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import kotlinx.coroutines.flow.first
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(SessionScope::class)
|
||||
class DefaultMediaOptimizationConfigProvider @Inject constructor(
|
||||
private val sessionPreferencesStore: SessionPreferencesStore,
|
||||
) : MediaOptimizationConfigProvider {
|
||||
override suspend fun get(): MediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = sessionPreferencesStore.doesOptimizeImages().first(),
|
||||
videoCompressionPreset = sessionPreferencesStore.getVideoCompressionPreset().first(),
|
||||
)
|
||||
}
|
||||
|
|
@ -11,10 +11,10 @@ import android.content.Context
|
|||
import android.media.MediaCodecInfo
|
||||
import android.media.MediaMetadataRetriever
|
||||
import android.net.Uri
|
||||
import android.util.Size
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MimeTypes
|
||||
import androidx.media3.common.util.Size
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.effect.Presentation
|
||||
import androidx.media3.transformer.Composition
|
||||
|
|
@ -31,6 +31,7 @@ import io.element.android.libraries.androidutils.file.createTmpFile
|
|||
import io.element.android.libraries.androidutils.file.safeDelete
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
@ -47,12 +48,12 @@ class VideoCompressor @Inject constructor(
|
|||
@ApplicationContext private val context: Context,
|
||||
) {
|
||||
@OptIn(UnstableApi::class)
|
||||
fun compress(uri: Uri, shouldBeCompressed: Boolean): Flow<VideoTranscodingEvent> = callbackFlow {
|
||||
fun compress(uri: Uri, videoCompressionPreset: VideoCompressionPreset): Flow<VideoTranscodingEvent> = callbackFlow {
|
||||
val metadata = getVideoMetadata(uri)
|
||||
|
||||
val videoCompressorConfig = VideoCompressorConfigFactory.create(
|
||||
metadata = metadata,
|
||||
shouldBeCompressed = shouldBeCompressed
|
||||
preset = videoCompressionPreset,
|
||||
)
|
||||
|
||||
val tmpFile = context.createTmpFile(extension = "mp4")
|
||||
|
|
@ -60,7 +61,7 @@ class VideoCompressor @Inject constructor(
|
|||
val width = metadata?.width ?: Int.MAX_VALUE
|
||||
val height = metadata?.height ?: Int.MAX_VALUE
|
||||
|
||||
val videoResizeEffect = videoCompressorConfig.resizer?.let {
|
||||
val videoResizeEffect = videoCompressorConfig.videoCompressorHelper?.let {
|
||||
val outputSize = it.getOutputSize(Size(width, height))
|
||||
if (metadata?.rotation == 90 || metadata?.rotation == 270) {
|
||||
// If the video is rotated, we need to swap width and height
|
||||
|
|
|
|||
|
|
@ -7,62 +7,37 @@
|
|||
|
||||
package io.element.android.libraries.mediaupload.impl
|
||||
|
||||
import android.util.Size
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.util.Size
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.transformer.VideoEncoderSettings
|
||||
import io.element.android.libraries.androidutils.media.VideoCompressorHelper
|
||||
import io.element.android.libraries.mediaupload.api.compressorHelper
|
||||
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
internal object VideoCompressorConfigFactory {
|
||||
// Major dimension of 720p
|
||||
private const val MAX_COMPRESSED_PIXEL_SIZE = 1280
|
||||
|
||||
// Major dimension of 1080p
|
||||
private const val MAX_PIXEL_SIZE = 1920
|
||||
|
||||
private const val DEFAULT_FRAME_RATE = 30
|
||||
|
||||
fun create(
|
||||
metadata: VideoFileMetadata?,
|
||||
shouldBeCompressed: Boolean,
|
||||
preset: VideoCompressionPreset,
|
||||
): VideoCompressorConfig {
|
||||
val width = metadata?.width?.takeIf { it >= 0 } ?: Int.MAX_VALUE
|
||||
val height = metadata?.height?.takeIf { it >= 0 } ?: Int.MAX_VALUE
|
||||
val originalBitrate = metadata?.bitrate?.takeIf { it >= 0 }
|
||||
val originalFrameRate = metadata?.frameRate?.takeIf { it >= 0 } ?: DEFAULT_FRAME_RATE
|
||||
|
||||
// We only create a resizer if needed
|
||||
val resizer = when {
|
||||
shouldBeCompressed && (width > MAX_COMPRESSED_PIXEL_SIZE || height > MAX_COMPRESSED_PIXEL_SIZE) -> VideoResizer(MAX_COMPRESSED_PIXEL_SIZE)
|
||||
width > MAX_PIXEL_SIZE || height > MAX_PIXEL_SIZE -> VideoResizer(MAX_PIXEL_SIZE)
|
||||
else -> null
|
||||
}
|
||||
val resizer = preset.compressorHelper()
|
||||
|
||||
// If we are resizing, we also want to reduce the frame rate to the default value (30fps)
|
||||
val newFrameRate = if (resizer is VideoResizer) {
|
||||
min(originalFrameRate, DEFAULT_FRAME_RATE)
|
||||
} else {
|
||||
originalFrameRate
|
||||
}
|
||||
val newFrameRate = min(originalFrameRate, DEFAULT_FRAME_RATE)
|
||||
|
||||
// If we need to resize the video, we also want to recalculate the bitrate
|
||||
val newBitrate = if (resizer is VideoResizer) {
|
||||
val maxSize = resizer.getOutputSize(Size(width, height))
|
||||
val pixelsPerFrame = maxSize.width * maxSize.height
|
||||
val frameRate = newFrameRate
|
||||
// Apparently, 0.1 bits per pixel is a sweet spot for video compression
|
||||
val bitsPerPixel = 0.1f
|
||||
|
||||
(pixelsPerFrame * bitsPerPixel * frameRate).toLong()
|
||||
} else {
|
||||
originalBitrate
|
||||
}
|
||||
val newBitrate = resizer.calculateOptimalBitrate(Size(width, height), newFrameRate)
|
||||
|
||||
return VideoCompressorConfig(
|
||||
resizer = resizer,
|
||||
newBitRate = newBitrate?.toInt() ?: VideoEncoderSettings.NO_VALUE,
|
||||
videoCompressorHelper = resizer,
|
||||
newBitRate = newBitrate.toInt(),
|
||||
newFrameRate = newFrameRate,
|
||||
)
|
||||
}
|
||||
|
|
@ -70,28 +45,7 @@ internal object VideoCompressorConfigFactory {
|
|||
|
||||
@OptIn(UnstableApi::class)
|
||||
internal data class VideoCompressorConfig(
|
||||
val resizer: VideoResizer?,
|
||||
val videoCompressorHelper: VideoCompressorHelper,
|
||||
val newBitRate: Int,
|
||||
val newFrameRate: Int,
|
||||
)
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
internal class VideoResizer(
|
||||
val maxSize: Int,
|
||||
) {
|
||||
fun getOutputSize(inputSize: Size): Size {
|
||||
val resultMajor = min(inputSize.major(), maxSize)
|
||||
val aspectRatio = inputSize.width.toFloat() / inputSize.height.toFloat()
|
||||
return if (inputSize.width > inputSize.height) {
|
||||
Size(resultMajor, (resultMajor / aspectRatio).roundToInt())
|
||||
} else {
|
||||
Size((resultMajor * aspectRatio).roundToInt(), resultMajor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
internal fun Size.major(): Int = if (width > height) width else height
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
internal fun Size.minor(): Int = if (width < height) width else height
|
||||
|
|
|
|||
|
|
@ -20,8 +20,10 @@ import io.element.android.libraries.matrix.api.media.FileInfo
|
|||
import io.element.android.libraries.matrix.api.media.ImageInfo
|
||||
import io.element.android.libraries.matrix.api.media.ThumbnailInfo
|
||||
import io.element.android.libraries.matrix.api.media.VideoInfo
|
||||
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfig
|
||||
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
|
||||
import io.element.android.libraries.mediaupload.api.MediaUploadInfo
|
||||
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
|
||||
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
|
||||
import io.element.android.tests.testutils.fake.FakeTemporaryUriDeleter
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
|
|
@ -41,7 +43,7 @@ import kotlin.time.Duration
|
|||
class AndroidMediaPreProcessorTest {
|
||||
private suspend fun TestScope.process(
|
||||
asset: Asset,
|
||||
compressIfPossible: Boolean,
|
||||
mediaOptimizationConfig: MediaOptimizationConfig,
|
||||
sdkIntVersion: Int = Build.VERSION_CODES.P,
|
||||
deleteOriginal: Boolean = false,
|
||||
): MediaUploadInfo {
|
||||
|
|
@ -57,7 +59,7 @@ class AndroidMediaPreProcessorTest {
|
|||
uri = file.toUri(),
|
||||
mimeType = asset.mimeType,
|
||||
deleteOriginal = deleteOriginal,
|
||||
compressIfPossible = compressIfPossible,
|
||||
mediaOptimizationConfig = mediaOptimizationConfig,
|
||||
)
|
||||
val data = result.getOrThrow()
|
||||
assertThat(data.file.path).endsWith(asset.filename)
|
||||
|
|
@ -70,7 +72,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing png`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetImagePng,
|
||||
compressIfPossible = true,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = true,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Image
|
||||
assertThat(info.thumbnailFile).isNotNull()
|
||||
|
|
@ -91,7 +96,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing png api Q`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetImagePng,
|
||||
compressIfPossible = true,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = true,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
sdkIntVersion = Build.VERSION_CODES.Q,
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Image
|
||||
|
|
@ -114,7 +122,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing png no compression`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetImagePng,
|
||||
compressIfPossible = false,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = false,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Image
|
||||
assertThat(info.thumbnailFile).isNotNull()
|
||||
|
|
@ -136,7 +147,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing png and delete`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetImagePng,
|
||||
compressIfPossible = false,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = false,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
deleteOriginal = true,
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Image
|
||||
|
|
@ -161,7 +175,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing jpeg`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetImageJpeg,
|
||||
compressIfPossible = true,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = true,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Image
|
||||
assertThat(info.thumbnailFile).isNotNull()
|
||||
|
|
@ -182,7 +199,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing jpeg api Q`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetImageJpeg,
|
||||
compressIfPossible = true,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = true,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
sdkIntVersion = Build.VERSION_CODES.Q,
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Image
|
||||
|
|
@ -205,7 +225,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing jpeg no compression`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetImageJpeg,
|
||||
compressIfPossible = false,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = false,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Image
|
||||
assertThat(info.thumbnailFile).isNotNull()
|
||||
|
|
@ -227,7 +250,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing jpeg and delete`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetImageJpeg,
|
||||
compressIfPossible = false,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = false,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
deleteOriginal = true,
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Image
|
||||
|
|
@ -252,7 +278,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing gif`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetAnimatedGif,
|
||||
compressIfPossible = true,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = true,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Image
|
||||
assertThat(info.thumbnailFile).isNotNull()
|
||||
|
|
@ -273,7 +302,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing file`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetText,
|
||||
compressIfPossible = true,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = true,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.AnyFile
|
||||
assertThat(info.fileInfo).isEqualTo(
|
||||
|
|
@ -291,7 +323,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing video`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetVideo,
|
||||
compressIfPossible = true,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = true,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Video
|
||||
assertThat(info.thumbnailFile).isNotNull()
|
||||
|
|
@ -315,7 +350,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing video no compression`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetVideo,
|
||||
compressIfPossible = false,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = true,
|
||||
videoCompressionPreset = VideoCompressionPreset.HIGH,
|
||||
),
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Video
|
||||
// Computing thumbnailFile is failing with Robolectric
|
||||
|
|
@ -341,7 +379,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing audio`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetAudio,
|
||||
compressIfPossible = true,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = true,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Audio
|
||||
assertThat(info.audioInfo).isEqualTo(
|
||||
|
|
@ -363,7 +404,10 @@ class AndroidMediaPreProcessorTest {
|
|||
uri = file.toUri(),
|
||||
mimeType = MimeTypes.PlainText,
|
||||
deleteOriginal = false,
|
||||
compressIfPossible = true,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = true,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
)
|
||||
assertThat(result.isFailure).isTrue()
|
||||
val failure = result.exceptionOrNull()
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ package io.element.android.libraries.mediaupload.impl
|
|||
|
||||
import androidx.media3.transformer.VideoEncoderSettings
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
|
@ -20,16 +21,16 @@ class VideoCompressorConfigFactoryTest {
|
|||
fun `if we don't have metadata the video will be resized`() {
|
||||
// Given
|
||||
val metadata = null
|
||||
val shouldBeCompressed = false
|
||||
val preset = VideoCompressionPreset.STANDARD
|
||||
|
||||
// When
|
||||
val videoCompressorConfig = VideoCompressorConfigFactory.create(
|
||||
metadata = metadata,
|
||||
shouldBeCompressed = shouldBeCompressed
|
||||
preset = preset,
|
||||
)
|
||||
|
||||
// Then
|
||||
assertThat(videoCompressorConfig.resizer).isNotNull()
|
||||
assertThat(videoCompressorConfig.videoCompressorHelper).isNotNull()
|
||||
assertThat(videoCompressorConfig.newFrameRate).isEqualTo(30)
|
||||
assertThat(videoCompressorConfig.newBitRate).isNotEqualTo(VideoEncoderSettings.NO_VALUE)
|
||||
}
|
||||
|
|
@ -38,71 +39,71 @@ class VideoCompressorConfigFactoryTest {
|
|||
fun `if the video should be compressed and is larger than 720p it will be resized`() {
|
||||
// Given
|
||||
val metadata = VideoFileMetadata(width = 1920, height = 1080, bitrate = 1_000_000, frameRate = 50, rotation = 0)
|
||||
val shouldBeCompressed = true
|
||||
val preset = VideoCompressionPreset.STANDARD
|
||||
|
||||
// When
|
||||
val videoCompressorConfig = VideoCompressorConfigFactory.create(
|
||||
metadata = metadata,
|
||||
shouldBeCompressed = shouldBeCompressed
|
||||
preset = preset,
|
||||
)
|
||||
|
||||
// Then
|
||||
assertIsResized(videoCompressorConfig)
|
||||
assertIsResized(videoCompressorConfig, metadata.width)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `if the video should be compressed and is smaller or equal to 720p it will not be resized`() {
|
||||
// Given
|
||||
val metadata = VideoFileMetadata(width = 1280, height = 720, bitrate = 1_000_000, frameRate = 50, rotation = 0)
|
||||
val shouldBeCompressed = true
|
||||
val preset = VideoCompressionPreset.STANDARD
|
||||
|
||||
// When
|
||||
val videoCompressorConfig = VideoCompressorConfigFactory.create(
|
||||
metadata = metadata,
|
||||
shouldBeCompressed = shouldBeCompressed
|
||||
preset = preset,
|
||||
)
|
||||
|
||||
// Then
|
||||
assertIsNotResized(videoCompressorConfig)
|
||||
assertIsNotResized(videoCompressorConfig, 1280)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `if the video should not be compressed and is larger than 1080p it will be resized`() {
|
||||
// Given
|
||||
val metadata = VideoFileMetadata(width = 2560, height = 1440, bitrate = 1_000_000, frameRate = 50, rotation = 0)
|
||||
val shouldBeCompressed = false
|
||||
val preset = VideoCompressionPreset.HIGH
|
||||
|
||||
// When
|
||||
val videoCompressorConfig = VideoCompressorConfigFactory.create(
|
||||
metadata = metadata,
|
||||
shouldBeCompressed = shouldBeCompressed
|
||||
preset = preset,
|
||||
)
|
||||
|
||||
// Then
|
||||
assertIsResized(videoCompressorConfig)
|
||||
assertIsResized(videoCompressorConfig, metadata.width)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `if the video should not be compressed and is smaller or equal than 1080p it will not be resized`() {
|
||||
// Given
|
||||
val metadata = VideoFileMetadata(width = 1920, height = 1080, bitrate = 1_000_000, frameRate = 50, rotation = 0)
|
||||
val shouldBeCompressed = false
|
||||
val preset = VideoCompressionPreset.HIGH
|
||||
|
||||
// When
|
||||
val videoCompressorConfig = VideoCompressorConfigFactory.create(
|
||||
metadata = metadata,
|
||||
shouldBeCompressed = shouldBeCompressed
|
||||
preset = preset,
|
||||
)
|
||||
|
||||
// Then
|
||||
assertIsNotResized(videoCompressorConfig)
|
||||
assertIsNotResized(videoCompressorConfig, 1920)
|
||||
}
|
||||
|
||||
private inline fun assertIsResized(videoCompressorConfig: VideoCompressorConfig) {
|
||||
assertThat(videoCompressorConfig.resizer).isNotNull()
|
||||
private inline fun assertIsResized(videoCompressorConfig: VideoCompressorConfig, referenceSize: Int) {
|
||||
assertThat(videoCompressorConfig.videoCompressorHelper.maxSize).isNotEqualTo(referenceSize)
|
||||
}
|
||||
|
||||
private inline fun assertIsNotResized(videoCompressorConfig: VideoCompressorConfig) {
|
||||
assertThat(videoCompressorConfig.resizer).isNull()
|
||||
private inline fun assertIsNotResized(videoCompressorConfig: VideoCompressorConfig, referenceSize: Int) {
|
||||
assertThat(videoCompressorConfig.videoCompressorHelper.maxSize).isEqualTo(referenceSize)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue