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

VideoFrameDecoder doesn't support videos in subfolder of Android /assets #1482

Closed
mhelder opened this issue Sep 30, 2022 · 2 comments · Fixed by #1489
Closed

VideoFrameDecoder doesn't support videos in subfolder of Android /assets #1482

mhelder opened this issue Sep 30, 2022 · 2 comments · Fixed by #1489

Comments

@mhelder
Copy link

mhelder commented Sep 30, 2022

Describe the bug
When attempting to decode video frames from a file in an Android assets subfolder, the image load fails.

Stack trace:

java.io.FileNotFoundException: video.mp4
	at android.content.res.AssetManager.nativeOpenAssetFd(Native Method)
	at android.content.res.AssetManager.openFd(AssetManager.java:963)
	at coil.decode.VideoFrameDecoder.setDataSource(VideoFrameDecoder.kt:185)
	at coil.decode.VideoFrameDecoder.decode(VideoFrameDecoder.kt:39)
	at coil.intercept.EngineInterceptor.decode(EngineInterceptor.kt:199)
	at coil.intercept.EngineInterceptor.access$decode(EngineInterceptor.kt:41)
	at coil.intercept.EngineInterceptor$execute$executeResult$1.invokeSuspend(EngineInterceptor.kt:127)
	at coil.intercept.EngineInterceptor$execute$executeResult$1.invoke(Unknown Source:8)
	at coil.intercept.EngineInterceptor$execute$executeResult$1.invoke(Unknown Source:4)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:89)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:169)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
	at coil.intercept.EngineInterceptor.execute(EngineInterceptor.kt:126)
	at coil.intercept.EngineInterceptor.access$execute(EngineInterceptor.kt:41)
	at coil.intercept.EngineInterceptor$intercept$2.invokeSuspend(EngineInterceptor.kt:75)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at kotlinx.coroutines.internal.LimitedDispatcher.run(LimitedDispatcher.kt:42)
	at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:95)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)

To Reproduce

  • Put a video file in a subfolder in /assets. E.g. /assets/videos/video.mp4
  • Attempt to load that file using file:///android_asset/videos/video.mp4
  • Observe the image load failing with above error logged.

You can also reproduce this in the following fork, where I simply moved the existing video.mp4 into a subfolder and updated the path through which it gets loaded:

https://github.com/mhelder/coil/tree/bug/videoframedecoder-assets-subfolder

As can be seen from the stack trace, the video can no longer be found. It looks like the root cause is in AssetUriFetcher, which only puts the last path segment into AssetMetadata:

val path = data.pathSegments.drop(1).joinToString("/")
return SourceResult(
source = ImageSource(
source = options.context.assets.open(path).source().buffer(),
context = options.context,
metadata = AssetMetadata(data.lastPathSegment!!)
),

That means that an asset url like file:///android_asset/videos/video.mp4 resolves into AssetMetadata("video.mp4"). When the VideoFrameDecoder then attempts to read from this asset, it's effectively trying to locate it in the 'root' of the assets folder, because any information about the subfolder got lost along the way.

Looking at other usages of AssetMetadata, it seems like the same failure will occur when decoding assets from subfolders using ImageDecoderDecoder:

if (metadata is AssetMetadata) {
return ImageDecoder.createSource(options.context.assets, metadata.fileName)
}

I may be missing some context here, but it seems like the AssetUriFetcher should perhaps feed the path from a few lines above it into the AssetMetaData? (The same path that is used to create a BufferedSource for the ImageSource). Then reads will pick up the correct location.

If that's the way to go, I'm happy to submit a PR. I would also rename AssetMetadata.fileName to something like filePath to reflect its contents.

Logs/Screenshots
See above.

Version
Latest (2.2.1). API level or device does not matter.

@colinrtwhite
Copy link
Member

Good catch, thanks! I added a quick fix for this here.

@mhelder
Copy link
Author

mhelder commented Oct 4, 2022

Nice! Left a small comment, but perhaps more of a moot point. Anyways, thanks for the quick fix!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants