mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-03-17 21:31:57 +01:00
Caches video and image aspect ratios to recompose with the correct size
This commit is contained in:
parent
4d4c7d5b72
commit
311964ce33
@ -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<String, Float>(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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -30,9 +30,11 @@ import androidx.media3.common.Player
|
|||||||
import androidx.media3.common.Player.PositionInfo
|
import androidx.media3.common.Player.PositionInfo
|
||||||
import androidx.media3.common.Player.STATE_IDLE
|
import androidx.media3.common.Player.STATE_IDLE
|
||||||
import androidx.media3.common.Player.STATE_READY
|
import androidx.media3.common.Player.STATE_READY
|
||||||
|
import androidx.media3.common.VideoSize
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.session.MediaSession
|
import androidx.media3.session.MediaSession
|
||||||
|
import com.vitorpamplona.amethyst.model.MediaAspectRatioCache
|
||||||
import com.vitorpamplona.amethyst.ui.MainActivity
|
import com.vitorpamplona.amethyst.ui.MainActivity
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -113,6 +115,10 @@ class MultiPlayerPlaybackManager(
|
|||||||
// avoids saving positions for live streams otherwise caching goes crazy
|
// avoids saving positions for live streams otherwise caching goes crazy
|
||||||
val mustCachePositions = !uri.contains(".m3u8", true)
|
val mustCachePositions = !uri.contains(".m3u8", true)
|
||||||
|
|
||||||
|
override fun onVideoSizeChanged(videoSize: VideoSize) {
|
||||||
|
MediaAspectRatioCache.add(uri, videoSize.width, videoSize.height)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
player.setWakeMode(C.WAKE_MODE_NETWORK)
|
player.setWakeMode(C.WAKE_MODE_NETWORK)
|
||||||
|
@ -98,6 +98,7 @@ import com.linc.audiowaveform.infiniteLinearGradient
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.commons.compose.GenericBaseCache
|
import com.vitorpamplona.amethyst.commons.compose.GenericBaseCache
|
||||||
import com.vitorpamplona.amethyst.commons.compose.produceCachedState
|
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.okhttp.HttpClientManager
|
||||||
import com.vitorpamplona.amethyst.service.playback.PlaybackClientController
|
import com.vitorpamplona.amethyst.service.playback.PlaybackClientController
|
||||||
import com.vitorpamplona.amethyst.ui.actions.MediaSaverToDisk
|
import com.vitorpamplona.amethyst.ui.actions.MediaSaverToDisk
|
||||||
@ -261,7 +262,8 @@ fun VideoView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (blurhash == null) {
|
if (blurhash == null) {
|
||||||
val ratio = dimensions?.aspectRatio()
|
val ratio = dimensions?.aspectRatio() ?: MediaAspectRatioCache.get(videoUri)
|
||||||
|
|
||||||
val modifier =
|
val modifier =
|
||||||
if (ratio != null && automaticallyStartPlayback.value) {
|
if (ratio != null && automaticallyStartPlayback.value) {
|
||||||
Modifier.aspectRatio(ratio)
|
Modifier.aspectRatio(ratio)
|
||||||
@ -295,7 +297,7 @@ fun VideoView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val ratio = dimensions?.aspectRatio()
|
val ratio = dimensions?.aspectRatio() ?: MediaAspectRatioCache.get(videoUri)
|
||||||
|
|
||||||
val modifier =
|
val modifier =
|
||||||
if (ratio != null) {
|
if (ratio != null) {
|
||||||
|
@ -43,6 +43,7 @@ import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.SideEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
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.MediaUrlContent
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
|
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
|
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
|
||||||
|
import com.vitorpamplona.amethyst.model.MediaAspectRatioCache
|
||||||
import com.vitorpamplona.amethyst.service.Blurhash
|
import com.vitorpamplona.amethyst.service.Blurhash
|
||||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||||
import com.vitorpamplona.amethyst.ui.actions.InformationDialog
|
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.HashCheckFailedIcon
|
||||||
import com.vitorpamplona.amethyst.ui.note.HashCheckIcon
|
import com.vitorpamplona.amethyst.ui.note.HashCheckIcon
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
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.stringRes
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size24dp
|
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) {
|
CrossfadeIfEnabled(targetState = showImage.value, contentAlignment = Alignment.Center, accountViewModel = accountViewModel) {
|
||||||
if (it) {
|
if (it) {
|
||||||
SubcomposeAsyncImage(
|
SubcomposeAsyncImage(
|
||||||
@ -276,6 +279,11 @@ fun LocalImageView(
|
|||||||
is AsyncImagePainter.State.Success -> {
|
is AsyncImagePainter.State.Success -> {
|
||||||
SubcomposeAsyncImageContent(loadedImageModifier)
|
SubcomposeAsyncImageContent(loadedImageModifier)
|
||||||
|
|
||||||
|
SideEffect {
|
||||||
|
val drawable = (state as AsyncImagePainter.State.Success).result.image
|
||||||
|
MediaAspectRatioCache.add(content.localFile.toString(), drawable.width, drawable.height)
|
||||||
|
}
|
||||||
|
|
||||||
content.isVerified?.let {
|
content.isVerified?.let {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = controllerVisible.value,
|
visible = controllerVisible.value,
|
||||||
@ -328,7 +336,7 @@ fun UrlImageView(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
alwayShowImage: Boolean = false,
|
alwayShowImage: Boolean = false,
|
||||||
) {
|
) {
|
||||||
val ratio = content.dim?.aspectRatio()
|
val ratio = content.dim?.aspectRatio() ?: MediaAspectRatioCache.get(content.url)
|
||||||
|
|
||||||
val showImage =
|
val showImage =
|
||||||
remember {
|
remember {
|
||||||
@ -385,6 +393,11 @@ fun UrlImageView(
|
|||||||
SubcomposeAsyncImageContent(loadedImageModifier)
|
SubcomposeAsyncImageContent(loadedImageModifier)
|
||||||
|
|
||||||
ShowHashAnimated(content, controllerVisible, Modifier.align(Alignment.TopEnd))
|
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 -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
@ -519,12 +532,6 @@ fun ShowHash(content: MediaUrlContent) {
|
|||||||
verifiedHash?.let { HashVerificationSymbol(it) }
|
verifiedHash?.let { HashVerificationSymbol(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aspectRatio(dim: DimensionTag?): Float? {
|
|
||||||
if (dim == null) return null
|
|
||||||
|
|
||||||
return dim.width.toFloat() / dim.height.toFloat()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WaitAndDisplay(
|
fun WaitAndDisplay(
|
||||||
content:
|
content:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user