feat: scan media store for pairing
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package at.lockstep.player.util
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
|
||||
object AudioReadPermission {
|
||||
fun permissionName(): String =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
Manifest.permission.READ_MEDIA_AUDIO
|
||||
} else {
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package at.lockstep.player.util
|
||||
|
||||
import android.content.ContentUris
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
|
||||
object MediaStoreMp3Scanner {
|
||||
private const val TAG = "LockstepPairing"
|
||||
|
||||
fun listMp3Candidates(context: Context): List<Mp3FolderCandidate> {
|
||||
val resolver = context.applicationContext.contentResolver
|
||||
val collection =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
|
||||
}
|
||||
|
||||
val projection =
|
||||
arrayOf(
|
||||
MediaStore.Audio.Media._ID,
|
||||
MediaStore.Audio.Media.DISPLAY_NAME,
|
||||
MediaStore.Audio.Media.TITLE,
|
||||
MediaStore.Audio.Media.ARTIST,
|
||||
)
|
||||
|
||||
val selection =
|
||||
"(${MediaStore.Audio.Media.MIME_TYPE} = ? OR " +
|
||||
"${MediaStore.Audio.Media.DISPLAY_NAME} LIKE ?)"
|
||||
val selectionArgs = arrayOf("audio/mpeg", "%.mp3")
|
||||
|
||||
val out = mutableListOf<Mp3FolderCandidate>()
|
||||
resolver.query(collection, projection, selection, selectionArgs, null)?.use { cursor ->
|
||||
val idCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)
|
||||
val nameCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME)
|
||||
val titleCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE)
|
||||
val artistCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST)
|
||||
while (cursor.moveToNext()) {
|
||||
val id = cursor.getLong(idCol)
|
||||
val displayName = cursor.getString(nameCol).orEmpty()
|
||||
val title = cursor.getString(titleCol)?.trim()?.takeIf { it.isNotEmpty() }
|
||||
val artist = cursor.getString(artistCol)?.trim()?.takeIf { it.isNotEmpty() }
|
||||
out.add(
|
||||
Mp3FolderCandidate(
|
||||
uri = ContentUris.withAppendedId(collection, id),
|
||||
fileBaseName = displayName.substringBeforeLast('.'),
|
||||
id3Title = title,
|
||||
id3Artist = artist,
|
||||
),
|
||||
)
|
||||
}
|
||||
} ?: Log.w(TAG, "MediaStore audio query returned null")
|
||||
|
||||
Log.d(TAG, "MediaStoreMp3Scanner found mp3Count=${out.size}")
|
||||
return out.distinctBy { it.uri }.sortedBy { it.fileBaseName.lowercase() }
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,41 @@
|
||||
package at.lockstep.player.util
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.storage.StorageManager
|
||||
import android.provider.DocumentsContract
|
||||
|
||||
object SafInitialUris {
|
||||
private const val EXTERNAL_STORAGE_AUTHORITY = "com.android.externalstorage.documents"
|
||||
private const val INITIAL_URI_EXTRA = "android.provider.extra.INITIAL_URI"
|
||||
|
||||
/** Internal-storage Documents — avoids the blocked volume root shown on Pixel devices. */
|
||||
fun internalDocuments(): Uri =
|
||||
DocumentsContract.buildDocumentUri(
|
||||
/**
|
||||
* Internal-storage Documents. Uses [StorageManager] on Android 10+ so the system picker
|
||||
* lands in a choosable folder instead of the blocked volume root on Pixel devices.
|
||||
*/
|
||||
fun internalDocuments(context: Context): Uri {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
val storageManager = context.getSystemService(StorageManager::class.java)
|
||||
val intent = storageManager.primaryStorageVolume.createOpenDocumentTreeIntent()
|
||||
val root =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
intent.getParcelableExtra(INITIAL_URI_EXTRA, Uri::class.java)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
intent.getParcelableExtra(INITIAL_URI_EXTRA)
|
||||
}
|
||||
if (root != null) {
|
||||
val scheme =
|
||||
root
|
||||
.toString()
|
||||
.replace("/root/", "/document/") + "%3A" + "Documents"
|
||||
return Uri.parse(scheme)
|
||||
}
|
||||
}
|
||||
return DocumentsContract.buildDocumentUri(
|
||||
EXTERNAL_STORAGE_AUTHORITY,
|
||||
"primary:Documents",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user