Skip to content

Instantly share code, notes, and snippets.

@saket
Last active September 4, 2023 19:01
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save saket/5c138a64e0bafa17f45c5b6d5efecc35 to your computer and use it in GitHub Desktop.
Save saket/5c138a64e0bafa17f45c5b6d5efecc35 to your computer and use it in GitHub Desktop.
A Coil decoder for generating animated previews of videos
import android.graphics.drawable.AnimationDrawable
import coil.ImageLoader
import coil.decode.DecodeResult
import coil.decode.Decoder
import coil.decode.VideoFrameDecoder
import coil.fetch.SourceResult
import coil.request.Options
import coil.request.Parameters
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
/*
* Copyright (C) 2023 Saket Narayan.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class AnimatedVideoFrameDecoder private constructor(
private val delegates: List<VideoFrameDecoder>,
private val frameDuration: Duration,
) : Decoder {
override suspend fun decode(): DecodeResult {
val results = delegates.map { it.decode() }
return DecodeResult(
drawable = AnimationDrawable().apply {
for (result in results) {
addFrame(result.drawable, frameDuration.inWholeMilliseconds.toInt())
}
},
isSampled = results.first().isSampled,
)
}
data class Factory(
private val frameDuration: Duration = 1.seconds,
private val framePercents: List<Double> = listOf(0.0, 0.25, 0.50, 0.75, 1.0),
) : Decoder.Factory {
override fun create(result: SourceResult, options: Options, imageLoader: ImageLoader): Decoder? {
if (!isApplicable(result.mimeType)) {
return null
}
return AnimatedVideoFrameDecoder(
frameDuration = frameDuration,
delegates = framePercents.map { framePercent ->
VideoFrameDecoder(
source = result.source,
options = options.copy(
parameters = Parameters.Builder(options.parameters)
.set(VideoFrameDecoder.VIDEO_FRAME_PERCENT_KEY, framePercent)
.build()
),
)
}
)
}
private fun isApplicable(mimeType: String?): Boolean {
return mimeType != null && mimeType.startsWith("video/")
}
}
}
@saket
Copy link
Author

saket commented Aug 31, 2023

Preview:

animated.frames.mp4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment