music: fix device files uris

This commit is contained in:
Alexander Capehart 2024-11-26 20:13:53 -07:00
parent ec19808cf1
commit cc9bb167c4
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47

View file

@ -42,20 +42,29 @@ class DeviceFilesImpl
@Inject @Inject
constructor( constructor(
@ApplicationContext private val context: Context, @ApplicationContext private val context: Context,
@Inject private val documentPathFactory: DocumentPathFactory private val documentPathFactory: DocumentPathFactory
) : DeviceFiles { ) : DeviceFiles {
private val contentResolver = context.contentResolverSafe private val contentResolver = context.contentResolverSafe
override fun explore(uris: Flow<Uri>): Flow<DeviceFile> = override fun explore(uris: Flow<Uri>): Flow<DeviceFile> =
uris.flatMapMerge { rootUri -> exploreImpl(contentResolver, rootUri, uris.flatMapMerge { rootUri ->
requireNotNull(documentPathFactory.unpackDocumentTreeUri(rootUri))) } Timber.d("$rootUri")
exploreImpl(
contentResolver,
rootUri,
DocumentsContract.getTreeDocumentId(rootUri),
requireNotNull(documentPathFactory.unpackDocumentTreeUri(rootUri)))
}
private fun exploreImpl( private fun exploreImpl(
contentResolver: ContentResolver, contentResolver: ContentResolver,
uri: Uri, rootUri: Uri,
treeDocumentId: String,
relativePath: Path relativePath: Path
): Flow<DeviceFile> = flow { ): Flow<DeviceFile> = flow {
contentResolver.useQuery(uri, PROJECTION) { cursor -> contentResolver.useQuery(
DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, treeDocumentId),
PROJECTION) { cursor ->
val childUriIndex = val childUriIndex =
cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DOCUMENT_ID) cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DOCUMENT_ID)
val displayNameIndex = val displayNameIndex =
@ -65,33 +74,32 @@ constructor(
val sizeIndex = cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_SIZE) val sizeIndex = cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_SIZE)
val lastModifiedIndex = val lastModifiedIndex =
cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_LAST_MODIFIED) cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_LAST_MODIFIED)
// Recurse into sub-directories as another flow val recursive = mutableListOf<Flow<DeviceFile>>()
val recursions = mutableListOf<Flow<DeviceFile>>()
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
val childId = cursor.getString(childUriIndex) val childId = cursor.getString(childUriIndex)
val childUri = DocumentsContract.buildDocumentUriUsingTree(uri, childId)
val displayName = cursor.getString(displayNameIndex) val displayName = cursor.getString(displayNameIndex)
val path = relativePath.file(displayName) val newPath = relativePath.file(displayName)
val mimeType = cursor.getString(mimeTypeIndex) val mimeType = cursor.getString(mimeTypeIndex)
if (mimeType == DocumentsContract.Document.MIME_TYPE_DIR) { if (mimeType == DocumentsContract.Document.MIME_TYPE_DIR) {
// This does NOT block the current coroutine. Instead, we will // This does NOT block the current coroutine. Instead, we will
// evaluate this flow in parallel later to maximize throughput. // evaluate this flow in parallel later to maximize throughput.
recursions.add(exploreImpl(contentResolver, childUri, path)) recursive.add(exploreImpl(contentResolver, rootUri, childId, newPath))
} else { } else if (mimeType.startsWith("audio/") && mimeType != "audio/x-mpegurl") {
// Immediately emit all files given that it's just an O(1) op. // Immediately emit all files given that it's just an O(1) op.
// This also just makes sure the outer flow has a reason to exist // This also just makes sure the outer flow has a reason to exist
// rather than just being a glorified async. // rather than just being a glorified async.
val lastModified = cursor.getLong(lastModifiedIndex) val lastModified = cursor.getLong(lastModifiedIndex)
val size = cursor.getLong(sizeIndex) val size = cursor.getLong(sizeIndex)
emit(DeviceFile(childUri, mimeType, path, size, lastModified)) emit(
DeviceFile(
DocumentsContract.buildDocumentUriUsingTree(rootUri, childId),
mimeType,
newPath,
size,
lastModified))
} }
} }
// Hypothetically, we could just emitAll as we recurse into a new directory, emitAll(recursive.asFlow().flattenMerge())
// but this will block the flow and force the tree search to be sequential.
// Instead, try to leverage flow parallelism and do all recursive calls in parallel.
// Kotlin coroutines can handle doing possibly thousands of parallel calls, it'll
// be fine. I hope.
emitAll(recursions.asFlow().flattenMerge())
} }
} }