如何扫描出Android系统媒体库中视频文件

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何扫描出Android系统媒体库中视频文件相关的知识,希望对你有一定的参考价值。

参考技术A android系统启动时会去扫描系统文件,并将系统支持的视频文件(mp4,3gp,wmv)扫描到媒体库(MediaStore)中,下面代码演示如何获得这些文件的信息:
public static List<VideoInfo> sysVideoList = null;// 视频信息集合
sysVideoList = new ArrayList<VideoInfo>();
setVideoList();

private void setVideoList()
// MediaStore.Video.Thumbnails.DATA:视频缩略图的文件路径
String[] thumbColumns = MediaStore.Video.Thumbnails.DATA,
MediaStore.Video.Thumbnails.VIDEO_ID ;

// MediaStore.Video.Media.DATA:视频文件路径;
// MediaStore.Video.Media.DISPLAY_NAME : 视频文件名,如 testVideo.mp4
// MediaStore.Video.Media.TITLE: 视频标题 : testVideo
String[] mediaColumns = MediaStore.Video.Media._ID,
MediaStore.Video.Media.DATA, MediaStore.Video.Media.TITLE,
MediaStore.Video.Media.MIME_TYPE,
MediaStore.Video.Media.DISPLAY_NAME ;

cursor = managedQuery(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
mediaColumns, null, null, null);

if(cursor==null)
Toast.makeText(SystemVideoChooseActivity.this, "没有找到可播放视频文件", 1).show();
return;

if (cursor.moveToFirst())
do
VideoInfo info = new VideoInfo();
int id = cursor.getInt(cursor
.getColumnIndex(MediaStore.Video.Media._ID));
Cursor thumbCursor = managedQuery(
MediaStore.Video.Thumbnails.EXTERNAL_CONTENT_URI,
thumbColumns, MediaStore.Video.Thumbnails.VIDEO_ID
+ "=" + id, null, null);
if (thumbCursor.moveToFirst())
info.setThumbPath(thumbCursor.getString(thumbCursor
.getColumnIndex(MediaStore.Video.Thumbnails.DATA)));

info.setPath(cursor.getString(cursor
.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)));
info.setTitle(cursor.getString(cursor
.getColumnIndexOrThrow(MediaStore.Video.Media.TITLE)));

info.setDisplayName(cursor.getString(cursor
.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME)));
LogUtil.log(TAG, "DisplayName:"+info.getDisplayName());
info.setMimeType(cursor
.getString(cursor
.getColumnIndexOrThrow(MediaStore.Video.Media.MIME_TYPE)));

sysVideoList.add(info);
while (cursor.moveToNext());



有一点需要注意的是:系统的媒体库并不会在我们添加视频文件后自动更新,我们如何去手动扫描媒体库,或者重启系统才能从媒体库中得到更新的视频文件:

sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"
+ Environment.getExternalStorageDirectory())));

用于 Android Q 上的辅助存储的媒体扫描仪

【中文标题】用于 Android Q 上的辅助存储的媒体扫描仪【英文标题】:Media scanner for secondary storage on Android Q 【发布时间】:2019-09-27 10:51:05 【问题描述】:

随着较新的 ​​Android Q,许多事情发生了变化,尤其是在范围存储和逐渐弃用 file:/// URI 的情况下。问题是缺乏关于如何在 Android Q 设备上正确处理媒体文件的文档。

我有一个媒体文件(音频)管理应用程序,但我找不到可靠的方法告诉操作系统我对文件执行了更改,以便它可以更新其 MediaStore 记录。

选项 #1:MediaScannerService

MediaScannerConnection.scanFile(context, new String[] filePath , new String[]"audio/*", new MediaScannerConnection.OnScanCompletedListener() 
    @Override
    public void onScanCompleted(String s, Uri uri) 

    
);
与来自主存储的 file:// URI 一起使用 不适用于来自辅助存储(例如可移动存储)的 file:// URI 不适用于任何 content:// URI

