安卓:是否可以显示视频缩略图?

101

我用图书馆对话框创建了一个视频录制应用程序。这个库对话框显示录制视频列表,其中每个项目包括图标、视频标题、标签和位置信息,如下所示:

alt text

请问是否可以将图标替换为视频缩略图(单帧预览)?

谢谢!


有人回答了这个问题吗?[http://stackoverflow.com/questions/16190374/how-to-display-the-videos-url-in-thumbnails] - Make it Simple
12个回答

98

如果你不能或不想使用游标,并且只有路径或文件对象,你可以使用自API级别8(2.2)开始提供的以下方法:

public static Bitmap createVideoThumbnail(String filePath, int kind)

Android文档

以下代码运行正常:

Bitmap bMap = ThumbnailUtils.createVideoThumbnail(file.getAbsolutePath(), MediaStore.Video.Thumbnails.MICRO_KIND);

7
当我试图创建缩略图时,出现 null 的情况。我认为我的路径可能有问题?我的路径是 "/external/video/media/14180"。 - haythem souissi
它就像魔法一样。即使我没有视频ID,它也能正常工作。为了获得更好的质量,请使用“MediaStore.Video.Thumbnails.FULL_SCREEN_KIND”。 - Sami Eltamawy
haythem souussi因为这不是路径,而是Uri,您需要将其转换为路径。 - Nadir Novruzov
这个代码可以运行,但是返回的视频图像不正确。我需要获取第一帧,但是却获得了5-6秒钟之后的图像。有什么建议吗? - speedynomads
1
"'createVideoThumbnail(java.lang.String, int)'"已被弃用。 - mufazmi
显示剩余2条评论

76
如果您正在使用API 2.0或更高版本,则这将起作用。
int id = **"The Video's ID"**
ImageView iv = (ImageView ) convertView.findViewById(R.id.imagePreview);
ContentResolver crThumb = getContentResolver();
BitmapFactory.Options options=new BitmapFactory.Options();
options.inSampleSize = 1;
Bitmap curThumb = MediaStore.Video.Thumbnails.getThumbnail(crThumb, id, MediaStore.Video.Thumbnails.MICRO_KIND, options);
iv.setImageBitmap(curThumb);

10
id是什么? - phunehehe
1
您可以查询手机上的视频媒体库。 "id" 只是您查询的信息之一。有关 MediaStore 的更多信息,请参见 http://developer.android.com/reference/android/provider/MediaStore.Video.html。 - Greg Zimmers
6
似乎所有人都能让这个工作,令人惊讶。我也尝试过,但是 curThumb 最终为空值。 - BlueVoodoo
8
如果视频来自一个URL,怎么办? - jayellos
2
使用Glide,它非常容易。 - Dhruvam Sharma
显示剩余3条评论

43

使用这个类:

import android.provider.MediaStore.Video.Thumbnails;

我们可以从视频中获取两种预览缩略图大小:

Thumbnails.MICRO_KIND 为 96 x 96

Thumbnails.MINI_KIND 为 512 x 384 px

这是一个代码示例:

String filePath = "/sdcard/DCIM/Camera/my_video.mp4"; //change the location of your file!

ImageView imageview_mini = (ImageView)findViewById(R.id.thumbnail_mini);
ImageView imageview_micro = (ImageView)findViewById(R.id.thumbnail_micro);

Bitmap bmThumbnail;

//MICRO_KIND, size: 96 x 96 thumbnail
bmThumbnail = ThumbnailUtils.createVideoThumbnail(filePath, Thumbnails.MICRO_KIND);
imageview_micro.setImageBitmap(bmThumbnail);
     
// MINI_KIND, size: 512 x 384 thumbnail 
bmThumbnail = ThumbnailUtils.createVideoThumbnail(filePath, Thumbnails.MINI_KIND);
imageview_mini.setImageBitmap(bmThumbnail);

如果我有一个文件路径的链接,像这样,这样设置到ImageView上不会起作用,因为它没有显示任何内容...这是文件路径"http:/ /unknown.com/v3-1aox9d1 .mp4",显然是一个真实的域名,但是这个路径不能生成缩略图吗? - Lion789
要使用ThumbnailUtils类,您必须先将文件保存到磁盘上,然后使用方法ThumbnailUtils.createVideoThumbnail()。 - Jorgesys
谢谢,请问我该如何获取创建缩略图的新文件路径? - Lion789
为什么在 Android 4 及以上版本不能正常工作? - Criss
1
嗨,Cris,我有三个设备4.1、4.2.2和5.0,它们都可以正常工作。发一个带有你的问题和一些代码的问题,我可以帮助你。 - Jorgesys

28

我强烈建议您使用Glide库。它是生成和显示本地视频文件缩略图最有效的方法之一。

只需将以下代码添加到您的gradle文件中:

compile 'com.github.bumptech.glide:glide:3.7.0'

并且它将变得像这样简单:

String filePath = "/storage/emulated/0/Pictures/example_video.mp4";

Glide  
    .with( context )
    .load( Uri.fromFile( new File( filePath ) ) )
    .into( imageViewGifAsBitmap );

在这里您可以找到更多信息:https://futurestud.io/blog/glide-displaying-gifs-and-videos

干杯!


4
滑动(Glide)只适用于本地视频,而不是URL视频,难道没有简单的方法可以解决这个问题吗? - Lutaaya Huzaifah Idris
有些设备无法显示本地视频的缩略图。 - Sathish Gadde

22

目前我使用以下代码:

Bitmap bMap = ThumbnailUtils.createVideoThumbnail(file.getAbsolutePath(), MediaStore.Video.Thumbnails.MICRO_KIND);

但我发现使用Glide库并采用以下代码可以获得更好的解决方案(它会缓存您的图像,并且比之前的方法表现更佳)

Glide.with(context)
                .load(uri)
                .placeholder(R.drawable.ic_video_place_holder)
                .into(imageView);

10

试试这个,它对我有效

RequestOptions requestOptions = new RequestOptions();
 Glide.with(getContext())
      .load("video_url")
      .apply(requestOptions)
      .thumbnail(Glide.with(getContext()).load("video_url"))
      .into("yourimageview");

6

这个解决方案适用于任何版本的Android系统。它已经被证明可以在1.5和2.2版本上使用,而不仅仅是“适用于Android 2.0+”的解决方案。我是从一封电子邮件留言板收集页面中发现了这个解决方案,但无法找到原始链接。所有的功劳都归原作者。

在您的应用程序中,您可以通过调用以下方法来使用它:

Bitmap bm = getVideoFrame(VideoStringUri);

在其自己的函数中(在OnCreate之外等),您需要:
private Bitmap getVideoFrame(String uri) {
        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
        try {
            retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY);
            retriever.setDataSource(uri);
            return retriever.captureFrame();
        } catch (IllegalArgumentException ex) {
            ex.printStackTrace();
        } catch (RuntimeException ex) {
            ex.printStackTrace();
        } finally {
            try {
                retriever.release();
            } catch (RuntimeException ex) {
            }
        }
        return null;
    }

