• Kotlin拿Android本地视频缩略图


            本文主要讨论如下三个问题:

    1. 如何拿到本地视频?
    2. 怎么拿视频缩略图?
    3. 缩略图如何压缩?

    1 如何拿到本地视频?

    1.1 定义数据结构

            先定义媒体信息数据结构MediaInfo,以及视频信息数据结构VideoInfo。

    1. open class MediaInfo(
    2. var size: Long = 0L, // 大小 单位B
    3. var width: Float = 0f, // 宽
    4. var height: Float = 0f, // 高
    5. var localPath: String = "", // 系统绝对路径
    6. var localPathUri: String = "", // 媒体文件Uri
    7. var fileName: String = "", // 文件名
    8. var mimeType: String = "", // 媒体类型
    9. var mediaId: String = "", // 媒体ID
    10. var lastModified: Long = 0L, // 最后更改时间
    11. /*----业务字段----*/
    12. var seq: Int = 0, // 事件序列号
    13. /*----业务字段----*/
    14. )
    1. /**
    2. * 码率(比特率),单位为 bps,比特率越高,传送的数据速度越快。在压缩视频时指定码率,则可确定压缩后的视频大小。
    3. * 视频大小(byte) = (duration(ms) / 1000) * (biteRate(bit/s) / 8)
    4. */
    5. data class VideoInfo(
    6. var firstFrame: Bitmap? = null, // 视频第一帧图
    7. var duration: Long = 0L, // 视频长度 ms
    8. var biteRate: Long = 0L, // 视频码率 bps
    9. /* --------not necessary, maybe not value---- */
    10. var addTime: Long = 0L, // 视频添加时间
    11. var videoRotation: Int = 0, // 视频方向
    12. /* --------not necessary, maybe not value---- */
    13. ) : MediaInfo()

    1.2 ContentResolver查询系统中视频

            这里直接去Android媒体库找到所有视频即可,代码使用了Kotlin协程,参考如下。

    1. import android.provider.MediaStore.Video.Media
    2. /**
    3. * 获取系统所有视频文件
    4. */
    5. suspend fun getSystemVideos(contentResolver: ContentResolver): MutableList =
    6. withContext(Dispatchers.IO) {
    7. val videoList: MutableList = mutableListOf()
    8. var cursor: Cursor? = null
    9. try {
    10. val queryArray = arrayOf(
    11. Media._ID,
    12. Media.SIZE, // 视频大小
    13. Media.WIDTH, // 视频宽
    14. Media.HEIGHT, // 视频高
    15. Media.DATA, // 视频绝对路径
    16. Media.DISPLAY_NAME, // 视频文件名
    17. Media.MIME_TYPE, // 媒体类型
    18. Media.DURATION, // 视频长度
    19. Media.DATE_ADDED, // 视频添加时间
    20. Media.DATE_MODIFIED, // 视频最后更改时间
    21. ).toMutableList()
    22. if (SDK_INT >= Build.VERSION_CODES.R) queryArray.add(Media.BITRATE)// 视频码率
    23. cursor = contentResolver.query(
    24. Media.EXTERNAL_CONTENT_URI, queryArray.toTypedArray(),
    25. null, null, Media.DATE_ADDED + " DESC", null
    26. )
    27. cursor?.moveToFirst()
    28. if (cursor == null || cursor.isAfterLast) return@withContext videoList
    29. while (!cursor.isAfterLast) {
    30. getVideoInfo(cursor).run { if (duration > 0 && size > 0) videoList.add(this) }
    31. cursor.moveToNext()
    32. }
    33. } catch (e: Exception) {
    34. } finally {
    35. cursor?.close()
    36. }
    37. videoList
    38. }

    注:getVideoInfo获取视频文件信息可参考附件1。

    2 怎么拿视频缩略图?

    2.1 MediaMetadataRetriever

            可以通过视频系统路径,直接使用getFrameAtTime方法拿到第一帧作为缩略图。

    1. val retriever = MediaMetadataRetriever()
    2. retriever.setDataSource("filePath")
    3. val bitmap: Bitmap? = retriever.frameAtTime

            也可以使用getScaledFrameAtTime,指定缩略图Bitmap尺寸512*512。

    retriever.getScaledFrameAtTime(-1, OPTION_CLOSEST_SYNC,512512)

    2.2 ThumbnailUtils

    • 第二个参数kind可取如下值:MICRO_KIND(3)不清晰;FULL_SCREEN_KIND(2)清晰;MINI_KIND(1)较清晰
    • MICRO_KIND:96*96的缩略图
    • MINI_KIND:512*384的缩略图
    • FULL_SCREEN_KIND:完整大小的图片
    ThumbnailUtils.createVideoThumbnail("filePath",MediaStore.Video.Thumbnails.FULL_SCREEN_KIND)

            ThumbnailUtils.createVideoThumbnail其实也是使用的MediaMetadataRetriever,如下源码截图:

    2.3 Glide

    1. Glide.with(mContext)
    2. .load(Uri.fromFile(File("filePath")))
    3. .into(binding.icon)

            项目中一般会使用图片加载框架如Glide,它内部也是支持加载视频作为图片的,亦是使用的MediaMetadataRetriever。 

    2.4 Android媒体库

            直接从Android媒体库中查询缩略图,但是调试时发现未找到(视频为手机本地录制mp4格式) ,下面是查询视频缩略图的方法:

    1. /**
    2. * 获取视频缩略图:从媒体库中查询
    3. */
    4. @Deprecated("暂时不可用", replaceWith = ReplaceWith("getVideoThumbnail"))
    5. suspend fun getVideoThumbnailDefault(
    6. contentResolver: ContentResolver,
    7. cursor: Cursor
    8. ): Bitmap? = withContext(Dispatchers.IO) {
    9. // // 方式一:能拿到路径"content://media/external/images/media/126",但没有具体图片
    10. // BitmapFactory.decodeFile(
    11. // ContentUris.withAppendedId(
    12. // MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
    13. // cursor.getLong(cursor.getColumnIndex(Media._ID))
    14. // ).toString()
    15. // )
    16. //
    17. // // 方式二:拿到的是video视频路径"/storage/emulated/0/DCIM/Camera/20221122_200648.mp4"
    18. // BitmapFactory.decodeFile(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)))
    19. // 方式三:开发调试时发现,thumbCursor.moveToFirst()为false,也就是说cursor为空,视频缩略图路径未找到
    20. contentResolver.query(
    21. Thumbnails.EXTERNAL_CONTENT_URI,
    22. arrayOf(Thumbnails.DATA, Thumbnails.VIDEO_ID),
    23. Thumbnails.VIDEO_ID + "=" + cursor.getInt(cursor.getColumnIndex(Media._ID)),
    24. null,
    25. null
    26. )?.let { thumbCursor ->
    27. if (thumbCursor.moveToFirst()) {
    28. // 获取视频缩略图路径,并转为bitmap
    29. // MediaStore.Video.Thumbnails.DATA: 视频缩略图的文件路径
    30. BitmapFactory.decodeFile(thumbCursor.getString(thumbCursor.getColumnIndex(Thumbnails.DATA)))
    31. } else null
    32. }
    33. }

    2.5 Android 10兼容

             Android 10对文件访问更加严格,不能直接操作视频绝对路径,如要操作则须加上在AndroidManifest.xml的application标签下声明requestLegacyExternalStorage=true,才可以访问非沙盒路径下的数据。

            但Google Play发布的应用,不允许带有requestLegacyExternalStorage,所以对于Android 10不能采用操作视频的绝对路径,改为操作视频的Uri即可。

           1) 针对Android 10可以采用更高版本API获取视频第一帧,如下:

    1. /**
    2. * 获取视频缩略图:兼容Android 10
    3. */
    4. suspend fun getVideoThumbnail(context: Context, uriString: String): Bitmap? =
    5. withContext(Dispatchers.IO) {
    6. var bitmap: Bitmap? = null
    7. if (SDK_INT >= Build.VERSION_CODES.Q) {
    8. try {
    9. bitmap = context.contentResolver.loadThumbnail(
    10. Uri.parse(uriString), Size(
    11. THUMBNAIL_DEFAULT_COMPRESS_VALUE.toInt(),
    12. THUMBNAIL_DEFAULT_COMPRESS_VALUE.toInt()
    13. ), null
    14. )
    15. } catch (e: Exception) {
    16. }
    17. } else getVideoThumbnail(uriString)
    18. bitmap
    19. }

            2)针对Android 10,也可以使用视频Uri去获取第一帧,如下:

    1. suspend fun getVideoThumbnail(context: Context, uriString: String): Bitmap? =
    2. withContext(Dispatchers.IO) {
    3. var bitmap: Bitmap? = null
    4. val retriever = MediaMetadataRetriever()
    5. try {
    6. retriever.setDataSource(context, Uri.parse(uriString))
    7. // OPTION_CLOSEST_SYNC:在给定的时间,检索最近一个同步与数据源相关联的的帧(关键帧)
    8. // OPTION_CLOSEST:表示获取离该时间戳最近帧(I帧或P帧)
    9. bitmap = if (SDK_INT >= Build.VERSION_CODES.O_MR1) {
    10. retriever.getScaledFrameAtTime(
    11. VIDEO_FIRST_FRAME_TIME_US, OPTION_CLOSEST_SYNC,
    12. THUMBNAIL_DEFAULT_COMPRESS_VALUE.toInt(),
    13. THUMBNAIL_DEFAULT_COMPRESS_VALUE.toInt()
    14. )
    15. } else {
    16. retriever.getFrameAtTime(VIDEO_FIRST_FRAME_TIME_US)
    17. ?.let { compressVideoThumbnail(it) }
    18. }
    19. } catch (e: Exception) {
    20. } finally {
    21. try {
    22. retriever.release()
    23. } catch (e: Exception) {
    24. }
    25. }
    26. bitmap
    27. }

            3)但,Android 10,不能使用视频绝对路径去获取第一帧,会存在权限不足问题,如下:

    1. /**
    2. * 获取视频缩略图:从路径中拿取第一帧,Android 10不可用
    3. */
    4. suspend fun getVideoThumbnail(filePath: String): Bitmap? = withContext(Dispatchers.IO) {
    5. var bitmap: Bitmap? = null
    6. val retriever = MediaMetadataRetriever()
    7. try {
    8. retriever.setDataSource(filePath)
    9. // OPTION_CLOSEST_SYNC:在给定的时间,检索最近一个同步与数据源相关联的的帧(关键帧)
    10. // OPTION_CLOSEST:表示获取离该时间戳最近帧(I帧或P帧)
    11. bitmap = if (SDK_INT >= Build.VERSION_CODES.O_MR1) {
    12. retriever.getScaledFrameAtTime(
    13. VIDEO_FIRST_FRAME_TIME_US, OPTION_CLOSEST_SYNC,
    14. THUMBNAIL_DEFAULT_COMPRESS_VALUE.toInt(),
    15. THUMBNAIL_DEFAULT_COMPRESS_VALUE.toInt()
    16. )
    17. } else {
    18. retriever.getFrameAtTime(VIDEO_FIRST_FRAME_TIME_US)
    19. ?.let { compressVideoThumbnail(it) }
    20. }
    21. } catch (e: Exception) {
    22. } finally {
    23. try {
    24. retriever.release()
    25. } catch (e: Exception) {
    26. }
    27. }
    28. bitmap
    29. }

             综上,使用第二种方式是一种比较统一的方式,不存在版本兼容问题。

    参考:

    https://blog.csdn.net/g984160547/article/details/115749804

    android 10(Q)上传图片及视频,访问媒体文件适配_李小白的生活的博客-CSDN博客_android 10 上传文件

    2.6 小结

    • Android媒体库可以获取缩略图但不稳定;

    • 第三方图片库如Glide以及ThumbnailUtils都是采用MediaMetadataRetriever。

              所以根本上来说,目前有两种方式拿到视频缩略图,Android媒体库或MediaMetadataRetriever,一般来说采用MediaMetadataRetriever方式,参考代码如下:

    1. const val VIDEO_FIRST_FRAME_TIME_US = 1000L
    2. /**
    3. * 视频缩略图默认压缩尺寸
    4. */
    5. const val THUMBNAIL_DEFAULT_COMPRESS_VALUE = 1024f
    6. /**
    7. * 获取视频缩略图:通过绝对路径抓取第一帧
    8. * @param filePath 视频在系统绝对路径。如:/storage/emulated/0/Pictures/Screenshots/SVID_20221109_181203_1.mp4
    9. */
    10. suspend fun getVideoThumbnail(filePath: String): Bitmap? = withContext(Dispatchers.IO) {
    11. var bitmap: Bitmap? = null
    12. val retriever = MediaMetadataRetriever()
    13. try {
    14. retriever.setDataSource(filePath)
    15. // OPTION_CLOSEST_SYNC:在给定的时间,检索最近一个同步与数据源相关联的的帧(关键帧)
    16. // OPTION_CLOSEST:表示获取离该时间戳最近帧(I帧或P帧)
    17. bitmap = if (SDK_INT >= Build.VERSION_CODES.O_MR1) {
    18. retriever.getScaledFrameAtTime(
    19. VIDEO_FIRST_FRAME_TIME_US, OPTION_CLOSEST_SYNC,
    20. THUMBNAIL_DEFAULT_COMPRESS_VALUE.toInt(),
    21. THUMBNAIL_DEFAULT_COMPRESS_VALUE.toInt()
    22. )
    23. } else {
    24. retriever.getFrameAtTime(VIDEO_FIRST_FRAME_TIME_US)
    25. ?.let { compressVideoThumbnail(it) }
    26. }
    27. } catch (e: Exception) {
    28. } finally {
    29. try {
    30. retriever.release()
    31. } catch (e: Exception) {
    32. }
    33. }
    34. bitmap
    35. }
    36. /**
    37. * 获取视频缩略图:通过Uri抓取第一帧
    38. * @param videoUriString 视频在媒体库中Uri。如:content://media/external/video/media/11378
    39. */
    40. suspend fun getVideoThumbnail(context: Context, videoUriString: String): Bitmap? =
    41. withContext(Dispatchers.IO) {
    42. var bitmap: Bitmap? = null
    43. val retriever = MediaMetadataRetriever()
    44. try {
    45. retriever.setDataSource(context, Uri.parse(videoUriString))
    46. // OPTION_CLOSEST_SYNC:在给定的时间,检索最近一个同步与数据源相关联的的帧(关键帧)
    47. // OPTION_CLOSEST:表示获取离该时间戳最近帧(I帧或P帧)
    48. bitmap = if (SDK_INT >= Build.VERSION_CODES.O_MR1) {
    49. retriever.getScaledFrameAtTime(
    50. VIDEO_FIRST_FRAME_TIME_US, OPTION_CLOSEST_SYNC,
    51. THUMBNAIL_DEFAULT_COMPRESS_VALUE.toInt(),
    52. THUMBNAIL_DEFAULT_COMPRESS_VALUE.toInt()
    53. )
    54. } else {
    55. retriever.getFrameAtTime(VIDEO_FIRST_FRAME_TIME_US)
    56. ?.let { compressVideoThumbnail(it) }
    57. }
    58. } catch (e: Exception) {
    59. } finally {
    60. try {
    61. retriever.release()
    62. } catch (e: Exception) {
    63. }
    64. }
    65. bitmap
    66. }

            为避免应用OOM,需要对缩略图大小进行压缩compressVideoThumbnail,下面看看Bitmap压缩方式。

    3 缩略图如何压缩?        

            宽高压缩、缩放法压缩可针对Bitmap操作,而采样率压缩和质量压缩针对于File、Resource操作,下面可主要看看宽高压缩、缩放法压缩。

    3.1 宽高压缩

    1. /**
    2. * 视频缩略图默认压缩尺寸
    3. */
    4. const val THUMBNAIL_DEFAULT_COMPRESS_VALUE = 512f
    5. /**
    6. * 压缩视频缩略图
    7. * @param bitmap 视频缩略图
    8. */
    9. fun compressVideoThumbnail(bitmap: Bitmap): Bitmap? {
    10. val width: Int = bitmap.width
    11. val height: Int = bitmap.height
    12. val max: Int = Math.max(width, height)
    13. if (max > THUMBNAIL_DEFAULT_COMPRESS_VALUE) {
    14. val scale: Float = THUMBNAIL_DEFAULT_COMPRESS_VALUE / max
    15. val w = (scale * width).roundToInt()
    16. val h = (scale * height).roundToInt()
    17. return compressVideoThumbnail(bitmap, w, h)
    18. }
    19. return bitmap
    20. }
    21. /**
    22. * 压缩视频缩略图:宽高压缩
    23. * 注:如果用户期望的长度和宽度和原图长度宽度相差太多的话,图片会很不清晰。
    24. * @param bitmap 视频缩略图
    25. */
    26. fun compressVideoThumbnail(bitmap: Bitmap, width: Int, height: Int): Bitmap? {
    27. return Bitmap.createScaledBitmap(bitmap, width, height, true)
    28. }

    3.2 缩放法压缩

    1. /**
    2. * 视频缩略图默认压缩比例
    3. */
    4. private const val THUMBNAIL_DEFAULT_SCALE_VALUE = 0.5f
    5. /**
    6. * 压缩视频缩略图:缩放法压缩
    7. * 注:长度和宽度没有变,内存缩小4倍(宽高各缩小一半)
    8. */
    9. fun compressVideoThumbnailMatrix(bitmap: Bitmap): Bitmap? {
    10. val matrix = Matrix()
    11. matrix.setScale(THUMBNAIL_DEFAULT_SCALE_VALUE, THUMBNAIL_DEFAULT_SCALE_VALUE)
    12. return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
    13. }

            其实,宽高压缩调用createScaledBitmap方法,最终也是走的缩放法压缩Matrix.setScale,如下图源码,通过宽高计算出scale值,进而进行压缩:

    3.3 采样率压缩

    1. /**
    2. * 压缩视频缩略图:采样率压缩
    3. * @param filePath 视频缩略图路径
    4. */
    5. fun compressVideoThumbnailSample(filePath: String, width: Int, height: Int): Bitmap? {
    6. return BitmapFactory.Options().run {
    7. // inJustDecodeBounds 设置为 true后,BitmapFactory.decodeFile 不生成Bitmap对象,而仅仅是读取该图片的尺寸和类型信息。
    8. inJustDecodeBounds = true
    9. BitmapFactory.decodeFile(filePath, this)
    10. inSampleSize = calculateInSampleSize(this, width, height)
    11. inJustDecodeBounds = false
    12. BitmapFactory.decodeFile(filePath, this)
    13. }
    14. }

            计算采样率,可有以下2种方式: 

    1. /**
    2. * 计算采样率:值为2的幂。例如, 一个分辨率为2048x1536的图片,如果设置 inSampleSize 为4,那么会产出一个大约512x384大小的Bitmap。
    3. * @param options
    4. * @param reqWidth 想要压缩到的宽度
    5. * @param reqHeight 想要压缩到的高度
    6. * @return
    7. */
    8. fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
    9. var inSampleSize = 1
    10. if (options.outHeight > reqHeight || options.outWidth > reqWidth) {
    11. val halfHeight: Int = options.outHeight / 2
    12. val halfWidth: Int = options.outWidth / 2
    13. while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
    14. inSampleSize *= 2
    15. }
    16. }
    17. return inSampleSize
    18. }
    19. /**
    20. * 计算采样率:值为整数的采样率。
    21. */
    22. fun calculateInSampleSizeFixed(options: BitmapFactory.Options, width: Int, height: Int): Int {
    23. val hRatio = ceil(options.outHeight.div(height.toDouble())) // 大于1:图片高度>手机屏幕高度
    24. val wRatio = ceil(options.outWidth.div(width.toDouble())) // 大于1:图片宽度>手机屏幕宽度
    25. return if (hRatio > wRatio) hRatio.toInt() else wRatio.toInt()
    26. }

            其实,这里的采样率压缩称之为采用邻近点插值算法邻近采样,即:如果采样率设置为2,意味着直接选择其中的一个像素作为生成像素,另一个像素直接抛弃。

            另外,宽高压缩或缩放法压缩称为双线性采样,且:它不像邻近点插值算法一样,直接粗暴的选择一个像素,而是参考了源像素相应位置周围 2x2 个点的值,根据相对位置取对应的权重,经过计算之后得到目标图像。

            双线性采样对比邻近采样的优势在于:

    • 它的系数可以是小数,而不一定是整数,在某些压缩限制下,效果尤为明显;
    • 处理文字比较多的图片在展示效果上的差别,双线性采样效果要更好。

    3.4 质量压缩

            因为,质量压缩会让图片在磁盘中的大小(File文件的大小)减小,但是图片的像素数不会改变,加载压缩后的图片,占据的内存不会减少,所以我们这里内存中视频缩略图的压缩,就先不展开讨论此种方式。具体实现可参考此文 。

    注:

    • 质量压缩依靠Bitmap.compress(CompressFormat format, int quality, OutputStream stream)实现;
    • 质量压缩可以减少文件大小,典型使用场景为:图片的上传。

    3.5 小结  

            图片压缩方法根据实际情况去选择即可,比如微信图片压缩算法,大致就是按照以下步骤进行的:

    • 第一步进行采样率压缩;
    • 第二步进行宽高压缩(微信对原图和缩略图限制了最大长宽或者最小长宽);
    • 第三步就是对图片的质量进行压缩(一般75或者70);
    • 第四部就是采用webP的格式。

    4 附件

    【附1:获取视频文件信息】

    1. /**
    2. * 获取视频文件信息
    3. * 注:(1)暂未包括videoRotation;(2)biteRate通过文件大小和视频时长计算
    4. */
    5. suspend fun getVideoInfo(cursor: Cursor): VideoInfo = withContext(Dispatchers.IO) {
    6. val videoInfo = VideoInfo()
    7. videoInfo.mediaId = cursor.getString(cursor.getColumnIndex(Media._ID))
    8. videoInfo.size = cursor.getLong(cursor.getColumnIndex(Media.SIZE))
    9. videoInfo.width = cursor.getFloat(cursor.getColumnIndex(Media.WIDTH))
    10. videoInfo.height = cursor.getFloat(cursor.getColumnIndex(Media.HEIGHT))
    11. videoInfo.localPath = cursor.getString(cursor.getColumnIndex(Media.DATA))
    12. videoInfo.localPathUri = getVideoPathUri(videoInfo.mediaId).toString()
    13. videoInfo.fileName = cursor.getString(cursor.getColumnIndex(Media.DISPLAY_NAME))
    14. videoInfo.mimeType = cursor.getString(cursor.getColumnIndex(Media.MIME_TYPE))
    15. // 不能在这获取第一帧,太耗时,改为使用的地方去获取第一帧。
    16. // videoInfo.firstFrame = getVideoThumbnail(cursor.getString(cursor.getColumnIndex(Media.DATA)))
    17. videoInfo.duration = cursor.getLong(cursor.getColumnIndex(Media.DURATION))
    18. videoInfo.biteRate =
    19. if (SDK_INT >= Build.VERSION_CODES.R) cursor.getLong(cursor.getColumnIndex(Media.BITRATE))
    20. else ((8 * videoInfo.size * 1024) / (videoInfo.duration / 1000f)).toLong()
    21. videoInfo.addTime = cursor.getLong(cursor.getColumnIndex(Media.DATE_ADDED))
    22. videoInfo.lastModified = cursor.getLong(cursor.getColumnIndex(Media.DATE_MODIFIED))
    23. videoInfo
    24. }

    【附2:通过视频路径直接获取文件信息】

    1. /**
    2. * 获取视频文件信息
    3. * 注:(1)暂未包括lastModified、addTime;(2)mediaId以filePath代替
    4. *
    5. * @param path 视频文件的路径
    6. * @return VideoInfo 视频文件信息
    7. */
    8. suspend fun getVideoInfo(path: String?): VideoInfo = withContext(Dispatchers.IO) {
    9. val videoInfo = VideoInfo()
    10. if (!path.isNullOrEmpty()) {
    11. val media = MediaMetadataRetriever()
    12. try {
    13. media.setDataSource(path)
    14. videoInfo.size =
    15. File(path).let { if (FileUtils.isFileExists(it)) it.length() else 0 }
    16. videoInfo.width = media.extractMetadata(METADATA_KEY_VIDEO_WIDTH)?.toFloat() ?: 0f
    17. videoInfo.height = media.extractMetadata(METADATA_KEY_VIDEO_HEIGHT)?.toFloat() ?: 0f
    18. videoInfo.localPath = path
    19. videoInfo.localPathUri =
    20. getVideoPathUri(ApplicationUtils.getApplication(), path).toString()
    21. videoInfo.fileName = path.split(File.separator).let {
    22. if (it.isNotEmpty()) it[it.size - 1] else ""
    23. }
    24. videoInfo.mimeType = media.extractMetadata(METADATA_KEY_MIMETYPE) ?: ""
    25. videoInfo.firstFrame =
    26. media.getFrameAtTime(VIDEO_FIRST_FRAME_TIME_US)
    27. ?.let { compressVideoThumbnail(it) }
    28. videoInfo.duration = media.extractMetadata(METADATA_KEY_DURATION)?.toLong() ?: 0
    29. videoInfo.biteRate = media.extractMetadata(METADATA_KEY_BITRATE)?.toLong() ?: 0
    30. videoInfo.videoRotation =
    31. media.extractMetadata(METADATA_KEY_VIDEO_ROTATION)?.toInt() ?: 0
    32. videoInfo.mediaId = path
    33. } catch (e: Exception) {
    34. } finally {
    35. media.release()
    36. }
    37. }
    38. videoInfo
    39. }

    【附3:获取视频Uri】

    1. private const val URI_VIDEO_PRE = "content://media/external/video/media"
    2. /**
    3. * 通过视频资源ID,直接获取视频Uri
    4. * @param mediaId 视频资源ID
    5. */
    6. fun getVideoPathUri(mediaId: String): Uri =
    7. Uri.withAppendedPath(Uri.parse(URI_VIDEO_PRE), mediaId)
    8. /**
    9. * 通过视频资源本地路径,获取视频Uri
    10. * @param path 视频资源本地路径
    11. */
    12. fun getVideoPathUri(context: Context, path: String): Uri? {
    13. var uri: Uri? = null
    14. var cursor: Cursor? = null
    15. try {
    16. cursor = context.contentResolver.query(
    17. Media.EXTERNAL_CONTENT_URI, arrayOf(Media._ID),
    18. Media.DATA + "=? ", arrayOf(path), null
    19. )
    20. uri = if (cursor != null && cursor.moveToFirst()) {
    21. Uri.withAppendedPath(
    22. Uri.parse(URI_VIDEO_PRE),
    23. "" + cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID))
    24. )
    25. } else {
    26. if (File(path).exists()) {
    27. val values = ContentValues()
    28. values.put(Media.DATA, path)
    29. context.contentResolver.insert(Media.EXTERNAL_CONTENT_URI, values)
    30. } else {
    31. null
    32. }
    33. }
    34. } catch (e: Exception) {
    35. } finally {
    36. cursor?.close()
    37. }
    38. return uri
    39. }

  • 相关阅读:
    Bearly:基于人工智能的AI写作文章生成工具
    拓扑排序和最短路径结合的一个问题
    Pytorch中的torch.device该如何迁移到MindSpore中
    pytorch单精度、半精度、混合精度、单卡、多卡(DP / DDP)、FSDP、DeepSpeed模型训练
    SCI简介和写作顺序
    2022年全国最新消防设施操作员(初级消防设施操作员)模拟题及答案
    jQuery 基本操作
    数字签名与数字信封
    什么是跨域,后端工程师如何处理跨域
    【MATLAB】求解含有三角函数的方程
  • 原文地址:https://blog.csdn.net/Agg_bin/article/details/128026082