选项 #2:广播

context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
根本不工作 即将弃用

选项 #3:手动插入 MediaStore

AudioFileContentValues 是来自MediaStore.Audio.AudioColumns 的一些列值。

基于file:// URI 的旧方法:

Uri uri = MediaStore.Audio.Media.getContentUriForPath(file_path);
newUri = context.getContentResolver().insert(uri, AudioFileContentValues);
MediaStore.Audio.Media.getContentUriForPath 已弃用 还是不行

基于我可以从documentation 汇总的新方法:

Uri collection = MediaStore.Audio.Media.getContentUri(correctVolume);
newUri = context.getContentResolver().insert(collection, AudioFileContentValues);

其中correctVolume 将是来自主存储的external,而类似于0000-0000 用于辅助存储,具体取决于文件所在的位置。

插入会返回一个内容 URI,例如 content://media/external/audio/media/125,但不会在 MediaStore 中为位于主存储中的文件保留任何记录 插入失败,没有返回 URI 并且 MediaStore 中没有记录

这些或多或少是以前 Android 版本中可用的所有方法,但现在它们都不允许我通知系统我更改了一些音频文件元数据并让 Android 更新 MediaStore 记录。事件虽然选项 #1 部分有效,但这绝不可能是一个有价值的解决方案,因为它显然不支持内容 URI。

是否有任何可靠的方法可以在 Android Q 上触发媒体扫描,尽管文件位于何处?根据 Google 的说法,我们甚至不应该关心文件位置,因为我们很快将只使用内容 URI。 MediaStore 在我看来一直有点令人沮丧,但现在情况更糟了。

【问题讨论】:

“我还没有找到一种可靠的方法告诉操作系统我对文件进行了更改,以便它可以更新其 MediaStore 记录”——如果MediaStore 已经知道内容,我认为您需要在选项#3 中使用update() 而不是insert()insert() 如果您正在创建新内容。 @CommonsWare 在测试中,我正在处理在 MediaStore 中还没有自己的行的音频文件,因为它们一次都没有被扫描过。无论如何,我感谢您的博客以及您如何让社区了解即将发生的 Android 行为变化。 【参考方案1】:

我目前也在为此苦苦挣扎。

我认为,一旦您在 Android Q 上,您想要做的事情就无法再做,因为您无法访问 Q 上的音乐目录。您只能在您创建的目录中创建和访问文件。您没有创建音乐目录。

现在对 Media 的每一次更改都必须发生在 MediaStore 中。所以你事先插入你的音乐文件,然后从 MediaStore 获取一个输出流来写入它。对 Q 的所有更改都应该在 MediaStore 上完成,因此您通知 MediaStore 更改甚至不能再发生,因为您永远不会直接访问文件。

这有一个巨大的问题,因为在旧版本的 Android 中不存在 MediaStore 中使这成为可能的所有新事物。因此,我目前确实认为您将需要两次实施所有内容,遗憾的是。至少如果您想积极影响音乐的保存位置。

这两个 MediaStore 列是 Q 中的新列,在 Q 之前不存在,你可能需要在 Q 中使用

MediaStore.Audio.Media.RELATIVE_PATH 可以影响保存路径。所以我把“Music/MyAppName/MyLibraryName”放在那里,最终将“song.mp3”保存到“Music/MyAppName/MyLibraryName/song.mp3”中 MediaStore.Audio.Media.IS_PENDING 这应该在歌曲仍在编写时设置为 1,然后您可以将其更新为 0。

我现在也开始使用 if 检查 Android 版本来实现两次。这很烦人。我不想这样做。但似乎这是唯一的方法。

我将在此处添加一些代码,说明我如何在 Android.Q 及以下版本中插入音乐。这并不完美。我必须为 Q 指定 MIME 类型,因为 flac 现在会以某种方式变为 .flac.mp3,因为它似乎不太明白。