在您的src文件夹中,需要一个名为android/media的新子目录,其中包含了允许您使用此功能的类(从android源代码本身复制而来)。此部分不应更改、重命名或放置在其他任何位置。为使所有内容正常工作,MediaMetadataRetriever.java需要位于您源文件夹中的android.media下。
/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * 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.
 */

package android.media;

import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;

import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.graphics.Bitmap;
import android.net.Uri;

/**
 * MediaMetadataRetriever class provides a unified interface for retrieving
 * frame and meta data from an input media file. {@hide}
 */
public class MediaMetadataRetriever {
    static {
        System.loadLibrary("media_jni");
        native_init();
    }

    // The field below is accessed by native methods
    private int mNativeContext;

    public MediaMetadataRetriever() {
        native_setup();
    }

    /**
     * Call this method before setDataSource() so that the mode becomes
     * effective for subsequent operations. This method can be called only once
     * at the beginning if the intended mode of operation for a
     * MediaMetadataRetriever object remains the same for its whole lifetime,
     * and thus it is unnecessary to call this method each time setDataSource()
     * is called. If this is not never called (which is allowed), by default the
     * intended mode of operation is to both capture frame and retrieve meta
     * data (i.e., MODE_GET_METADATA_ONLY | MODE_CAPTURE_FRAME_ONLY). Often,
     * this may not be what one wants, since doing this has negative performance
     * impact on execution time of a call to setDataSource(), since both types
     * of operations may be time consuming.
     * 
     * @param mode
     *            The intended mode of operation. Can be any combination of
     *            MODE_GET_METADATA_ONLY and MODE_CAPTURE_FRAME_ONLY: 1.
     *            MODE_GET_METADATA_ONLY & MODE_CAPTURE_FRAME_ONLY: For neither
     *            frame capture nor meta data retrieval 2.
     *            MODE_GET_METADATA_ONLY: For meta data retrieval only 3.
     *            MODE_CAPTURE_FRAME_ONLY: For frame capture only 4.
     *            MODE_GET_METADATA_ONLY | MODE_CAPTURE_FRAME_ONLY: For both
     *            frame capture and meta data retrieval
     */
    public native void setMode(int mode);

