如何从自定义Content Provider获取音频文件元数据?
解决Content Provider存储的MP3文件元数据获取问题
这个报错的核心原因是你的Content Provider没有正确处理content:// URI的文件请求,导致MediaMetadataRetriever无法获取到有效的文件描述符。下面分步骤给出解决方案:
1. 修复Content Provider的文件访问实现
你需要确保ContentProvider的openAssetFileDescriptor方法能正确解析content:// URI,并返回对应MP3文件的AssetFileDescriptor。示例实现如下:
override fun openAssetFileDescriptor(uri: Uri, mode: String): AssetFileDescriptor? { // 根据你的URI结构解析出本地文件路径,这里适配示例URI content://.../player/audio.mp3 val pathSegments = uri.pathSegments if (pathSegments.size < 2 || pathSegments[0] != "player") { return null } val fileName = pathSegments[1] // 替换成你实际存储MP3的本地路径 val filePath = "${context.filesDir.absolutePath}/player/$fileName" val targetFile = File(filePath) if (!targetFile.exists()) { throw FileNotFoundException("MP3 file not found: $filePath") } // 以只读模式打开文件并返回描述符 val parcelFd = ParcelFileDescriptor.open(targetFile, ParcelFileDescriptor.MODE_READ_ONLY) return AssetFileDescriptor(parcelFd, 0, AssetFileDescriptor.UNKNOWN_LENGTH) }
注意:
- 要根据你的实际URI结构调整路径解析逻辑
- 必须处理文件不存在的情况,避免抛出未捕获异常
2. 调整客户端元数据获取代码
修复Content Provider后,原有的MediaMetadataRetriever代码大概率能正常工作。如果仍有问题,可以尝试直接通过AssetFileDescriptor设置数据源,兼容性更好:
fun metadataDuration(context: Context, uri: Uri): Long { val retriever = MediaMetadataRetriever() context.contentResolver.openAssetFileDescriptor(uri, "r")?.use { fd -> retriever.setDataSource(fd.fileDescriptor, fd.startOffset, fd.length) val durationStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION) return durationStr?.toLong() ?: 0L } ?: throw FileNotFoundException("Failed to access file via URI: $uri") }
3. 额外优化:直接在Content Provider中提供元数据查询
如果不想每次都解析文件获取元数据,可以在Content Provider中提前存储MP3的元数据(比如时长),客户端直接通过query方法获取,效率更高:
步骤1:定义元数据列
object AudioProviderContract { const val COLUMN_DURATION = "duration" // 可添加其他元数据列,如标题、艺术家等 }
步骤2:在Content Provider的query方法中返回元数据
override fun query(uri: Uri, projection: Array<String>?, selection: String?, selectionArgs: Array<String>?, sortOrder: String?): Cursor? { // 假设你用SQLite存储文件元数据,替换成你的数据库实例 val dbHelper = AudioDbHelper(context) val db = dbHelper.readableDatabase val cursor = db.query( "audio_files", // 你的表名 projection, "file_name = ?", // 根据URI中的文件名筛选 arrayOf(uri.lastPathSegment), null, null, sortOrder ) cursor?.setNotificationUri(context.contentResolver, uri) return cursor }
步骤3:客户端查询获取时长
fun getDurationFromProvider(context: Context, uri: Uri): Long { context.contentResolver.query(uri, arrayOf(AudioProviderContract.COLUMN_DURATION), null, null, null)?.use { cursor -> if (cursor.moveToFirst()) { return cursor.getLong(cursor.getColumnIndexOrThrow(AudioProviderContract.COLUMN_DURATION)) } } return 0L }
内容的提问来源于stack exchange,提问作者Bob
相关产品推荐
相关产品推荐

