Android下无需逐个调用文件选择器,从M3U播放列表获取文件读取权限并读取元数据的实现方案问询
Android下无需逐个调用文件选择器,从M3U播放列表获取文件读取权限并读取元数据的实现方案问询
我现在想做这么个功能:读取一个M3U播放列表文件,自动获取列表里每个音频文件的元数据,但卡在权限这块了。明确说下,我不想给列表里每个文件都弹文件选择器,只选播放列表本身就行,剩下的操作必须是自动完成的。
我当前的播放列表权限获取代码
我用ActivityResultContracts.OpenDocument来获取播放列表的读取权限,还做了持久化权限,代码大概是这样的:
val filePickerLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.OpenDocument(), onResult = { uri: Uri? -> uri?.let { context.contentResolver.takePersistableUriPermission( it, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION ) context.contentResolver.openInputStream(it)?.use { inputStream -> // 读取播放列表文件内容 } // 这里要加下面说的方案1或者方案2的逻辑 } } )
我想到的两个实现方案
方案一(不太理想的方案):通过MediaStore全局扫描匹配路径
思路是先读取播放列表里的路径,再和MediaStore扫描出来的所有音频文件做路径匹配。我写的MediaStore扫描函数大概是这样的:
fun scanMediaStore(): MutableList<...> { val mediaFiles: MutableList<...> = mutableListOf() val projection = arrayOf( MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA, MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.DURATION, MediaStore.Audio.Media.ALBUM_ID, ) val selectionArgs = arrayOf("%.mp3", "%.m4a") val selection = selectionArgs.joinToString(" OR ") { "${MediaStore.Audio.Media.DATA} LIKE ?" } val sortOrder = null fun queryMediaStore(uri: Uri) { Log.d("Scanning", "queryMediaStore: $uri") app.contentResolver.query( uri, projection, selection, selectionArgs, sortOrder )?.use { cursor -> while (cursor.moveToNext()) { val getOrThrow: (String) -> Int = { columnName -> cursor.getColumnIndexOrThrow(columnName) } val mediaID: Int = cursor.getInt(getOrThrow(MediaStore.Audio.Media._ID)) val name: String = cursor.getString(getOrThrow(MediaStore.Audio.Media.TITLE)) val path: String = cursor.getString(getOrThrow(MediaStore.Audio.Media.DATA)) val artist: String = cursor.getString(getOrThrow(MediaStore.Audio.Media.ARTIST)) val duration: Int = cursor.getInt(getOrThrow(MediaStore.Audio.Media.DURATION)) Log.d("MediaFiles", "Found: $path") mediaFiles.add(...) // 把扫描到的文件信息加入列表 } } } queryMediaStore(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI) queryMediaStore(MediaStore.Audio.Media.INTERNAL_CONTENT_URI) return mediaFiles }
之后遍历播放列表里的每个路径,和scanMediaStore()返回的列表做匹配:
// 伪代码 for (path in 播放列表内容) { MatchAgainst(path, scanMediaStore()) // 匹配路径或文件名 }
这个方案的问题在于MediaStore扫描可能不全,而且相对路径的匹配很容易出问题,所以我更倾向于方案二,但目前卡壳了。
方案二(更优但未实现的方案):通过路径获取MediaID再获取元数据
思路是读取播放列表里的路径后,先拿到对应的MediaID,再用这个ID去获取元数据,如果MediaStore里没有对应条目再补充获取。我只写了伪代码,核心函数还不知道怎么实现:
// 伪代码 fun getMediaIDFor(path: String): Int { // 如何通过路径获取对应的MediaID? } fun getMetaDataAndAdd(path: String) { // 如何通过路径或MediaID获取元数据? } // 遍历播放列表 for (path in 播放列表内容) { val itemID = getMediaIDFor(path) if (itemID !in scanMediaStore()) { // 检查是否在已扫描列表中 getMetaDataAndAdd(itemID/path) } }
补充说明:我处理的是三星音乐生成的M3U文件,里面的路径都是相对路径,比如../../.../SDCARDID/foo/bar/song.mp3这种格式。
我尝试过但失败的方法
- 直接用路径读取文件:触发权限错误
- 尝试通过MediaStore查询构造的URI:同样报权限错误
我还写了个解析相对路径生成URI的函数,但用这个URI去查询还是没权限:
fun resolveRelativeUri(baseUri: Uri, relativePath: String): Uri { val baseDocId = DocumentsContract.getDocumentId(baseUri) val baseParts = baseDocId.split(":", limit = 2) val storageRoot = baseParts[0] val baseDir = baseParts.getOrNull(1) ?: "" val normalizedPath = File("/$baseDir", relativePath).normalize().path.removePrefix("/") val resolvedDocId = "$storageRoot:$normalizedPath" return DocumentsContract.buildDocumentUri( "com.android.externalstorage.documents", resolvedDocId ) } // 尝试用这个URI获取元数据的函数 suspend fun getSongFromUri(uri: Uri): ...? = withContext(Dispatchers.IO) { Log.d("ViewModel", "Trying to read $uri") val projection = arrayOf( MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA, MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.DURATION ) app.contentResolver.query(uri, projection, null, null, null)?.use { // 处理查询结果 } }
想请教下,方案二的核心函数该怎么实现?或者有没有其他更好的方法,能在只选择一次播放列表的情况下,自动获取里面所有音频文件的读取权限并拿到元数据?
内容来源于stack exchange
相关产品推荐
相关产品推荐