    /**
     * @return the current mode of operation. A negative return value indicates
     *         some runtime error has occurred.
     */
    public native int getMode();

    /**
     * Sets the data source (file pathname) to use. Call this method before the
     * rest of the methods in this class. This method may be time-consuming.
     * 
     * @param path
     *            The path of the input media file.
     * @throws IllegalArgumentException
     *             If the path is invalid.
     */
    public native void setDataSource(String path)
            throws IllegalArgumentException;

    /**
     * Sets the data source (FileDescriptor) to use. It is the caller's
     * responsibility to close the file descriptor. It is safe to do so as soon
     * as this call returns. Call this method before the rest of the methods in
     * this class. This method may be time-consuming.
     * 
     * @param fd
     *            the FileDescriptor for the file you want to play
     * @param offset
     *            the offset into the file where the data to be played starts,
     *            in bytes. It must be non-negative
     * @param length
     *            the length in bytes of the data to be played. It must be
     *            non-negative.
     * @throws IllegalArgumentException
     *             if the arguments are invalid
     */
    public native void setDataSource(FileDescriptor fd, long offset, long length)
            throws IllegalArgumentException;

    /**
     * Sets the data source (FileDescriptor) to use. It is the caller's
     * responsibility to close the file descriptor. It is safe to do so as soon
     * as this call returns. Call this method before the rest of the methods in
     * this class. This method may be time-consuming.
     * 
     * @param fd
     *            the FileDescriptor for the file you want to play
     * @throws IllegalArgumentException
     *             if the FileDescriptor is invalid
     */
    public void setDataSource(FileDescriptor fd)
            throws IllegalArgumentException {
        // intentionally less than LONG_MAX
        setDataSource(fd, 0, 0x7ffffffffffffffL);
    }

    /**
     * Sets the data source as a content Uri. Call this method before the rest
     * of the methods in this class. This method may be time-consuming.
     * 
     * @param context
     *            the Context to use when resolving the Uri
     * @param uri
     *            the Content URI of the data you want to play
     * @throws IllegalArgumentException
     *             if the Uri is invalid
     * @throws SecurityException
     *             if the Uri cannot be used due to lack of permission.
     */
    public void setDataSource(Context context, Uri uri)
            throws IllegalArgumentException, SecurityException {
        if (uri == null) {
            throw new IllegalArgumentException();
        }

        String scheme = uri.getScheme();
        if (scheme == null || scheme.equals("file")) {
            setDataSource(uri.getPath());
            return;
        }

        AssetFileDescriptor fd = null;
        try {
            ContentResolver resolver = context.getContentResolver();
            try {
                fd = resolver.openAssetFileDescriptor(uri, "r");
            } catch (FileNotFoundException e) {
                throw new IllegalArgumentException();
            }
            if (fd == null) {
                throw new IllegalArgumentException();
            }
            FileDescriptor descriptor = fd.getFileDescriptor();
            if (!descriptor.valid()) {
                throw new IllegalArgumentException();
            }
            // Note: using getDeclaredLength so that our behavior is the same
            // as previous versions when the content provider is returning
            // a full file.
            if (fd.getDeclaredLength() < 0) {
                setDataSource(descriptor);
            } else {
                setDataSource(descriptor, fd.getStartOffset(),
                        fd.getDeclaredLength());
            }
            return;
        } catch (SecurityException ex) {
        } finally {
            try {
                if (fd != null) {
                    fd.close();
                }
            } catch (IOException ioEx) {
            }
        }
        setDataSource(uri.toString());
    }

    /**
     * Call this method after setDataSource(). This method retrieves the meta
     * data value associated with the keyCode.
     * 
     * The keyCode currently supported is listed below as METADATA_XXX
     * constants. With any other value, it returns a null pointer.
     * 
     * @param keyCode
     *            One of the constants listed below at the end of the class.
     * @return The meta data value associate with the given keyCode on success;
     *         null on failure.
     */
    public native String extractMetadata(int keyCode);

    /**
     * Call this method after setDataSource(). This method finds a
     * representative frame if successful and returns it as a bitmap. This is
     * useful for generating a thumbnail for an input media source.
     * 
     * @return A Bitmap containing a representative video frame, which can be
     *         null, if such a frame cannot be retrieved.
     */
    public native Bitmap captureFrame();

    /**
     * Call this method after setDataSource(). This method finds the optional
     * graphic or album art associated (embedded or external url linked) the
     * related data source.
     * 
     * @return null if no such graphic is found.
     */
    public native byte[] extractAlbumArt();

    /**
     * Call it when one is done with the object. This method releases the memory
     * allocated internally.
     */
    public native void release();

