You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 06:57:21