diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/MediaAspectRatioCache.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/MediaAspectRatioCache.kt new file mode 100644 index 000000000..6790b244a --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/MediaAspectRatioCache.kt @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.model + +import android.util.LruCache + +object MediaAspectRatioCache { + val mediaAspectRatioCacheByUrl = LruCache(1000) + + fun get(url: String) = mediaAspectRatioCacheByUrl.get(url) + + fun add( + url: String, + width: Int, + height: Int, + ) { + if (height > 1) { + mediaAspectRatioCacheByUrl.put(url, width.toFloat() / height.toFloat()) + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/MultiPlayerPlaybackManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/MultiPlayerPlaybackManager.kt index f04170dc1..80989981a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/MultiPlayerPlaybackManager.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/MultiPlayerPlaybackManager.kt @@ -30,9 +30,11 @@ import androidx.media3.common.Player import androidx.media3.common.Player.PositionInfo import androidx.media3.common.Player.STATE_IDLE import androidx.media3.common.Player.STATE_READY +import androidx.media3.common.VideoSize import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.ExoPlayer import androidx.media3.session.MediaSession +import com.vitorpamplona.amethyst.model.MediaAspectRatioCache import com.vitorpamplona.amethyst.ui.MainActivity import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers @@ -113,6 +115,10 @@ class MultiPlayerPlaybackManager( // avoids saving positions for live streams otherwise caching goes crazy val mustCachePositions = !uri.contains(".m3u8", true) + override fun onVideoSizeChanged(videoSize: VideoSize) { + MediaAspectRatioCache.add(uri, videoSize.width, videoSize.height) + } + override fun onIsPlayingChanged(isPlaying: Boolean) { if (isPlaying) { player.setWakeMode(C.WAKE_MODE_NETWORK) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt index 0fbc34067..e9cacf55c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt @@ -98,6 +98,7 @@ import com.linc.audiowaveform.infiniteLinearGradient import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.compose.GenericBaseCache import com.vitorpamplona.amethyst.commons.compose.produceCachedState +import com.vitorpamplona.amethyst.model.MediaAspectRatioCache import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import com.vitorpamplona.amethyst.service.playback.PlaybackClientController import com.vitorpamplona.amethyst.ui.actions.MediaSaverToDisk @@ -261,7 +262,8 @@ fun VideoView( } if (blurhash == null) { - val ratio = dimensions?.aspectRatio() + val ratio = dimensions?.aspectRatio() ?: MediaAspectRatioCache.get(videoUri) + val modifier = if (ratio != null && automaticallyStartPlayback.value) { Modifier.aspectRatio(ratio) @@ -295,7 +297,7 @@ fun VideoView( } } } else { - val ratio = dimensions?.aspectRatio() + val ratio = dimensions?.aspectRatio() ?: MediaAspectRatioCache.get(videoUri) val modifier = if (ratio != null) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt index 378a2dddc..e9de960dd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt @@ -43,6 +43,7 @@ import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState +import androidx.compose.runtime.SideEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -77,6 +78,7 @@ import com.vitorpamplona.amethyst.commons.richtext.MediaPreloadedContent import com.vitorpamplona.amethyst.commons.richtext.MediaUrlContent import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo +import com.vitorpamplona.amethyst.model.MediaAspectRatioCache import com.vitorpamplona.amethyst.service.Blurhash import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.actions.InformationDialog @@ -87,6 +89,7 @@ import com.vitorpamplona.amethyst.ui.note.DownloadForOfflineIcon import com.vitorpamplona.amethyst.ui.note.HashCheckFailedIcon import com.vitorpamplona.amethyst.ui.note.HashCheckIcon import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.gallery.UrlImageView import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size20dp import com.vitorpamplona.amethyst.ui.theme.Size24dp @@ -235,7 +238,7 @@ fun LocalImageView( ) } - val ratio = remember(content) { aspectRatio(content.dim) } + val ratio = remember(content) { content.dim?.aspectRatio() ?: MediaAspectRatioCache.get(content.localFile.toString()) } CrossfadeIfEnabled(targetState = showImage.value, contentAlignment = Alignment.Center, accountViewModel = accountViewModel) { if (it) { SubcomposeAsyncImage( @@ -276,6 +279,11 @@ fun LocalImageView( is AsyncImagePainter.State.Success -> { SubcomposeAsyncImageContent(loadedImageModifier) + SideEffect { + val drawable = (state as AsyncImagePainter.State.Success).result.image + MediaAspectRatioCache.add(content.localFile.toString(), drawable.width, drawable.height) + } + content.isVerified?.let { AnimatedVisibility( visible = controllerVisible.value, @@ -328,7 +336,7 @@ fun UrlImageView( accountViewModel: AccountViewModel, alwayShowImage: Boolean = false, ) { - val ratio = content.dim?.aspectRatio() + val ratio = content.dim?.aspectRatio() ?: MediaAspectRatioCache.get(content.url) val showImage = remember { @@ -385,6 +393,11 @@ fun UrlImageView( SubcomposeAsyncImageContent(loadedImageModifier) ShowHashAnimated(content, controllerVisible, Modifier.align(Alignment.TopEnd)) + + SideEffect { + val drawable = (state as AsyncImagePainter.State.Success).result.image + MediaAspectRatioCache.add(content.url, drawable.width, drawable.height) + } } else -> {} } @@ -519,12 +532,6 @@ fun ShowHash(content: MediaUrlContent) { verifiedHash?.let { HashVerificationSymbol(it) } } -fun aspectRatio(dim: DimensionTag?): Float? { - if (dim == null) return null - - return dim.width.toFloat() / dim.height.toFloat() -} - @Composable fun WaitAndDisplay( content: