Pith - jamb
jamb/app/src/main/java/com/vgmlr/jamb/JambMediaStore.kt [4.5 kb]
Modified: 20:33:38 112 026 (09 Jul 026)
16 Days Ago
package com.vgmlr.jamb

import android.content.ContentUris
import android.content.Context
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Environment
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.provider.OpenableColumns
import kotlin.coroutines.resume
import kotlinx.coroutines.suspendCancellableCoroutine

class JambMediaStore(private val context: Context) {

    fun queryTracks(): List<JambTrack> {
        val out = mutableListOf<JambTrack>()
        val collection = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
        val projection = arrayOf(
            MediaStore.Audio.Media._ID,
            MediaStore.Audio.Media.DISPLAY_NAME,
            MediaStore.Audio.Media.DURATION,
            MediaStore.Audio.Media.BUCKET_DISPLAY_NAME,
            MediaStore.Audio.Media.RELATIVE_PATH,
            MediaStore.Audio.Media.SIZE
        )
        val selection = "${MediaStore.Audio.Media.IS_MUSIC} != 0"
        val sortOrder = "${MediaStore.Audio.Media.DISPLAY_NAME} COLLATE NOCASE ASC"

        context.contentResolver.query(collection, projection, selection, null, sortOrder)?.use { c ->
            val idCol = c.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)
            val nameCol = c.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME)
            val durCol = c.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION)
            val bucketCol = c.getColumnIndexOrThrow(MediaStore.Audio.Media.BUCKET_DISPLAY_NAME)
            val relCol = c.getColumnIndexOrThrow(MediaStore.Audio.Media.RELATIVE_PATH)
            val sizeCol = c.getColumnIndexOrThrow(MediaStore.Audio.Media.SIZE)
            while (c.moveToNext()) {
                val id = c.getLong(idCol)
                val name = c.getString(nameCol) ?: continue
                val dur = c.getLong(durCol)
                val folder = c.getString(bucketCol) ?: "Unknown"
                val relPath = c.getString(relCol) ?: ""
                val size = c.getLong(sizeCol)
                val uri = ContentUris.withAppendedId(collection, id).toString()
                out.add(JambTrack(id, uri, name, folder, relPath, dur, size))
            }
        }
        return out
    }

    fun matchTrack(uri: Uri, master: List<JambTrack>): JambTrack? {
        if (uri.authority == "media") {
            val id = runCatching { ContentUris.parseId(uri) }.getOrNull()
            if (id != null && id >= 0) {
                master.firstOrNull { it.id == id }?.let { return it }
            }
        }
        var name: String? = null
        var size = -1L
        runCatching {
            context.contentResolver.query(
                uri,
                arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE),
                null, null, null
            )?.use { c ->
                if (c.moveToFirst()) {
                    val nCol = c.getColumnIndex(OpenableColumns.DISPLAY_NAME)
                    val sCol = c.getColumnIndex(OpenableColumns.SIZE)
                    if (nCol >= 0 && !c.isNull(nCol)) name = c.getString(nCol)
                    if (sCol >= 0 && !c.isNull(sCol)) size = c.getLong(sCol)
                }
            }
        }
        if (name == null && uri.scheme == "file") {
            name = uri.lastPathSegment
        }
        val display = name ?: return null
        master.firstOrNull { it.displayName == display && size >= 0 && it.sizeBytes == size }
            ?.let { return it }
        return master.firstOrNull { it.displayName == display }
    }

    suspend fun forceScan(uri: Uri): Uri? {
        val path = resolvePath(uri) ?: return null
        return scanFile(path)
    }

    private fun resolvePath(uri: Uri): String? {
        if (uri.scheme == "file") return uri.path
        if (uri.scheme == "content" &&
            uri.authority == "com.android.externalstorage.documents"
        ) {
            val docId = runCatching { DocumentsContract.getDocumentId(uri) }.getOrNull()
                ?: return null
            val split = docId.split(":", limit = 2)
            if (split.size == 2 && split[0].equals("primary", ignoreCase = true)) {
                return Environment.getExternalStorageDirectory().absolutePath + "/" + split[1]
            }
        }
        return null
    }

    private suspend fun scanFile(path: String): Uri? =
        suspendCancellableCoroutine { cont ->
            MediaScannerConnection.scanFile(context, arrayOf(path), null) { _, resultUri ->
                if (cont.isActive) cont.resume(resultUri)
            }
        }
    
}
Updates
Wedge - Android 126.026
Wedge - Linux 124.026
Shim - Android 117.026
Miter - 114.026
Kerf - Android 112.026
Dev
TVShow (227) 'CSA'
TVShow (228) 'APT'
TVProgram (83) 'BXT'
Miter Update(s)
Peen (Messaging)

Menu
Calendar
Project Tin (024/029)
Miter
RSS Feed
User Avatar
@vgmlr
=SUM(parts)