Fixed thumbnails acting up and added support for library content thumbnail when casting.

This commit is contained in:
Koen J
2025-12-03 15:37:53 +01:00
parent eba995f87d
commit 961710cc8b
4 changed files with 50 additions and 32 deletions
@@ -444,15 +444,9 @@ fun addressScore(addr: InetAddress): Int {
fun <T> Enumeration<T>.toList(): List<T> = Collections.list(this) fun <T> Enumeration<T>.toList(): List<T> = Collections.list(this)
fun <T> RequestBuilder<T>.withMaxSizePx(maxSizePx: Int = 1920, useCenterCrop: Boolean = false): RequestBuilder<T> { fun <T> RequestBuilder<T>.withMaxSizePx(maxSizePx: Int = 1920): RequestBuilder<T> {
var builder = this return this
.downsample(DownsampleStrategy.AT_MOST) .downsample(DownsampleStrategy.AT_MOST)
.override(maxSizePx, maxSizePx) .override(maxSizePx, maxSizePx)
builder = if (useCenterCrop) { .centerInside()
builder.centerCrop()
} else {
builder.fitCenter()
}
return builder
} }
@@ -137,14 +137,7 @@ class HttpContentUriHandler(
var size: Long? = null var size: Long? = null
var lastModifiedMillis: Long? = null var lastModifiedMillis: Long? = null
val projection = arrayOf( resolver.query(uri, null, null, null, null)?.use { cursor ->
OpenableColumns.DISPLAY_NAME,
OpenableColumns.SIZE,
MediaStore.MediaColumns.DATE_MODIFIED,
MediaStore.MediaColumns.DATE_ADDED
)
resolver.query(uri, projection, null, null, null)?.use { cursor ->
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
if (nameIndex != -1 && !cursor.isNull(nameIndex)) { if (nameIndex != -1 && !cursor.isNull(nameIndex)) {
@@ -177,6 +170,10 @@ class HttpContentUriHandler(
} }
} }
if (displayName == null) {
displayName = uri.lastPathSegment
}
if (size == null) { if (size == null) {
try { try {
resolver.openAssetFileDescriptor(uri, "r")?.use { afd -> resolver.openAssetFileDescriptor(uri, "r")?.use { afd ->
@@ -188,7 +185,11 @@ class HttpContentUriHandler(
} catch (_: Exception) { } } catch (_: Exception) { }
} }
return ContentMeta(displayName = displayName, size = size, lastModifiedMillis = lastModifiedMillis) return ContentMeta(
displayName = displayName,
size = size,
lastModifiedMillis = lastModifiedMillis
)
} }
private fun parseRange(header: String, totalLength: Long): LongRange? { private fun parseRange(header: String, totalLength: Long): LongRange? {
@@ -239,9 +239,9 @@ abstract class StateCasting {
Logger.i(TAG, "Connect to device ${device.name}") Logger.i(TAG, "Connect to device ${device.name}")
} }
fun metadataFromVideo(video: IPlatformVideoDetails): Metadata { fun metadataFromVideo(video: IPlatformVideoDetails, videoThumbnailOverrideUrl: String? = null): Metadata {
return Metadata( return Metadata(
title = video.name, thumbnailUrl = video.thumbnails.getHQThumbnail() title = video.name, thumbnailUrl = videoThumbnailOverrideUrl ?: video.thumbnails.getHQThumbnail()
) )
} }
@@ -479,6 +479,16 @@ abstract class StateCasting {
val id = UUID.randomUUID(); val id = UUID.randomUUID();
val videoPath = "/video-${id}" val videoPath = "/video-${id}"
val videoUrl = url + videoPath; val videoUrl = url + videoPath;
val thumbnailPath = "/thumbnail-${id}"
val thumbnailUrl = url + thumbnailPath;
val thumbnailContentUrl = video.thumbnails.getHQThumbnail()
if (thumbnailContentUrl != null) {
_castServer.addHandlerWithAllowAllOptions(
HttpContentUriHandler("GET", thumbnailPath, contentResolver, thumbnailContentUrl.toUri())
.withHeader("Access-Control-Allow-Origin", "*"), true
).withTag("cast");
}
_castServer.addHandlerWithAllowAllOptions( _castServer.addHandlerWithAllowAllOptions(
HttpContentUriHandler("GET", videoPath, contentResolver, videoSource.contentUrl.toUri()) HttpContentUriHandler("GET", videoPath, contentResolver, videoSource.contentUrl.toUri())
@@ -486,7 +496,7 @@ abstract class StateCasting {
).withTag("cast"); ).withTag("cast");
Logger.i(TAG, "Casting local video (videoUrl: $videoUrl)."); Logger.i(TAG, "Casting local video (videoUrl: $videoUrl).");
ad.loadVideo("BUFFERED", videoSource.container, videoUrl, resumePosition, video.duration.toDouble(), speed, metadataFromVideo(video)); ad.loadVideo("BUFFERED", videoSource.container, videoUrl, resumePosition, video.duration.toDouble(), speed, metadataFromVideo(video, if (thumbnailContentUrl != null) thumbnailUrl else null));
return listOf(videoUrl); return listOf(videoUrl);
} }
@@ -498,6 +508,16 @@ abstract class StateCasting {
val id = UUID.randomUUID(); val id = UUID.randomUUID();
val audioPath = "/audio-${id}" val audioPath = "/audio-${id}"
val audioUrl = url + audioPath; val audioUrl = url + audioPath;
val thumbnailPath = "/thumbnail-${id}"
val thumbnailUrl = url + thumbnailPath;
val thumbnailContentUrl = video.thumbnails.getHQThumbnail()
if (thumbnailContentUrl != null) {
_castServer.addHandlerWithAllowAllOptions(
HttpContentUriHandler("GET", thumbnailPath, contentResolver, thumbnailContentUrl.toUri())
.withHeader("Access-Control-Allow-Origin", "*"), true
).withTag("cast");
}
_castServer.addHandlerWithAllowAllOptions( _castServer.addHandlerWithAllowAllOptions(
HttpContentUriHandler("GET", audioPath, contentResolver, audioSource.contentUrl.toUri()) HttpContentUriHandler("GET", audioPath, contentResolver, audioSource.contentUrl.toUri())
@@ -505,7 +525,7 @@ abstract class StateCasting {
).withTag("cast"); ).withTag("cast");
Logger.i(TAG, "Casting local audio (audioUrl: $audioUrl)."); Logger.i(TAG, "Casting local audio (audioUrl: $audioUrl).");
ad.loadVideo("BUFFERED", audioSource.container, audioUrl, resumePosition, video.duration.toDouble(), speed, metadataFromVideo(video)); ad.loadVideo("BUFFERED", audioSource.container, audioUrl, resumePosition, video.duration.toDouble(), speed, metadataFromVideo(video, if (thumbnailContentUrl != null) thumbnailUrl else null));
return listOf(audioUrl); return listOf(audioUrl);
} }
@@ -488,9 +488,10 @@ class StateLibrary {
""; "";
val albumContentUrl = if(albumId > 0) val albumArtBase = Uri.parse("content://media/external/audio/albumart")
ContentUris.withAppendedId(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, albumId)?.toString() val albumContentUrl = if (albumId > 0)
else null; ContentUris.withAppendedId(albumArtBase, albumId).toString()
else null
val dateObj = if(date > 0) val dateObj = if(date > 0)
OffsetDateTime.ofInstant(Instant.ofEpochSecond(date), ZoneOffset.UTC) OffsetDateTime.ofInstant(Instant.ofEpochSecond(date), ZoneOffset.UTC)
@@ -625,11 +626,12 @@ class Artist {
val numTracks = cursor.getInt(2); val numTracks = cursor.getInt(2);
val numAlbums = cursor.getInt(3); val numAlbums = cursor.getInt(3);
val idLong = id.toLongOrNull(); val idLong = id.toLongOrNull()
val uri = if(idLong != null) ContentUris.withAppendedId(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, idLong) else null; val uri = if (idLong != null)
ContentUris.withAppendedId(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, idLong)
else null
return Artist(artist, numTracks, numAlbums, null, id, uri?.toString()); return Artist(artist, numTracks, numAlbums, null, id, uri?.toString()) }
}
fun getArtist(id: Long): Artist? { fun getArtist(id: Long): Artist? {
val resolver = StateApp.instance.contextOrNull?.contentResolver; val resolver = StateApp.instance.contextOrNull?.contentResolver;
@@ -733,9 +735,10 @@ class Album {
val numTracks = cursor.getInt(2); val numTracks = cursor.getInt(2);
val artist = cursor.getString(3); val artist = cursor.getString(3);
val idLong = id.toLongOrNull(); val idLong = id.toLongOrNull()
val uri = if(idLong != null) ContentUris.withAppendedId(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, idLong) else null; val albumArtBase = Uri.parse("content://media/external/audio/albumart")
return Album(album, numTracks, artist, id, uri?.toString()); val uri = if (idLong != null) ContentUris.withAppendedId(albumArtBase, idLong) else null
return Album(album, numTracks, artist, id, uri?.toString())
} }
fun getAlbumTracks(albumId: Long): List<IPlatformVideo> { fun getAlbumTracks(albumId: Long): List<IPlatformVideo> {