17

I'm trying to use Glide to step through frames in a video file (without running into the keyframe seeking issue that Android suffers from). I can do this in Picasso by doing something like:

picasso = new Picasso.Builder(MainActivity.this).addRequestHandler(new PicassoVideoFrameRequestHandler()).build();
picasso.load("videoframe://" + Environment.getExternalStorageDirectory().toString() +
                    "/source.mp4#" + frameNumber)
                    .placeholder(drawable)
                    .memoryPolicy(MemoryPolicy.NO_CACHE)
                    .into(imageView);

(frameNumber is simply an int which increases by 50000 microseconds each time). I also have a PicassoVideoFrameRequestHandler like this:

public class PicassoVideoFrameRequestHandler extends RequestHandler {
public static final String SCHEME = "videoframe";

@Override public boolean canHandleRequest(Request data) {
    return SCHEME.equals(data.uri.getScheme());
}

@Override
public Result load(Request data, int networkPolicy) throws IOException {
    FFmpegMediaMetadataRetriever mediaMetadataRetriever = new FFmpegMediaMetadataRetriever();
    mediaMetadataRetriever.setDataSource(data.uri.getPath());
    String offsetString = data.uri.getFragment();
    long offset = Long.parseLong(offsetString);
    Bitmap bitmap = mediaMetadataRetriever.getFrameAtTime(offset, FFmpegMediaMetadataRetriever.OPTION_CLOSEST);
    return new Result(bitmap, Picasso.LoadedFrom.DISK);
}

}

I'd like to use Glide instead, as it handles memory a little better. Is there any way to have this functionality in Glide?

Or, really, any other way to create a set of frames from a video which I can step through!

Thanks!

jgads
  • 257
  • 1
  • 3
  • 13

3 Answers3

15

You'll need to pass ".override(width, height)" to make Sam Judd's method to work. Otherwise you'll get only the first frame of the video as I've tested variety of approaches for hours. Hope it saves time for someone.

BitmapPool bitmapPool = Glide.get(getApplicationContext()).getBitmapPool();
int microSecond = 6000000;// 6th second as an example
VideoBitmapDecoder videoBitmapDecoder = new VideoBitmapDecoder(microSecond);
FileDescriptorBitmapDecoder fileDescriptorBitmapDecoder = new FileDescriptorBitmapDecoder(videoBitmapDecoder, bitmapPool, DecodeFormat.PREFER_ARGB_8888);
Glide.with(getApplicationContext())
    .load(yourUri)
    .asBitmap()
    .override(50,50)// Example
    .videoDecoder(fileDescriptorBitmapDecoder)
    .into(yourImageView);
Eftekhari
  • 1,001
  • 1
  • 19
  • 37
  • Did you actually test with 50,50? Override only replaces the ImageView size, so if you use an ImageView with the width and height that you provide to override, you should get exactly the same affect. – Sam Judd Jan 11 '16 at 20:37
  • And to be more clear, it's probable that Glide is loading small sizes from the Media Store, rather than from it's own video decoder. I'd guess that large sizes will retrieve the frame you specify because they will be retrieved with our decoder, but small sizes will not because they will be retrieved from media store. – Sam Judd Jan 11 '16 at 20:39
  • I've tested variety of sizes and many different approaches with Glide and when the time I didn't use override method there was only the first frame as the result. I don't know the real reason behind it as I never tried to dig into the source code. – Eftekhari Jan 12 '16 at 05:19
  • Another strange behaviour of Glide that made me leave it to use FFmpeg instead in future was that I couldn't get many frames. I mean, this approach only gives me one frame even if I use different VideoBitmapDecoders for each call. Do you know why? Is it because of Glides unity of BitmapPool or something like that? I've never tried custom bitmapPools on Glide to see the difference. – Eftekhari Jan 12 '16 at 05:24
  • I tested with a video of a timer and this code sample is going to keyframes. – Greg Marsh Oct 05 '16 at 21:52
  • 3
    Thanks it working fine in offline video but i wanna to show thumbnail using network url. – Arpit Patel Oct 28 '16 at 08:02
  • It doesn't work when i load many frames of one video in the RecycleView. It always show the first frame. – AnswerZhao Dec 20 '16 at 13:11
  • Can this be used to get frame-by-frame images, and then viewing them and mimic as if it's playing a video, but on ImageView? – android developer Oct 22 '20 at 23:55
15

Thanks, this Post help me get there. Btw if you're using Glide 4.4 they change the way you get this result. To load a specific frame from a video uri.

