package com.example.data.mediastore import android.content.ContentResolver import android.content.ContentUris import android.content.Context import android.media.MediaMetadataRetriever import android.net.Uri import android.os.Build import android.provider.MediaStore import com.example.data.model.MediaItemData import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.io.File class MediaStoreScanner(private val context: Context) { suspend fun scanAllMedia(includeDemoIfEmpty: Boolean = true): List = withContext(Dispatchers.IO) { val audioList = scanAudio() val videoList = scanVideo() val combined = (audioList + videoList).toMutableList() if (combined.isEmpty() && includeDemoIfEmpty) { combined.addAll(DemoMediaProvider.getOrCreateDemoMedia(context)) } combined.sortedByDescending { it.dateAdded } } suspend fun scanAudio(): List = withContext(Dispatchers.IO) { val items = mutableListOf() val contentResolver: ContentResolver = context.contentResolver val uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI val projection = arrayOf( MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM, MediaStore.Audio.Media.DURATION, MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Media.SIZE, MediaStore.Audio.Media.DATE_ADDED, MediaStore.Audio.Media.ALBUM_ID, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { MediaStore.Audio.Media.RELATIVE_PATH } else { MediaStore.Audio.Media.DATA } ) val selection = "${MediaStore.Audio.Media.IS_MUSIC} != 0 OR ${MediaStore.Audio.Media.DURATION} > 5000" try { contentResolver.query( uri, projection, selection, null, "${MediaStore.Audio.Media.DATE_ADDED} DESC" )?.use { cursor -> val idCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID) val titleCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE) val artistCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST) val albumCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM) val durationCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION) val mimeCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.MIME_TYPE) val sizeCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.SIZE) val dateAddedCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATE_ADDED) val albumIdCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID) val pathCol = cursor.getColumnIndex( if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) MediaStore.Audio.Media.RELATIVE_PATH else MediaStore.Audio.Media.DATA ) while (cursor.moveToNext()) { val id = cursor.getLong(idCol) val title = cursor.getString(titleCol) ?: "Unknown Track" val artist = cursor.getString(artistCol)?.takeIf { it != "" } ?: "Unknown Artist" val album = cursor.getString(albumCol)?.takeIf { it != "" } ?: "Unknown Album" val durationMs = cursor.getLong(durationCol) val mime = cursor.getString(mimeCol) ?: "audio/mpeg" val size = cursor.getLong(sizeCol) val dateAdded = cursor.getLong(dateAddedCol) val albumId = cursor.getLong(albumIdCol) val path = if (pathCol >= 0) cursor.getString(pathCol) ?: "" else "" val contentUri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id) val albumArtUri = ContentUris.withAppendedId( Uri.parse("content://media/external/audio/albumart"), albumId ).toString() val folderName = extractFolderName(path) items.add( MediaItemData( id = "audio_$id", contentUri = contentUri.toString(), title = title, artist = artist, album = album, durationMs = durationMs, mimeType = mime, isVideo = false, sizeBytes = size, dateAdded = dateAdded, folderName = folderName, relativePath = path, albumArtUri = albumArtUri ) ) } } } catch (_: Exception) { } items } suspend fun scanVideo(): List = withContext(Dispatchers.IO) { val items = mutableListOf() val contentResolver: ContentResolver = context.contentResolver val uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI val projection = arrayOf( MediaStore.Video.Media._ID, MediaStore.Video.Media.TITLE, MediaStore.Video.Media.DURATION, MediaStore.Video.Media.MIME_TYPE, MediaStore.Video.Media.SIZE, MediaStore.Video.Media.DATE_ADDED, MediaStore.Video.Media.WIDTH, MediaStore.Video.Media.HEIGHT, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { MediaStore.Video.Media.RELATIVE_PATH } else { MediaStore.Video.Media.DATA } ) try { contentResolver.query( uri, projection, null, null, "${MediaStore.Video.Media.DATE_ADDED} DESC" )?.use { cursor -> val idCol = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID) val titleCol = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.TITLE) val durationCol = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION) val mimeCol = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.MIME_TYPE) val sizeCol = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE) val dateAddedCol = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATE_ADDED) val widthCol = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.WIDTH) val heightCol = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.HEIGHT) val pathCol = cursor.getColumnIndex( if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) MediaStore.Video.Media.RELATIVE_PATH else MediaStore.Video.Media.DATA ) while (cursor.moveToNext()) { val id = cursor.getLong(idCol) val title = cursor.getString(titleCol) ?: "Video $id" val durationMs = cursor.getLong(durationCol) val mime = cursor.getString(mimeCol) ?: "video/mp4" val size = cursor.getLong(sizeCol) val dateAdded = cursor.getLong(dateAddedCol) val width = cursor.getInt(widthCol) val height = cursor.getInt(heightCol) val path = if (pathCol >= 0) cursor.getString(pathCol) ?: "" else "" val contentUri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id) val folderName = extractFolderName(path) items.add( MediaItemData( id = "video_$id", contentUri = contentUri.toString(), title = title, artist = "Video", album = folderName, durationMs = durationMs, mimeType = mime, isVideo = true, width = width, height = height, sizeBytes = size, dateAdded = dateAdded, folderName = folderName, relativePath = path, albumArtUri = contentUri.toString() ) ) } } } catch (_: Exception) { } items } suspend fun resolveMediaUri(uri: Uri): MediaItemData = withContext(Dispatchers.IO) { val resolver = context.contentResolver val mimeType = resolver.getType(uri) ?: "application/octet-stream" val isVideo = mimeType.startsWith("video/") var title = "Imported Media" var size = 0L try { resolver.query(uri, null, null, null, null)?.use { cursor -> val nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME) val sizeIndex = cursor.getColumnIndex(MediaStore.MediaColumns.SIZE) if (cursor.moveToFirst()) { if (nameIndex != -1) title = cursor.getString(nameIndex) ?: title if (sizeIndex != -1) size = cursor.getLong(sizeIndex) } } } catch (_: Exception) { } var duration = 0L var width = 0 var height = 0 var artist = if (isVideo) "Video" else "Unknown Artist" var album = "Imported" try { val retriever = MediaMetadataRetriever() retriever.setDataSource(context, uri) retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.let { duration = it.toLongOrNull() ?: 0L } retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST)?.let { if (it.isNotBlank()) artist = it } retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM)?.let { if (it.isNotBlank()) album = it } if (isVideo) { retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.let { width = it.toIntOrNull() ?: 0 } retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.let { height = it.toIntOrNull() ?: 0 } } retriever.release() } catch (_: Exception) { } MediaItemData( id = "saf_${uri.hashCode()}", contentUri = uri.toString(), title = title, artist = artist, album = album, durationMs = duration, mimeType = mimeType, isVideo = isVideo, width = width, height = height, sizeBytes = size, dateAdded = System.currentTimeMillis() / 1000, folderName = "Imported", relativePath = "Storage/Imported" ) } private fun extractFolderName(path: String): String { if (path.isBlank()) return "Internal" val clean = path.trimEnd('/') val lastSlash = clean.lastIndexOf('/') return if (lastSlash >= 0) { val folder = clean.substring(lastSlash + 1) if (folder.isNotBlank()) folder else "Internal" } else { if (clean.isNotBlank()) clean else "Internal" } } }