Reputation: 93
I wrote an mp3 player (called DJ) and I want to add compatibility with Android Auto. I wrote the service and everything seems to work, except no album art appears on the Windows Desktop Head Unit, and no sound plays (doesn't even call onPlay). Clicking on a song in the head unit, it displays "Getting your selection..." - then seems to hang there. Should there be a play button?
The songs are in an ArrayList called songItemList. Here's the code creating the MediaItems. I confirm that the album art file exists, then do Uri.parse on the file path. I also tried a fromFile with the same result. I do the same thing for the mp3 file. I must be missing something.
for (SongItem songItem:songItemList) {
String id = SONG + "_" + songItem.getTrackid();
Uri uri = null;
Uri mediaUri = null;
if (songItem.getArtLocation() != null) {
File f = new File(songItem.getArtLocation());
if (f.exists()) {
//uri = Uri.fromFile(f);
uri = Uri.parse(songItem.getArtLocation());
} else {
Log.d(TAG,"File doesn't exist:" + songItem.getArtLocation());
}
f = new File(songItem.getLocation());
if (f.exists()) {
//mediaUri = Uri.fromFile(f);
mediaUri = Uri.parse(songItem.getLocation());
} else {
Log.d(TAG, "File doesn't exist: " + songItem.getLocation());
}
}
Bundle extras = new Bundle();
MediaBrowserCompat.MediaItem item =
new MediaBrowserCompat.MediaItem(descBuilder.setMediaId(id)
.setDescription(songItem.getName())
.setSubtitle(songItem.getArtist())
.setIconUri(uri)
.setMediaUri(mediaUri)
.setMediaId(id)
.setTitle(songItem.getName())
.setExtras(extras)
.build(),MediaBrowserCompat.MediaItem.FLAG_PLAYABLE);
mediaItems.add(item);
}
saveResult.sendResult(mediaItems);
Additional info: When I use the Uri.parse, I get uri's like this:
/data/data/com.emrick.dj/albumart/Judy Collins/Fires Of Eden/Fires Of Eden.jpg /data/data/com.emrick.dj/audio/Judy Collins/Fires Of Eden/From A Distance.mp3
When I use the Uri.fromFile call, I get uri's like this: file:///data/data/com.emrick.dj/albumart/Judy%20Collins/Fires%20Of%20Eden/Fires%20Of%20Eden.jpg file:///data/data/com.emrick.dj/audio/Judy%20Collins/Fires%20Of%20Eden/From%20A%20Distance.mp3
Also, running the mp3 player app (that this is based on) on my phone, I see all the album art and the mp3 files play, so I believe my music database is good.
Update: I tried using .setIconBitmap instead of .setIconUri, creating a bitmap instead of a Uri for the album art. If I limit the number of songs to 200 or less, then the album art appears. Somewhere between 200 and 390 songs apparently causes a problem, the head unit spins for a few seconds and then says the app isn't working. Then I tried going back to the .setIconUri and limited the songs to 200, that still doesn't work (gives blank album art like below). Still hangs on "Getting your selection...." if I click on a song.
Update (answer from Ben Sagmoe)
I'll put my follow up questions here - they don't show up well as a comment. Still not clear about implementing/accessing the ContentProvider.
I tried this: using uri = Uri.parse("content://" + songItem.getArtLocation()); uri = albumArtContent(uri);
public static Uri albumArtContent(Uri uri) {
String path = uri.getEncodedPath() != null ? uri.getEncodedPath().substring(1).replace('/', ':') : Uri.EMPTY.toString();
Uri contentUri = new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority("com.emrick.dj")
.path(path)
.build();
return contentUri;
}
Still not working. Do I need to create a class that extends ContentProvider? I don't see any methods that return a Uri in the required methods for ContentProvider though... openFile and copy look promising, but how do I get a Uri from either of those?
Upvotes: 0
Views: 91
Reputation: 93
For future reference, here's what I learned while trying to get this working:
I have the album art loading now and it will play the mp3 files. For the album art, I was creating the Uri with the fromFile method, instead of the parse method. That was messing up the path. fromFile + the mapUri method were encoding the path twice, replacing spaces with %20, then replacing the % with %25, ending up with %2520. I adjusted and called the mapUri method in the ContentProvider that I copied from the uamp sample. It was trying to get the content from a web address and my app gets the content from a local file. The path needed to look like this content://data/data/com.emrick.dj/albumart/album.jpg.
I had been testing the play function by putting a break point on the onPlay method in the mediaSession.setCallback. It never hit the breakpoint - then I realized it was actually calling the onPlayFromMediaId method, not onPlay. I wrote that method and then the breakpoint on it was reached. I wired it to my existing MediaService (for the mp3 player) but it didn't play. Then I realized it had to handle the audio focus. Apparently the Windows Head Unit takes the audio focus away from my mp3 player. I changed my mediaService to ask for the audio focus back and then it would play my mp3 files.
It took a lot of work to get to this point - I could have really used a complete example in Java, not Kotlin. Lots of details that weren't obvious to me. Anyway, I hope these notes help others trying to get this to work! Thanks Ben Sagmoe for pointing me in the right directions.
Upvotes: 0
Reputation: 1447
For the album art to load using setIconUri
, you need to implement a ContentProvider
for the files to actually be loaded by Android Auto.
What your code is currently doing is validating that the file exists and telling Android Auto what the location is. But it ultimately doesn't actually load the file and provide it to Android Auto. See Content provider basics for more details on how content providers work and why they're used.
As you discovered, you could use setIconBitmap
, which includes the full images in the serialized media items sent in sendResult
. But, as you noticed, this breaks at a certain point, because of Android's 1MB binder size limit.
As far as playing the mp3 files goes, that's generally handled using something like ExoPlayer. To allow Android Auto to control your player (e.g. to handle play/pause commands from the user), you need to expose control via MediaSession.
Upvotes: 1