    private native void native_setup();

    private static native void native_init();

    private native final void native_finalize();

    @Override
    protected void finalize() throws Throwable {
        try {
            native_finalize();
        } finally {
            super.finalize();
        }
    }

    public static final int MODE_GET_METADATA_ONLY = 0x01;
    public static final int MODE_CAPTURE_FRAME_ONLY = 0x02;

    /*
     * Do not change these values without updating their counterparts in
     * include/media/mediametadataretriever.h!
     */
    public static final int METADATA_KEY_CD_TRACK_NUMBER = 0;
    public static final int METADATA_KEY_ALBUM = 1;
    public static final int METADATA_KEY_ARTIST = 2;
    public static final int METADATA_KEY_AUTHOR = 3;
    public static final int METADATA_KEY_COMPOSER = 4;
    public static final int METADATA_KEY_DATE = 5;
    public static final int METADATA_KEY_GENRE = 6;
    public static final int METADATA_KEY_TITLE = 7;
    public static final int METADATA_KEY_YEAR = 8;
    public static final int METADATA_KEY_DURATION = 9;
    public static final int METADATA_KEY_NUM_TRACKS = 10;
    public static final int METADATA_KEY_IS_DRM_CRIPPLED = 11;
    public static final int METADATA_KEY_CODEC = 12;
    public static final int METADATA_KEY_RATING = 13;
    public static final int METADATA_KEY_COMMENT = 14;
    public static final int METADATA_KEY_COPYRIGHT = 15;
    public static final int METADATA_KEY_BIT_RATE = 16;
    public static final int METADATA_KEY_FRAME_RATE = 17;
    public static final int METADATA_KEY_VIDEO_FORMAT = 18;
    public static final int METADATA_KEY_VIDEO_HEIGHT = 19;
    public static final int METADATA_KEY_VIDEO_WIDTH = 20;
    public static final int METADATA_KEY_WRITER = 21;
    public static final int METADATA_KEY_MIMETYPE = 22;
    public static final int METADATA_KEY_DISCNUMBER = 23;
    public static final int METADATA_KEY_ALBUMARTIST = 24;
    // Add more here...
}

1
我可以确认这个不起作用。我也需要这个功能。它不会起作用,因为它使用了常规应用程序没有权限使用的本地系统调用。 - Andy
MediaMetadataRetriever 支持 API 级别 10 及以上。 - Asahi
@LoungeKatt,它是否可以在多个时间点从同一视频中捕获多个图像? - android developer
@androiddeveloper 这只是用来获取通用单帧的。它不会在本地执行捕获。系统会选择帧。 - Abandoned Cart
@LoungeKatt 系统是如何实现的?是否可以覆盖它或使用您自定义的方式来获取特定时间的缩略图? - android developer
显示剩余4条评论

5

Android 1.5和1.6没有提供这样的缩略图功能,但2.0提供了,如官方发布说明所示:

媒体

  • MediaScanner现在在将所有图片插入到MediaStore时生成缩略图。
  • 新的缩略图API可以按需检索图像和视频缩略图。

4
我虽然回答这个问题有些晚了,但我希望能够帮助到其他遇到同样问题的候选人。
我使用了两种方法来为视频列表加载缩略图,第一种方法是:
    Bitmap bmThumbnail;
    bmThumbnail = ThumbnailUtils.createVideoThumbnail(FILE_PATH
                    + videoList.get(position),
            MediaStore.Video.Thumbnails.MINI_KIND);

    if (bmThumbnail != null) {
        Log.d("VideoAdapter","video thumbnail found");
        holder.imgVideo.setImageBitmap(bmThumbnail);
    } else {
        Log.d("VideoAdapter","video thumbnail not found");
    }

这个方案看起来不错,但存在一个问题,当我滚动视频列表时,由于处理量太大,它会有时候会冻结。

因此,在此之后,我找到了另一个完美的解决方案,使用了Glide库。

 Glide
            .with( mContext )
            .load( Uri.fromFile( new File( FILE_PATH+videoList.get(position) ) ) )
            .into( holder.imgVideo );

我推荐使用后一种方法来显示视频列表中的缩略图。谢谢。

嗨@AllanRibas,很高兴听到这个解决方案对你有用。 - Mudassir Khan

1

我知道这是一个旧问题,并且已经有了一个被接受的答案,但是我想发表我的答案以便于某些人搜索到:

当我只想使用Uri时,我这样做:

val mmr = MediaMetadataRetriever()
mmr.setDataSource(videoUri)
val thummbnailBitmap = mmr.frameAtTime
imageView.setImageBitmap(thummbnailBitmap)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接