Use the right video preset when sharing videos (#5892)

This commit is contained in:
Jorge Martin Espinosa 2025-12-16 16:40:44 +01:00 committed by GitHub
parent 028741d81c
commit d298200934
6 changed files with 30 additions and 17 deletions

View file

@ -37,6 +37,7 @@ import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfig import io.element.android.libraries.mediaupload.api.MediaOptimizationConfig
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider
import io.element.android.libraries.mediaupload.api.MediaSenderFactory import io.element.android.libraries.mediaupload.api.MediaSenderFactory
import io.element.android.libraries.mediaupload.api.MediaUploadInfo import io.element.android.libraries.mediaupload.api.MediaUploadInfo
import io.element.android.libraries.mediaupload.api.allFiles import io.element.android.libraries.mediaupload.api.allFiles
@ -62,6 +63,7 @@ class AttachmentsPreviewPresenter(
private val mediaOptimizationSelectorPresenterFactory: MediaOptimizationSelectorPresenter.Factory, private val mediaOptimizationSelectorPresenterFactory: MediaOptimizationSelectorPresenter.Factory,
@SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope, @SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope,
private val dispatchers: CoroutineDispatchers, private val dispatchers: CoroutineDispatchers,
private val mediaOptimizationConfigProvider: MediaOptimizationConfigProvider,
) : Presenter<AttachmentsPreviewState> { ) : Presenter<AttachmentsPreviewState> {
@AssistedFactory @AssistedFactory
interface Factory { interface Factory {
@ -107,13 +109,9 @@ class AttachmentsPreviewPresenter(
// to prepare it for sending. This is done to avoid blocking the UI thread when the // to prepare it for sending. This is done to avoid blocking the UI thread when the
// user clicks on the send button. // user clicks on the send button.
if (mediaOptimizationSelectorState.displayMediaSelectorViews == false) { if (mediaOptimizationSelectorState.displayMediaSelectorViews == false) {
val mediaOptimizationConfig = MediaOptimizationConfig(
compressImages = mediaOptimizationSelectorState.isImageOptimizationEnabled == true,
videoCompressionPreset = mediaOptimizationSelectorState.selectedVideoPreset ?: VideoCompressionPreset.STANDARD,
)
preprocessMediaJob = preProcessAttachment( preprocessMediaJob = preProcessAttachment(
attachment = attachment, attachment = attachment,
mediaOptimizationConfig = mediaOptimizationConfig, mediaOptimizationConfig = mediaOptimizationConfigProvider.get(),
displayProgress = false, displayProgress = false,
sendActionState = sendActionState, sendActionState = sendActionState,
) )

View file

@ -25,13 +25,12 @@ import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.mediaupload.api.MaxUploadSizeProvider import io.element.android.libraries.mediaupload.api.MaxUploadSizeProvider
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider
import io.element.android.libraries.mediaupload.api.compressorHelper import io.element.android.libraries.mediaupload.api.compressorHelper
import io.element.android.libraries.mediaviewer.api.local.LocalMedia import io.element.android.libraries.mediaviewer.api.local.LocalMedia
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.first
import timber.log.Timber import timber.log.Timber
import kotlin.math.roundToLong import kotlin.math.roundToLong
@ -39,8 +38,8 @@ import kotlin.math.roundToLong
class DefaultMediaOptimizationSelectorPresenter( class DefaultMediaOptimizationSelectorPresenter(
@Assisted private val localMedia: LocalMedia, @Assisted private val localMedia: LocalMedia,
private val maxUploadSizeProvider: MaxUploadSizeProvider, private val maxUploadSizeProvider: MaxUploadSizeProvider,
private val sessionPreferencesStore: SessionPreferencesStore,
private val featureFlagService: FeatureFlagService, private val featureFlagService: FeatureFlagService,
private val mediaOptimizationConfigProvider: MediaOptimizationConfigProvider,
mediaExtractorFactory: VideoMetadataExtractor.Factory, mediaExtractorFactory: VideoMetadataExtractor.Factory,
) : MediaOptimizationSelectorPresenter { ) : MediaOptimizationSelectorPresenter {
@ContributesBinding(SessionScope::class) @ContributesBinding(SessionScope::class)
@ -124,11 +123,12 @@ class DefaultMediaOptimizationSelectorPresenter(
var selectedVideoOptimizationPreset by remember { mutableStateOf<AsyncData<VideoCompressionPreset>>(AsyncData.Loading()) } var selectedVideoOptimizationPreset by remember { mutableStateOf<AsyncData<VideoCompressionPreset>>(AsyncData.Loading()) }
LaunchedEffect(videoSizeEstimations.dataOrNull()) { LaunchedEffect(videoSizeEstimations.dataOrNull()) {
selectedImageOptimization = AsyncData.Success(sessionPreferencesStore.doesOptimizeImages().first()) val mediaOptimizationConfig = mediaOptimizationConfigProvider.get()
selectedImageOptimization = AsyncData.Success(mediaOptimizationConfig.compressImages)
// Find the best video preset based on the default preset and the video size estimations // Find the best video preset based on the default preset and the video size estimations
// Since the estimation for the current preset may be way too large to upload, we check the ones that provide lower file sizes // Since the estimation for the current preset may be way too large to upload, we check the ones that provide lower file sizes
selectedVideoOptimizationPreset = findBestVideoPreset( selectedVideoOptimizationPreset = findBestVideoPreset(
defaultVideoPreset = sessionPreferencesStore.getVideoCompressionPreset().first(), defaultVideoPreset = mediaOptimizationConfig.videoCompressionPreset,
videoSizeEstimations = videoSizeEstimations, videoSizeEstimations = videoSizeEstimations,
) )
} }

View file

@ -44,6 +44,7 @@ import io.element.android.libraries.mediaupload.api.MediaPreProcessor
import io.element.android.libraries.mediaupload.api.MediaSenderFactory import io.element.android.libraries.mediaupload.api.MediaSenderFactory
import io.element.android.libraries.mediaupload.api.MediaUploadInfo import io.element.android.libraries.mediaupload.api.MediaUploadInfo
import io.element.android.libraries.mediaupload.impl.DefaultMediaSender import io.element.android.libraries.mediaupload.impl.DefaultMediaSender
import io.element.android.libraries.mediaupload.test.FakeMediaOptimizationConfigProvider
import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
import io.element.android.libraries.mediaviewer.api.aVideoMediaInfo import io.element.android.libraries.mediaviewer.api.aVideoMediaInfo
import io.element.android.libraries.mediaviewer.api.anApkMediaInfo import io.element.android.libraries.mediaviewer.api.anApkMediaInfo
@ -598,6 +599,7 @@ class AttachmentsPreviewPresenterTest {
) )
} }
), ),
mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(),
): AttachmentsPreviewPresenter { ): AttachmentsPreviewPresenter {
return AttachmentsPreviewPresenter( return AttachmentsPreviewPresenter(
attachment = aMediaAttachment(localMedia), attachment = aMediaAttachment(localMedia),
@ -619,6 +621,7 @@ class AttachmentsPreviewPresenterTest {
mediaOptimizationSelectorPresenterFactory = mediaOptimizationSelectorPresenterFactory, mediaOptimizationSelectorPresenterFactory = mediaOptimizationSelectorPresenterFactory,
timelineMode = timelineMode, timelineMode = timelineMode,
inReplyToEventId = null, inReplyToEventId = null,
mediaOptimizationConfigProvider = mediaOptimizationConfigProvider,
) )
} }

View file

@ -22,12 +22,12 @@ import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.mediaupload.api.MaxUploadSizeProvider import io.element.android.libraries.mediaupload.api.MaxUploadSizeProvider
import io.element.android.libraries.mediaupload.test.FakeMediaOptimizationConfigProvider
import io.element.android.libraries.mediaviewer.api.aVideoMediaInfo import io.element.android.libraries.mediaviewer.api.aVideoMediaInfo
import io.element.android.libraries.mediaviewer.api.anImageMediaInfo import io.element.android.libraries.mediaviewer.api.anImageMediaInfo
import io.element.android.libraries.mediaviewer.api.local.LocalMedia import io.element.android.libraries.mediaviewer.api.local.LocalMedia
import io.element.android.libraries.mediaviewer.test.viewer.aLocalMedia import io.element.android.libraries.mediaviewer.test.viewer.aLocalMedia
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.WarmUpRule
import io.mockk.mockk import io.mockk.mockk
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
@ -233,16 +233,16 @@ class DefaultMediaOptimizationSelectorPresenterTest {
private fun createDefaultMediaOptimizationSelectorPresenter( private fun createDefaultMediaOptimizationSelectorPresenter(
localMedia: LocalMedia = aLocalMedia(mockMediaUrl, aVideoMediaInfo()), localMedia: LocalMedia = aLocalMedia(mockMediaUrl, aVideoMediaInfo()),
maxUploadSizeProvider: MaxUploadSizeProvider = MaxUploadSizeProvider { Result.success(1_000L) }, maxUploadSizeProvider: MaxUploadSizeProvider = MaxUploadSizeProvider { Result.success(1_000L) },
sessionPreferencesStore: InMemorySessionPreferencesStore = InMemorySessionPreferencesStore(),
featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SelectableMediaQuality.key to true)), featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SelectableMediaQuality.key to true)),
mediaExtractorFactory: FakeVideoMetadataExtractorFactory = FakeVideoMetadataExtractorFactory(), mediaExtractorFactory: FakeVideoMetadataExtractorFactory = FakeVideoMetadataExtractorFactory(),
mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(),
): DefaultMediaOptimizationSelectorPresenter { ): DefaultMediaOptimizationSelectorPresenter {
return DefaultMediaOptimizationSelectorPresenter( return DefaultMediaOptimizationSelectorPresenter(
localMedia = localMedia, localMedia = localMedia,
maxUploadSizeProvider = maxUploadSizeProvider, maxUploadSizeProvider = maxUploadSizeProvider,
sessionPreferencesStore = sessionPreferencesStore,
featureFlagService = featureFlagService, featureFlagService = featureFlagService,
mediaExtractorFactory = mediaExtractorFactory, mediaExtractorFactory = mediaExtractorFactory,
mediaOptimizationConfigProvider = mediaOptimizationConfigProvider,
) )
} }
} }

