From b403d69982bf47cf1288c377c00d6ee535ec6960 Mon Sep 17 00:00:00 2001 From: Denis Machard <5562930+dmachard@users.noreply.github.com> Date: Sun, 22 Feb 2026 17:08:01 +0100 Subject: [PATCH] feat: radio logos support for AndroidAuto (#435) * feat: radio logos support for AndroidAuto * resolve a merge conflict. * fix auto lint * fix auto lint * fix auto break line * fix auto break line * fix auto break line * fix: add alternate serialized name for InternetRadioStation homePageUrl to support both `homePageUrl` and `homepageUrl` JSON keys. * improve internet radio station cover art handling by prioritizing home page URLs * fix: remove unnecessary blank line and adjust formatting in MusicUtil * refactor: improve formatting and clean up whitespace in MappingUtil and MusicUtil --- .../tempo/model/SessionMediaItem.kt | 13 ++++++++++-- .../provider/AlbumArtContentProvider.java | 11 +++++++++- .../subsonic/models/InternetRadioStation.kt | 2 ++ .../adapter/InternetRadioStationAdapter.java | 7 ++++++- .../tempo/util/MappingUtil.java | 20 +++++++++++++++++-- .../tempo/util/MusicUtil.java | 11 ++++++++++ .../service/MediaLibraryServiceCallback.kt | 2 +- 7 files changed, 59 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/cappielloantonio/tempo/model/SessionMediaItem.kt b/app/src/main/java/com/cappielloantonio/tempo/model/SessionMediaItem.kt index 39ac3f03..f90229aa 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/model/SessionMediaItem.kt +++ b/app/src/main/java/com/cappielloantonio/tempo/model/SessionMediaItem.kt @@ -195,11 +195,20 @@ class SessionMediaItem() { title = internetRadioStation.name streamUrl = internetRadioStation.streamUrl type = Constants.MEDIA_TYPE_RADIO + + val homePageUrl = internetRadioStation.homePageUrl + if (homePageUrl != null && homePageUrl.isNotEmpty() && MusicUtil.isImageUrl(homePageUrl)) { + val encodedUrl = android.util.Base64.encodeToString( + homePageUrl.toByteArray(java.nio.charset.StandardCharsets.UTF_8), + android.util.Base64.URL_SAFE or android.util.Base64.NO_WRAP + ) + coverArtId = "ir_$encodedUrl" + } } fun getMediaItem(): MediaItem { val uri: Uri = getStreamUri() - val artworkUri = AlbumArtContentProvider.contentUri(coverArtId) + val artworkUri = if (coverArtId != null) AlbumArtContentProvider.contentUri(coverArtId!!) else null val bundle = Bundle() bundle.putString("id", id) @@ -229,7 +238,7 @@ class SessionMediaItem() { bundle.putLong("starred", starred?.time ?: 0) bundle.putString("albumId", albumId) bundle.putString("artistId", artistId) - bundle.putString("type", Constants.MEDIA_TYPE_MUSIC) + bundle.putString("type", type) bundle.putLong("bookmarkPosition", bookmarkPosition ?: 0) bundle.putInt("originalWidth", originalWidth ?: 0) bundle.putInt("originalHeight", originalHeight ?: 0) diff --git a/app/src/main/java/com/cappielloantonio/tempo/provider/AlbumArtContentProvider.java b/app/src/main/java/com/cappielloantonio/tempo/provider/AlbumArtContentProvider.java index 9095e4f6..5063b3a4 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/provider/AlbumArtContentProvider.java +++ b/app/src/main/java/com/cappielloantonio/tempo/provider/AlbumArtContentProvider.java @@ -8,6 +8,7 @@ import android.content.UriMatcher; import android.database.Cursor; import android.net.Uri; import android.os.ParcelFileDescriptor; +import android.util.Base64; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -53,7 +54,15 @@ public class AlbumArtContentProvider extends ContentProvider { 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())); + Uri artworkUri; + + if (albumId != null && albumId.startsWith("ir_")) { + String encodedUrl = albumId.substring("ir_".length()); + String decodedUrl = new String(Base64.decode(encodedUrl, Base64.URL_SAFE | Base64.NO_WRAP)); + artworkUri = Uri.parse(decodedUrl); + } else { + artworkUri = Uri.parse(CustomGlideRequest.createUrl(albumId, Preferences.getImageSize())); + } try { // use pipe to communicate between background thread and caller of openFile() diff --git a/app/src/main/java/com/cappielloantonio/tempo/subsonic/models/InternetRadioStation.kt b/app/src/main/java/com/cappielloantonio/tempo/subsonic/models/InternetRadioStation.kt index 07f00c5b..dd82d247 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/subsonic/models/InternetRadioStation.kt +++ b/app/src/main/java/com/cappielloantonio/tempo/subsonic/models/InternetRadioStation.kt @@ -3,6 +3,7 @@ package com.cappielloantonio.tempo.subsonic.models import android.os.Parcelable import androidx.annotation.Keep import kotlinx.parcelize.Parcelize +import com.google.gson.annotations.SerializedName @Keep @Parcelize @@ -10,5 +11,6 @@ class InternetRadioStation( var id: String? = null, var name: String? = null, var streamUrl: String? = null, + @SerializedName("homePageUrl", alternate = ["homepageUrl"]) var homePageUrl: String? = null, ) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/InternetRadioStationAdapter.java b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/InternetRadioStationAdapter.java index b4bef70b..a9db373b 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/InternetRadioStationAdapter.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/InternetRadioStationAdapter.java @@ -42,8 +42,13 @@ public class InternetRadioStationAdapter extends RecyclerView.Adapter onRefresh.run()); } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/cappielloantonio/tempo/util/MusicUtil.java b/app/src/main/java/com/cappielloantonio/tempo/util/MusicUtil.java index e04a268a..43212132 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/util/MusicUtil.java +++ b/app/src/main/java/com/cappielloantonio/tempo/util/MusicUtil.java @@ -377,4 +377,15 @@ public class MusicUtil { toFilter.addAll(filtered); } + + public static boolean isImageUrl(String url) { + if (url == null || url.isEmpty()) + return false; + String path = url.toLowerCase().trim().split("\\?")[0]; + + return path.endsWith(".jpg") || path.endsWith(".jpeg") || + path.endsWith(".png") || path.endsWith(".webp") || + path.endsWith(".gif") || path.endsWith(".bmp") || + path.endsWith(".svg"); + } } \ No newline at end of file diff --git a/app/src/tempus/java/com/cappielloantonio/tempo/service/MediaLibraryServiceCallback.kt b/app/src/tempus/java/com/cappielloantonio/tempo/service/MediaLibraryServiceCallback.kt index fb482496..a68fa713 100644 --- a/app/src/tempus/java/com/cappielloantonio/tempo/service/MediaLibraryServiceCallback.kt +++ b/app/src/tempus/java/com/cappielloantonio/tempo/service/MediaLibraryServiceCallback.kt @@ -375,7 +375,7 @@ open class MediaLibrarySessionCallback( automotiveRepository.internetRadioStations, { result -> val stations = result?.value - val selected = stations?.find { it.mediaId == firstItem?.mediaId } + val selected = stations?.find { item -> item.mediaId == firstItem?.mediaId } if (selected != null) { val updatedSelected = selected.buildUpon() .setMimeType(selected.localConfiguration?.mimeType)