From 45f621763abe83ac18d86de0ca5d34648b142f0f Mon Sep 17 00:00:00 2001 From: Koen J Date: Mon, 1 Dec 2025 14:20:41 +0100 Subject: [PATCH] Fixed thumbnail to consider max size 1920 and fitcenter and implemented fixes for pagination of library. --- .../java/com/futo/platformplayer/Utility.kt | 17 +- .../fragment/mainactivity/main/FeedView.kt | 87 +++---- .../main/LibraryVideosFragment.kt | 1 - .../main/RemotePlaylistFragment.kt | 2 +- .../fragment/mainactivity/main/ShortView.kt | 3 +- .../mainactivity/main/VideoDetailView.kt | 3 +- .../mainactivity/main/VideoListEditorView.kt | 3 +- .../futo/platformplayer/images/GlideHelper.kt | 3 +- .../services/MediaPlaybackService.kt | 3 +- .../platformplayer/states/StateLibrary.kt | 217 +++++++++++++----- .../states/StateNotifications.kt | 3 +- .../views/adapters/PlaylistsViewHolder.kt | 3 +- .../adapters/VideoListEditorViewHolder.kt | 3 +- .../viewholders/LocalVideoTileViewHolder.kt | 3 +- .../platformplayer/views/casting/CastView.kt | 3 +- .../views/items/ActiveDownloadItem.kt | 2 +- .../views/items/PlaylistDownloadItem.kt | 3 +- .../views/video/FutoThumbnailPlayer.kt | 3 +- .../views/video/FutoVideoPlayer.kt | 6 +- .../views/videometa/UpNextView.kt | 3 +- 20 files changed, 242 insertions(+), 129 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/Utility.kt b/app/src/main/java/com/futo/platformplayer/Utility.kt index 108fd93e..b154cb67 100644 --- a/app/src/main/java/com/futo/platformplayer/Utility.kt +++ b/app/src/main/java/com/futo/platformplayer/Utility.kt @@ -43,6 +43,8 @@ import java.util.concurrent.ThreadLocalRandom import java.util.zip.GZIPInputStream import java.util.zip.GZIPOutputStream import androidx.core.graphics.scale +import com.bumptech.glide.RequestBuilder +import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy private val _allowedCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz "; fun getRandomString(sizeOfRandomString: Int): String { @@ -440,4 +442,17 @@ fun addressScore(addr: InetAddress): Int { } } -fun Enumeration.toList(): List = Collections.list(this) \ No newline at end of file +fun Enumeration.toList(): List = Collections.list(this) + +fun RequestBuilder.withMaxSizePx(maxSizePx: Int = 1920, useCenterCrop: Boolean = false): RequestBuilder { + var builder = this + .downsample(DownsampleStrategy.AT_MOST) + .override(maxSizePx, maxSizePx) + builder = if (useCenterCrop) { + builder.centerCrop() + } else { + builder.fitCenter() + } + + return builder +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/FeedView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/FeedView.kt index a85e5951..4ba75e44 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/FeedView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/FeedView.kt @@ -55,7 +55,7 @@ abstract class FeedView : L protected val _toolbarContentView: LinearLayout; protected val _bottomContentView: LinearLayout; - private var _loading: Boolean = true; + private var _loading: Boolean = false; private val _pagerLock = Object(); private var _cache: ItemCache? = null; @@ -180,10 +180,9 @@ abstract class FeedView : L val visibleItemCount = _recyclerResults.childCount; val firstVisibleItem = recyclerData.layoutManager.findFirstVisibleItemPosition() - //Logger.i(TAG, "onScrolled loadNextPage visibleItemCount=$visibleItemCount firstVisibleItem=$visibleItemCount") + //Logger.i(TAG, "onScrolled loadNextPage(): firstVisibleItem=$firstVisibleItem visibleItemCount=$visibleItemCount visibleThreshold=$visibleThreshold recyclerData.results.size=${recyclerData.results.size}") - if (!_loading && firstVisibleItem + visibleItemCount + visibleThreshold >= recyclerData.results.size && firstVisibleItem > 0) { - //Logger.i(TAG, "onScrolled loadNextPage(): firstVisibleItem=$firstVisibleItem visibleItemCount=$visibleItemCount visibleThreshold=$visibleThreshold recyclerData.results.size=${recyclerData.results.size}") + if (!_loading && firstVisibleItem + visibleItemCount + visibleThreshold >= recyclerData.results.size) { loadNextPage(); } } @@ -197,57 +196,44 @@ abstract class FeedView : L } private fun ensureEnoughContentVisible(filteredResults: List) { - val canScroll = if (recyclerData.results.isEmpty()) false else { - val height = resources.displayMetrics.heightPixels; + _recyclerResults.post { + val canScroll = _recyclerResults.canScrollVertically(1) + Logger.i( + TAG, + "ensureEnoughContentVisible loadNextPage canScroll=$canScroll _automaticNextPageCounter=$_automaticNextPageCounter" + ) + if (!canScroll || filteredResults.isEmpty()) { + _automaticNextPageCounter++ + if (_automaticNextPageCounter < _automaticBackoff.size) { + if (_automaticNextPageCounter > 0) { + val automaticNextPageCounterSaved = _automaticNextPageCounter; + fragment.lifecycleScope.launch(Dispatchers.Default) { + val backoff = _automaticBackoff[Math.min( + _automaticBackoff.size - 1, + _automaticNextPageCounter + )]; - val layoutManager = recyclerData.layoutManager - val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition() - val firstVisibleItemView = if(firstVisibleItemPosition != RecyclerView.NO_POSITION) layoutManager.findViewByPosition(firstVisibleItemPosition) else null; - val lastVisibleItemPosition = layoutManager.findLastCompletelyVisibleItemPosition(); - val lastVisibleItemView = if(lastVisibleItemPosition != RecyclerView.NO_POSITION) layoutManager.findViewByPosition(lastVisibleItemPosition) else null; - val rows = if(recyclerData.layoutManager is GridLayoutManager) Math.max(1, recyclerData.results.size / recyclerData.layoutManager.spanCount) else 1; - val rowsHeight = (firstVisibleItemView?.height ?: 0) * rows; - if(lastVisibleItemView != null && lastVisibleItemPosition == (recyclerData.results.size - 1)) { - false; - } - else if (firstVisibleItemView != null && height != null && rowsHeight < height) { - false; - } else { - true; - } - } - - Logger.i(TAG, "ensureEnoughContentVisible loadNextPage canScroll=$canScroll _automaticNextPageCounter=$_automaticNextPageCounter") - if (!canScroll || filteredResults.isEmpty()) { - _automaticNextPageCounter++ - if(_automaticNextPageCounter < _automaticBackoff.size) { - if(_automaticNextPageCounter > 0) { - val automaticNextPageCounterSaved = _automaticNextPageCounter; - fragment.lifecycleScope.launch(Dispatchers.Default) { - val backoff = _automaticBackoff[Math.min(_automaticBackoff.size - 1, _automaticNextPageCounter)]; - - withContext(Dispatchers.Main) { - setLoading(true); - } - delay(backoff.toLong()); - if(automaticNextPageCounterSaved == _automaticNextPageCounter) { withContext(Dispatchers.Main) { - loadNextPage(); + setLoading(true); + } + delay(backoff.toLong()); + if (automaticNextPageCounterSaved == _automaticNextPageCounter) { + withContext(Dispatchers.Main) { + loadNextPage(); + } + } else { + withContext(Dispatchers.Main) { + setLoading(false); + } } } - else { - withContext(Dispatchers.Main) { - setLoading(false); - } - } - } + } else + loadNextPage(); } - else - loadNextPage(); + } else { + Logger.i(TAG, "ensureEnoughContentVisible automaticNextPageCounter reset"); + _automaticNextPageCounter = 0; } - } else { - Logger.i(TAG, "ensureEnoughContentVisible automaticNextPageCounter reset"); - _automaticNextPageCounter = 0; } } fun resetAutomaticNextPageCounter(){ @@ -484,6 +470,7 @@ abstract class FeedView : L recyclerData.resultsUnfiltered.addAll(toAdd); recyclerData.adapter.notifyDataSetChanged(); recyclerData.loadedFeedStyle = feedStyle; + setLoading(false) if(pager.hasMorePages()) ensureEnoughContentVisible(filteredResults) } @@ -519,7 +506,7 @@ abstract class FeedView : L synchronized(_pagerLock) { val pager: TPager = recyclerData.pager ?: return; val hasMorePages = pager.hasMorePages(); - Logger.i(TAG, "loadNextPage() hasMorePages=$hasMorePages, page size=${pager.getResults().size}"); + //Logger.i(TAG, "loadNextPage() hasMorePages=$hasMorePages, page size=${pager.getResults().size}"); //loadCachedPage(); if (pager.hasMorePages()) { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryVideosFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryVideosFragment.kt index 43ca3700..0a9f8bf7 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryVideosFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryVideosFragment.kt @@ -96,7 +96,6 @@ class LibraryVideosFragment : MainFragment() { fun onShown() { val initialAlbums = StateLibrary.instance.getAlbums(); Logger.i(TAG, "Initial album count: " + initialAlbums.size); - val buckets = StateLibrary.instance.getVideoBucketNames(); setPager(StateLibrary.instance.getVideos(fragment._toggleBuckets)); } diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/RemotePlaylistFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/RemotePlaylistFragment.kt index 7e819f72..0b3a0d6b 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/RemotePlaylistFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/RemotePlaylistFragment.kt @@ -364,7 +364,7 @@ class RemotePlaylistFragment : MainFragment() { _imagePlaylistThumbnail.let { Glide.with(it) .load(video.thumbnails.getHQThumbnail()) - .downsample(DownsampleStrategy.AT_MOST).override(1080, 1080) + .withMaxSizePx() .placeholder(R.drawable.placeholder_video_thumbnail) .crossfade() .into(it); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ShortView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ShortView.kt index 19be4777..753fcb81 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ShortView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ShortView.kt @@ -78,6 +78,7 @@ import com.futo.platformplayer.views.video.FutoShortPlayer import com.futo.platformplayer.views.video.FutoVideoPlayerBase import com.futo.platformplayer.views.video.FutoVideoPlayerBase.Companion.PREFERED_AUDIO_CONTAINERS import com.futo.platformplayer.views.video.FutoVideoPlayerBase.Companion.PREFERED_VIDEO_CONTAINERS +import com.futo.platformplayer.withMaxSizePx import com.futo.polycentric.core.ApiMethods import com.futo.polycentric.core.ContentType import com.futo.polycentric.core.Models @@ -859,7 +860,7 @@ class ShortView : FrameLayout { val thumbnail = videoDetails.thumbnails.getHQThumbnail() if (videoSource == null && !thumbnail.isNullOrBlank()) Glide.with(context).asBitmap() - .load(thumbnail).downsample(DownsampleStrategy.AT_MOST).override(1080, 1080).into(object : CustomTarget() { + .load(thumbnail).withMaxSizePx().into(object : CustomTarget() { override fun onResourceReady(resource: Bitmap, transition: Transition?) { player.setArtwork(resource.toDrawable(resources)) } diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index 3e9319a7..5e18f335 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -162,6 +162,7 @@ import com.futo.platformplayer.views.subscriptions.SubscribeButton import com.futo.platformplayer.views.video.FutoVideoPlayer import com.futo.platformplayer.views.video.FutoVideoPlayerBase import com.futo.platformplayer.views.videometa.UpNextView +import com.futo.platformplayer.withMaxSizePx import com.futo.polycentric.core.ApiMethods import com.futo.polycentric.core.ContentType import com.futo.polycentric.core.Models @@ -2050,7 +2051,7 @@ class VideoDetailView : ConstraintLayout { } else { val thumbnail = video.thumbnails.getHQThumbnail(); if ((videoSource == null) && !thumbnail.isNullOrBlank()) // || _player.isAudioMode - Glide.with(context).asBitmap().load(thumbnail).downsample(DownsampleStrategy.AT_MOST).override(1080, 1080) + Glide.with(context).asBitmap().load(thumbnail).withMaxSizePx() .into(object: CustomTarget() { override fun onResourceReady(resource: Bitmap, transition: Transition?) { _player.setArtwork(BitmapDrawable(resources, resource)); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoListEditorView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoListEditorView.kt index 9ae8eeea..830df866 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoListEditorView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoListEditorView.kt @@ -29,6 +29,7 @@ import com.futo.platformplayer.toHumanDuration import com.futo.platformplayer.toHumanTime import com.futo.platformplayer.views.SearchView import com.futo.platformplayer.views.lists.VideoListEditorView +import com.futo.platformplayer.withMaxSizePx abstract class VideoListEditorView : LinearLayout { private var _videoListEditorView: VideoListEditorView; @@ -212,7 +213,7 @@ abstract class VideoListEditorView : LinearLayout { _imagePlaylistThumbnail.let { Glide.with(it) .load(video.thumbnails.getHQThumbnail()) - .downsample(DownsampleStrategy.AT_MOST).override(1080, 1080) + .withMaxSizePx() .placeholder(R.drawable.placeholder_video_thumbnail) .crossfade() .into(it); diff --git a/app/src/main/java/com/futo/platformplayer/images/GlideHelper.kt b/app/src/main/java/com/futo/platformplayer/images/GlideHelper.kt index bb0cdf38..842fd5c6 100644 --- a/app/src/main/java/com/futo/platformplayer/images/GlideHelper.kt +++ b/app/src/main/java/com/futo/platformplayer/images/GlideHelper.kt @@ -7,6 +7,7 @@ import com.bumptech.glide.RequestBuilder import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions import com.futo.platformplayer.api.media.models.Thumbnails +import com.futo.platformplayer.withMaxSizePx class GlideHelper { @@ -15,7 +16,7 @@ class GlideHelper { fun ImageView.loadThumbnails(thumbnails: Thumbnails, isHQ: Boolean = true, continuation: ((RequestBuilder) -> Unit)? = null) { val url = if(isHQ) thumbnails.getHQThumbnail() ?: thumbnails.getLQThumbnail() else thumbnails.getLQThumbnail(); - val req = Glide.with(this).load(url).downsample(DownsampleStrategy.AT_MOST).override(1080, 1080); + val req = Glide.with(this).load(url).withMaxSizePx() if (thumbnails.hasMultiple() && false) { //TODO: Resolve issue where fallback triggered on second loads? val fallbackUrl = if (isHQ) thumbnails.getLQThumbnail() else thumbnails.getHQThumbnail(); diff --git a/app/src/main/java/com/futo/platformplayer/services/MediaPlaybackService.kt b/app/src/main/java/com/futo/platformplayer/services/MediaPlaybackService.kt index a3abc9ab..0ad801bc 100644 --- a/app/src/main/java/com/futo/platformplayer/services/MediaPlaybackService.kt +++ b/app/src/main/java/com/futo/platformplayer/services/MediaPlaybackService.kt @@ -39,6 +39,7 @@ import com.futo.platformplayer.receivers.MediaControlReceiver import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.states.StatePlayer import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.withMaxSizePx class MediaPlaybackService : Service() { private val TAG = "MediaPlaybackService"; @@ -225,7 +226,7 @@ class MediaPlaybackService : Service() { val tag = video; Glide.with(this).asBitmap() .load(thumbnail) - .downsample(DownsampleStrategy.AT_MOST).override(1080, 1080) + .withMaxSizePx() .into(object: CustomTarget() { override fun onResourceReady(resource: Bitmap,transition: Transition?) { if (tag != _notif_last_video) return diff --git a/app/src/main/java/com/futo/platformplayer/states/StateLibrary.kt b/app/src/main/java/com/futo/platformplayer/states/StateLibrary.kt index 2a5c4c8e..9176b8ae 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateLibrary.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateLibrary.kt @@ -1,10 +1,12 @@ package com.futo.platformplayer.states +import android.content.ContentResolver import android.content.ContentUris import android.content.Intent import android.database.Cursor import android.net.Uri import android.os.Build +import android.os.Bundle import android.provider.MediaStore import android.provider.MediaStore.Audio.Artists import android.webkit.MimeTypeMap @@ -154,34 +156,101 @@ class StateLibrary { fun getArtist(id: Long): Artist? { return Artist.getArtist(id); } + fun getVideos( + buckets: List? = null, + pageSize: Int = 20 + ): IPager { + val resolver = StateApp.instance.contextOrNull?.contentResolver ?: return EmptyPager() + val selection: String? + val selectionArgs: Array? - fun getVideos(buckets: List? = null): IPager { - var query = if(buckets != null) "${MediaStore.Video.Media.BUCKET_DISPLAY_NAME} IN " + "(" + buckets.map { "'${it}'" }.joinToString(",") + ")" else null; - val cursor = StateApp.instance.contextOrNull?.contentResolver?.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, PROJECTION_VIDEO, - query, - null, - MediaStore.Video.Media.DATE_ADDED + " DESC") ?: return EmptyPager(); + if (!buckets.isNullOrEmpty()) { + val placeholders = buckets.joinToString(",") { "?" } + selection = "${MediaStore.Video.Media.BUCKET_DISPLAY_NAME} IN ($placeholders)" + selectionArgs = buckets.toTypedArray() + } else { + selection = null + selectionArgs = null + } - //Ongoing usage of cursor..todo disposal - //return cursor.use { - cursor.moveToFirst(); - val list = mutableListOf() - while(!cursor.isAfterLast && list.size < 10) { - list.add(videoFromCursor(cursor)); - cursor.moveToNext(); + val collectionUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL) + } else { + MediaStore.Video.Media.EXTERNAL_CONTENT_URI + } + + var nextPageIndex = 0 + fun loadPage(pageIndex: Int): List { + Logger.i(TAG, "loadPage $pageIndex") + val offset = pageIndex * pageSize + + val queryArgs = Bundle().apply { + selection?.let { + putString(ContentResolver.QUERY_ARG_SQL_SELECTION, it) + } + selectionArgs?.let { + putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, it) + } + + putStringArray( + ContentResolver.QUERY_ARG_SORT_COLUMNS, + arrayOf( + MediaStore.Video.Media.DATE_ADDED, + MediaStore.Video.Media._ID + ) + ) + putInt( + ContentResolver.QUERY_ARG_SORT_DIRECTION, + ContentResolver.QUERY_SORT_DIRECTION_DESCENDING + ) + + putInt(ContentResolver.QUERY_ARG_LIMIT, pageSize) + putInt(ContentResolver.QUERY_ARG_OFFSET, offset) } - return AdhocPager({ - val list = mutableListOf() - while(!cursor.isAfterLast && list.size < 10) { - list.add(videoFromCursor(cursor)); - cursor.moveToNext(); + val cursor = resolver.query( + collectionUri, + PROJECTION_VIDEO, + queryArgs, + null + ) + + if (cursor == null) { + Logger.i(TAG, "loadPage $pageIndex null, returning empty list") + return emptyList() + } + + cursor.use { c -> + if (!c.moveToFirst()) { + Logger.i(TAG, "loadPage $pageIndex moveToFirst failed, returning empty list") + return emptyList() } - Logger.i(TAG, "Videos nextPage: ${list.size}") - return@AdhocPager list; - }, list); - //} + + val list = ArrayList(pageSize) + do { + list.add(videoFromCursor(c)) + } while (c.moveToNext() && list.size < pageSize) + + Logger.i(TAG, "loadPage $pageIndex found ${list.size} items") + return list + } + } + + val firstPage = loadPage(0) + if (firstPage.isEmpty()) { + return EmptyPager() + } + nextPageIndex = 1 + + return AdhocPager({ + val page = loadPage(nextPageIndex) + nextPageIndex++ + + Logger.i(TAG, "loadPage nextPage: ${page.size}") + page + }, firstPage) } + fun getRecentVideos(buckets: List? = null, count: Int = 20): List { val videoPager = getVideos(buckets); val items = mutableListOf(); @@ -193,48 +262,80 @@ class StateLibrary { return items; } - private var _cacheBucketNames: List? = null; - fun getVideoBucketNames(): List { - if(Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) - return listOf(); - if(_cacheBucketNames != null) - return _cacheBucketNames ?: listOf(); - try { - val cur: Cursor = StateApp.instance.contextOrNull?.contentResolver?.query( - MediaStore.Video.Media.EXTERNAL_CONTENT_URI, arrayOf( - MediaStore.Video.Media.BUCKET_ID, - MediaStore.Video.Media.BUCKET_DISPLAY_NAME, - ), null, null, null - ) ?: return listOf(); + @Volatile + private var _cachedVideoBuckets: List? = null + private val _bucketCacheLock = Any() - return cur.use { - val buckets = mutableListOf(); - val list = HashSet(); - if (cur.moveToFirst()) { - var id: Long; - var bucket: String - do { - try { - id = cur.getLong(0); - bucket = cur.getStringOrNull(1) ?: continue; - if (!list.contains(id)) { - list.add(id); - buckets.add(Bucket(id, bucket)); - } - } catch (ex: Throwable) { - Logger.e(TAG, "Failed to parse bucket due to ${ex.message}", ex); - } - } while (cur.moveToNext()) + fun getVideoBucketNames(forceRefresh: Boolean = false): List { + if (!forceRefresh) { + _cachedVideoBuckets?.let { return it } + } + + val resolver = StateApp.instance.contextOrNull?.contentResolver + ?: return emptyList() + + val projection = arrayOf( + MediaStore.Video.VideoColumns.BUCKET_ID, + MediaStore.Video.VideoColumns.BUCKET_DISPLAY_NAME + ) + + val sortOrder = "${MediaStore.Video.VideoColumns.BUCKET_DISPLAY_NAME} COLLATE NOCASE ASC" + val loadedBuckets: List = try { + resolver.query( + MediaStore.Video.Media.EXTERNAL_CONTENT_URI, + projection, + null, + null, + sortOrder + )?.use { cursor -> + if (!cursor.moveToFirst()) { + return@use emptyList() } - _cacheBucketNames = buckets.toList() - return@use _cacheBucketNames ?: listOf(); + + val idxId = cursor.getColumnIndexOrThrow(MediaStore.Video.VideoColumns.BUCKET_ID) + val idxName = cursor.getColumnIndexOrThrow(MediaStore.Video.VideoColumns.BUCKET_DISPLAY_NAME) + val seenIds = HashSet() + val buckets = ArrayList() + + do { + try { + val id = cursor.getLong(idxId) + if (!seenIds.add(id)) { + continue + } + + val name = cursor.getStringOrNull(idxName) ?: continue + buckets.add(Bucket(id, name)) + } catch (e: Exception) { + Logger.e(TAG, "Failed to parse video bucket row: ${e.message}", e) + } + } while (cursor.moveToNext()) + + buckets + } ?: emptyList() + } catch (e: Exception) { + Logger.e(TAG, "Buckets loading failed, returning empty: ${e.message}", e) + emptyList() + } + + if (loadedBuckets.isEmpty()) { + if (!forceRefresh) { + _cachedVideoBuckets?.let { return it } } + return emptyList() } - catch(ex: Throwable) { - Logger.e(TAG, "Buckets loading failed, returning empty"); - return listOf(); + + synchronized(_bucketCacheLock) { + if (!forceRefresh) { + _cachedVideoBuckets?.let { return it } + } + _cachedVideoBuckets = loadedBuckets + return loadedBuckets } } + fun invalidateVideoBucketNamesCache() { + _cachedVideoBuckets = null + } companion object { diff --git a/app/src/main/java/com/futo/platformplayer/states/StateNotifications.kt b/app/src/main/java/com/futo/platformplayer/states/StateNotifications.kt index 7e011eae..11b9719f 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateNotifications.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateNotifications.kt @@ -23,6 +23,7 @@ import com.futo.platformplayer.serializers.PlatformContentSerializer import com.futo.platformplayer.stores.FragmentedStorage import com.futo.platformplayer.toHumanNowDiffString import com.futo.platformplayer.toHumanNowDiffStringMinDay +import com.futo.platformplayer.withMaxSizePx import java.time.OffsetDateTime class StateNotifications { @@ -97,7 +98,7 @@ class StateNotifications { if(thumbnail != null) Glide.with(context).asBitmap() .load(thumbnail) - .downsample(DownsampleStrategy.AT_MOST).override(1080, 1080) + .withMaxSizePx() .into(object: CustomTarget() { override fun onResourceReady(resource: Bitmap, transition: Transition?) { notifyNewContent(context, manager, notificationChannel, id, content, resource); diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistsViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistsViewHolder.kt index d6f0a001..35d01f13 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistsViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistsViewHolder.kt @@ -12,6 +12,7 @@ import com.futo.platformplayer.R import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.images.GlideHelper.Companion.crossfade import com.futo.platformplayer.models.Playlist +import com.futo.platformplayer.withMaxSizePx class PlaylistsViewHolder : ViewHolder { private val _root: ConstraintLayout; @@ -45,7 +46,7 @@ class PlaylistsViewHolder : ViewHolder { if (p.videos.isNotEmpty()) { Glide.with(_imageThumbnail) .load(p.videos[0].thumbnails.getMinimumThumbnail(380)) - .downsample(DownsampleStrategy.AT_MOST).override(1080, 1080) + .withMaxSizePx() .placeholder(R.drawable.placeholder_video_thumbnail) .crossfade() .into(_imageThumbnail); diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/VideoListEditorViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/VideoListEditorViewHolder.kt index 1ece9278..61e840d6 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/VideoListEditorViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/VideoListEditorViewHolder.kt @@ -24,6 +24,7 @@ import com.futo.platformplayer.toHumanNumber import com.futo.platformplayer.toHumanTime import com.futo.platformplayer.views.others.ProgressBar import com.futo.platformplayer.views.platform.PlatformIndicator +import com.futo.platformplayer.withMaxSizePx class VideoListEditorViewHolder : ViewHolder { private val _root: ConstraintLayout; @@ -90,7 +91,7 @@ class VideoListEditorViewHolder : ViewHolder { fun bind(v: IPlatformVideo, canEdit: Boolean) { Glide.with(_imageThumbnail) .load(v.thumbnails.getHQThumbnail()) - .downsample(DownsampleStrategy.AT_MOST).override(1080, 1080) + .withMaxSizePx() .placeholder(R.drawable.placeholder_video_thumbnail) .crossfade() .into(_imageThumbnail); diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/LocalVideoTileViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/LocalVideoTileViewHolder.kt index 2c32b32f..609220f8 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/LocalVideoTileViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/LocalVideoTileViewHolder.kt @@ -17,6 +17,7 @@ import com.futo.platformplayer.states.Artist import com.futo.platformplayer.toHumanNowDiffString import com.futo.platformplayer.toHumanTime import com.futo.platformplayer.views.adapters.AnyAdapter +import com.futo.platformplayer.withMaxSizePx import com.google.android.material.imageview.ShapeableImageView @@ -50,7 +51,7 @@ class LocalVideoTileViewHolder(val _viewGroup: ViewGroup) : AnyAdapter.AnyViewHo Glide.with(it) .load(content.thumbnails.getHQThumbnail()) .placeholder(R.drawable.unknown_music) - .downsample(DownsampleStrategy.AT_MOST).override(1080, 1080) + .withMaxSizePx() .into(it) else Glide.with(it).load(R.drawable.unknown_music).into(it); diff --git a/app/src/main/java/com/futo/platformplayer/views/casting/CastView.kt b/app/src/main/java/com/futo/platformplayer/views/casting/CastView.kt index b8b67528..7095eca2 100644 --- a/app/src/main/java/com/futo/platformplayer/views/casting/CastView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/casting/CastView.kt @@ -33,6 +33,7 @@ import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.states.StatePlayer import com.futo.platformplayer.views.TargetTapLoaderView import com.futo.platformplayer.views.behavior.GestureControlView +import com.futo.platformplayer.withMaxSizePx import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -307,7 +308,7 @@ class CastView : ConstraintLayout { Glide.with(_thumbnail) .load(video.thumbnails.getHQThumbnail()) .placeholder(R.drawable.placeholder_video_thumbnail) - .downsample(DownsampleStrategy.AT_MOST).override(1080, 1080) + .withMaxSizePx() .into(_thumbnail); _textPosition.text = (position * 1000).formatDuration(); _textDuration.text = (video.duration * 1000).formatDuration(); diff --git a/app/src/main/java/com/futo/platformplayer/views/items/ActiveDownloadItem.kt b/app/src/main/java/com/futo/platformplayer/views/items/ActiveDownloadItem.kt index af6cd6c0..a45bfdeb 100644 --- a/app/src/main/java/com/futo/platformplayer/views/items/ActiveDownloadItem.kt +++ b/app/src/main/java/com/futo/platformplayer/views/items/ActiveDownloadItem.kt @@ -61,8 +61,8 @@ class ActiveDownloadItem: LinearLayout { } Glide.with(_videoImage) - .downsample(DownsampleStrategy.AT_MOST).override(1080, 1080) .load(download.thumbnail) + .withMaxSizePx() .crossfade() .into(_videoImage); diff --git a/app/src/main/java/com/futo/platformplayer/views/items/PlaylistDownloadItem.kt b/app/src/main/java/com/futo/platformplayer/views/items/PlaylistDownloadItem.kt index df519035..a9d2d830 100644 --- a/app/src/main/java/com/futo/platformplayer/views/items/PlaylistDownloadItem.kt +++ b/app/src/main/java/com/futo/platformplayer/views/items/PlaylistDownloadItem.kt @@ -9,6 +9,7 @@ import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy import com.futo.platformplayer.R import com.futo.platformplayer.images.GlideHelper.Companion.crossfade import com.futo.platformplayer.models.PlaylistDownloaded +import com.futo.platformplayer.withMaxSizePx class PlaylistDownloadItem(context: Context, playlistName: String, playlistThumbnail: String?, val obj: Any): LinearLayout(context) { init { inflate(context, R.layout.list_downloaded_playlist, this) } @@ -20,7 +21,7 @@ class PlaylistDownloadItem(context: Context, playlistName: String, playlistThumb imageText.text = playlistName; Glide.with(imageView) .load(playlistThumbnail) - .downsample(DownsampleStrategy.AT_MOST).override(1080, 1080) + .withMaxSizePx() .crossfade() .into(imageView); } diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoThumbnailPlayer.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoThumbnailPlayer.kt index 400a4825..3a6457fd 100644 --- a/app/src/main/java/com/futo/platformplayer/views/video/FutoThumbnailPlayer.kt +++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoThumbnailPlayer.kt @@ -26,6 +26,7 @@ import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails import com.futo.platformplayer.helpers.VideoHelper import com.futo.platformplayer.toHumanTime import com.futo.platformplayer.video.PlayerManager +import com.futo.platformplayer.withMaxSizePx class FutoThumbnailPlayer : FutoVideoPlayerBase { @@ -136,7 +137,7 @@ class FutoThumbnailPlayer : FutoVideoPlayerBase { if (videoSource == null && audioSource != null) { val thumbnail = video.thumbnails.getHQThumbnail(); if (!thumbnail.isNullOrBlank()) { - Glide.with(videoView).asBitmap().load(thumbnail).downsample(DownsampleStrategy.AT_MOST).override(1080, 1080).into(_loadArtwork); + Glide.with(videoView).asBitmap().load(thumbnail).withMaxSizePx().into(_loadArtwork); } else { Glide.with(videoView).clear(_loadArtwork); setArtwork(null); diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt index 52fa0c63..af666ca9 100644 --- a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt +++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt @@ -54,6 +54,7 @@ import com.futo.platformplayer.states.StatePlayer import com.futo.platformplayer.views.TargetTapLoaderView import com.futo.platformplayer.views.behavior.GestureControlView import com.futo.platformplayer.views.others.ProgressBar +import com.futo.platformplayer.withMaxSizePx import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope @@ -928,11 +929,9 @@ class FutoVideoPlayer : FutoVideoPlayerBase { override fun switchToAudioMode(video: IPlatformVideoDetails?) { super.switchToAudioMode(video) - //This causes issues, and is in general confusing, needs improvements - /* val thumbnail = video?.thumbnails?.getHQThumbnail() if (!thumbnail.isNullOrBlank()) { - Glide.with(context).asBitmap().load(thumbnail) + Glide.with(context).asBitmap().load(thumbnail).withMaxSizePx() .into(object : CustomTarget() { override fun onResourceReady( resource: Bitmap, @@ -946,6 +945,5 @@ class FutoVideoPlayer : FutoVideoPlayerBase { } }) } - */ } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/videometa/UpNextView.kt b/app/src/main/java/com/futo/platformplayer/views/videometa/UpNextView.kt index 5ba0c4bd..0b1ac688 100644 --- a/app/src/main/java/com/futo/platformplayer/views/videometa/UpNextView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/videometa/UpNextView.kt @@ -17,6 +17,7 @@ import com.futo.platformplayer.R import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.toHumanNowDiffString import com.futo.platformplayer.toHumanNumber +import com.futo.platformplayer.withMaxSizePx class UpNextView : LinearLayout { private val _layoutContainer: LinearLayout; @@ -161,7 +162,7 @@ class UpNextView : LinearLayout { _textChannelName.text = nextItem.author.name; Glide.with(_imageThumbnail) .load(nextItem.thumbnails.getHQThumbnail()) - .downsample(DownsampleStrategy.AT_MOST).override(1080, 1080) + .withMaxSizePx() .placeholder(R.drawable.placeholder_video_thumbnail) .into(_imageThumbnail); Glide.with(_imageChannelThumbnail)