View file

@ -31,6 +31,7 @@ dependencies {
implementation(projects.libraries.androidutils) implementation(projects.libraries.androidutils)
implementation(projects.libraries.core) implementation(projects.libraries.core)
implementation(projects.libraries.di) implementation(projects.libraries.di)
implementation(projects.libraries.featureflag.api)
implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrix.api)
implementation(projects.services.toolbox.api) implementation(projects.services.toolbox.api)
implementation(libs.androidx.exifinterface) implementation(libs.androidx.exifinterface)

View file

@ -10,17 +10,28 @@ package io.element.android.libraries.mediaupload.impl
import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfig import io.element.android.libraries.mediaupload.api.MediaOptimizationConfig
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
@ContributesBinding(SessionScope::class) @ContributesBinding(SessionScope::class)
class DefaultMediaOptimizationConfigProvider( class DefaultMediaOptimizationConfigProvider(
private val sessionPreferencesStore: SessionPreferencesStore, private val sessionPreferencesStore: SessionPreferencesStore,
private val featureFlagsService: FeatureFlagService,
) : MediaOptimizationConfigProvider { ) : MediaOptimizationConfigProvider {
override suspend fun get(): MediaOptimizationConfig = MediaOptimizationConfig( override suspend fun get(): MediaOptimizationConfig {
compressImages = sessionPreferencesStore.doesOptimizeImages().first(), val compressImages = sessionPreferencesStore.doesOptimizeImages().first()
videoCompressionPreset = sessionPreferencesStore.getVideoCompressionPreset().first(), return MediaOptimizationConfig(
) compressImages = compressImages,
videoCompressionPreset = if (featureFlagsService.isFeatureEnabled(FeatureFlags.SelectableMediaQuality)) {
sessionPreferencesStore.getVideoCompressionPreset().first()
} else {
if (compressImages) VideoCompressionPreset.STANDARD else VideoCompressionPreset.HIGH
},
)
}
} }