Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support video thumbnail in android #632

Merged
merged 3 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import com.seiko.imageloader.demo.scene.NetworkImagesScene
import com.seiko.imageloader.demo.scene.OtherImagesScene
import com.seiko.imageloader.demo.scene.PokemonScene
import com.seiko.imageloader.demo.scene.SvgImagesScene
import com.seiko.imageloader.demo.scene.VideoScene
import com.seiko.imageloader.demo.scene.WanAndroidScene

@Composable
Expand All @@ -45,6 +46,7 @@ fun App(modifier: Modifier = Modifier) {
Route.Network -> NetworkImagesScene(::onBack)
Route.Gif -> GifImagesScene(::onBack)
Route.Svg -> SvgImagesScene(::onBack)
Route.Video -> VideoScene(::onBack)
Route.Pokemon -> PokemonScene(::onBack)
Route.LocalResource -> LocalResourceScene(::onBack)
Route.Other -> OtherImagesScene(::onBack)
Expand Down Expand Up @@ -92,6 +94,7 @@ private enum class Route {
Network,
Gif,
Svg,
Video,
Pokemon,
LocalResource,
Other,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.seiko.imageloader.demo.scene

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier

@Composable
fun VideoScene(
onBack: () -> Unit,
) {
BackScene(
onBack = onBack,
title = { Text("Video") },
) { innerPadding ->
val images = remember {
listOf(
"http://mirror.aarnet.edu.au/pub/TED-talks/911Mothers_2010W-480p.mp4",
"https://video.twimg.com/ext_tw_video/1712110948700352512/pu/vid/avc1/720x1280/i43wruptl2R9KHAZ.mp4?tag=12",
// "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
// "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4",
// "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4",
// "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4",
// "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4",
)
}
LazyColumn(
Modifier.padding(innerPadding).fillMaxSize(),
) {
items(images.size) { index ->
ImageItem(
data = images[index],
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ actual fun FilePickerScene(onBack: () -> Unit) {
title = { Text("FilePicker") },
) { innerPadding ->

val fileType = remember { listOf("jpg", "png") }
val fileType = remember { listOf("jpg", "png", "mp4") }
var selectPlatformFile by remember { mutableStateOf<Any?>(null) }

var pathSingleChosen by remember { mutableStateOf("") }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

package com.seiko.imageloader.component.fetcher

import com.seiko.imageloader.model.ImageSourceFrom
import com.seiko.imageloader.model.toImageSource
import com.seiko.imageloader.option.Options
import okio.Buffer
import org.jetbrains.compose.resources.DrawableResource
import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.InternalResourceApi
Expand All @@ -28,9 +29,8 @@ class ComposeResourceFetcher {
override suspend fun fetch(): FetchResult {
val path = resource.getResourceItemByEnvironment(getSystemEnvironment()).path
return FetchResult.OfSource(
source = Buffer().apply {
write(readResourceBytes(path))
},
imageSource = readResourceBytes(path).toImageSource(),
imageSourceFrom = ImageSourceFrom.Disk,
)
}
}
Expand Down
2 changes: 1 addition & 1 deletion extension/imageio/api/imageio.api
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
public final class com/seiko/imageloader/component/decoder/ImageIODecoder : com/seiko/imageloader/component/decoder/Decoder {
public fun <init> (Lokio/BufferedSource;)V
public fun <init> (Lcom/seiko/imageloader/model/ImageSource;)V
public fun decode (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
package com.seiko.imageloader.component.decoder

import androidx.compose.ui.graphics.toPainter
import com.seiko.imageloader.model.ImageSource
import com.seiko.imageloader.model.InputStreamImageSource
import com.seiko.imageloader.option.Options
import com.seiko.imageloader.util.isGif
import kotlinx.coroutines.runInterruptible
import okio.BufferedSource
import javax.imageio.ImageIO

class ImageIODecoder(
private val source: BufferedSource,
private val source: ImageSource,
) : Decoder {

override suspend fun decode(): DecodeResult {
val image = runInterruptible {
ImageIO.read(source.inputStream())
ImageIO.read(
if (source is InputStreamImageSource) {
source.inputStream
} else {
source.bufferedSource.inputStream()
},
)
}
return DecodeResult.OfPainter(
painter = image.toPainter(),
Expand All @@ -22,8 +29,8 @@ class ImageIODecoder(

class Factory : Decoder.Factory {
override fun create(source: DecodeSource, options: Options): Decoder? {
if (isGif(source.source)) return null
return ImageIODecoder(source.source)
if (isGif(source.imageSource.bufferedSource)) return null
return ImageIODecoder(source.imageSource)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,5 @@ package com.seiko.imageloader.component.fetcher

import io.ktor.client.engine.HttpClientEngine
import io.ktor.client.engine.darwin.Darwin
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.cancel
import io.ktor.utils.io.toByteArray
import okio.Buffer
import okio.BufferedSource

internal actual suspend fun ByteReadChannel.source(): BufferedSource {
return Buffer().apply {
write(toByteArray())
[email protected]()
}
}

internal actual val httpEngine: HttpClientEngine get() = Darwin.create()
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")

package com.seiko.imageloader.component.fetcher

import com.seiko.imageloader.model.ImageSource
import com.seiko.imageloader.model.ImageSourceFrom
import com.seiko.imageloader.model.KtorRequestData
import com.seiko.imageloader.model.extraData
import com.seiko.imageloader.model.ktorRequestData
import com.seiko.imageloader.model.mimeType
import com.seiko.imageloader.model.toImageSource
import com.seiko.imageloader.option.Options
import io.ktor.client.HttpClient
import io.ktor.client.engine.HttpClientEngine
import io.ktor.client.request.headers
import io.ktor.client.request.request
import io.ktor.client.request.prepareRequest
import io.ktor.client.request.url
import io.ktor.client.statement.bodyAsChannel
import io.ktor.http.HttpMethod
import io.ktor.http.Url
import io.ktor.http.contentType
import io.ktor.http.isSuccess
import io.ktor.utils.io.ByteReadChannel
import okio.BufferedSource
import io.ktor.utils.io.readRemaining
import kotlinx.io.readByteArray
import okio.Buffer

class KtorUrlFetcher private constructor(
private val httpUrl: Url,
Expand All @@ -27,7 +34,7 @@ class KtorUrlFetcher private constructor(
private val httpClient by lazy(httpClient)

override suspend fun fetch(): FetchResult {
val response = httpClient.request {
return httpClient.prepareRequest {
url(httpUrl)
method = ktorRequestData?.method ?: HttpMethod.Get
ktorRequestData?.headers?.let {
Expand All @@ -37,16 +44,19 @@ class KtorUrlFetcher private constructor(
}
}
}
}
if (response.status.isSuccess()) {
return FetchResult.OfSource(
source = response.bodyAsChannel().source(),
}.execute { response ->
if (!response.status.isSuccess()) {
throw KtorUrlRequestException("code:${response.status.value}, ${response.status.description}")
}

FetchResult.OfSource(
imageSource = channelToImageSource(response.bodyAsChannel()),
imageSourceFrom = ImageSourceFrom.Network,
extra = extraData {
mimeType(response.contentType()?.toString())
},
)
}
throw KtorUrlRequestException("code:${response.status.value}, ${response.status.description}")
}

class Factory(
Expand All @@ -68,8 +78,18 @@ class KtorUrlFetcher private constructor(
}
}

private class KtorUrlRequestException(msg: String) : RuntimeException(msg)
private suspend fun channelToImageSource(channel: ByteReadChannel): ImageSource {
val buffer = Buffer()
while (!channel.isClosedForRead) {
val packet = channel.readRemaining(2048)
while (!packet.exhausted()) {
val bytes = packet.readByteArray()
buffer.write(bytes)
}
}
return buffer.toImageSource()
}

internal expect suspend fun ByteReadChannel.source(): BufferedSource
private class KtorUrlRequestException(msg: String) : RuntimeException(msg)

internal expect val httpEngine: HttpClientEngine
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,6 @@ package com.seiko.imageloader.component.fetcher

import io.ktor.client.engine.HttpClientEngine
import io.ktor.client.engine.js.Js
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.cancel
import io.ktor.utils.io.toByteArray
import okio.Buffer
import okio.BufferedSource

internal actual suspend fun ByteReadChannel.source(): BufferedSource {
return Buffer().apply {
write(toByteArray())
[email protected]()
}
}

internal actual val httpEngine: HttpClientEngine
get() = Js.create()
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,5 @@ package com.seiko.imageloader.component.fetcher

import io.ktor.client.engine.HttpClientEngine
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.jvm.javaio.toInputStream
import okio.BufferedSource
import okio.buffer
import okio.source

internal actual suspend fun ByteReadChannel.source(): BufferedSource {
return toInputStream().source().buffer()
}

internal actual val httpEngine: HttpClientEngine get() = OkHttp.create()
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,6 @@ package com.seiko.imageloader.component.fetcher

import io.ktor.client.engine.HttpClientEngine
import io.ktor.client.engine.js.Js
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.cancel
import io.ktor.utils.io.toByteArray
import okio.Buffer
import okio.BufferedSource

internal actual suspend fun ByteReadChannel.source(): BufferedSource {
return Buffer().apply {
write(toByteArray())
[email protected]()
}
}

internal actual val httpEngine: HttpClientEngine
get() = Js.create()
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@ package com.seiko.imageloader.component.fetcher

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.ColorPainter
import com.seiko.imageloader.model.ImageSourceFrom
import com.seiko.imageloader.model.toImageSource
import com.seiko.imageloader.option.Options
import com.seiko.imageloader.option.androidContext
import com.seiko.imageloader.toImage
import dev.icerock.moko.resources.AssetResource
import dev.icerock.moko.resources.ColorResource
import dev.icerock.moko.resources.FileResource
import dev.icerock.moko.resources.ImageResource
import okio.buffer
import okio.source

internal actual suspend fun AssetResource.toFetchResult(options: Options): FetchResult? {
return FetchResult.OfSource(
source = getInputStream(options.androidContext).source().buffer(),
imageSource = getInputStream(options.androidContext).toImageSource(),
imageSourceFrom = ImageSourceFrom.Disk,
)
}

Expand All @@ -26,7 +27,8 @@ internal actual suspend fun ColorResource.toFetchResult(options: Options): Fetch

internal actual suspend fun FileResource.toFetchResult(options: Options): FetchResult? {
return FetchResult.OfSource(
source = options.androidContext.resources.openRawResource(rawResId).source().buffer(),
imageSource = options.androidContext.resources.openRawResource(rawResId).toImageSource(),
imageSourceFrom = ImageSourceFrom.Disk,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ package com.seiko.imageloader.component.fetcher

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.ColorPainter
import com.seiko.imageloader.model.ImageSourceFrom
import com.seiko.imageloader.model.toImageSource
import com.seiko.imageloader.option.Options
import dev.icerock.moko.resources.AssetResource
import dev.icerock.moko.resources.ColorResource
import dev.icerock.moko.resources.FileResource
import dev.icerock.moko.resources.ImageResource
import okio.buffer
import okio.source
import java.io.FileNotFoundException

internal actual suspend fun AssetResource.toFetchResult(options: Options): FetchResult? {
Expand All @@ -32,14 +32,16 @@ internal actual suspend fun FileResource.toFetchResult(options: Options): FetchR
val stream = resourcesClassLoader.getResourceAsStream(filePath)
?: throw FileNotFoundException("Couldn't open resource as stream at: $filePath")
return FetchResult.OfSource(
source = stream.source().buffer(),
imageSource = stream.toImageSource(),
imageSourceFrom = ImageSourceFrom.Disk,
)
}

internal actual suspend fun ImageResource.toFetchResult(options: Options): FetchResult? {
val stream = resourcesClassLoader.getResourceAsStream(filePath)
?: throw FileNotFoundException("Couldn't open resource as stream at: $filePath")
return FetchResult.OfSource(
source = stream.source().buffer(),
imageSource = stream.toImageSource(),
imageSourceFrom = ImageSourceFrom.Disk,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package com.seiko.imageloader.component.fetcher

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.ColorPainter
import com.seiko.imageloader.model.ImageSourceFrom
import com.seiko.imageloader.model.toImageSource
import com.seiko.imageloader.option.Options
import com.seiko.imageloader.util.toSkiaImage
import dev.icerock.moko.resources.AssetResource
Expand Down Expand Up @@ -58,7 +60,8 @@ internal actual suspend fun FileResource.toFetchResult(options: Options): FetchR
inDirectory = "files",
)!!.toPath()
return FetchResult.OfSource(
source = FileSystem.SYSTEM.source(path).buffer(),
imageSource = FileSystem.SYSTEM.source(path).buffer().toImageSource(),
imageSourceFrom = ImageSourceFrom.Disk,
)
}

Expand Down
Loading
Loading