@ -17,42 +17,93 @@
@@ -17,42 +17,93 @@
package io.element.android.libraries.matrix.ui.media
import coil.ImageLoader
import coil.decode.DataSource
import coil.decode.ImageSource
import coil.fetch.FetchResult
import coil.fetch.Fetcher
import coil.fetch.SourceResult
import coil.request.Options
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.media.toFile
import okio.Buffer
import okio.Path.Companion.toOkioPath
import timber.log.Timber
import java.nio.ByteBuffer
internal class CoilMediaFetcher (
private val mediaLoader : MatrixMediaLoader ,
private val mediaData : MediaRequestData ? ,
private val options : Options ,
private val imageLoader : ImageLoader
private val options : Options
) : Fetcher {
override suspend fun fetch ( ) : FetchResult ? {
return loadMedia ( )
. map { data ->
val byteBuffer = ByteBuffer . wrap ( data )
imageLoader . components . newFetcher ( byteBuffer , options , imageLoader ) ?. first ?. fetch ( )
} . getOrThrow ( )
if ( mediaData ?. source == null ) return null
return when ( mediaData . kind ) {
is MediaRequestData . Kind . Content -> fetchContent ( mediaData . source , options )
is MediaRequestData . Kind . Thumbnail -> fetchThumbnail ( mediaData . source , mediaData . kind , options )
is MediaRequestData . Kind . File -> fetchFile ( mediaData . source , mediaData . kind )
}
}
private suspend fun loadMedia ( ) : Result < ByteArray > {
if ( mediaData ?. source == null ) return Result . failure ( IllegalStateException ( " No media data to fetch. " ) )
return when ( mediaData . kind ) {
is MediaRequestData . Kind . Content -> mediaLoader . loadMediaContent ( source = mediaData . source )
is MediaRequestData . Kind . Thumbnail -> mediaLoader . loadMediaThumbnail (
source = mediaData . source ,
width = mediaData . kind . width ,
height = mediaData . kind . height
/ * *
* This method is here to avoid using [ MatrixMediaLoader . loadMediaContent ] as too many ByteArray allocations will flood the memory and cause lots of GC .
* The MediaFile will be closed ( and so destroyed from disk ) when the image source is closed .
*
* /
private suspend fun fetchFile ( mediaSource : MediaSource , kind : MediaRequestData . Kind . File ) : FetchResult ? {
return mediaLoader . downloadMediaFile ( mediaSource , kind . mimeType , kind . body )
. map { mediaFile ->
val file = mediaFile . toFile ( )
SourceResult (
source = ImageSource ( file = file . toOkioPath ( ) , closeable = mediaFile ) ,
mimeType = null ,
dataSource = DataSource . DISK
)
}
. onFailure {
Timber . e ( it )
}
. getOrNull ( )
}
private suspend fun fetchContent ( mediaSource : MediaSource , options : Options ) : FetchResult ? {
return mediaLoader . loadMediaContent (
source = mediaSource ,
) . map { byteArray ->
byteArray . asSourceResult ( options )
} . getOrNull ( )
}
private suspend fun fetchThumbnail ( mediaSource : MediaSource , kind : MediaRequestData . Kind . Thumbnail , options : Options ) : FetchResult ? {
return mediaLoader . loadMediaThumbnail (
source = mediaSource ,
width = kind . width ,
height = kind . height
) . map { byteArray ->
byteArray . asSourceResult ( options )
} . getOrNull ( )
}
private fun ByteArray . asSourceResult ( options : Options ) : SourceResult {
val byteBuffer = ByteBuffer . wrap ( this )
val bufferedSource = try {
Buffer ( ) . apply { write ( byteBuffer ) }
} finally {
byteBuffer . position ( 0 )
}
return SourceResult (
source = ImageSource ( bufferedSource , options . context ) ,
mimeType = null ,
dataSource = DataSource . MEMORY
)
}
class MediaRequestDataFactory ( private val client : MatrixClient ) :
class MediaRequestDataFactory (
private val client : MatrixClient
) :
Fetcher . Factory < MediaRequestData > {
override fun create (
data : MediaRequestData ,
@ -62,13 +113,14 @@ internal class CoilMediaFetcher(
@@ -62,13 +113,14 @@ internal class CoilMediaFetcher(
return CoilMediaFetcher (
mediaLoader = client . mediaLoader ,
mediaData = data ,
options = options ,
imageLoader = imageLoader
options = options
)
}
}
class AvatarFactory ( private val client : MatrixClient ) :
class AvatarFactory (
private val client : MatrixClient
) :
Fetcher . Factory < AvatarData > {
override fun create (
@ -79,8 +131,7 @@ internal class CoilMediaFetcher(
@@ -79,8 +131,7 @@ internal class CoilMediaFetcher(
return CoilMediaFetcher (
mediaLoader = client . mediaLoader ,
mediaData = data . toMediaRequestData ( ) ,
options = options ,
imageLoader = imageLoader
options = options
)
}
}