Merge pull request #3779 from element-hq/feature/bma/mediaUpload

Optimize media upload
This commit is contained in:
Benoit Marty
2024-11-04 09:43:21 +01:00
committed by GitHub
25 changed files with 370 additions and 187 deletions

View File

@@ -37,7 +37,7 @@ class AdvancedSettingsPresenter @Inject constructor(
.collectAsState(initial = true)
val doesCompressMedia by sessionPreferencesStore
.doesCompressMedia()
.collectAsState(initial = false)
.collectAsState(initial = true)
val theme by remember {
appPreferencesStore.getThemeFlow().mapToTheme()
}

View File

@@ -8,8 +8,8 @@
<string name="screen_advanced_settings_element_call_base_url">"Custom Element Call base URL"</string>
<string name="screen_advanced_settings_element_call_base_url_description">"Set a custom base URL for Element Call."</string>
<string name="screen_advanced_settings_element_call_base_url_validation_error">"Invalid URL, please make sure you include the protocol (http/https) and the correct address."</string>
<string name="screen_advanced_settings_media_compression_description">"Optimize for upload"</string>
<string name="screen_advanced_settings_media_compression_title">"Media"</string>
<string name="screen_advanced_settings_media_compression_description">"Upload photos and videos faster and reduce data usage"</string>
<string name="screen_advanced_settings_media_compression_title">"Optimise media quality"</string>
<string name="screen_advanced_settings_push_provider_android">"Push notification provider"</string>
<string name="screen_advanced_settings_rich_text_editor_description">"Disable the rich text editor to type Markdown manually."</string>
<string name="screen_advanced_settings_send_read_receipts">"Read receipts"</string>

View File

@@ -34,7 +34,7 @@ class AdvancedSettingsPresenterTest {
assertThat(initialState.isDeveloperModeEnabled).isFalse()
assertThat(initialState.showChangeThemeDialog).isFalse()
assertThat(initialState.isSharePresenceEnabled).isTrue()
assertThat(initialState.doesCompressMedia).isFalse()
assertThat(initialState.doesCompressMedia).isTrue()
assertThat(initialState.theme).isEqualTo(Theme.System)
}
}
@@ -76,11 +76,11 @@ class AdvancedSettingsPresenterTest {
presenter.present()
}.test {
val initialState = awaitLastSequentialItem()
assertThat(initialState.doesCompressMedia).isFalse()
initialState.eventSink.invoke(AdvancedSettingsEvents.SetCompressMedia(true))
assertThat(awaitItem().doesCompressMedia).isTrue()
assertThat(initialState.doesCompressMedia).isTrue()
initialState.eventSink.invoke(AdvancedSettingsEvents.SetCompressMedia(false))
assertThat(awaitItem().doesCompressMedia).isFalse()
initialState.eventSink.invoke(AdvancedSettingsEvents.SetCompressMedia(true))
assertThat(awaitItem().doesCompressMedia).isTrue()
}
}

View File

@@ -17,6 +17,7 @@ import io.element.android.features.roomdetails.aMatrixRoom
import io.element.android.features.roomdetails.impl.edit.RoomDetailsEditEvents
import io.element.android.features.roomdetails.impl.edit.RoomDetailsEditPresenter
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.StateEventType
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
@@ -513,7 +514,7 @@ class RoomDetailsEditPresenterTest {
initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto))
initialState.eventSink(RoomDetailsEditEvents.Save)
skipItems(4)
updateAvatarResult.assertions().isCalledOnce().with(value("image/jpeg"), value(fakeFileContents))
updateAvatarResult.assertions().isCalledOnce().with(value(MimeTypes.Jpeg), value(fakeFileContents))
}
}

View File

