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
This commit is contained in:
Denis Machard 2026-02-22 17:08:01 +01:00 committed by GitHub
parent a49f2b97a2
commit b403d69982
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 59 additions and 7 deletions

View file

@ -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)

View file

@ -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()

View file

@ -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

View file

@ -42,8 +42,13 @@ public class InternetRadioStationAdapter extends RecyclerView.Adapter<InternetRa
holder.item.internetRadioStationTitleTextView.setText(internetRadioStation.getName());
holder.item.internetRadioStationSubtitleTextView.setText(internetRadioStation.getStreamUrl());
String imageId = internetRadioStation.getHomePageUrl();
if (imageId == null || imageId.isEmpty()) {
imageId = internetRadioStation.getStreamUrl();
}
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), internetRadioStation.getStreamUrl(), CustomGlideRequest.ResourceType.Radio)
.from(holder.itemView.getContext(), imageId, CustomGlideRequest.ResourceType.Radio)
.build()
.into(holder.item.internetRadioStationCoverImageView);
}

View file

@ -4,6 +4,7 @@ import android.content.ContentResolver;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.util.Base64;
import androidx.annotation.OptIn;
import androidx.lifecycle.LifecycleOwner;
@ -25,6 +26,7 @@ import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
import java.nio.charset.StandardCharsets;
@OptIn(markerClass = UnstableApi.class)
public class MappingUtil {
@ -207,6 +209,16 @@ public class MappingUtil {
public static MediaItem mapInternetRadioStation(InternetRadioStation internetRadioStation) {
Uri uri = Uri.parse(internetRadioStation.getStreamUrl());
Uri artworkUri = null;
String homePageUrl = internetRadioStation.getHomePageUrl();
String coverArtId = null;
if (homePageUrl != null && !homePageUrl.isEmpty() && MusicUtil.isImageUrl(homePageUrl)) {
String encodedUrl = Base64.encodeToString(homePageUrl.getBytes(StandardCharsets.UTF_8),
Base64.URL_SAFE | Base64.NO_WRAP);
coverArtId = "ir_" + encodedUrl;
artworkUri = AlbumArtContentProvider.contentUri(coverArtId);
}
Bundle bundle = new Bundle();
bundle.putString("id", internetRadioStation.getId());
@ -214,13 +226,17 @@ public class MappingUtil {
bundle.putString("stationName", internetRadioStation.getName());
bundle.putString("uri", uri.toString());
bundle.putString("type", Constants.MEDIA_TYPE_RADIO);
bundle.putString("coverArtId", coverArtId);
if (homePageUrl != null) {
bundle.putString("homepageUrl", homePageUrl);
}
return new MediaItem.Builder()
.setMediaId(internetRadioStation.getId())
.setMediaMetadata(
new MediaMetadata.Builder()
.setTitle(internetRadioStation.getName())
.setMediaType(MediaMetadata.MEDIA_TYPE_RADIO_STATION)
.setArtworkUri(artworkUri)
.setExtras(bundle)
.setIsBrowsable(false)
.setIsPlayable(true)

View file

@ -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");
}
}

View file

@ -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)