You just need to use the object RequestOptions like this:

long interval = positionInMillis * 1000;
RequestOptions options = new RequestOptions().frame(interval);
Glide.with(context).asBitmap()
                    .load(videoUri)
                    .apply(options)
                    .into(viewHolder.imgPreview);

Where "positionInMillis" its a long variable from the video position that you want the image.

jobernas
  • 686
  • 1
  • 12
  • 24
6

You can pass in a frame time (In microseconds, see the MediaMetadataRetriever docs) to VideoBitmapDecoder. This is untested, but it should work:

BitmapPool bitmapPool = Glide.get(context).getBitmapPool();
FileDescriptorBitmapDecoder decoder = new FileDescriptorBitmapDecoder(
    new VideoBitmapDecoder(frameTimeMicros),
    bitmapPool,
    DecodeFormat.PREFER_ARGB_8888);

Glide.with(fragment)
    .load(uri)
    .asBitmap()
    .videoDecoder(decoder)
    .into(imageView);
Sam Judd
  • 7,317
  • 1
  • 38
  • 38
  • 1
    Looks good! But ".videoDecoder" only seems to support a ResourceDecoder, not a VideoBitmapDecoder... – jgads Jun 11 '15 at 15:41
  • 1
    Ahh I missed the wrapping decoder, I updated my answer to include the correct one: http://bumptech.github.io/glide/javadocs/360/com/bumptech/glide/load/resource/bitmap/FileDescriptorBitmapDecoder.html – Sam Judd Jun 11 '15 at 15:48
  • Thank you! Is there any reason why it has to be ARGB_8888? – jgads Jun 11 '15 at 15:50
  • 1
    No, in this case the decode format is simply ignored because MediaMetadataRetriever doesn't give us the ability to specify which one we want. – Sam Judd Jun 11 '15 at 15:56
  • I implemented a custom version of the FileDescriptorBitmapDecoder and the VideoBitmapDecoder - by changing the following in the 'decode()' method, from: result = mediaMetadataRetriever.getFrameAtTime(frame);, to: – jgads Jun 11 '15 at 16:29
  • result = mediaMetadataRetriever.getFrameAtTime(frame, MediaMetadataRetriever.OPTION_CLOSEST); - this allows you to grab exactly that frame at that moment, and might be a worthy change for Glide in the future? – jgads Jun 11 '15 at 16:30
  • 1
    After some more testing, it seems this method still doesn't grab anything other than the first frame of the video. In the 'onResourceReady' callback, I added a log to see what was happening, like so: `Log.e("glide", "loaded bitmap of size " + resource.getByteCount() + " (position " + position + ") in " + (System.currentTimeMillis() - time) + "ms");` - The results look something like this: – jgads Jun 11 '15 at 16:48
  • `06-11 12:45:15.830 19526-19526/com.example.videoconversiondemo E/glide﹕ loaded bitmap of size 1936512 (position 1000000) in 72ms 06-11 12:45:19.260 19526-19526/com.example.videoconversiondemo E/glide﹕ loaded bitmap of size 1936512 (position 2000000) in 2ms 06-11 12:45:22.450 19526-19526/com.example.videoconversiondemo E/glide﹕ loaded bitmap of size 1936512 (position 3000000) in 1ms` - Any ideas? Thanks for all your help! (I tried turning off memory caching, but it still just returns the first frame) – jgads Jun 11 '15 at 16:48
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/80334/discussion-between-samajudd-and-jgads). – Sam Judd Jun 11 '15 at 23:33
  • @jgads What was the result of chat about your problem. Same here. I'm only getting the first frame with this approach. – Eftekhari Jan 08 '16 at 10:54
  • @SamJudd What was the solution for getting only the first frame of the video with your approach? – Eftekhari Jan 08 '16 at 11:26
  • @Eeftekhari - I ended up going with FFmpeg in the end. Nice that you found a fix for this, though! – jgads Jan 11 '16 at 18:28
  • @jgads Did you build FFmpeg for android yourself or you used one of the made libraries on github? I'm asking this because I couldn't get the job done on windows. If you are a successful FFmpeg for android builder please provide me your useful links or give your gmail. thanks. – Eftekhari Jan 12 '16 at 05:30
  • I built it myself to be LGPL compliant. – jgads Jan 14 '16 at 15:05
  • Since many things are changed now. Like support from disk cache etc. Can you update the answer for the future audiences? This is already Viewed 21k times – Shubham AgaRwal Sep 08 '21 at 10:10