Thomas Vos
Thomas Vos

Reputation: 12571

Android get all music and genres without nested Cursors. (MediaStore.Audio...)

My app needs to list all music on the device, including: Song title, artist, album, genre and more.

The way I know of to get the genre for a song, is to create another Cursor with the song ID like this:

Uri uri = MediaStore.Audio.Genres.getContentUriForAudioId("external", (int) id);
Cursor genresCursor = mContentResolver.query(uri, genresProjection, null, null, null);

However, if I do this within the Cursor that lists all songs, the load time will increase a lot. (for the amount of songs on my phone, from 0.5s to about 3s).

This is my code:

// If you change this to `false`, it will load a lot quicker.
private static final boolean LOAD_GENRE = true;

private static String[] mediaProjection = {
        MediaStore.Audio.AudioColumns._ID,
        MediaStore.Audio.AudioColumns.DATA,
        MediaStore.Audio.AudioColumns.TITLE,
        MediaStore.Audio.AudioColumns.ARTIST,
        MediaStore.Audio.AudioColumns.ALBUM,
        MediaStore.Audio.AudioColumns.ALBUM_ID,
        MediaStore.Audio.AudioColumns.DURATION
};
private static String[] genresProjection = {
        MediaStore.Audio.GenresColumns.NAME
};

private ArrayList<MediaMetadataCompat> getAllMusic() {
    Uri uriAudio = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
    Cursor cursor = mContentResolver.query(uriAudio, mediaProjection, null, null, null);

    if (cursor != null) {
        int count = cursor.getCount();

        if (count > 0) {
            ArrayList<MediaMetadataCompat> audioFiles = new ArrayList<>();

            while (cursor.moveToNext()) {
                MediaMetadataCompat song = buildFromCursor(cursor);
                if (song != null) {
                    audioFiles.add(song);
                }
            }
            cursor.close();

            return audioFiles;
        }
    }
    return new ArrayList<>();
}

private MediaMetadataCompat buildFromCursor(Cursor cursor, boolean loadGenre) {
    long id = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.AudioColumns._ID));
    String data = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.AudioColumns.DATA));
    String title = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.AudioColumns.TITLE));
    String artist = ...;
    String album = ...;
    long albumId = ...;
    long duration = ...;

    //File file = new File(data);
    //if (!file.exists()) {
    //    Log.e(TAG, "Skipping song because it does not exist: " + title);
    //    return null;
    //}


    Uri uri = MediaStore.Audio.Genres.getContentUriForAudioId("external", (int) id);

    if (LOAD_GENRE) {
        String genre = null;
        Cursor genresCursor = mContentResolver.query(uri, genresProjection, null, null, null);
        if (genresCursor != null && genresCursor.moveToFirst()) {
            int genreColumnIndex = genresCursor.getColumnIndex(MediaStore.Audio.GenresColumns.NAME);
            do {
                String genreColumn = genresCursor.getString(genreColumnIndex);
                if (genre == null) {
                    genre = genreColumn;
                } else {
                    genre += " " + genre;
                }
            } while (genresCursor.moveToNext());
            genresCursor.close();
        }
    }

    // Workaround to avoid ANOTHER cursor to load album art.
    Uri albumArtUri = ContentUris.withAppendedId(Uri.parse("content://media/external/audio/albumart"), albumId);

    MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder()
            .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, String.valueOf(id))
            .put...
            .put...;


    return builder.build();
}

Is there any way I can improve this code, to avoid a nested Cursor? I already found a workaround for the album art. Is it possible to combine them in some way? Will a CursorJoiner work for this? The problem is that to get the genre, you need to have the media id. Thanks.

Upvotes: 0

Views: 1387

Answers (1)

Theo
Theo

Reputation: 2042

A SO user Suhaib Roomey posted this recently (get genre_id and audio_id from audio_genres_map from mediastore). Perhaps it helps in what you are trying to achieve

 String[] genresProjection = {
        Audio.Genres.Members.AUDIO_ID,
        Audio.Genres.Members.GENRE_ID
 };
       context.getContentResolver().query(Uri.parse("content://media/external/audio/genres/all/members"), genresProjection, null, null, null);

Upvotes: 1

Related Questions