It is possible to retrieve each track image from the mediastore audio content provider? I am not sure whether they have separate images for each audio track, or if they only have a single image for each album. If possible, please provide a solution or corresponding link.
Android does not save it as the "track image", but the "album image". So you have to do another query to the Album data:
albumId comes from the cursor that loads the songs:
long albumId = cursor.getLong(7);
Cursor artCursor = context.getContentResolver().query(
MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
new String[] {MediaStore.Audio.AlbumColumns.ALBUM_ART},
MediaStore.Audio.Media._ID+" =?",
new String[]{String.valueOf(albumId)},
null);
String albumArt;
if(artCursor.moveToNext()) {
albumArt = "file://"+artCursor.getString(0);
} else {
albumArt = null;
}
artCursor.close();
if(albumArt != null) {
BitmapFactory.decodeFile(new File(albumArt));
}
This solution works on Android 10, using kotlin and glide:
val uri = ContentUris.withAppendedId(
Uri.parse("content://media/external/audio/albumart"),
getItem(position)?.album_id?.toLong() ?: -1
)
Glide.with(holder.song_thumb.context)
.load(uri)
.into(holder.song_thumb)
getItem(position)?.album_id is the album id on the track retrieved using cursor on the content provider.
On Android 10, this Uri is accessible, while the path retrieved with the query on the content provider is not (regardless the READ_EXTERNAL_STORAGE permission).
Related
I'm building a music player app. I'm trying to populate a recyclerView with album arts of songs. I successfully did that with the code that is given below. But some of the songs do not have embedded album art not any album art in the folder. So, I'm trying to check if the album art is null or not before adding it to the recyclerView. If the album art is null, the app will automatically fetch it from the internet. I tried checking if the album art is null or not, but everytime it gives me a uri whether the album art is available or not.
Code for fetching album art from mediastore:
String[] projection = {MediaStore.Audio.Media._ID,
MediaStore.Audio.Media.TITLE,
MediaStore.Audio.Media.ARTIST,
MediaStore.Audio.Media.ALBUM,
MediaStore.Audio.Media.DURATION,
MediaStore.Audio.Media.DATA,
MediaStore.Audio.Media.ALBUM_ID};
Cursor cursor = getActivity().getContentResolver().query(uri, projection, selection, whereVal, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
do {
long id = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media._ID));
String name = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.TITLE));
String artist = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST));
String album = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM));
Long duration = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.DURATION));
String data = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA));
Long albumId = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID));
String albumArtUri = String.valueOf(ContentUris.withAppendedId(Uri.parse("content://media/external/audio/albumart"),albumId));
SongInfoModel s = new SongInfoModel(id, name, artist, null, album, null, duration, data, albumId,albumArtUri);
SongList.add(s);
} while (cursor.moveToNext());
}
The value for albumArtUri is never null.
It always gives the following uri(s):
content://media/external/audio/albumart/12
content://media/external/audio/albumart/5
content://media/external/audio/albumart/6
content://media/external/audio/albumart/3
content://media/external/audio/albumart/8
content://media/external/audio/albumart/9
But among these uri(s), content://media/external/audio/albumart/9 has no album art. It always displays the placeholder I said using Glide
So my question, how to check if album art is available(as embedded art or in song folder) before populating the recyclerView? I know I can use Glide placeholder and I'm using it. But I also want to fetch album art from the internet if album art is not available offline, that is why I need to check, so that I can add the internet links into the arraylist. Hope you guys get what I'm trying to say.
Thank you!
EDIT:
I found that the below given code snippet returns if album art exists or not but it consumes a lot of time.
public String fetchAlbumArt(long albumID){
String art;
Cursor cur = getActivity().getContentResolver().query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, new String[]
{MediaStore.Audio.Albums.ALBUM_ART}, MediaStore.Audio.Albums._ID + "=?", new String[] {String.valueOf(albumID)}, null);
cur.moveToFirst();
art = cur.getString(cur.getColumnIndex(MediaStore.Audio.Albums.ALBUM_ART));
cur.close();
return art;
}
The easiest way:
// loading album cover using Glide library
String stralbumId = c.getString(c
.getColumnIndex(BaseColumns._ID));
Uri ImageUrl = getAlbumUri(mContext, stralbumId);
if (ImageUrl != null) {
Glide.with(mContext)
.asBitmap()
.load(ImageUrl)
.into(image);
}
and
public Uri getAlbumUri(Context mContext,String album_id){
if(mContext!=null) {
Uri sArtworkUri = Uri.parse("content://media/external/audio/albumart");
Uri imageUri = Uri.withAppendedPath(sArtworkUri, String.valueOf(album_id));
return imageUri;
}
return null;
}
you can check for album cover by using the MediaMetadataRetriever
try the code below and also read the MediaMetadataRetriever Documentation
MediaMetadataRetriever retriver = new MediaMetadataRetriever();
retriver.setDataSource(musicFile.getAbsolutePath());
byte[] cover = retriver.getEmbeddedPicture();
if(cover == null){
// get cover from the internet
}else{
// use glide to load the album art
}
Try to use the code below
var albumArtExists = true
try {
val contentUri = ContentUris.withAppendedId(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, albumId)
context.contentResolver.loadThumbnail(contentUri, Size(64, 64), null)
} catch(e: FileNotFoundException) {
albumArtExists = false
}
This works for sdk 29 and higher
Using this method of audio file retrieval from Android's external storage
Cursor cursor = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, null);
can I actually find a resonable way to fetch a genre of the given song? MediaStore class seems to provide everything else - from song's title to its composer info - except for the genre field. Should I use MediaMetadataRetriever then? If so, how drastically can creating a MediaMetadataRetriever instance for every song on a device reduce app's performance?
Maybe there are some better ways to retrieve all audio files from both external and internal storages in android?
As mentioned at Developer's Site,
You can fetch the Genres of the Audio file using MediaStore.Audio.Genres
Sample Code :
private static String[] genresProj = {
MediaStore.Audio.Genres.NAME,
MediaStore.Audio.Genres._ID
};
int idIndex = cursor
.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
while (cursor.moveToNext()){
int id = Integer.parseInt(mediaCursor.getString(idIndex));
Uri uri = MediaStore.Audio.Genres.getContentUriForAudioId("external", id );
genresCursor = context.getContentResolver().query(uri,
genresProj , null, null, null);
int genreIndex = genresCursor.getColumnIndexOrThrow(MediaStore.Audio.Genres.NAME);
while (genresCursor.moveToNext()) {
Log.d(TAG, "Genre = " +genresCursor.getString(genreIndex));
}
}
}
To fetch other details of the Audio file, please check here .
So I've been trying this for some time now. To get the album art for an mp3 file and displaying it on its respective ImageView and I am using the uri("content://media/external/audio/albumart")
This is my method for getting the album art
public Bitmap getAlbumArt(long idAlbum){
Bitmap bitmap = null;
try{
final Uri sArtworkUri = Uri.parse("content://media/external/audio/albumart");
Uri uri = ContentUris.withAppendedId(sArtworkUri, idAlbum);
ParcelFileDescriptor parcelFileDescriptor = getContext().getContentResolver().openFileDescriptor(uri,"r");
if (parcelFileDescriptor != null){
FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
}
}catch (Exception e){
e.printStackTrace();
}
return bitmap;
}
and this method always returns
java.io.FileNotFoundException: No entry for content://media/external/audio/albumart/31726
where the 31726 is the album id.
Since I'm catching this exception and I set it to a default Album art if it returns null, every mp3 has its ImageView set to the default album art. I am using my Samsung Galaxy s3 to run the application and my device runs android 4.2.2 JellyBean. Please someone help me getting this right.
This is how I request the album id
Uri musicUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
String[] columns = {
MediaStore.Audio.Media.TITLE,
MediaStore.Audio.Media.ARTIST,
MediaStore.Audio.Albums._ID,
MediaStore.Audio.Media.DATA
};
String where = MediaStore.Audio.Media.IS_MUSIC + "=1";
Cursor musicCursor = usicResolver.query(musicUri,columns,where,null, null);
Then in an if loop with condition
if(musicCursor.moveToFirst()){
int albumId = musicCursor.getColumnIndex
(MediaStore.Audio.Albums._ID);
do{
long idAlbum = musicCursor.getLong(albumId);
//Then i send it to my above method getAlbumArt
Bitmap songAlbumArt = getAlbumArt(idAlbum);
}while(musicCursor.moveToNext());
}
This is how I query the cover art path from the album id:
private static String getCoverArtPath(long albumId, Context context) {
Cursor albumCursor = context.getContentResolver().query(
MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
new String[]{MediaStore.Audio.Albums.ALBUM_ART},
MediaStore.Audio.Albums._ID + " = ?",
new String[]{Long.toString(albumId)},
null
);
boolean queryResult = albumCursor.moveToFirst();
String result = null;
if (queryResult) {
result = albumCursor.getString(0);
}
albumCursor.close();
return result;
}
You can get the Bitmap from the BitmapFactory using the path returned from the method above:
BitmapFactory.decodeFile(coverArtPath);
ImageLoader.getInstance().
displayImage( ContentUris.withAppendedId(Uri.parse("content://media/external/audio/albumart"), song_tag.albumId).toString(),
image,
new DisplayImageOptions.Builder().cacheInMemory(true).showImageOnFail(R.drawable.stock6).resetViewBeforeLoading(true).build());
image is the object of ImageView
song_tag.albumid is the album id which you have to send to get the image
stock6 is the default image
EDIT:
It seems that ALBUM_ART was deprecated on API 29:
https://developer.android.com/reference/android/provider/MediaStore.Audio.AlbumColumns#ALBUM_ART
Instead, they suggest using ContentResolver#loadThumbnail:
https://developer.android.com/reference/android/content/ContentResolver#loadThumbnail(android.net.Uri,%20android.util.Size,%20android.os.CancellationSignal)
--
ORIGINAL:
Querying the album art field from content://media/external/audio/albums would do it for me some time ago, but I noticed it stopped working since a few Android versions ago, for some reason.
Now I querying the albums table gets me a null album art field:
adb shell content query --uri content://media/external/audio/albums
Row: 0 numsongs=1, artist=My Artist, numsongs_by_artist=1, _id=8821305607184940112, album=My Album, album_art=NULL, album_key=4c3a3434044e5052303a4604342a3e32044248194e, artist_id=7053871990187004266, artist_key=484c465046503832464c2a04422a402a563a32444e3a4e, maxyear=NULL, minyear=NULL, album_id=8821305607184940112
Querying the album art route directly, also gives me no results...
adb shell content query --uri content://media/external/audio/albumart
No result found.
It seems that the MediaStore on the platform side... stopped trying to parse album art from music files, and add them to its db?
Gah, another scenario here something that should be simple is proving to be very time-consuming and painful.
I'm using this to query the contacts provider:
private Cursor getContacts(){
Uri uri = ContactsContract.Contacts.CONTENT_URI;
String[] projection = new String[] {
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.PHOTO_ID
};
......
return managedQuery(uri, projection, selection, selectionArgs, sortOrder);
}
This works fine and retrieves contact names, and on a handful of contacts it shows a numeric ID for the PHOTO_ID field, which I assume is the PHOTO_ID I'm requesting. But then I push that ID into this method to extract the bitmap, it fails on every contact and the stream is null every time. I'm testing against a set of contacts that includes some with Android contact photos (I know there are some issues extracting photos from Facebook contacts).
private Bitmap loadContactPhoto(long id) {
Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id);
InputStream input = ContactsContract.Contacts.openContactPhotoInputStream(contentResolver, uri);
if (input == null) return null;
Bitmap bitmap = BitmapFactory.decodeStream(input);
return bitmap;
}
What have I missed?
openContactPhotoInputStream() takes the uri of the contact, try calling it with the ContactsContract.Contacts._ID column instead of the PHOTO_ID column and you should see better results.
There's a bunch of relevant discussion here with some code to check out:
How do I load a contact Photo?
Note that in some cases you'll see a photo in the native contacts app which won't load through the content resolver. Some sync info, like Facebook for example, is flagged to be used only by the contacts app itself and doesn't get exported to other apps :-(
However, using the contactUri should take care of at least some of your issues.
I'm trying to provide an in-app Activity which displays thumbnails of photos in the
device's media store, and allow the user to select one. After the user makes a
selection, the application reads the original full-size image and does things with it.
I'm using the following code to create a Cursor over all the images on the external
storage:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView( R.layout.image_select );
mGridView = (GridView) findViewById( R.id.image_select_grid );
// Query for all images on external storage
String[] projection = { MediaStore.Images.Media._ID };
String selection = "";
String [] selectionArgs = null;
mImageCursor = managedQuery( MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI,
projection, selection, selectionArgs, null );
// Initialize an adapter to display images in grid
if ( mImageCursor != null ) {
mImageCursor.moveToFirst();
mAdapter = new LazyCursorAdapter(this, mImageCursor, R.drawable.image_select_default);
mGridView.setAdapter( mAdapter );
} else {
Log.i(TAG, "System media store is empty.");
}
}
And the following code to load the thumbnail image (Android 2.x code is shown):
// ...
// Build URI to the main image from the cursor
int imageID = cursor.getInt( cursor.getColumnIndex(MediaStore.Images.Media._ID) );
Uri uri = Uri.withAppendedPath( MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
Integer.toString(imageID) );
loadThumbnailImage( uri.toString() );
// ...
protected Bitmap loadThumbnailImage( String url ) {
// Get original image ID
int originalImageId = Integer.parseInt(url.substring(url.lastIndexOf("/") + 1, url.length()));
// Get (or create upon demand) the micro thumbnail for the original image.
return MediaStore.Images.Thumbnails.getThumbnail(mContext.getContentResolver(),
originalImageId, MediaStore.Images.Thumbnails.MICRO_KIND, null);
}
And the following code to load the original image from the URL once the user makes a selection:
public Bitmap loadFullImage( Context context, Uri photoUri ) {
Cursor photoCursor = null;
try {
// Attempt to fetch asset filename for image
String[] projection = { MediaStore.Images.Media.DATA };
photoCursor = context.getContentResolver().query( photoUri,
projection, null, null, null );
if ( photoCursor != null && photoCursor.getCount() == 1 ) {
photoCursor.moveToFirst();
String photoFilePath = photoCursor.getString(
photoCursor.getColumnIndex(MediaStore.Images.Media.DATA) );
// Load image from path
return BitmapFactory.decodeFile( photoFilePath, null );
}
} finally {
if ( photoCursor != null ) {
photoCursor.close();
}
}
return null;
}
The problem I'm seeing on some Android devices, including my own personal phone, is that the
cursor I get from the query in onCreate() contains a few entries for which the actual full-sized image file (JPG or PNG) is missing. (In the case of my phone, the images had been imported and subsequently erased by iPhoto).
The orphaned entries may or may not have thumbnails, depending upon whether thumbnails where generated before the actual media file when AWOL. The end result is that the app displays thumbnails for images that don't actually exist.
I have a few questions:
Is there a query I can make to the MediaStore content provider that will filter out
images with missing media in the returned Cursor?
Is there a means, or an API to force the MediaStore to rescan, and eliminate the orphan entries? On my phone, I USB-mounted then unmounted the external media, which is supposed to trigger a rescan. But the orphan entries remain.
Or is there something fundamentally wrong with my approach that's causing this problem?
Thanks.
Okay, I've found the problem with this code sample.
In the onCreate() method, I had this line:
mImageCursor = managedQuery( MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI,
projection, selection, selectionArgs, null );
The problem here is that it's querying for the thumbnails, rather than the actual images. The camera app on HTC devices does not create thumbnails by default, and so this query will fail to return images that do not already have thumbnails calculated.
Instead, query for the actual images themselves:
mImageCursor = managedQuery( MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
projection, selection, selectionArgs, null );
This will return a cursor containing all the full-sized images on the system. You can then call:
Bitmap bm = MediaStore.Images.Thumbnails.getThumbnail(context.getContentResolver(),
imageId, MediaStore.Images.Thumbnails.MINI_KIND, null);
which will return the medium-sized thumbnail for the associated full-size image, generating it if necessary. To get the micro-sized thumbnail, just use MediaStore.Images.Thumbnails.MICRO_KIND instead.
This also solved the problem of finding thumbnails that have dangling references to the original full-sized images.
Please note that things will be changing soon, managedQuery method is deprecated. Use CursorLoader instead(since api level 11).