mirror of
https://github.com/antebudimir/tempus.git
synced 2026-04-15 16:27:26 +00:00
fix: album art now displays on android auto (#414)
Co-authored-by: Thomas R <tdr@thomasr.co> Co-authored-by: eddyizm <eddyizm@gmail.com>
This commit is contained in:
parent
d215581e19
commit
3de5390140
5 changed files with 173 additions and 13 deletions
|
|
@ -96,7 +96,12 @@
|
|||
android:resource="@xml/widget_info"/>
|
||||
</receiver>
|
||||
|
||||
|
||||
<provider
|
||||
android:name=".provider.AlbumArtContentProvider"
|
||||
android:authorities="com.cappielloantonio.tempo.provider"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
/>
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.cappielloantonio.tempo.model
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.annotation.Keep
|
||||
|
|
@ -13,6 +14,7 @@ import androidx.room.ColumnInfo
|
|||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest
|
||||
import com.cappielloantonio.tempo.provider.AlbumArtContentProvider
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child
|
||||
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation
|
||||
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode
|
||||
|
|
@ -197,7 +199,7 @@ class SessionMediaItem() {
|
|||
|
||||
fun getMediaItem(): MediaItem {
|
||||
val uri: Uri = getStreamUri()
|
||||
val artworkUri = Uri.parse(CustomGlideRequest.createUrl(coverArtId, getImageSize()))
|
||||
val artworkUri = AlbumArtContentProvider.contentUri(coverArtId)
|
||||
|
||||
val bundle = Bundle()
|
||||
bundle.putString("id", id)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,149 @@
|
|||
package com.cappielloantonio.tempo.provider;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.UriMatcher;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class AlbumArtContentProvider extends ContentProvider {
|
||||
public static final String AUTHORITY = "com.cappielloantonio.tempo.provider";
|
||||
public static final String ALBUM_ART = "albumArt";
|
||||
private ExecutorService executor;
|
||||
|
||||
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
|
||||
static {
|
||||
uriMatcher.addURI(AUTHORITY, "albumArt/*", 1);
|
||||
}
|
||||
|
||||
public static Uri contentUri(String artworkId) {
|
||||
return new Uri.Builder()
|
||||
.scheme(ContentResolver.SCHEME_CONTENT)
|
||||
.authority(AUTHORITY)
|
||||
.appendPath(ALBUM_ART)
|
||||
.appendPath(artworkId)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
|
||||
Context context = getContext();
|
||||
String albumId = uri.getLastPathSegment();
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(albumId, Preferences.getImageSize()));
|
||||
|
||||
try {
|
||||
// use pipe to communicate between background thread and caller of openFile()
|
||||
ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
|
||||
ParcelFileDescriptor readSide = pipe[0];
|
||||
ParcelFileDescriptor writeSide = pipe[1];
|
||||
|
||||
// perform loading in background thread to avoid blocking UI
|
||||
executor.execute(() -> {
|
||||
try (OutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream(writeSide)) {
|
||||
|
||||
// request artwork from API using Glide
|
||||
File file = Glide.with(context)
|
||||
.asFile()
|
||||
.load(artworkUri)
|
||||
.diskCacheStrategy(DiskCacheStrategy.DATA)
|
||||
.submit()
|
||||
.get();
|
||||
|
||||
// copy artwork down pipe returned by ContentProvider
|
||||
try (InputStream in = new FileInputStream(file)) {
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
while ((bytesRead = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, bytesRead);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
writeSide.closeWithError("Failed to load image: " + e.getMessage());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
writeSide.closeWithError("Failed to load image: " + e.getMessage());
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
});
|
||||
|
||||
return readSide;
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new FileNotFoundException("Could not create pipe: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
executor = Executors.newFixedThreadPool(
|
||||
Math.max(2, Runtime.getRuntime().availableProcessors() / 2)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
if (executor != null) {
|
||||
executor.shutdown();
|
||||
try {
|
||||
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getType(@NonNull Uri uri) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.net.Uri;
|
||||
import android.view.View;
|
||||
|
||||
|
|
@ -22,6 +23,7 @@ import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
|||
import com.cappielloantonio.tempo.model.Chronology;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.model.SessionMediaItem;
|
||||
import com.cappielloantonio.tempo.provider.AlbumArtContentProvider;
|
||||
import com.cappielloantonio.tempo.service.DownloaderManager;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
|
|
@ -70,7 +72,7 @@ public class AutomotiveRepository {
|
|||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (AlbumID3 album : albums) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(album.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(album.getName())
|
||||
|
|
@ -217,7 +219,7 @@ public class AutomotiveRepository {
|
|||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (AlbumID3 album : albums) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(album.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(album.getName())
|
||||
|
|
@ -272,7 +274,7 @@ public class AutomotiveRepository {
|
|||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (ArtistID3 artist : artists) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(artist.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(artist.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(artist.getName())
|
||||
|
|
@ -397,7 +399,7 @@ public class AutomotiveRepository {
|
|||
List<Child> children = response.body().getSubsonicResponse().getIndexes().getChildren();
|
||||
|
||||
for (Child song : children) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(song.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(song.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(song.getTitle())
|
||||
|
|
@ -451,7 +453,7 @@ public class AutomotiveRepository {
|
|||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (Child child : directory.getChildren()) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(child.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(child.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(child.getTitle())
|
||||
|
|
@ -550,7 +552,7 @@ public class AutomotiveRepository {
|
|||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (PodcastEpisode episode : episodes) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(episode.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(episode.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(episode.getTitle())
|
||||
|
|
@ -687,7 +689,7 @@ public class AutomotiveRepository {
|
|||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (AlbumID3 album : albums) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(album.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(album.getName())
|
||||
|
|
@ -800,7 +802,7 @@ public class AutomotiveRepository {
|
|||
|
||||
if (response.body().getSubsonicResponse().getSearchResult3().getArtists() != null) {
|
||||
for (ArtistID3 artist : response.body().getSubsonicResponse().getSearchResult3().getArtists()) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(artist.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(artist.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(artist.getName())
|
||||
|
|
@ -822,7 +824,7 @@ public class AutomotiveRepository {
|
|||
|
||||
if (response.body().getSubsonicResponse().getSearchResult3().getAlbums() != null) {
|
||||
for (AlbumID3 album : response.body().getSubsonicResponse().getSearchResult3().getAlbums()) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(album.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(album.getName())
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.cappielloantonio.tempo.util;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
|
@ -15,6 +16,7 @@ import androidx.media3.common.HeartRating;
|
|||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.provider.AlbumArtContentProvider;
|
||||
import com.cappielloantonio.tempo.repository.DownloadRepository;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
|
||||
|
|
@ -45,7 +47,7 @@ public class MappingUtil {
|
|||
Uri artworkUri = null;
|
||||
|
||||
if (coverArtId != null) {
|
||||
artworkUri = Uri.parse(CustomGlideRequest.createUrl(coverArtId, Preferences.getImageSize()));
|
||||
artworkUri = AlbumArtContentProvider.contentUri(coverArtId);
|
||||
}
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
|
|
@ -235,7 +237,7 @@ public class MappingUtil {
|
|||
|
||||
public static MediaItem mapMediaItem(PodcastEpisode podcastEpisode) {
|
||||
Uri uri = getUri(podcastEpisode);
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(podcastEpisode.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(podcastEpisode.getCoverArtId());
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("id", podcastEpisode.getId());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue