|
|
|
@ -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 |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|