如何在Android Q媒体库中更新音频文件的元数据?

7

在Android Q操作系统中,更新媒体库中音频文件的元数据不起作用,但它在所有其他操作系统中都有效。

我正在使用指定为MediaStore.Audio.Media.EXTERNAL_CONTENT_URI的内容提供程序。 在Android Q以下的所有设备上都可以正常工作。下面是我用于更新音轨元数据的代码。

ContentValues cv = new ContentValues();
ContentResolver resolver = getContentResolver();
Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;

cv.put(MediaStore.Audio.Media.TITLE, newTitle);
cv.put(MediaStore.Audio.Media.ALBUM, newAlbumName);
cv.put(MediaStore.Audio.Media.ARTIST, newArtistName);

int rowsUpdated = resolver.update(uri, cv, 
MediaStore.Audio.Media._ID + " = ? ", new String[]{audioId});

对于Android Q设备,不论何种情况下rowsUpdated均为0。 其他音乐播放器是如何在Android Q中更新音轨元数据的呢?

3个回答

12

最终,花了一些时间,但我弄清楚了。

首先,您需要获取文件的访问权限。在这里可以阅读有关此内容的信息。

接下来,我发现要更新标题或艺术家字段(也许还包括其他字段,但我没有测试过),您需要将列MediaStore.Audio.Media.IS_PENDING的值设置为1。像这样:

    val id = //Your audio file id

    val values = ContentValues()
    values.put(MediaStore.Audio.Media.IS_PENDING, 1)

    val uri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id)
    contentResolver.update(uri, values, null, null)

然后您可以编辑需要的字段。另外,为了结束更新过程,请再次将MediaStore.Audio.Media.IS_PENDING设置为0:

    val id = //Your audio file id
    val title = //New title
    val artist = //New artist

    val values = ContentValues()
    values.put(MediaStore.Audio.Media.IS_PENDING, 0)
    values.put(MediaStore.Audio.Media.TITLE, title)
    values.put(MediaStore.Audio.Media.ARTIST, artist)

    val uri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id)
    contentResolver.update(uri, values, null, null)

那么在一个函数中,它会像这样:

    @RequiresApi(value = android.os.Build.VERSION_CODES.Q)
    fun updateMetadata(contentResolver: ContentResolver, id: Long, title: String, artist: String) {
        val uri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id)
        val values = ContentValues()
        
        values.put(MediaStore.Audio.Media.IS_PENDING, 1)
        contentResolver.update(uri, values, null, null)
        
        values.clear()
        values.put(MediaStore.Audio.Media.IS_PENDING, 0)
        values.put(MediaStore.Audio.Media.TITLE, title)
        values.put(MediaStore.Audio.Media.ARTIST, artist)
        contentResolver.update(uri, values, null, null)
    }

虽然它是用Kotlin编写的,但我认为您会弄清楚如何在Java中实现。

更新

通过更新MediaStore而不在任何Android版本上更新实际文件。这意味着,如果一个文件被更新(例如:重命名)和/或通过MediaScannerConnection扫描,则您的更改将丢失。此答案是正确的。


我无法在MediaStore.Audio.Media中找到IS_PENDING列。 - AndroidDev
1
@Velu:请确保你的compileSdkVersion设置为29或更高版本。 - CommonsWare
你的答案其实是正确的。虽然在不写入文件时可能没有更改一些元数据或数据,但如果例如DISPLAY_NAME更改,则通过ContentResolver更新可能会触及Android Q中的实际文件。这被认为是一个位置更改列,不需要PENDING状态。大多数列都需要PENDING状态。请参见MediaProvider实现的sMutableColumns以获取所有不需要PENDING状态的列。 - CodeRed
1
这对我有用。如果我选择退出作用域存储,那么我就不必使用RecoverableSecurityException来访问文件,事实上,异常不会被抛出,它可以在没有异常的情况下工作。然而,同样的事情对于相册并不起作用,https://stackoverflow.com/q/62277997/4732846 - AndroidDev
1
在 API >= Q 中,无法处理非自有音频文件。 - Farzad

5

在使用Android Q及以上版本时,您需要先获取文件。

例如:

resolver.openInputStream(uri)?.use { stream -> outputFile.copyInputStreamToFile(stream) }
return outputFile.absolutePath

辅助函数

private fun File.copyInputStreamToFile(inputStream: InputStream?) {
    this.outputStream().use { fileOut ->
        inputStream?.copyTo(fileOut)
    }
}

然后通过第三方工具修改元数据,我使用 J Audio Tagger 进行操作

接着覆盖掉旧文件

// From https://developer.android.com/reference/android/content/ContentProvider
// String: Access mode for the file. May be
// "r" for read-only access,
// "w" for write-only access (erasing whatever data is currently in the file),
// "wa" for write-only access to append to any existing data,
// "rw" for read and write access on any existing data, and
// "rwt" for read and write access that truncates any existing file. This value must never be null.

mContext.application.contentResolver.openOutputStream(uri, "w")?.use { stream ->
            stream.write(file.readBytes())
        }

当文件是由你的应用程序创建时,这个工作正常。


1
请提供以下信息:J Audio Tagger的版本、targetSdkVersion和compileSdkVersion是多少? - mustafiz012
1
org:jaudiotagger:2.0.3, compileSdkVersion 29, targetSdkVersion 29 - Andy Cass

2

我一直通过ContentResolver更新MediaStore中的元数据,但在Android Q(API 29)中不再起作用。以下代码会给我一个警告,而且描述没有被更新:

ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DESCRIPTION, "Some text");

res = getContext().getContentResolver().update(
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        values,
        MediaStore.Images.Media._ID + "= ?", new String[]{sImageId});

android.process.media W/MediaProvider: 忽略来自com.example.android.someapp.app的描述变化

这篇Medium文章描述了Google如何改变访问和更新文件的API,但是如何仅更新元数据呢?警告似乎告诉我Google不再希望允许第三方应用程序使用MediaStore,并且我还找到了警告来自哪里的信息: https://android.googlesource.com/platform/packages/providers/MediaProvider/+/master/src/com/android/providers/media/MediaProvider.java#2960


请参见 https://dev59.com/Tbnoa4cB1Zd3GeqPQW1b。 - joakimk

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