Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Fix: artists() method to group by album artist with proper URI handling

Problem

The artists() method needs to:

  1. Group by album artist (with fallback to track artist)
  2. Generate proper URIs that work with the artist() method
  3. Handle artist selection correctly

Solution

Replace the artists() method with this implementation that:

  • Queries all audios to get album artist information
  • Groups by album artist (falling back to track artist)
  • Generates URIs based on the artist name hash to ensure consistency
  • Works with the existing artist() method

Implementation

Replace the entire artists() method with:

override fun artists(
    providerIdentifier: ProviderIdentifier,
    sortingRule: SortingRule,
) = providersManager.flatMapWithInstanceOf(providerIdentifier) {
    // Get all audios to extract album artist information
    contentResolver.queryFlow(
        audiosUri,
        audiosProjection,
    ).mapEachRowToAudio().mapLatest { audios ->
        // Group by album artist, falling back to track artist
        val artistGroups = audios
            .groupBy { audio ->
                audio.albumArtist ?: audio.artistName ?: "Unknown Artist"
            }
        
        // Sort based on sorting rule
        val sortedArtistNames = when (sortingRule.strategy) {
            SortingStrategy.NAME -> {
                if (sortingRule.reverse) {
                    artistGroups.keys.sortedDescending()
                } else {
                    artistGroups.keys.sorted()
                }
            }
            else -> artistGroups.keys.sorted()
        }
        
        // Create Artist objects with URIs based on artist name hash
        val artistsUri = getArtistsUri(volumeName)
        sortedArtistNames.map { artistName ->
            // Use hash of artist name to generate a consistent URI
            val artistId = artistName.hashCode().toLong()
            val uri = ContentUris.withAppendedId(artistsUri, artistId)
            
            Artist.Builder(uri)
                .setName(artistName)
                .build()
        }
    }.mapLatest {
        Result.Success(it)
    }
}

Update artist() method

Also update the artist() method to handle the new URI scheme by matching artist name instead of ID:

Replace the artist() method with:

override fun artist(artistUri: Uri) = withVolumeName(artistUri) { volumeName ->
    // Get all audios to find matching artist
    contentResolver.queryFlow(
        getAudiosUri(volumeName),
        audiosProjection,
    ).mapEachRowToAudio(volumeName).mapLatest { audios ->
        // Extract the artist name from URI (we'll match by name)
        val targetArtistId = ContentUris.parseId(artistUri)
        
        // Find audios matching this artist (by album artist or track artist)
        val matchingAudios = audios.filter { audio ->
            val artistName = audio.albumArtist ?: audio.artistName ?: "Unknown Artist"
            artistName.hashCode().toLong() == targetArtistId
        }
        
        if (matchingAudios.isEmpty()) {
            return@mapLatest Result.Error<Pair<Artist, ArtistWorks>, Error>(Error.NOT_FOUND)
        }
        
        // Get the artist name from first matching audio
        val artistName = matchingAudios.first().let { audio ->
            audio.albumArtist ?: audio.artistName ?: "Unknown Artist"
        }
        
        // Get unique album URIs from matching audios
        val albumUris = matchingAudios.map { it.albumUri }.distinct()
        
        // Query albums by URI
        contentResolver.queryFlow(
            getAlbumsUri(volumeName),
            albumsProjection,
            bundleOf(
                ContentResolver.QUERY_ARG_SQL_SELECTION to query {
                    MediaStore.Audio.AudioColumns._ID `in` List(albumUris.size) {
                        Query.ARG
                    }
                },
                ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS to albumUris.map {
                    ContentUris.parseId(it).toString()
                }.toTypedArray(),
            )
        ).mapEachRowToAlbum(volumeName).mapLatest { albums ->
            val artist = Artist.Builder(artistUri)
                .setName(artistName)
                .build()
            
            val artistWorks = ArtistWorks(
                albums,
                listOf(),
                listOf(),
            )
            
            Result.Success(artist to artistWorks)
        }
    }.flatMapLatest { it }
}

How it works

  1. artists() method:

    • Queries all audios with album artist information
    • Groups by album artist (falls back to track artist)
    • Generates URIs using hash of artist name for consistency
    • Returns grouped artist list
  2. artist() method:

    • Receives URI with artist ID (which is hash of artist name)
    • Finds all audios matching that artist name
    • Queries albums for those audios
    • Returns artist details with albums

Testing

After implementing:

  1. Rebuild APK
  2. Test artist selection - should now work
  3. Verify compilation albums are grouped correctly
  4. Check that artist details show all albums for that artist