所以,无论如何,这是我已经更新的部分,可以与 Q 一起使用,之前它会从我 NAS 上的音乐播放器下载音乐文件。该应用程序是用 kotlin 编写的,不确定这对您来说是否有问题。

override fun execute(library : Library, remoteApi: RemoteApi, ctx: Context) : Boolean 

    var success = false

    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) 
        val values = ContentValues().apply 
            put(MediaStore.Audio.Media.RELATIVE_PATH, library.rootFolderRelativePath)
            put(MediaStore.Audio.Media.DISPLAY_NAME, remoteLibraryEntry.getFilename())
            put(MediaStore.Audio.Media.IS_PENDING, 1)
        

        val collection = MediaStore.Audio.Media
                .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)

        val uri = ctx.contentResolver.insert(collection, values)

        ctx.contentResolver.openOutputStream(uri!!).use 
            success = remoteApi.downloadMusic(remoteLibraryEntry, it!!)
        

        if(success) 
            values.clear()
            val songId = JDrop.mediaHelper.getSongId(uri)
            JDrop.db.music.insert(Music(mediaStoreId = songId, remoteId = remoteLibraryEntry.remoteId, libraryId = library.id))
            values.put(MediaStore.Audio.Media.IS_PENDING, 0)
            ctx.contentResolver.update(uri, values, null, null)
         else 
            ctx.contentResolver.delete(uri, null, null)
        
     else 

        val file = File("$library.rootFolderPublicDirectory/$remoteLibraryEntry.getFilename()")

        if(file.exists()) file.delete()

        success = remoteApi.downloadMusic(remoteLibraryEntry, file.outputStream())

        if (success) 
            MediaScannerConnection.scanFile(ctx, arrayOf(file.path), arrayOf("audio/*"))  _, uri ->
                val songId = JDrop.mediaHelper.getSongId(uri)
                JDrop.db.music.insert(Music(mediaStoreId = songId, remoteId = remoteLibraryEntry.remoteId, libraryId = library.id))
            
        
    

    return success

MediaStoreHelper 方法在这里

    fun getSongId(uri : Uri) : Long 

    val cursor = resolver.query(uri, arrayOf(Media._ID), null, null, null)

    return if(cursor != null && cursor.moveToNext()) 
        val idIndex = cursor.getColumnIndex(Media._ID)
        val id = cursor.getLong(idIndex)
        cursor.close()
        id
     else 
        cursor?.close()
        -1
    

当您没有指定 MIME 类型时,它似乎假定 mp3 是 MIME 类型。所以 .flac 文件将保存为 name.flac.mp3,因为如果没有,它会添加 mp3 文件类型并且它认为它是 mp3。它不会为 mp3 文件添加另一个 .mp3。我目前在任何地方都没有 MIME 类型……所以我想我现在就去做吧。

还有一个有用的 google IO 讨论关于作用域/共享存储https://youtu.be/3EtBw5s9iRY

这可能无法回答您的所有问题。果然不适合我。但是,粗略了解他们一开始所做的更改是一个有益的开始。


对于删除和更新文件,如果您在媒体存储条目上调用 delete,则在 Q 上它有点相同,该文件将被删除。在此之前,Q 您还必须手动删除该文件。但是如果你在 Q 上这样做,你的应用程序就会崩溃。因此,您必须再次检查您是否使用 Q 或旧版本的 android 并采取适当的措施。

【讨论】:

以上是关于如何扫描出Android系统媒体库中视频文件的主要内容,如果未能解决你的问题,请参考以下文章

Android如何通知MediaScanner扫描出指定文件

android4.4 中如何获取最近应用的缩略图

用于 Android Q 上的辅助存储的媒体扫描仪

如何在 android 库中创建应用程序特定文件夹?

安卓获取多媒体(包括 视频音频图片)数据

如何在android中运行媒体扫描仪