@@ -18,6 +18,7 @@ import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.media.AudioInfo
@@ -580,7 +581,7 @@ fun anImageInfo(): ImageInfo {
return ImageInfo(
height = 100,
width = 100,
mimetype = "image/jpeg",
mimetype = MimeTypes.Jpeg,
size = 1000,
thumbnailInfo = null,
thumbnailSource = aMediaSource(),

View File

@@ -125,9 +125,13 @@ class AndroidMediaPreProcessor @Inject constructor(
val compressionResult = imageCompressor.compressToTmpFile(
inputStreamProvider = { contentResolver.openInputStream(uri)!! },
resizeMode = ResizeMode.Approximate(IMAGE_SCALE_REF_SIZE, IMAGE_SCALE_REF_SIZE),
mimeType = mimeType,
orientation = orientation,
).getOrThrow()
val thumbnailResult = thumbnailFactory.createImageThumbnail(compressionResult.file)
val thumbnailResult = thumbnailFactory.createImageThumbnail(
file = compressionResult.file,
mimeType = mimeType,
)
val imageInfo = compressionResult.toImageInfo(
mimeType = mimeType,
thumbnailResult = thumbnailResult
@@ -142,7 +146,10 @@ class AndroidMediaPreProcessor @Inject constructor(
suspend fun processImageWithoutCompression(): MediaUploadInfo {
val file = copyToTmpFile(uri)
val thumbnailResult = thumbnailFactory.createImageThumbnail(file)
val thumbnailResult = thumbnailFactory.createImageThumbnail(
file = file,
mimeType = mimeType,
)
val imageInfo = contentResolver.openInputStream(uri).use { input ->
val bitmap = BitmapFactory.decodeStream(input, null, null)!!
ImageInfo(
@@ -171,17 +178,13 @@ class AndroidMediaPreProcessor @Inject constructor(
}
private suspend fun processVideo(uri: Uri, mimeType: String?, shouldBeCompressed: Boolean): MediaUploadInfo {
val resultFile = if (shouldBeCompressed) {
videoCompressor.compress(uri)
.onEach {
// TODO handle progress
}
.filterIsInstance<VideoTranscodingEvent.Completed>()
.first()
.file
} else {
copyToTmpFile(uri)
}
val resultFile = videoCompressor.compress(uri, shouldBeCompressed)
.onEach {
// TODO handle progress
}
.filterIsInstance<VideoTranscodingEvent.Completed>()
.first()
.file
val thumbnailInfo = thumbnailFactory.createVideoThumbnail(resultFile)
val videoInfo = extractVideoMetadata(resultFile, mimeType, thumbnailInfo)
return MediaUploadInfo.Video(

View File

@@ -34,14 +34,16 @@ class ImageCompressor @Inject constructor(
suspend fun compressToTmpFile(
inputStreamProvider: () -> InputStream,
resizeMode: ResizeMode,
format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG,
mimeType: String,
orientation: Int = ExifInterface.ORIENTATION_UNDEFINED,
desiredQuality: Int = 78,
): Result<ImageCompressionResult> = withContext(dispatchers.io) {
runCatching {
val format = mimeTypeToCompressFormat(mimeType)
val extension = mimeTypeToCompressFileExtension(mimeType)
val compressedBitmap = compressToBitmap(inputStreamProvider, resizeMode, orientation).getOrThrow()
// Encode bitmap to the destination temporary file
val tmpFile = context.createTmpFile(extension = "jpeg")
val tmpFile = context.createTmpFile(extension = extension)
tmpFile.outputStream().use {
compressedBitmap.compress(format, desiredQuality, it)
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.mediaupload.impl
import android.graphics.Bitmap
import io.element.android.libraries.core.mimetype.MimeTypes
fun mimeTypeToCompressFormat(mimeType: String) = when (mimeType) {
MimeTypes.Png -> Bitmap.CompressFormat.PNG
else -> Bitmap.CompressFormat.JPEG
}
fun mimeTypeToCompressFileExtension(mimeType: String) = when (mimeType) {
MimeTypes.Png -> "png"
else -> "jpeg"
}
fun mimeTypeToThumbnailMimeType(mimeType: String) = when (mimeType) {
MimeTypes.Png -> MimeTypes.Png
else -> MimeTypes.Jpeg
}

View File

@@ -53,8 +53,11 @@ class ThumbnailFactory @Inject constructor(
private val sdkIntProvider: BuildVersionSdkIntProvider
) {
@SuppressLint("NewApi")
suspend fun createImageThumbnail(file: File): ThumbnailResult? {
return createThumbnail { cancellationSignal ->
suspend fun createImageThumbnail(
file: File,
mimeType: String,
): ThumbnailResult? {
return createThumbnail(mimeType = mimeType) { cancellationSignal ->
try {
// This API works correctly with GIF
if (sdkIntProvider.isAtLeast(Build.VERSION_CODES.Q)) {
@@ -83,7 +86,7 @@ class ThumbnailFactory @Inject constructor(
}
suspend fun createVideoThumbnail(file: File): ThumbnailResult? {
return createThumbnail {
return createThumbnail(mimeType = MimeTypes.Jpeg) {
MediaMetadataRetriever().runAndRelease {
setDataSource(context, file.toUri())
getFrameAtTime(VIDEO_THUMB_FRAME)
@@ -91,7 +94,10 @@ class ThumbnailFactory @Inject constructor(
}
}
private suspend fun createThumbnail(bitmapFactory: (CancellationSignal) -> Bitmap?): ThumbnailResult? = suspendCancellableCoroutine { continuation ->
private suspend fun createThumbnail(
mimeType: String,
bitmapFactory: (CancellationSignal) -> Bitmap?,
): ThumbnailResult? = suspendCancellableCoroutine { continuation ->
val cancellationSignal = CancellationSignal()
continuation.invokeOnCancellation {
cancellationSignal.cancel()
@@ -101,9 +107,11 @@ class ThumbnailFactory @Inject constructor(
continuation.resume(null)
return@suspendCancellableCoroutine
}
val thumbnailFile = context.createTmpFile(extension = "jpeg")
val format = mimeTypeToCompressFormat(mimeType)
val extension = mimeTypeToCompressFileExtension(mimeType)
val thumbnailFile = context.createTmpFile(extension = extension)
thumbnailFile.outputStream().use { outputStream ->
bitmapThumbnail.compress(Bitmap.CompressFormat.JPEG, 80, outputStream)
bitmapThumbnail.compress(format, 78, outputStream)
}
val blurhash = BlurHash.encode(bitmapThumbnail, 3, 3)
val thumbnailResult = ThumbnailResult(
@@ -111,7 +119,7 @@ class ThumbnailFactory @Inject constructor(
info = ThumbnailInfo(
height = bitmapThumbnail.height.toLong(),
width = bitmapThumbnail.width.toLong(),
mimetype = MimeTypes.Jpeg,
mimetype = mimeTypeToThumbnailMimeType(mimeType),
size = thumbnailFile.length()
),
blurhash = blurhash

View File

@@ -24,12 +24,20 @@ import javax.inject.Inject
class VideoCompressor @Inject constructor(
@ApplicationContext private val context: Context,
) {
fun compress(uri: Uri) = callbackFlow {
fun compress(uri: Uri, shouldBeCompressed: Boolean) = callbackFlow {
val tmpFile = context.createTmpFile(extension = "mp4")
val future = Transcoder.into(tmpFile.path)
.setVideoTrackStrategy(
DefaultVideoStrategy.Builder()
.addResizer(AtMostResizer(720, 480))
.addResizer(
AtMostResizer(
if (shouldBeCompressed) {
720
} else {
1080
}
)
)
.build()
)
.addDataSource(context, uri)

Binary file not shown.

View File

@@ -35,113 +35,194 @@ import kotlin.time.Duration
@RunWith(RobolectricTestRunner::class)
class AndroidMediaPreProcessorTest {
@Test
fun `test processing image`() = runTest {
private suspend fun TestScope.process(
asset: Asset,
compressIfPossible: Boolean,
sdkIntVersion: Int = Build.VERSION_CODES.P,
deleteOriginal: Boolean = false,
): MediaUploadInfo {
val context = InstrumentationRegistry.getInstrumentation().context
val sut = createAndroidMediaPreProcessor(context)
val file = getFileFromAssets(context, "image.png")
val sut = createAndroidMediaPreProcessor(context, sdkIntVersion)
val file = getFileFromAssets(context, asset.filename)
val result = sut.process(
uri = file.toUri(),
mimeType = MimeTypes.Png,
deleteOriginal = false,
compressIfPossible = true,
mimeType = asset.mimeType,
deleteOriginal = deleteOriginal,
compressIfPossible = compressIfPossible,
)
val data = result.getOrThrow()
assertThat(data.file.path).endsWith("image.png")
val info = data as MediaUploadInfo.Image
assertThat(info.thumbnailFile).isNotNull()
assertThat(info.imageInfo).isEqualTo(
ImageInfo(
height = 1_178,
width = 1_818,
mimetype = MimeTypes.Png,
size = 109_908,
ThumbnailInfo(height = 294, width = 454, mimetype = "image/jpeg", size = 4484),
thumbnailSource = null,
blurhash = "K13]7q%zWC00R4of%\$baad"
)
)
assertThat(file.exists()).isTrue()
assertThat(data.file.path).endsWith(asset.filename)
return data
}
@Test
fun `test processing image api Q`() = runTest {
val context = InstrumentationRegistry.getInstrumentation().context
val sut = createAndroidMediaPreProcessor(context, sdkIntVersion = Build.VERSION_CODES.Q)
val file = getFileFromAssets(context, "image.png")
val result = sut.process(
uri = file.toUri(),
mimeType = MimeTypes.Png,
deleteOriginal = false,
fun `test processing png`() = runTest {
val mediaUploadInfo = process(
asset = assetImagePng,
compressIfPossible = true,
)
val data = result.getOrThrow()
assertThat(data.file.path).endsWith("image.png")
val info = data as MediaUploadInfo.Image
val info = mediaUploadInfo as MediaUploadInfo.Image
assertThat(info.thumbnailFile).isNotNull()
assertThat(info.imageInfo).isEqualTo(
ImageInfo(
height = assetImagePng.height,
width = assetImagePng.width,
mimetype = assetImagePng.mimeType,
size = 2_026_433,
ThumbnailInfo(height = 25, width = 25, mimetype = MimeTypes.Png, size = 91),
thumbnailSource = null,
blurhash = "K00000fQfQfQfQfQfQfQfQ"
)
)
}
@Test
fun `test processing png api Q`() = runTest {
val mediaUploadInfo = process(
asset = assetImagePng,
compressIfPossible = true,
sdkIntVersion = Build.VERSION_CODES.Q,
)
val info = mediaUploadInfo as MediaUploadInfo.Image
assertThat(info.thumbnailFile).isNull()
assertThat(info.imageInfo).isEqualTo(
ImageInfo(
height = 1_178,
width = 1_818,
mimetype = MimeTypes.Png,
size = 109_908,
height = assetImagePng.height,
width = assetImagePng.width,
mimetype = assetImagePng.mimeType,
size = 2_026_433,
thumbnailInfo = null,
thumbnailSource = null,
blurhash = null,
)
)
assertThat(file.exists()).isTrue()
}
@Test
fun `test processing image no compression`() = runTest {
val context = InstrumentationRegistry.getInstrumentation().context
val sut = createAndroidMediaPreProcessor(context)
val file = getFileFromAssets(context, "image.png")
val result = sut.process(
uri = file.toUri(),
mimeType = MimeTypes.Png,
deleteOriginal = false,
fun `test processing png no compression`() = runTest {
val mediaUploadInfo = process(
asset = assetImagePng,
compressIfPossible = false,
).getOrThrow()
assertThat(result.file.path).endsWith("image.png")
val info = result as MediaUploadInfo.Image
)
val info = mediaUploadInfo as MediaUploadInfo.Image
assertThat(info.thumbnailFile).isNotNull()
assertThat(info.imageInfo).isEqualTo(
ImageInfo(
height = 1_178,
width = 1_818,
mimetype = MimeTypes.Png,
size = 1_856_786,
thumbnailInfo = ThumbnailInfo(height = 25, width = 25, mimetype = MimeTypes.Jpeg, size = 643),
height = assetImagePng.height,
width = assetImagePng.width,
mimetype = assetImagePng.mimeType,
size = assetImagePng.size,
thumbnailInfo = ThumbnailInfo(height = 25, width = 25, mimetype = MimeTypes.Png, size = 91),
thumbnailSource = null,
blurhash = "K00000fQfQfQfQfQfQfQfQ",
)
)
assertThat(file.exists()).isTrue()
}
@Test
fun `test processing image and delete`() = runTest {
val context = InstrumentationRegistry.getInstrumentation().context
val sut = createAndroidMediaPreProcessor(context)
val file = getFileFromAssets(context, "image.png")
val result = sut.process(
uri = file.toUri(),
mimeType = MimeTypes.Png,
deleteOriginal = true,
fun `test processing png and delete`() = runTest {
val mediaUploadInfo = process(
asset = assetImagePng,
compressIfPossible = false,
).getOrThrow()
assertThat(result.file.path).endsWith("image.png")
val info = result as MediaUploadInfo.Image
deleteOriginal = true,
)
val info = mediaUploadInfo as MediaUploadInfo.Image
assertThat(info.thumbnailFile).isNotNull()
assertThat(info.imageInfo).isEqualTo(
ImageInfo(
height = 1_178,
width = 1_818,
mimetype = MimeTypes.Png,
size = 1_856_786,
thumbnailInfo = ThumbnailInfo(height = 25, width = 25, mimetype = MimeTypes.Jpeg, size = 643),
height = assetImagePng.height,
width = assetImagePng.width,
mimetype = assetImagePng.mimeType,
size = assetImagePng.size,
thumbnailInfo = ThumbnailInfo(height = 25, width = 25, mimetype = MimeTypes.Png, size = 91),
thumbnailSource = null,
blurhash = "K00000fQfQfQfQfQfQfQfQ",
)
)
// Does not work
// assertThat(file.exists()).isFalse()
}
@Test
fun `test processing jpeg`() = runTest {
val mediaUploadInfo = process(
asset = assetImageJpeg,
compressIfPossible = true,
)
val info = mediaUploadInfo as MediaUploadInfo.Image
assertThat(info.thumbnailFile).isNotNull()
assertThat(info.imageInfo).isEqualTo(
ImageInfo(
height = 979,
width = 3006,
mimetype = MimeTypes.Jpeg,
size = 84_845,
ThumbnailInfo(height = 244, width = 751, mimetype = MimeTypes.Jpeg, size = 7_178),
thumbnailSource = null,
blurhash = "K07gBzX=j_D4xZjoaSe,s:"
)
)
}
@Test
fun `test processing jpeg api Q`() = runTest {
val mediaUploadInfo = process(
asset = assetImageJpeg,
compressIfPossible = true,
sdkIntVersion = Build.VERSION_CODES.Q,
)
val info = mediaUploadInfo as MediaUploadInfo.Image
assertThat(info.thumbnailFile).isNull()
assertThat(info.imageInfo).isEqualTo(
ImageInfo(
height = 979,
width = 3_006,
mimetype = MimeTypes.Jpeg,
size = 84_845,
thumbnailInfo = null,
thumbnailSource = null,
blurhash = null,
)
)
}
@Test
fun `test processing jpeg no compression`() = runTest {
val mediaUploadInfo = process(
asset = assetImageJpeg,
compressIfPossible = false,
)
val info = mediaUploadInfo as MediaUploadInfo.Image
assertThat(info.thumbnailFile).isNotNull()
assertThat(info.imageInfo).isEqualTo(
ImageInfo(
height = assetImageJpeg.height,
width = assetImageJpeg.width,
mimetype = assetImageJpeg.mimeType,
size = assetImageJpeg.size,
thumbnailInfo = ThumbnailInfo(height = 6, width = 6, mimetype = MimeTypes.Jpeg, size = 631),
thumbnailSource = null,
blurhash = "K00000fQfQfQfQfQfQfQfQ",
)
)
}
@Test
fun `test processing jpeg and delete`() = runTest {
val mediaUploadInfo = process(
asset = assetImageJpeg,
compressIfPossible = false,
deleteOriginal = true,
)
val info = mediaUploadInfo as MediaUploadInfo.Image
assertThat(info.thumbnailFile).isNotNull()
assertThat(info.imageInfo).isEqualTo(
ImageInfo(
height = assetImageJpeg.height,
width = assetImageJpeg.width,
mimetype = assetImageJpeg.mimeType,
size = assetImageJpeg.size,
thumbnailInfo = ThumbnailInfo(height = 6, width = 6, mimetype = MimeTypes.Jpeg, size = 631),
thumbnailSource = null,
blurhash = "K00000fQfQfQfQfQfQfQfQ",
)
@@ -152,70 +233,50 @@ class AndroidMediaPreProcessorTest {
@Test
fun `test processing gif`() = runTest {
val context = InstrumentationRegistry.getInstrumentation().context
val sut = createAndroidMediaPreProcessor(context)
val file = getFileFromAssets(context, "animated_gif.gif")
val result = sut.process(
uri = file.toUri(),
mimeType = MimeTypes.Gif,
deleteOriginal = false,
val mediaUploadInfo = process(
asset = assetAnimatedGif,
compressIfPossible = true,
).getOrThrow()
assertThat(result.file.path).endsWith("animated_gif.gif")
val info = result as MediaUploadInfo.Image
)
val info = mediaUploadInfo as MediaUploadInfo.Image
assertThat(info.thumbnailFile).isNotNull()
assertThat(info.imageInfo).isEqualTo(
ImageInfo(
height = 600,
width = 800,
mimetype = MimeTypes.Gif,
size = 687_979,
height = assetAnimatedGif.height,
width = assetAnimatedGif.width,
mimetype = assetAnimatedGif.mimeType,
size = assetAnimatedGif.size,
thumbnailInfo = ThumbnailInfo(height = 50, width = 50, mimetype = MimeTypes.Jpeg, size = 691),
thumbnailSource = null,
blurhash = "K00000fQfQfQfQfQfQfQfQ",
)
)
assertThat(file.exists()).isTrue()
}
@Test
fun `test processing file`() = runTest {
val context = InstrumentationRegistry.getInstrumentation().context
val sut = createAndroidMediaPreProcessor(context)
val file = getFileFromAssets(context, "text.txt")
val result = sut.process(
uri = file.toUri(),
mimeType = MimeTypes.PlainText,
deleteOriginal = false,
val mediaUploadInfo = process(
asset = assetText,
compressIfPossible = true,
).getOrThrow()
assertThat(result.file.path).endsWith("text.txt")
val info = result as MediaUploadInfo.AnyFile
)
val info = mediaUploadInfo as MediaUploadInfo.AnyFile
assertThat(info.fileInfo).isEqualTo(
FileInfo(
mimetype = MimeTypes.PlainText,
size = 13,
mimetype = assetText.mimeType,
size = assetText.size,
thumbnailInfo = null,
thumbnailSource = null,
)
)
assertThat(file.exists()).isTrue()
}
@Ignore("Compressing video is not working with Robolectric")
@Test
fun `test processing video`() = runTest {
val context = InstrumentationRegistry.getInstrumentation().context
val sut = createAndroidMediaPreProcessor(context)
val file = getFileFromAssets(context, "video.mp4")
val result = sut.process(
uri = file.toUri(),
mimeType = MimeTypes.Mp4,
deleteOriginal = false,
val mediaUploadInfo = process(
asset = assetVideo,
compressIfPossible = true,
).getOrThrow()
assertThat(result.file.path).endsWith("video.mp4")
val info = result as MediaUploadInfo.Video
)
val info = mediaUploadInfo as MediaUploadInfo.Video
assertThat(info.thumbnailFile).isNotNull()
assertThat(info.videoInfo).isEqualTo(
VideoInfo(
@@ -230,22 +291,16 @@ class AndroidMediaPreProcessorTest {
blurhash = null,
)
)
assertThat(file.exists()).isTrue()
}
@Ignore("Compressing video is not working with Robolectric")
@Test
fun `test processing video no compression`() = runTest {
val context = InstrumentationRegistry.getInstrumentation().context
val sut = createAndroidMediaPreProcessor(context)
val file = getFileFromAssets(context, "video.mp4")
val result = sut.process(
uri = file.toUri(),
mimeType = MimeTypes.Mp4,
deleteOriginal = false,
val mediaUploadInfo = process(
asset = assetVideo,
compressIfPossible = false,
).getOrThrow()
assertThat(result.file.path).endsWith("video.mp4")
val info = result as MediaUploadInfo.Video
)
val info = mediaUploadInfo as MediaUploadInfo.Video
// Computing thumbnailFile is failing with Robolectric
assertThat(info.thumbnailFile).isNull()
assertThat(info.videoInfo).isEqualTo(
@@ -263,22 +318,15 @@ class AndroidMediaPreProcessorTest {
blurhash = null,
)
)
assertThat(file.exists()).isTrue()
}
@Test
fun `test processing audio`() = runTest {
val context = InstrumentationRegistry.getInstrumentation().context
val sut = createAndroidMediaPreProcessor(context)
val file = getFileFromAssets(context, "sample3s.mp3")
val result = sut.process(
uri = file.toUri(),
mimeType = MimeTypes.Mp3,
deleteOriginal = false,
val mediaUploadInfo = process(
asset = assetAudio,
compressIfPossible = true,
).getOrThrow()
assertThat(result.file.path).endsWith("sample3s.mp3")
val info = result as MediaUploadInfo.Audio
)
val info = mediaUploadInfo as MediaUploadInfo.Audio
assertThat(info.audioInfo).isEqualTo(
AudioInfo(
// Not available with Robolectric?
@@ -287,7 +335,6 @@ class AndroidMediaPreProcessorTest {
mimetype = MimeTypes.Mp3,
)
)
assertThat(file.exists()).isTrue()
}
@Test

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.mediaupload.impl
import io.element.android.libraries.core.mimetype.MimeTypes
data class Asset(
val filename: String,
val mimeType: String,
val size: Long,
val width: Long?,
val height: Long?,
)
/**
* "image.png" is a 1_818 x 1_178 PNG image with a size of 1_856_786 bytes.
*/
val assetImagePng = Asset(
filename = "image.png",
mimeType = MimeTypes.Png,
size = 1_856_786,
width = 1_818,
height = 1_178,
)
/**
* "image.jpeg" is a 12_024 x 3_916, JPEG image with a size of 9_986_336 bytes.
*/
val assetImageJpeg = Asset(
filename = "image.jpeg",
mimeType = MimeTypes.Jpeg,
size = 9_986_336,
width = 12_024,
height = 3_916,
)
/**
* "video.mp4" is a 1_280 x 720, MP4 video with a size of 1_673_712 bytes.
*/
val assetVideo = Asset(
filename = "video.mp4",
mimeType = MimeTypes.Mp4,
size = 1_673_712,
width = 1_280,
height = 720,
)
/**
* "sample3s.mp3" is a 3 seconds MP3 audio file with a size of 52_079 bytes.
*/
val assetAudio = Asset(
filename = "sample3s.mp3",
mimeType = MimeTypes.Mp3,
size = 52_079,
width = null,
height = null,
)
/**
* "text.txt" is a 13 bytes text file.
*/
val assetText = Asset(
filename = "text.txt",
mimeType = MimeTypes.PlainText,
size = 13,
width = null,
height = null,
)
/**
* "animated_gif.gif" is a 800 x 600, GIF image with a size of 687_979 bytes.
*/
val assetAnimatedGif = Asset(
filename = "animated_gif.gif",
mimeType = MimeTypes.Gif,
size = 687_979,
width = 800,
height = 600,
)

View File

@@ -83,7 +83,7 @@ class DefaultSessionPreferencesStore(
override fun isSessionVerificationSkipped(): Flow<Boolean> = get(skipSessionVerification) { false }
override suspend fun setCompressMedia(compress: Boolean) = update(compressMedia, compress)
override fun doesCompressMedia(): Flow<Boolean> = get(compressMedia) { false }
override fun doesCompressMedia(): Flow<Boolean> = get(compressMedia) { true }
override suspend fun clear() {
dataStoreFile.safeDelete()

View File

@@ -18,7 +18,7 @@ class InMemorySessionPreferencesStore(
isSendTypingNotificationsEnabled: Boolean = true,
isRenderTypingNotificationsEnabled: Boolean = true,
isSessionVerificationSkipped: Boolean = false,
doesCompressMedia: Boolean = false,
doesCompressMedia: Boolean = true,
) : SessionPreferencesStore {
private val isSharePresenceEnabled = MutableStateFlow(isSharePresenceEnabled)
private val isSendPublicReadReceiptsEnabled = MutableStateFlow(isSendPublicReadReceiptsEnabled)