Making video thumbnail in Android

Post date: Jan 13, 2015 7:37:06 AM

Here is a simple tutorial on how to make a thumbnail for a video file. We are going to work with Android version 4.0.3 or higher and on Android you already have a way of making thumbnails, that being ThumbnailUtils class. However what that class provides us with is the ability to make one frame preview of our video with a play button overlay in the center of it (If I remember right), and what we often want is a thumbnail preview that shows a bunch of frames from our video taken consecutively from different parts of the video (See example above) to better represent video's content. Well, here is how you do that:

Lets start by making imageview that will hold our thumbnail. This must be done on main thread of your application to avoid AsyncTask bug that exist in Android 4.1 and earlier versions.

Code:

LruCache<String, Bitmap> MemoryCache = new LruCache<String, Bitmap>(((int) (Runtime.getRuntime().maxMemory() / 1024)) / 4) { @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getByteCount() / 1024; } };

File file = new File("/sdcard/my_video.mkv");

ImageView imageview = new ImageView(getBaseContext());

imageview.setImageResource(R.drawable.image_holder);

DecodeVideoPreviewInBackground task = new DecodeVideoPreviewInBackground(getBaseContext(), MemoryCache, imageview);

task.execute(file);

In the above code snippet "/sdcard/my_video.mkv" is the location of video file. Replace it with your own.

MemoryCache only needs to be initialized once in your application.

R.drawable.image_holder is the location of a place holder image that user will see first while your app is creating preview in background thread, and it will be located in /drawable directory of your project. Do not forget to add imageview to some layout in your application - you got to display it to see it.

Now lets create DecodeVideoPreviewInBackground class. This class will work in background thread and will create pretty representation of our video file. Once that preview is created it will replace place holder image in imageview with it. Note that created preview image will have width of 2600 pixels which might be too large to display on some mobile devices as such putting imageview in to HorizontalScrollView would be wise. Or you could reduce the size of created preview to something smaller.

Code:

final class DecodeVideoPreviewInBackground extends AsyncTask<File, Void, Bitmap>

{

private File file;

private WeakReference<ImageView> weakimageview;

LruCache<String, Bitmap> MemoryCache;

Context context;

// This overridden method will be called by .execute,

// After the decoding the bitmap is cached for later use using

// file.getAbsolutePath() as a key.

@Override

protected Bitmap doInBackground(File... files)

{

file = files[0];

if (!file.exists()) return null;

final String string_two = String.valueOf(file.getAbsolutePath());

Bitmap bitmap = GetFromMemoryCache(string_two);

long total_frame_time;

if (bitmap != null) return bitmap;

else {

bitmap = Bitmap.createBitmap(2600, 250, Bitmap.Config.ARGB_8888);

Bitmap bitmap_temp = Bitmap.createBitmap(325, 250, Bitmap.Config.ARGB_8888);

bitmap.eraseColor(0xFF000000);

try {

MediaPlayer mediaplayer = MediaPlayer.create(context, Uri.fromFile(file));

if (mediaplayer != null) total_frame_time = (mediaplayer.getDuration() * 1000 / 8);

else total_frame_time = 1;

mediaplayer.release();

MediaMetadataRetriever medimetadataretriever = new MediaMetadataRetriever();

medimetadataretriever.setDataSource(file.getPath());

for (int i = 0; i < 8; i++)

{

bitmap_temp = medimetadataretriever.getFrameAtTime(i*total_frame_time, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);

Canvas canvas = new Canvas(bitmap);

canvas.drawBitmap(bitmap_temp, new Rect(1, 1, bitmap_temp.getWidth(), bitmap_temp.getHeight()), new Rect(((i * 325) + 8), 8, ((i * 325) + 325), 242), new Paint());

bitmap_temp.recycle();

}

medimetadataretriever.release();

}

catch (Exception e) { return null; }

AddToMemoryCache(string_two, bitmap);

return bitmap; // note: returns a Bitmap that goes into onPostExecute

}

}

// This overridden method will attach adjusted Bitmap

// to our ImageView

@Override

protected void onPostExecute(Bitmap bitmap)

{

if (weakimageview != null && bitmap != null)

{

ImageView imageview = weakimageview.get();

if (imageview != null) imageview.setImageBitmap(bitmap);

}

}

public void AddToMemoryCache(String string, Bitmap bitmap) { if (GetFromMemoryCache(string) == null) MemoryCache.put(string, bitmap); }

public Bitmap GetFromMemoryCache(String string) { return MemoryCache.get(string); }

// Constructor

public DecodeVideoPreviewInBackground(Context context, LruCache<String, Bitmap> MemoryCache, ImageView imageview)

{

this.context = context;

this.weakimageview = new WeakReference<ImageView>(imageview);

this.MemoryCache = MemoryCache;

}

}

That's it folks.