From fcef783bbbd32124e4b761776497babf60c25bbe Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 27 Mar 2021 14:37:44 +0100 Subject: [PATCH 1/7] Replace UniversalImageLoader with Picasso --- app/build.gradle | 3 +- app/src/main/java/org/schabi/newpipe/App.java | 23 +-- .../java/org/schabi/newpipe/BaseFragment.java | 3 - .../org/schabi/newpipe/DownloaderImpl.java | 31 ---- .../org/schabi/newpipe/ImageDownloader.java | 48 ------ .../fragments/detail/VideoDetailFragment.java | 61 +++----- .../list/channel/ChannelFragment.java | 23 ++- .../list/playlist/PlaylistFragment.java | 12 +- .../newpipe/info_list/InfoItemBuilder.java | 7 - .../newpipe/info_list/StreamSegmentItem.kt | 9 +- .../holder/ChannelMiniInfoItemHolder.java | 7 +- .../holder/CommentsMiniInfoItemHolder.java | 19 +-- .../holder/PlaylistMiniInfoItemHolder.java | 6 +- .../holder/StreamMiniInfoItemHolder.java | 7 +- .../newpipe/local/LocalItemBuilder.java | 10 -- .../newpipe/local/feed/item/StreamItem.kt | 8 +- .../local/holder/LocalPlaylistItemHolder.java | 5 +- .../holder/LocalPlaylistStreamItemHolder.java | 6 +- .../LocalStatisticStreamItemHolder.java | 6 +- .../holder/RemotePlaylistItemHolder.java | 6 +- .../local/subscription/item/ChannelItem.kt | 8 +- .../item/PickerSubscriptionItem.kt | 9 +- .../org/schabi/newpipe/player/Player.java | 137 +++++++++--------- .../playqueue/PlayQueueItemBuilder.java | 7 +- .../SeekbarPreviewThumbnailHolder.java | 27 +--- .../SyncImageLoadingListener.java | 87 ----------- .../settings/ContentSettingsFragment.java | 29 ++-- .../settings/SelectChannelFragment.java | 14 +- .../settings/SelectPlaylistFragment.java | 18 +-- .../newpipe/util/ImageDisplayConstants.java | 65 --------- .../schabi/newpipe/util/NavigationHelper.java | 11 +- .../schabi/newpipe/util/PicassoHelper.java | 110 ++++++++++++++ 32 files changed, 294 insertions(+), 528 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/ImageDownloader.java delete mode 100644 app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SyncImageLoadingListener.java delete mode 100644 app/src/main/java/org/schabi/newpipe/util/ImageDisplayConstants.java create mode 100644 app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java diff --git a/app/build.gradle b/app/build.gradle index c4a8afea415..cf28230245f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -246,7 +246,8 @@ dependencies { // Circular ImageView implementation "de.hdodenhof:circleimageview:3.1.0" // Image loading - implementation "com.nostra13.universalimageloader:universal-image-loader:1.9.5" + //noinspection GradleDependency --> 2.8 is the last version, not 2.71828! + implementation "com.squareup.picasso:picasso:2.8" // Markdown library for Android implementation "io.noties.markwon:core:${markwonVersion}" diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index ddbac44224f..a7d9d7c26ee 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -11,10 +11,6 @@ import androidx.multidex.MultiDexApplication; import androidx.preference.PreferenceManager; -import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache; -import com.nostra13.universalimageloader.core.ImageLoader; -import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; - import org.acra.ACRA; import org.acra.config.ACRAConfigurationException; import org.acra.config.CoreConfiguration; @@ -28,6 +24,7 @@ import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.settings.NewPipeSettings; import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.StateSaver; @@ -65,9 +62,9 @@ */ public class App extends MultiDexApplication { - protected static final String TAG = App.class.toString(); - private static App app; public static final String PACKAGE_NAME = BuildConfig.APPLICATION_ID; + private static final String TAG = App.class.toString(); + private static App app; @Nullable private Disposable disposable = null; @@ -103,7 +100,9 @@ public void onCreate() { ServiceHelper.initServices(this); // Initialize image loader - ImageLoader.getInstance().init(getImageLoaderConfigurations(10, 50)); + PicassoHelper.init(this); + PicassoHelper.setShouldLoadImages(PreferenceManager.getDefaultSharedPreferences(this) + .getBoolean(getString(R.string.download_thumbnail_key), true)); configureRxJavaErrorHandler(); @@ -117,6 +116,7 @@ public void onTerminate() { disposable.dispose(); } super.onTerminate(); + PicassoHelper.terminate(); } protected Downloader getDownloader() { @@ -201,15 +201,6 @@ private void reportException(@NonNull final Throwable throwable) { }); } - private ImageLoaderConfiguration getImageLoaderConfigurations(final int memoryCacheSizeMb, - final int diskCacheSizeMb) { - return new ImageLoaderConfiguration.Builder(this) - .memoryCache(new LRULimitedMemoryCache(memoryCacheSizeMb * 1024 * 1024)) - .diskCacheSize(diskCacheSizeMb * 1024 * 1024) - .imageDownloader(new ImageDownloader(getApplicationContext())) - .build(); - } - /** * Called in {@link #attachBaseContext(Context)} after calling the {@code super} method. * Should be overridden if MultiDex is enabled, since it has to be initialized before ACRA. diff --git a/app/src/main/java/org/schabi/newpipe/BaseFragment.java b/app/src/main/java/org/schabi/newpipe/BaseFragment.java index 0d241277837..c8b6969c66a 100644 --- a/app/src/main/java/org/schabi/newpipe/BaseFragment.java +++ b/app/src/main/java/org/schabi/newpipe/BaseFragment.java @@ -10,14 +10,11 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; -import com.nostra13.universalimageloader.core.ImageLoader; - import icepick.Icepick; import icepick.State; import leakcanary.AppWatcher; public abstract class BaseFragment extends Fragment { - public static final ImageLoader IMAGE_LOADER = ImageLoader.getInstance(); protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()); protected final boolean DEBUG = MainActivity.DEBUG; protected AppCompatActivity activity; diff --git a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java index 90bb0e8259b..fde991655d6 100644 --- a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java +++ b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java @@ -17,7 +17,6 @@ import org.schabi.newpipe.util.TLSSocketFactoryCompat; import java.io.IOException; -import java.io.InputStream; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; @@ -194,36 +193,6 @@ public long getContentLength(final String url) throws IOException { } } - public InputStream stream(final String siteUrl) throws IOException { - try { - final okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder() - .method("GET", null).url(siteUrl) - .addHeader("User-Agent", USER_AGENT); - - final String cookies = getCookies(siteUrl); - if (!cookies.isEmpty()) { - requestBuilder.addHeader("Cookie", cookies); - } - - final okhttp3.Request request = requestBuilder.build(); - final okhttp3.Response response = client.newCall(request).execute(); - final ResponseBody body = response.body(); - - if (response.code() == 429) { - throw new ReCaptchaException("reCaptcha Challenge requested", siteUrl); - } - - if (body == null) { - response.close(); - return null; - } - - return body.byteStream(); - } catch (final ReCaptchaException e) { - throw new IOException(e.getMessage(), e.getCause()); - } - } - @Override public Response execute(@NonNull final Request request) throws IOException, ReCaptchaException { diff --git a/app/src/main/java/org/schabi/newpipe/ImageDownloader.java b/app/src/main/java/org/schabi/newpipe/ImageDownloader.java deleted file mode 100644 index ceae117778d..00000000000 --- a/app/src/main/java/org/schabi/newpipe/ImageDownloader.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.schabi.newpipe; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.res.Resources; - -import androidx.preference.PreferenceManager; - -import com.nostra13.universalimageloader.core.download.BaseImageDownloader; - -import org.schabi.newpipe.extractor.NewPipe; - -import java.io.IOException; -import java.io.InputStream; - -public class ImageDownloader extends BaseImageDownloader { - private final Resources resources; - private final SharedPreferences preferences; - private final String downloadThumbnailKey; - - public ImageDownloader(final Context context) { - super(context); - this.resources = context.getResources(); - this.preferences = PreferenceManager.getDefaultSharedPreferences(context); - this.downloadThumbnailKey = context.getString(R.string.download_thumbnail_key); - } - - private boolean isDownloadingThumbnail() { - return preferences.getBoolean(downloadThumbnailKey, true); - } - - @SuppressLint("ResourceType") - @Override - public InputStream getStream(final String imageUri, final Object extra) throws IOException { - if (isDownloadingThumbnail()) { - return super.getStream(imageUri, extra); - } else { - return resources.openRawResource(R.drawable.dummy_thumbnail_dark); - } - } - - protected InputStream getStreamFromNetwork(final String imageUri, final Object extra) - throws IOException { - final DownloaderImpl downloader = (DownloaderImpl) NewPipe.getDownloader(); - return downloader.stream(imageUri); - } -} diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index bc983fbe63e..e9d1a12e2da 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -48,9 +48,7 @@ import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.tabs.TabLayout; -import com.nostra13.universalimageloader.core.assist.FailReason; -import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; -import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; +import com.squareup.picasso.Callback; import org.schabi.newpipe.App; import org.schabi.newpipe.R; @@ -90,14 +88,14 @@ import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ExtractorHelper; -import org.schabi.newpipe.util.ImageDisplayConstants; -import org.schabi.newpipe.util.external_communication.KoreUtils; import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PermissionHelper; -import org.schabi.newpipe.util.external_communication.ShareUtils; +import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.ThemeHelper; +import org.schabi.newpipe.util.external_communication.KoreUtils; +import org.schabi.newpipe.util.external_communication.ShareUtils; import java.util.ArrayList; import java.util.Iterator; @@ -151,6 +149,8 @@ public final class VideoDetailFragment private static final String DESCRIPTION_TAB_TAG = "DESCRIPTION TAB"; private static final String EMPTY_TAB_TAG = "EMPTY TAB"; + private static final String PICASSO_VIDEO_DETAILS_TAG = "PICASSO_VIDEO_DETAILS_TAG"; + // tabs private boolean showComments; private boolean showRelatedItems; @@ -686,33 +686,23 @@ private View.OnTouchListener getOnControlsTouchListener() { } private void initThumbnailViews(@NonNull final StreamInfo info) { - binding.detailThumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark); - - if (!isEmpty(info.getThumbnailUrl())) { - final ImageLoadingListener onFailListener = new SimpleImageLoadingListener() { - @Override - public void onLoadingFailed(final String imageUri, final View view, - final FailReason failReason) { - showSnackBarError(new ErrorInfo(failReason.getCause(), UserAction.LOAD_IMAGE, - imageUri, info)); - } - }; - - IMAGE_LOADER.displayImage(info.getThumbnailUrl(), binding.detailThumbnailImageView, - ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, onFailListener); - } + PicassoHelper.loadThumbnail(info.getThumbnailUrl()).tag(PICASSO_VIDEO_DETAILS_TAG) + .into(binding.detailThumbnailImageView, new Callback() { + @Override + public void onSuccess() { + } - if (!isEmpty(info.getSubChannelAvatarUrl())) { - IMAGE_LOADER.displayImage(info.getSubChannelAvatarUrl(), - binding.detailSubChannelThumbnailView, - ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS); - } + @Override + public void onError(final Exception e) { + showSnackBarError(new ErrorInfo(e, UserAction.LOAD_IMAGE, + info.getThumbnailUrl(), info)); + } + }); - if (!isEmpty(info.getUploaderAvatarUrl())) { - IMAGE_LOADER.displayImage(info.getUploaderAvatarUrl(), - binding.detailUploaderThumbnailView, - ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS); - } + PicassoHelper.loadAvatar(info.getSubChannelAvatarUrl()).tag(PICASSO_VIDEO_DETAILS_TAG) + .into(binding.detailSubChannelThumbnailView); + PicassoHelper.loadAvatar(info.getUploaderAvatarUrl()).tag(PICASSO_VIDEO_DETAILS_TAG) + .into(binding.detailUploaderThumbnailView); } /*////////////////////////////////////////////////////////////////////////// @@ -1446,8 +1436,7 @@ public void showLoading() { } } - IMAGE_LOADER.cancelDisplayTask(binding.detailThumbnailImageView); - IMAGE_LOADER.cancelDisplayTask(binding.detailSubChannelThumbnailView); + PicassoHelper.cancelTag(PICASSO_VIDEO_DETAILS_TAG); binding.detailThumbnailImageView.setImageBitmap(null); binding.detailSubChannelThumbnailView.setImageBitmap(null); } @@ -2278,10 +2267,8 @@ private void updateOverlayData(@Nullable final String overlayTitle, binding.overlayTitleTextView.setText(isEmpty(overlayTitle) ? "" : overlayTitle); binding.overlayChannelTextView.setText(isEmpty(uploader) ? "" : uploader); binding.overlayThumbnail.setImageResource(R.drawable.dummy_thumbnail_dark); - if (!isEmpty(thumbnailUrl)) { - IMAGE_LOADER.displayImage(thumbnailUrl, binding.overlayThumbnail, - ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, null); - } + PicassoHelper.loadThumbnail(thumbnailUrl).tag(PICASSO_VIDEO_DETAILS_TAG) + .into(binding.overlayThumbnail); } private void setOverlayPlayPauseImage(final boolean playerIsPlaying) { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index bc67180214c..548ae7b2cdd 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -40,10 +40,10 @@ import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.util.ExtractorHelper; -import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.external_communication.ShareUtils; +import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.ThemeHelper; import java.util.ArrayList; @@ -66,7 +66,10 @@ public class ChannelFragment extends BaseListInfoFragment implements View.OnClickListener { + private static final int BUTTON_DEBOUNCE_INTERVAL = 100; + private static final String PICASSO_CHANNEL_TAG = "PICASSO_CHANNEL_TAG"; + private final CompositeDisposable disposables = new CompositeDisposable(); private Disposable subscribeButtonMonitor; @@ -421,10 +424,7 @@ public void onClick(final View v) { @Override public void showLoading() { super.showLoading(); - - IMAGE_LOADER.cancelDisplayTask(headerBinding.channelBannerImage); - IMAGE_LOADER.cancelDisplayTask(headerBinding.channelAvatarView); - IMAGE_LOADER.cancelDisplayTask(headerBinding.subChannelAvatarView); + PicassoHelper.cancelTag(PICASSO_CHANNEL_TAG); animate(headerBinding.channelSubscribeButton, false, 100); } @@ -433,13 +433,12 @@ public void handleResult(@NonNull final ChannelInfo result) { super.handleResult(result); headerBinding.getRoot().setVisibility(View.VISIBLE); - IMAGE_LOADER.displayImage(result.getBannerUrl(), headerBinding.channelBannerImage, - ImageDisplayConstants.DISPLAY_BANNER_OPTIONS); - IMAGE_LOADER.displayImage(result.getAvatarUrl(), headerBinding.channelAvatarView, - ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS); - IMAGE_LOADER.displayImage(result.getParentChannelAvatarUrl(), - headerBinding.subChannelAvatarView, - ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS); + PicassoHelper.loadBanner(result.getBannerUrl()).tag(PICASSO_CHANNEL_TAG) + .into(headerBinding.channelBannerImage); + PicassoHelper.loadAvatar(result.getAvatarUrl()).tag(PICASSO_CHANNEL_TAG) + .into(headerBinding.channelAvatarView); + PicassoHelper.loadAvatar(result.getParentChannelAvatarUrl()).tag(PICASSO_CHANNEL_TAG) + .into(headerBinding.subChannelAvatarView); headerBinding.channelSubscriberView.setVisibility(View.VISIBLE); if (result.getSubscriberCount() >= 0) { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index 824aa26126f..513fbbc91c9 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -41,7 +41,7 @@ import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue; import org.schabi.newpipe.util.ExtractorHelper; -import org.schabi.newpipe.util.ImageDisplayConstants; +import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.external_communication.KoreUtils; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; @@ -64,12 +64,16 @@ import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling; public class PlaylistFragment extends BaseListInfoFragment { + + private static final String PICASSO_PLAYLIST_TAG = "PICASSO_PLAYLIST_TAG"; + private CompositeDisposable disposables; private Subscription bookmarkReactor; private AtomicBoolean isBookmarkButtonReady; private RemotePlaylistManager remotePlaylistManager; private PlaylistRemoteEntity playlistEntity; + /*////////////////////////////////////////////////////////////////////////// // Views //////////////////////////////////////////////////////////////////////////*/ @@ -274,7 +278,7 @@ public void showLoading() { animate(headerBinding.getRoot(), false, 200); animateHideRecyclerViewAllowingScrolling(itemsList); - IMAGE_LOADER.cancelDisplayTask(headerBinding.uploaderAvatarView); + PicassoHelper.cancelTag(PICASSO_PLAYLIST_TAG); animate(headerBinding.uploaderLayout, false, 200); } @@ -317,8 +321,8 @@ public void handleResult(@NonNull final PlaylistInfo result) { R.drawable.ic_radio) ); } else { - IMAGE_LOADER.displayImage(avatarUrl, headerBinding.uploaderAvatarView, - ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS); + PicassoHelper.loadAvatar(avatarUrl).tag(PICASSO_PLAYLIST_TAG) + .into(headerBinding.uploaderAvatarView); } headerBinding.playlistStreamCount.setText(Localization diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java index ac7a3f49940..d78bf10769d 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java @@ -6,8 +6,6 @@ import androidx.annotation.NonNull; -import com.nostra13.universalimageloader.core.ImageLoader; - import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.channel.ChannelInfoItem; import org.schabi.newpipe.extractor.comments.CommentsInfoItem; @@ -51,7 +49,6 @@ public class InfoItemBuilder { private final Context context; - private final ImageLoader imageLoader = ImageLoader.getInstance(); private OnClickGesture onStreamSelectedListener; private OnClickGesture onChannelSelectedListener; @@ -101,10 +98,6 @@ public Context getContext() { return context; } - public ImageLoader getImageLoader() { - return imageLoader; - } - public OnClickGesture getOnStreamSelectedListener() { return onStreamSelectedListener; } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/StreamSegmentItem.kt b/app/src/main/java/org/schabi/newpipe/info_list/StreamSegmentItem.kt index f6d3587d6a0..f233c76277a 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/StreamSegmentItem.kt +++ b/app/src/main/java/org/schabi/newpipe/info_list/StreamSegmentItem.kt @@ -3,13 +3,12 @@ package org.schabi.newpipe.info_list import android.view.View import android.widget.ImageView import android.widget.TextView -import com.nostra13.universalimageloader.core.ImageLoader import com.xwray.groupie.GroupieViewHolder import com.xwray.groupie.Item import org.schabi.newpipe.R import org.schabi.newpipe.extractor.stream.StreamSegment -import org.schabi.newpipe.util.ImageDisplayConstants import org.schabi.newpipe.util.Localization +import org.schabi.newpipe.util.PicassoHelper class StreamSegmentItem( private val item: StreamSegment, @@ -24,10 +23,8 @@ class StreamSegmentItem( override fun bind(viewHolder: GroupieViewHolder, position: Int) { item.previewUrl?.let { - ImageLoader.getInstance().displayImage( - it, viewHolder.root.findViewById(R.id.previewImage), - ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS - ) + PicassoHelper.loadThumbnail(it) + .into(viewHolder.root.findViewById(R.id.previewImage)) } viewHolder.root.findViewById(R.id.textViewTitle).text = item.title if (item.channelName == null) { diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java index 9d93abcd965..78acb752b54 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java @@ -8,7 +8,7 @@ import org.schabi.newpipe.extractor.channel.ChannelInfoItem; import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.util.ImageDisplayConstants; +import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.Localization; import de.hdodenhof.circleimageview.CircleImageView; @@ -43,10 +43,7 @@ public void updateFromItem(final InfoItem infoItem, itemTitleView.setText(item.getName()); itemAdditionalDetailView.setText(getDetailLine(item)); - itemBuilder.getImageLoader() - .displayImage(item.getThumbnailUrl(), - itemThumbnailView, - ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); + PicassoHelper.loadThumbnail(item.getThumbnailUrl()).into(itemThumbnailView); itemView.setOnClickListener(view -> { if (itemBuilder.getOnChannelSelectedListener() != null) { diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java index b2b60b24363..079efa4a8b4 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java @@ -1,6 +1,5 @@ package org.schabi.newpipe.info_list.holder; -import android.content.SharedPreferences; import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.text.style.URLSpan; @@ -12,7 +11,6 @@ import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; -import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; import org.schabi.newpipe.error.ErrorActivity; @@ -22,11 +20,11 @@ import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.util.CommentTextOnTouchListener; import org.schabi.newpipe.util.DeviceUtils; -import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.external_communication.TimestampExtractor; import org.schabi.newpipe.util.external_communication.ShareUtils; +import org.schabi.newpipe.util.PicassoHelper; import java.util.regex.Matcher; @@ -38,11 +36,9 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder { private static final int COMMENT_DEFAULT_LINES = 2; private static final int COMMENT_EXPANDED_LINES = 1000; - private final String downloadThumbnailKey; private final int commentHorizontalPadding; private final int commentVerticalPadding; - private SharedPreferences preferences = null; private final RelativeLayout itemRoot; public final CircleImageView itemThumbnailView; private final TextView itemContentView; @@ -83,9 +79,6 @@ public String transformUrl(final Matcher match, final String url) { itemPublishedTime = itemView.findViewById(R.id.itemPublishedTime); itemContentView = itemView.findViewById(R.id.itemCommentContentView); - downloadThumbnailKey = infoItemBuilder.getContext(). - getString(R.string.download_thumbnail_key); - commentHorizontalPadding = (int) infoItemBuilder.getContext() .getResources().getDimension(R.dimen.comments_horizontal_padding); commentVerticalPadding = (int) infoItemBuilder.getContext() @@ -105,14 +98,8 @@ public void updateFromItem(final InfoItem infoItem, } final CommentsInfoItem item = (CommentsInfoItem) infoItem; - preferences = PreferenceManager.getDefaultSharedPreferences(itemBuilder.getContext()); - - itemBuilder.getImageLoader() - .displayImage(item.getUploaderAvatarUrl(), - itemThumbnailView, - ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); - - if (preferences.getBoolean(downloadThumbnailKey, true)) { + PicassoHelper.loadAvatar(item.getUploaderAvatarUrl()).into(itemThumbnailView); + if (PicassoHelper.getShouldLoadImages()) { itemThumbnailView.setVisibility(View.VISIBLE); itemRoot.setPadding(commentVerticalPadding, commentVerticalPadding, commentVerticalPadding, commentVerticalPadding); diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java index d4af630623b..bf5f57db3a7 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java @@ -9,7 +9,7 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.util.ImageDisplayConstants; +import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.Localization; public class PlaylistMiniInfoItemHolder extends InfoItemHolder { @@ -46,9 +46,7 @@ public void updateFromItem(final InfoItem infoItem, .localizeStreamCountMini(itemStreamCountView.getContext(), item.getStreamCount())); itemUploaderView.setText(item.getUploaderName()); - itemBuilder.getImageLoader() - .displayImage(item.getThumbnailUrl(), itemThumbnailView, - ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); + PicassoHelper.loadPlaylistThumbnail(item.getThumbnailUrl()).into(itemThumbnailView); itemView.setOnClickListener(view -> { if (itemBuilder.getOnPlaylistSelectedListener() != null) { diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java index 98699eb95e7..79772a6a307 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java @@ -15,7 +15,7 @@ import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.ktx.ViewUtils; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.util.ImageDisplayConstants; +import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.views.AnimatedProgressBar; @@ -83,10 +83,7 @@ public void updateFromItem(final InfoItem infoItem, } // Default thumbnail is shown on error, while loading and if the url is empty - itemBuilder.getImageLoader() - .displayImage(item.getThumbnailUrl(), - itemThumbnailView, - ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); + PicassoHelper.loadThumbnail(item.getThumbnailUrl()).into(itemThumbnailView); itemView.setOnClickListener(view -> { if (itemBuilder.getOnStreamSelectedListener() != null) { diff --git a/app/src/main/java/org/schabi/newpipe/local/LocalItemBuilder.java b/app/src/main/java/org/schabi/newpipe/local/LocalItemBuilder.java index d7aaddcc413..041d16d4387 100644 --- a/app/src/main/java/org/schabi/newpipe/local/LocalItemBuilder.java +++ b/app/src/main/java/org/schabi/newpipe/local/LocalItemBuilder.java @@ -1,10 +1,6 @@ package org.schabi.newpipe.local; import android.content.Context; -import android.widget.ImageView; - -import com.nostra13.universalimageloader.core.DisplayImageOptions; -import com.nostra13.universalimageloader.core.ImageLoader; import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.util.OnClickGesture; @@ -31,7 +27,6 @@ public class LocalItemBuilder { private final Context context; - private final ImageLoader imageLoader = ImageLoader.getInstance(); private OnClickGesture onSelectedListener; @@ -43,11 +38,6 @@ public Context getContext() { return context; } - public void displayImage(final String url, final ImageView view, - final DisplayImageOptions options) { - imageLoader.displayImage(url, view, options); - } - public OnClickGesture getOnItemSelectedListener() { return onSelectedListener; } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/item/StreamItem.kt b/app/src/main/java/org/schabi/newpipe/local/feed/item/StreamItem.kt index 13ba7592b1f..c454f7eec22 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/item/StreamItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/item/StreamItem.kt @@ -5,7 +5,6 @@ import android.text.TextUtils import android.view.View import androidx.core.content.ContextCompat import androidx.preference.PreferenceManager -import com.nostra13.universalimageloader.core.ImageLoader import com.xwray.groupie.viewbinding.BindableItem import org.schabi.newpipe.MainActivity import org.schabi.newpipe.R @@ -16,8 +15,8 @@ import org.schabi.newpipe.extractor.stream.StreamType.AUDIO_LIVE_STREAM import org.schabi.newpipe.extractor.stream.StreamType.AUDIO_STREAM import org.schabi.newpipe.extractor.stream.StreamType.LIVE_STREAM import org.schabi.newpipe.extractor.stream.StreamType.VIDEO_STREAM -import org.schabi.newpipe.util.ImageDisplayConstants import org.schabi.newpipe.util.Localization +import org.schabi.newpipe.util.PicassoHelper import java.util.concurrent.TimeUnit data class StreamItem( @@ -93,10 +92,7 @@ data class StreamItem( viewBinding.itemProgressView.visibility = View.GONE } - ImageLoader.getInstance().displayImage( - stream.thumbnailUrl, viewBinding.itemThumbnailView, - ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS - ) + PicassoHelper.loadThumbnail(stream.thumbnailUrl).into(viewBinding.itemThumbnailView) if (itemVersion != ItemVersion.MINI) { viewBinding.itemAdditionalDetails.text = diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java index 5560df3e068..f8c5176ec2d 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java @@ -7,7 +7,7 @@ import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; import org.schabi.newpipe.local.LocalItemBuilder; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.util.ImageDisplayConstants; +import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.Localization; import java.time.format.DateTimeFormatter; @@ -36,8 +36,7 @@ public void updateFromItem(final LocalItem localItem, itemStreamCountView.getContext(), item.streamCount)); itemUploaderView.setVisibility(View.INVISIBLE); - itemBuilder.displayImage(item.thumbnailUrl, itemThumbnailView, - ImageDisplayConstants.DISPLAY_PLAYLIST_OPTIONS); + PicassoHelper.loadPlaylistThumbnail(item.thumbnailUrl).into(itemThumbnailView); super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter); } diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java index 903f104405e..561cde56013 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java @@ -15,7 +15,7 @@ import org.schabi.newpipe.ktx.ViewUtils; import org.schabi.newpipe.local.LocalItemBuilder; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.util.ImageDisplayConstants; +import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.views.AnimatedProgressBar; @@ -81,8 +81,8 @@ public void updateFromItem(final LocalItem localItem, } // Default thumbnail is shown on error, while loading and if the url is empty - itemBuilder.displayImage(item.getStreamEntity().getThumbnailUrl(), itemThumbnailView, - ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); + PicassoHelper.loadThumbnail(item.getStreamEntity().getThumbnailUrl()) + .into(itemThumbnailView); itemView.setOnClickListener(view -> { if (itemBuilder.getOnItemSelectedListener() != null) { diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java index adf6bd5c262..d2fe8b40f13 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java @@ -15,7 +15,7 @@ import org.schabi.newpipe.ktx.ViewUtils; import org.schabi.newpipe.local.LocalItemBuilder; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.util.ImageDisplayConstants; +import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.views.AnimatedProgressBar; @@ -114,8 +114,8 @@ public void updateFromItem(final LocalItem localItem, } // Default thumbnail is shown on error, while loading and if the url is empty - itemBuilder.displayImage(item.getStreamEntity().getThumbnailUrl(), itemThumbnailView, - ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); + PicassoHelper.loadThumbnail(item.getStreamEntity().getThumbnailUrl()) + .into(itemThumbnailView); itemView.setOnClickListener(view -> { if (itemBuilder.getOnItemSelectedListener() != null) { diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java index a39e3cecb5b..440353ac71c 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java @@ -8,7 +8,7 @@ import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.local.LocalItemBuilder; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.util.ImageDisplayConstants; +import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.Localization; import java.time.format.DateTimeFormatter; @@ -44,9 +44,7 @@ public void updateFromItem(final LocalItem localItem, itemUploaderView.setText(NewPipe.getNameOfService(item.getServiceId())); } - - itemBuilder.displayImage(item.getThumbnailUrl(), itemThumbnailView, - ImageDisplayConstants.DISPLAY_PLAYLIST_OPTIONS); + PicassoHelper.loadPlaylistThumbnail(item.getThumbnailUrl()).into(itemThumbnailView); super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter); } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt index a87ffb6954b..2b964779cfb 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt @@ -3,14 +3,13 @@ package org.schabi.newpipe.local.subscription.item import android.content.Context import android.widget.ImageView import android.widget.TextView -import com.nostra13.universalimageloader.core.ImageLoader import com.xwray.groupie.GroupieViewHolder import com.xwray.groupie.Item import org.schabi.newpipe.R import org.schabi.newpipe.extractor.channel.ChannelInfoItem -import org.schabi.newpipe.util.ImageDisplayConstants import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.OnClickGesture +import org.schabi.newpipe.util.PicassoHelper class ChannelItem( private val infoItem: ChannelInfoItem, @@ -40,10 +39,7 @@ class ChannelItem( itemChannelDescriptionView.text = infoItem.description } - ImageLoader.getInstance().displayImage( - infoItem.thumbnailUrl, itemThumbnailView, - ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS - ) + PicassoHelper.loadThumbnail(infoItem.thumbnailUrl).into(itemThumbnailView) gesturesListener?.run { viewHolder.root.setOnClickListener { selected(infoItem) } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt index d4d4e7db196..aadb2fc73e6 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt @@ -3,7 +3,6 @@ package org.schabi.newpipe.local.subscription.item import android.view.View import androidx.core.view.isGone import androidx.core.view.isVisible -import com.nostra13.universalimageloader.core.ImageLoader import com.xwray.groupie.viewbinding.BindableItem import com.xwray.groupie.viewbinding.GroupieViewHolder import org.schabi.newpipe.R @@ -11,7 +10,7 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity import org.schabi.newpipe.databinding.PickerSubscriptionItemBinding import org.schabi.newpipe.ktx.AnimationType import org.schabi.newpipe.ktx.animate -import org.schabi.newpipe.util.ImageDisplayConstants +import org.schabi.newpipe.util.PicassoHelper data class PickerSubscriptionItem( val subscriptionEntity: SubscriptionEntity, @@ -22,11 +21,7 @@ data class PickerSubscriptionItem( override fun getSpanSize(spanCount: Int, position: Int): Int = 1 override fun bind(viewBinding: PickerSubscriptionItemBinding, position: Int) { - ImageLoader.getInstance().displayImage( - subscriptionEntity.avatarUrl, - viewBinding.thumbnailView, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS - ) - + PicassoHelper.loadAvatar(subscriptionEntity.avatarUrl).into(viewBinding.thumbnailView) viewBinding.titleView.text = subscriptionEntity.name viewBinding.selectedHighlight.isVisible = isSelected } diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index d8d8ac14bea..df0812c29f3 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -18,6 +18,7 @@ import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.net.Uri; import android.os.Build; @@ -82,9 +83,9 @@ import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoListener; import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.nostra13.universalimageloader.core.ImageLoader; -import com.nostra13.universalimageloader.core.assist.FailReason; -import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; +import com.squareup.picasso.Picasso; +import com.squareup.picasso.Target; +import com.squareup.picasso.Transformation; import org.schabi.newpipe.DownloaderImpl; import org.schabi.newpipe.MainActivity; @@ -128,11 +129,11 @@ import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper; import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHolder; import org.schabi.newpipe.util.DeviceUtils; -import org.schabi.newpipe.util.ImageDisplayConstants; +import org.schabi.newpipe.util.external_communication.KoreUtils; import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.NavigationHelper; +import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.SerializedCache; -import org.schabi.newpipe.util.external_communication.KoreUtils; import org.schabi.newpipe.util.external_communication.ShareUtils; import org.schabi.newpipe.views.ExpandableSurfaceView; @@ -196,7 +197,6 @@ public final class Player implements EventListener, PlaybackListener, - ImageLoadingListener, VideoListener, SeekBar.OnSeekBarChangeListener, View.OnClickListener, @@ -250,6 +250,9 @@ public final class Player implements private static final int RENDERER_UNAVAILABLE = -1; + private static final String PICASSO_PLAYER_TAG = "PICASSO_PLAYER_TAG"; + private static final String PICASSO_TRANSFORMATION_KEY = "PICASSO_TRANSFORMATION_KEY"; + /*////////////////////////////////////////////////////////////////////////// // Playback //////////////////////////////////////////////////////////////////////////*/ @@ -820,7 +823,7 @@ public void destroy() { databaseUpdateDisposable.clear(); progressUpdateDisposable.set(null); - ImageLoader.getInstance().stop(); + PicassoHelper.cancelTag(PICASSO_PLAYER_TAG); if (binding != null) { binding.endScreen.setImageBitmap(null); @@ -1215,14 +1218,71 @@ private void unregisterBroadcastReceiver() { private void initThumbnail(final String url) { if (DEBUG) { - Log.d(TAG, "Thumbnail - initThumbnail() called"); + Log.d(TAG, "Thumbnail - initThumbnail() called with url = [" + + (url == null ? "null" : url) + "]"); } if (url == null || url.isEmpty()) { return; } - ImageLoader.getInstance().resume(); - ImageLoader.getInstance() - .loadImage(url, ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, this); + + // scale down the notification thumbnail for performance + PicassoHelper.loadThumbnail(url) + .tag(PICASSO_PLAYER_TAG) + .transform(new Transformation() { + @Override + public Bitmap transform(final Bitmap source) { + final float notificationThumbnailWidth = Math.min( + context.getResources() + .getDimension(R.dimen.player_notification_thumbnail_width), + source.getWidth()); + return Bitmap.createScaledBitmap( + source, + (int) notificationThumbnailWidth, + (int) (source.getHeight() + / (source.getWidth() / notificationThumbnailWidth)), + true); + } + + @Override + public String key() { + return PICASSO_TRANSFORMATION_KEY; + } + }) + .into(new Target() { + @Override + public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { + + if (DEBUG) { + Log.d(TAG, "Thumbnail - onLoadingComplete() called with: " + + "url = [" + url + "], " + "loadedImage = [" + bitmap + " -> " + + bitmap.getWidth() + "x" + bitmap.getHeight() + + "], from = [" + from + "]"); + } + + currentThumbnail = bitmap; + NotificationUtil.getInstance() + .createNotificationIfNeededAndUpdate(Player.this, false); + // there is a new thumbnail, so changed the end screen thumbnail, too. + updateEndScreenThumbnail(); + } + + @Override + public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { + Log.e(TAG, "Thumbnail - onBitmapFailed() called with: url = [" + + url + "]", e); + currentThumbnail = null; + NotificationUtil.getInstance() + .createNotificationIfNeededAndUpdate(Player.this, false); + } + + @Override + public void onPrepareLoad(final Drawable placeHolderDrawable) { + if (DEBUG) { + Log.d(TAG, "Thumbnail - onLoadingStarted() called with: url = [" + + url + "]"); + } + } + }); } /** @@ -1296,61 +1356,6 @@ private float calculateMaxEndScreenThumbnailHeight() { return Math.min(currentThumbnail.getHeight(), screenHeight); } } - - @Override - public void onLoadingStarted(final String imageUri, final View view) { - if (DEBUG) { - Log.d(TAG, "Thumbnail - onLoadingStarted() called on: " - + "imageUri = [" + imageUri + "], view = [" + view + "]"); - } - } - - @Override - public void onLoadingFailed(final String imageUri, final View view, - final FailReason failReason) { - Log.e(TAG, "Thumbnail - onLoadingFailed() called on imageUri = [" + imageUri + "]", - failReason.getCause()); - currentThumbnail = null; - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); - } - - @Override - public void onLoadingComplete(final String imageUri, final View view, - final Bitmap loadedImage) { - // scale down the notification thumbnail for performance - final float notificationThumbnailWidth = Math.min( - context.getResources().getDimension(R.dimen.player_notification_thumbnail_width), - loadedImage.getWidth()); - currentThumbnail = Bitmap.createScaledBitmap( - loadedImage, - (int) notificationThumbnailWidth, - (int) (loadedImage.getHeight() - / (loadedImage.getWidth() / notificationThumbnailWidth)), - true); - - if (DEBUG) { - Log.d(TAG, "Thumbnail - onLoadingComplete() called with: " - + "imageUri = [" + imageUri + "], view = [" + view + "], " - + "loadedImage = [" + loadedImage + "], " - + loadedImage.getWidth() + "x" + loadedImage.getHeight() - + ", scaled notification width = " + notificationThumbnailWidth); - } - - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); - - // there is a new thumbnail, thus the end screen thumbnail needs to be changed, too. - updateEndScreenThumbnail(); - } - - @Override - public void onLoadingCancelled(final String imageUri, final View view) { - if (DEBUG) { - Log.d(TAG, "Thumbnail - onLoadingCancelled() called with: " - + "imageUri = [" + imageUri + "], view = [" + view + "]"); - } - currentThumbnail = null; - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); - } //endregion diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java index 3e0865a3e97..f2e98d86661 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java @@ -5,11 +5,9 @@ import android.view.MotionEvent; import android.view.View; -import com.nostra13.universalimageloader.core.ImageLoader; - import org.schabi.newpipe.extractor.NewPipe; -import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.PicassoHelper; public class PlayQueueItemBuilder { private static final String TAG = PlayQueueItemBuilder.class.toString(); @@ -35,8 +33,7 @@ public void buildStreamInfoItem(final PlayQueueItemHolder holder, final PlayQueu holder.itemDurationView.setVisibility(View.GONE); } - ImageLoader.getInstance().displayImage(item.getThumbnailUrl(), holder.itemThumbnailView, - ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); + PicassoHelper.loadThumbnail(item.getThumbnailUrl()).into(holder.itemThumbnailView); holder.itemRoot.setOnClickListener(view -> { if (onItemClickListener != null) { diff --git a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHolder.java b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHolder.java index 30c5ce9101f..08c6366c8d3 100644 --- a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHolder.java +++ b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHolder.java @@ -1,16 +1,18 @@ package org.schabi.newpipe.player.seekbarpreview; +import static org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper.SeekbarPreviewThumbnailType; + import android.content.Context; import android.graphics.Bitmap; import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.google.common.base.Stopwatch; -import com.nostra13.universalimageloader.core.ImageLoader; import org.schabi.newpipe.extractor.stream.Frameset; -import org.schabi.newpipe.util.ImageDisplayConstants; +import org.schabi.newpipe.util.PicassoHelper; import java.util.Comparator; import java.util.HashMap; @@ -21,11 +23,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; import java.util.function.Supplier; -import static org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper.SeekbarPreviewThumbnailType; - public class SeekbarPreviewThumbnailHolder { // This has to be <= 23 chars on devices running Android 7 or lower (API <= 25) @@ -174,6 +173,7 @@ private void generateDataFrom( } } + @Nullable private Bitmap getBitMapFrom(final String url) { if (url == null) { Log.w(TAG, "url is null; This should never happen"); @@ -182,24 +182,11 @@ private Bitmap getBitMapFrom(final String url) { final Stopwatch sw = Log.isLoggable(TAG, Log.DEBUG) ? Stopwatch.createStarted() : null; try { - final SyncImageLoadingListener syncImageLoadingListener = - new SyncImageLoadingListener(); - Log.d(TAG, "Downloading bitmap for seekbarPreview from '" + url + "'"); - // Ensure that everything is running - ImageLoader.getInstance().resume(); - // Load the image - // Impl-Note: + // Gets the bitmap within the timeout of 15 seconds imposed by default by OkHttpClient // Ensure that your are not running on the main-Thread this will otherwise hang - ImageLoader.getInstance().loadImage( - url, - ImageDisplayConstants.DISPLAY_SEEKBAR_PREVIEW_OPTIONS, - syncImageLoadingListener); - - // Get the bitmap within the timeout - final Bitmap bitmap = - syncImageLoadingListener.waitForBitmapOrThrow(30, TimeUnit.SECONDS); + final Bitmap bitmap = PicassoHelper.loadSeekbarThumbnailPreview(url).get(); if (sw != null) { Log.d(TAG, diff --git a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SyncImageLoadingListener.java b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SyncImageLoadingListener.java deleted file mode 100644 index 46c278bf28b..00000000000 --- a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SyncImageLoadingListener.java +++ /dev/null @@ -1,87 +0,0 @@ -package org.schabi.newpipe.player.seekbarpreview; - -import android.graphics.Bitmap; -import android.view.View; - -import com.nostra13.universalimageloader.core.assist.FailReason; -import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; - -import java.util.concurrent.CancellationException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -/** - * Listener for synchronously downloading of an image/bitmap. - */ -public class SyncImageLoadingListener extends SimpleImageLoadingListener { - - private final CountDownLatch countDownLatch = new CountDownLatch(1); - - private Bitmap bitmap; - private boolean cancelled = false; - private FailReason failReason = null; - - @SuppressWarnings("checkstyle:HiddenField") - @Override - public void onLoadingFailed( - final String imageUri, - final View view, - final FailReason failReason) { - - this.failReason = failReason; - countDownLatch.countDown(); - } - - @Override - public void onLoadingComplete( - final String imageUri, - final View view, - final Bitmap loadedImage) { - - bitmap = loadedImage; - countDownLatch.countDown(); - } - - @Override - public void onLoadingCancelled(final String imageUri, final View view) { - cancelled = true; - countDownLatch.countDown(); - } - - public Bitmap waitForBitmapOrThrow(final long timeout, final TimeUnit timeUnit) - throws InterruptedException, TimeoutException { - - // Wait for the download to finish - if (!countDownLatch.await(timeout, timeUnit)) { - throw new TimeoutException("Couldn't get the image in time"); - } - - if (isCancelled()) { - throw new CancellationException("Download of image was cancelled"); - } - - if (getFailReason() != null) { - throw new RuntimeException("Failed to download image" + getFailReason().getType(), - getFailReason().getCause()); - } - - if (getBitmap() == null) { - throw new NullPointerException("Bitmap is null"); - } - - return getBitmap(); - } - - public Bitmap getBitmap() { - return bitmap; - } - - public boolean isCancelled() { - return cancelled; - } - - public FailReason getFailReason() { - return failReason; - } -} diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index f1e19af9493..bb27a80eb64 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -17,8 +17,6 @@ import androidx.preference.Preference; import androidx.preference.PreferenceManager; -import com.nostra13.universalimageloader.core.ImageLoader; - import org.schabi.newpipe.DownloaderImpl; import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; @@ -29,6 +27,7 @@ import org.schabi.newpipe.extractor.localization.Localization; import org.schabi.newpipe.streams.io.StoredFileHelper; import org.schabi.newpipe.util.NavigationHelper; +import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.ZipHelper; import java.io.File; @@ -50,7 +49,6 @@ public class ContentSettingsFragment extends BasePreferenceFragment { private ContentSettingsManager manager; private String importExportDataPathKey; - private String thumbnailLoadToggleKey; private String youtubeRestrictedModeEnabledKey; private Localization initialSelectedLocalization; @@ -69,7 +67,6 @@ public void onCreatePreferences(final Bundle savedInstanceState, final String ro manager.deleteSettingsFile(); importExportDataPathKey = getString(R.string.import_export_data_path); - thumbnailLoadToggleKey = getString(R.string.download_thumbnail_key); youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled); addPreferencesFromResource(R.xml.content_settings); @@ -112,20 +109,24 @@ public void onCreatePreferences(final Bundle savedInstanceState, final String ro if (defaultPreferences.getString(getString(R.string.recaptcha_cookies_key), "").isEmpty()) { clearCookiePref.setVisible(false); } + + findPreference(getString(R.string.download_thumbnail_key)).setOnPreferenceChangeListener( + (preference, newValue) -> { + PicassoHelper.setShouldLoadImages((Boolean) newValue); + try { + PicassoHelper.clearCache(preference.getContext()); + Toast.makeText(preference.getContext(), + R.string.thumbnail_cache_wipe_complete_notice, Toast.LENGTH_SHORT) + .show(); + } catch (final IOException e) { + Log.e(TAG, "Unable to clear Picasso cache", e); + } + return true; + }); } @Override public boolean onPreferenceTreeClick(final Preference preference) { - if (preference.getKey().equals(thumbnailLoadToggleKey)) { - final ImageLoader imageLoader = ImageLoader.getInstance(); - imageLoader.stop(); - imageLoader.clearDiskCache(); - imageLoader.clearMemoryCache(); - imageLoader.resume(); - Toast.makeText(preference.getContext(), R.string.thumbnail_cache_wipe_complete_notice, - Toast.LENGTH_SHORT).show(); - } - if (preference.getKey().equals(youtubeRestrictedModeEnabledKey)) { final Context context = getContext(); if (context != null) { diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java index 7f706be779e..a0105a11f80 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java @@ -14,13 +14,11 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import com.nostra13.universalimageloader.core.DisplayImageOptions; -import com.nostra13.universalimageloader.core.ImageLoader; - import org.schabi.newpipe.R; import org.schabi.newpipe.database.subscription.SubscriptionEntity; import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.local.subscription.SubscriptionManager; +import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.ThemeHelper; import java.util.List; @@ -54,13 +52,6 @@ */ public class SelectChannelFragment extends DialogFragment { - /** - * This contains the base display options for images. - */ - private static final DisplayImageOptions DISPLAY_IMAGE_OPTIONS - = new DisplayImageOptions.Builder().cacheInMemory(true).build(); - - private final ImageLoader imageLoader = ImageLoader.getInstance(); private OnSelectedListener onSelectedListener = null; private OnCancelListener onCancelListener = null; @@ -199,8 +190,7 @@ public void onBindViewHolder(final SelectChannelItemHolder holder, final int pos final SubscriptionEntity entry = subscriptions.get(position); holder.titleView.setText(entry.getName()); holder.view.setOnClickListener(view -> clickedItem(position)); - imageLoader.displayImage(entry.getAvatarUrl(), holder.thumbnailView, - DISPLAY_IMAGE_OPTIONS); + PicassoHelper.loadAvatar(entry.getAvatarUrl()).into(holder.thumbnailView); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java index 63da3274f39..f94e391babe 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java @@ -14,9 +14,6 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import com.nostra13.universalimageloader.core.DisplayImageOptions; -import com.nostra13.universalimageloader.core.ImageLoader; - import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; import org.schabi.newpipe.database.AppDatabase; @@ -29,6 +26,7 @@ import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.local.playlist.LocalPlaylistManager; import org.schabi.newpipe.local.playlist.RemotePlaylistManager; +import org.schabi.newpipe.util.PicassoHelper; import java.util.List; import java.util.Vector; @@ -38,13 +36,6 @@ import io.reactivex.rxjava3.disposables.Disposable; public class SelectPlaylistFragment extends DialogFragment { - /** - * This contains the base display options for images. - */ - private static final DisplayImageOptions DISPLAY_IMAGE_OPTIONS - = new DisplayImageOptions.Builder().cacheInMemory(true).build(); - - private final ImageLoader imageLoader = ImageLoader.getInstance(); private OnSelectedListener onSelectedListener = null; @@ -170,16 +161,15 @@ public void onBindViewHolder(@NonNull final SelectPlaylistItemHolder holder, holder.titleView.setText(entry.name); holder.view.setOnClickListener(view -> clickedItem(position)); - imageLoader.displayImage(entry.thumbnailUrl, holder.thumbnailView, - DISPLAY_IMAGE_OPTIONS); + PicassoHelper.loadPlaylistThumbnail(entry.thumbnailUrl).into(holder.thumbnailView); } else if (selectedItem instanceof PlaylistRemoteEntity) { final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem); holder.titleView.setText(entry.getName()); holder.view.setOnClickListener(view -> clickedItem(position)); - imageLoader.displayImage(entry.getThumbnailUrl(), holder.thumbnailView, - DISPLAY_IMAGE_OPTIONS); + PicassoHelper.loadPlaylistThumbnail(entry.getThumbnailUrl()) + .into(holder.thumbnailView); } } diff --git a/app/src/main/java/org/schabi/newpipe/util/ImageDisplayConstants.java b/app/src/main/java/org/schabi/newpipe/util/ImageDisplayConstants.java deleted file mode 100644 index 62e80275e49..00000000000 --- a/app/src/main/java/org/schabi/newpipe/util/ImageDisplayConstants.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.schabi.newpipe.util; - -import android.graphics.Bitmap; - -import com.nostra13.universalimageloader.core.DisplayImageOptions; -import com.nostra13.universalimageloader.core.assist.ImageScaleType; -import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer; - -import org.schabi.newpipe.R; - -public final class ImageDisplayConstants { - private static final int BITMAP_FADE_IN_DURATION_MILLIS = 250; - - /** - * This constant contains the base display options. - */ - private static final DisplayImageOptions BASE_DISPLAY_IMAGE_OPTIONS = - new DisplayImageOptions.Builder() - .cacheInMemory(true) - .cacheOnDisk(true) - .resetViewBeforeLoading(true) - .bitmapConfig(Bitmap.Config.RGB_565) - .imageScaleType(ImageScaleType.EXACTLY) - .displayer(new FadeInBitmapDisplayer(BITMAP_FADE_IN_DURATION_MILLIS)) - .build(); - - /*////////////////////////////////////////////////////////////////////////// - // DisplayImageOptions default configurations - //////////////////////////////////////////////////////////////////////////*/ - - public static final DisplayImageOptions DISPLAY_AVATAR_OPTIONS = - new DisplayImageOptions.Builder() - .cloneFrom(BASE_DISPLAY_IMAGE_OPTIONS) - .showImageForEmptyUri(R.drawable.buddy) - .showImageOnFail(R.drawable.buddy) - .build(); - - public static final DisplayImageOptions DISPLAY_THUMBNAIL_OPTIONS = - new DisplayImageOptions.Builder() - .cloneFrom(BASE_DISPLAY_IMAGE_OPTIONS) - .showImageForEmptyUri(R.drawable.dummy_thumbnail) - .showImageOnFail(R.drawable.dummy_thumbnail) - .build(); - - public static final DisplayImageOptions DISPLAY_BANNER_OPTIONS = - new DisplayImageOptions.Builder() - .cloneFrom(BASE_DISPLAY_IMAGE_OPTIONS) - .showImageForEmptyUri(R.drawable.channel_banner) - .showImageOnFail(R.drawable.channel_banner) - .build(); - - public static final DisplayImageOptions DISPLAY_PLAYLIST_OPTIONS = - new DisplayImageOptions.Builder() - .cloneFrom(BASE_DISPLAY_IMAGE_OPTIONS) - .showImageForEmptyUri(R.drawable.dummy_thumbnail_playlist) - .showImageOnFail(R.drawable.dummy_thumbnail_playlist) - .build(); - - public static final DisplayImageOptions DISPLAY_SEEKBAR_PREVIEW_OPTIONS = - new DisplayImageOptions.Builder() - .cloneFrom(BASE_DISPLAY_IMAGE_OPTIONS) - .build(); - - private ImageDisplayConstants() { } -} diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index 27db9a1f9d6..ad9654073a9 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -18,8 +18,6 @@ import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; -import com.nostra13.universalimageloader.core.ImageLoader; - import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; @@ -259,10 +257,9 @@ public static void resolveActivityOrAskToInstall(final Context context, final In if (context instanceof Activity) { new AlertDialog.Builder(context) .setMessage(R.string.no_player_found) - .setPositiveButton(R.string.install, (dialog, which) -> { - ShareUtils.openUrlInBrowser(context, - context.getString(R.string.fdroid_vlc_url), false); - }) + .setPositiveButton(R.string.install, + (dialog, which) -> ShareUtils.openUrlInBrowser(context, + context.getString(R.string.fdroid_vlc_url), false)) .setNegativeButton(R.string.cancel, (dialog, which) -> Log.i("NavigationHelper", "You unlocked a secret unicorn.")) .show(); @@ -284,8 +281,6 @@ private static FragmentTransaction defaultTransaction(final FragmentManager frag } public static void gotoMainFragment(final FragmentManager fragmentManager) { - ImageLoader.getInstance().clearMemoryCache(); - final boolean popped = fragmentManager.popBackStackImmediate(MAIN_FRAGMENT_TAG, 0); if (!popped) { openMainFragment(fragmentManager); diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java new file mode 100644 index 00000000000..bcacb0c13b8 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -0,0 +1,110 @@ +package org.schabi.newpipe.util; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Bitmap; + +import com.squareup.picasso.Cache; +import com.squareup.picasso.LruCache; +import com.squareup.picasso.OkHttp3Downloader; +import com.squareup.picasso.Picasso; +import com.squareup.picasso.RequestCreator; + +import org.schabi.newpipe.R; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import okhttp3.OkHttpClient; + +import static org.schabi.newpipe.extractor.utils.Utils.isBlank; + +public final class PicassoHelper { + + private PicassoHelper() { + } + + private static Cache picassoCache; + private static OkHttpClient picassoDownloaderClient; + + // suppress because terminate() is called in App.onTerminate(), preventing leaks + @SuppressLint("StaticFieldLeak") + private static Picasso picassoInstance; + + private static boolean shouldLoadImages; + + public static void init(final Context context) { + picassoCache = new LruCache(10 * 1024 * 1024); + picassoDownloaderClient = new OkHttpClient.Builder() + .cache(new okhttp3.Cache(new File(context.getExternalCacheDir(), "picasso"), + 50 * 1024 * 1024)) + // this should already be the default timeout in OkHttp3, but just to be sure... + .callTimeout(15, TimeUnit.SECONDS) + .build(); + + picassoInstance = new Picasso.Builder(context) + .memoryCache(picassoCache) // memory cache + .downloader(new OkHttp3Downloader(picassoDownloaderClient)) // disk cache + .defaultBitmapConfig(Bitmap.Config.RGB_565) + .build(); + } + + public static void terminate() { + picassoCache = null; + picassoDownloaderClient = null; + + if (picassoInstance != null) { + picassoInstance.shutdown(); + picassoInstance = null; + } + } + + public static void clearCache(final Context context) throws IOException { + picassoInstance.shutdown(); + picassoCache.clear(); // clear memory cache + picassoDownloaderClient.cache().delete(); // clear disk cache + init(context); + } + + public static void cancelTag(final Object tag) { + picassoInstance.cancelTag(tag); + } + + public static void setShouldLoadImages(final boolean shouldLoadImages) { + PicassoHelper.shouldLoadImages = shouldLoadImages; + } + + public static boolean getShouldLoadImages() { + return shouldLoadImages; + } + + + public static RequestCreator loadAvatar(final String url) { + return loadImageDefault(url, R.drawable.buddy); + } + + public static RequestCreator loadThumbnail(final String url) { + return loadImageDefault(url, R.drawable.dummy_thumbnail); + } + + public static RequestCreator loadBanner(final String url) { + return loadImageDefault(url, R.drawable.channel_banner); + } + + public static RequestCreator loadPlaylistThumbnail(final String url) { + return loadImageDefault(url, R.drawable.dummy_thumbnail_playlist); + } + + public static RequestCreator loadSeekbarThumbnailPreview(final String url) { + return picassoInstance.load(url); + } + + + private static RequestCreator loadImageDefault(final String url, final int placeholderResId) { + return picassoInstance + .load((!shouldLoadImages || isBlank(url)) ? null : url) + .placeholder(placeholderResId) + .error(placeholderResId); + } +} From 314964c5f98c6699599df0f47ae2b4b2d8020b40 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 27 Mar 2021 14:44:45 +0100 Subject: [PATCH 2/7] Recycle Bitmap in transformation --- app/src/main/java/org/schabi/newpipe/player/Player.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index df0812c29f3..72540453d35 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -1235,12 +1235,18 @@ public Bitmap transform(final Bitmap source) { context.getResources() .getDimension(R.dimen.player_notification_thumbnail_width), source.getWidth()); - return Bitmap.createScaledBitmap( + + final Bitmap result = Bitmap.createScaledBitmap( source, (int) notificationThumbnailWidth, (int) (source.getHeight() / (source.getWidth() / notificationThumbnailWidth)), true); + + if (result != source) { + source.recycle(); + } + return result; } @Override From 52189fc5df86b1309d4be99e9d90ce5f3878a15e Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 27 Mar 2021 14:59:24 +0100 Subject: [PATCH 3/7] Add debug setting to enable Picasso indicators --- .../schabi/newpipe/settings/DebugSettingsFragment.java | 9 +++++++++ app/src/main/java/org/schabi/newpipe/App.java | 7 +++++-- .../main/java/org/schabi/newpipe/util/PicassoHelper.java | 4 ++++ app/src/main/res/values/settings_keys.xml | 7 ++++--- app/src/main/res/values/strings.xml | 6 +++++- app/src/main/res/xml/debug_settings.xml | 7 +++++++ 6 files changed, 34 insertions(+), 6 deletions(-) diff --git a/app/src/debug/java/org/schabi/newpipe/settings/DebugSettingsFragment.java b/app/src/debug/java/org/schabi/newpipe/settings/DebugSettingsFragment.java index d5d223ff245..55b2c770860 100644 --- a/app/src/debug/java/org/schabi/newpipe/settings/DebugSettingsFragment.java +++ b/app/src/debug/java/org/schabi/newpipe/settings/DebugSettingsFragment.java @@ -5,6 +5,7 @@ import androidx.preference.Preference; import org.schabi.newpipe.R; +import org.schabi.newpipe.util.PicassoHelper; import leakcanary.LeakCanary; @@ -15,10 +16,13 @@ public void onCreatePreferences(final Bundle savedInstanceState, final String ro final Preference showMemoryLeaksPreference = findPreference(getString(R.string.show_memory_leaks_key)); + final Preference showImageIndicatorsPreference + = findPreference(getString(R.string.show_image_indicators_key)); final Preference crashTheAppPreference = findPreference(getString(R.string.crash_the_app_key)); assert showMemoryLeaksPreference != null; + assert showImageIndicatorsPreference != null; assert crashTheAppPreference != null; showMemoryLeaksPreference.setOnPreferenceClickListener(preference -> { @@ -26,6 +30,11 @@ public void onCreatePreferences(final Bundle savedInstanceState, final String ro return true; }); + showImageIndicatorsPreference.setOnPreferenceChangeListener((preference, newValue) -> { + PicassoHelper.setIndicatorsEnabled((Boolean) newValue); + return true; + }); + crashTheAppPreference.setOnPreferenceClickListener(preference -> { throw new RuntimeException(); }); diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index a7d9d7c26ee..c27dc7de2e4 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -100,9 +100,12 @@ public void onCreate() { ServiceHelper.initServices(this); // Initialize image loader + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); PicassoHelper.init(this); - PicassoHelper.setShouldLoadImages(PreferenceManager.getDefaultSharedPreferences(this) - .getBoolean(getString(R.string.download_thumbnail_key), true)); + PicassoHelper.setShouldLoadImages( + prefs.getBoolean(getString(R.string.download_thumbnail_key), true)); + PicassoHelper.setIndicatorsEnabled(BuildConfig.DEBUG + && prefs.getBoolean(getString(R.string.show_image_indicators_key), false)); configureRxJavaErrorHandler(); diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index bcacb0c13b8..3a9ce090bb1 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -71,6 +71,10 @@ public static void cancelTag(final Object tag) { picassoInstance.cancelTag(tag); } + public static void setIndicatorsEnabled(final boolean enabled) { + picassoInstance.setIndicatorsEnabled(enabled); // useful for debugging + } + public static void setShouldLoadImages(final boolean shouldLoadImages) { PicassoHelper.shouldLoadImages = shouldLoadImages; } diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index c57b24f1630..e0e0a613a42 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -1,5 +1,5 @@ - + last_used_version last_used_preferences_version @@ -139,13 +139,13 @@ scale_to_square_image_in_notifications notification_slot_0_key - notification_slot_1_key + notification_slot_1_key notification_slot_2_key notification_slot_3_key notification_slot_4_key notification_slot_compact_0_key - notification_slot_compact_1_key + notification_slot_compact_1_key notification_slot_compact_2_key notification_colorize_key @@ -189,6 +189,7 @@ show_original_time_ago_key disable_media_tunneling_key crash_the_app_key + show_image_indicators_key theme diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cc381f42c16..aecb6e6d558 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,5 +1,7 @@ - + + Tap the magnifying glass to get started. %1$s views Published on %1$s @@ -526,6 +528,8 @@ Original texts from services will be visible in stream items Disable media tunneling Disable media tunneling if you experience a black screen or stuttering on video playback + Show image indicators + Show Picasso colored ribbons on top of images indicating their source: red for network, blue for disk and green for memory Crash the app Import/export diff --git a/app/src/main/res/xml/debug_settings.xml b/app/src/main/res/xml/debug_settings.xml index 931f16e0aab..22abebcae94 100644 --- a/app/src/main/res/xml/debug_settings.xml +++ b/app/src/main/res/xml/debug_settings.xml @@ -42,6 +42,13 @@ app:singleLineTitle="false" app:iconSpaceReserved="false" /> + + Date: Sun, 28 Mar 2021 23:05:31 +0200 Subject: [PATCH 4/7] Always create new bitmap when resizing thumbnail This prevents strange crashes on some devices, fixes #4638 --- .../java/org/schabi/newpipe/player/Player.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 72540453d35..4031321163a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -1243,10 +1243,21 @@ public Bitmap transform(final Bitmap source) { / (source.getWidth() / notificationThumbnailWidth)), true); - if (result != source) { + if (result == source) { + // create a new mutable bitmap to prevent strange crashes on some + // devices (see #4638) + final Bitmap copied = Bitmap.createScaledBitmap( + source, + (int) notificationThumbnailWidth - 1, + (int) (source.getHeight() / (source.getWidth() + / (notificationThumbnailWidth - 1))), + true); source.recycle(); + return copied; + } else { + source.recycle(); + return result; } - return result; } @Override From c0664c1cb64ef2d3f3409e7d2b44367fd20575e8 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 1 Apr 2021 22:12:02 +0200 Subject: [PATCH 5/7] Add Picasso to licences and remove Universal Image Loader --- .../main/java/org/schabi/newpipe/about/AboutActivity.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt index 0199f30d889..a18d15af3bf 100644 --- a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt +++ b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt @@ -162,6 +162,10 @@ class AboutActivity : AppCompatActivity() { "OkHttp", "2019", "Square, Inc.", "https://square.github.io/okhttp/", StandardLicenses.APACHE2 ), + SoftwareComponent( + "Picasso", "2013", "Square, Inc.", + "https://square.github.io/picasso/", StandardLicenses.APACHE2 + ), SoftwareComponent( "PrettyTime", "2012 - 2020", "Lincoln Baxter, III", "https://github.com/ocpsoft/prettytime", StandardLicenses.APACHE2 @@ -177,11 +181,6 @@ class AboutActivity : AppCompatActivity() { SoftwareComponent( "RxJava", "2016 - 2020", "RxJava Contributors", "https://github.com/ReactiveX/RxJava", StandardLicenses.APACHE2 - ), - SoftwareComponent( - "Universal Image Loader", "2011 - 2015", "Sergey Tarasevich", - "https://github.com/nostra13/Android-Universal-Image-Loader", - StandardLicenses.APACHE2 ) ) private const val POS_ABOUT = 0 From 6eaff5ca6a0628c526351394670fad73528c1679 Mon Sep 17 00:00:00 2001 From: Stypox Date: Mon, 7 Jun 2021 11:24:13 +0200 Subject: [PATCH 6/7] Apply review: move thumbnail loading out of Player --- app/src/main/java/org/schabi/newpipe/App.java | 2 +- .../fragments/detail/VideoDetailFragment.java | 1 + .../org/schabi/newpipe/player/Player.java | 108 +++++------------- .../schabi/newpipe/util/PicassoHelper.java | 53 ++++++++- 4 files changed, 85 insertions(+), 79 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index c27dc7de2e4..9f4b6d550e3 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -104,7 +104,7 @@ public void onCreate() { PicassoHelper.init(this); PicassoHelper.setShouldLoadImages( prefs.getBoolean(getString(R.string.download_thumbnail_key), true)); - PicassoHelper.setIndicatorsEnabled(BuildConfig.DEBUG + PicassoHelper.setIndicatorsEnabled(MainActivity.DEBUG && prefs.getBoolean(getString(R.string.show_image_indicators_key), false)); configureRxJavaErrorHandler(); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index e9d1a12e2da..7ee70fcc416 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -690,6 +690,7 @@ private void initThumbnailViews(@NonNull final StreamInfo info) { .into(binding.detailThumbnailImageView, new Callback() { @Override public void onSuccess() { + // nothing to do, the image was loaded correctly into the thumbnail } @Override diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 4031321163a..83c567cb66d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -85,7 +85,6 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.squareup.picasso.Picasso; import com.squareup.picasso.Target; -import com.squareup.picasso.Transformation; import org.schabi.newpipe.DownloaderImpl; import org.schabi.newpipe.MainActivity; @@ -161,6 +160,7 @@ import static com.google.android.exoplayer2.Player.RepeatMode; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.schabi.newpipe.extractor.ServiceList.YouTube; +import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; import static org.schabi.newpipe.ktx.ViewUtils.animate; import static org.schabi.newpipe.ktx.ViewUtils.animateRotation; import static org.schabi.newpipe.player.MainPlayer.ACTION_CLOSE; @@ -250,9 +250,6 @@ public final class Player implements private static final int RENDERER_UNAVAILABLE = -1; - private static final String PICASSO_PLAYER_TAG = "PICASSO_PLAYER_TAG"; - private static final String PICASSO_TRANSFORMATION_KEY = "PICASSO_TRANSFORMATION_KEY"; - /*////////////////////////////////////////////////////////////////////////// // Playback //////////////////////////////////////////////////////////////////////////*/ @@ -823,7 +820,7 @@ public void destroy() { databaseUpdateDisposable.clear(); progressUpdateDisposable.set(null); - PicassoHelper.cancelTag(PICASSO_PLAYER_TAG); + PicassoHelper.cancelTag(PicassoHelper.PLAYER_THUMBNAIL_TAG); // cancel thumbnail loading if (binding != null) { binding.endScreen.setImageBitmap(null); @@ -1221,85 +1218,42 @@ private void initThumbnail(final String url) { Log.d(TAG, "Thumbnail - initThumbnail() called with url = [" + (url == null ? "null" : url) + "]"); } - if (url == null || url.isEmpty()) { + if (isNullOrEmpty(url)) { return; } // scale down the notification thumbnail for performance - PicassoHelper.loadThumbnail(url) - .tag(PICASSO_PLAYER_TAG) - .transform(new Transformation() { - @Override - public Bitmap transform(final Bitmap source) { - final float notificationThumbnailWidth = Math.min( - context.getResources() - .getDimension(R.dimen.player_notification_thumbnail_width), - source.getWidth()); - - final Bitmap result = Bitmap.createScaledBitmap( - source, - (int) notificationThumbnailWidth, - (int) (source.getHeight() - / (source.getWidth() / notificationThumbnailWidth)), - true); - - if (result == source) { - // create a new mutable bitmap to prevent strange crashes on some - // devices (see #4638) - final Bitmap copied = Bitmap.createScaledBitmap( - source, - (int) notificationThumbnailWidth - 1, - (int) (source.getHeight() / (source.getWidth() - / (notificationThumbnailWidth - 1))), - true); - source.recycle(); - return copied; - } else { - source.recycle(); - return result; - } - } - - @Override - public String key() { - return PICASSO_TRANSFORMATION_KEY; - } - }) - .into(new Target() { - @Override - public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { - - if (DEBUG) { - Log.d(TAG, "Thumbnail - onLoadingComplete() called with: " - + "url = [" + url + "], " + "loadedImage = [" + bitmap + " -> " - + bitmap.getWidth() + "x" + bitmap.getHeight() - + "], from = [" + from + "]"); - } + PicassoHelper.loadScaledDownThumbnail(context, url).into(new Target() { + @Override + public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { + if (DEBUG) { + Log.d(TAG, "Thumbnail - onLoadingComplete() called with: url = [" + url + + "], " + "loadedImage = [" + bitmap + " -> " + bitmap.getWidth() + "x" + + bitmap.getHeight() + "], from = [" + from + "]"); + } - currentThumbnail = bitmap; - NotificationUtil.getInstance() - .createNotificationIfNeededAndUpdate(Player.this, false); - // there is a new thumbnail, so changed the end screen thumbnail, too. - updateEndScreenThumbnail(); - } + currentThumbnail = bitmap; + NotificationUtil.getInstance() + .createNotificationIfNeededAndUpdate(Player.this, false); + // there is a new thumbnail, so changed the end screen thumbnail, too. + updateEndScreenThumbnail(); + } - @Override - public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { - Log.e(TAG, "Thumbnail - onBitmapFailed() called with: url = [" - + url + "]", e); - currentThumbnail = null; - NotificationUtil.getInstance() - .createNotificationIfNeededAndUpdate(Player.this, false); - } + @Override + public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { + Log.e(TAG, "Thumbnail - onBitmapFailed() called with: url = [" + url + "]", e); + currentThumbnail = null; + NotificationUtil.getInstance() + .createNotificationIfNeededAndUpdate(Player.this, false); + } - @Override - public void onPrepareLoad(final Drawable placeHolderDrawable) { - if (DEBUG) { - Log.d(TAG, "Thumbnail - onLoadingStarted() called with: url = [" - + url + "]"); - } - } - }); + @Override + public void onPrepareLoad(final Drawable placeHolderDrawable) { + if (DEBUG) { + Log.d(TAG, "Thumbnail - onLoadingStarted() called with: url = [" + url + "]"); + } + } + }); } /** diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index 3a9ce090bb1..173b4577625 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -9,6 +9,7 @@ import com.squareup.picasso.OkHttp3Downloader; import com.squareup.picasso.Picasso; import com.squareup.picasso.RequestCreator; +import com.squareup.picasso.Transformation; import org.schabi.newpipe.R; @@ -21,6 +22,9 @@ import static org.schabi.newpipe.extractor.utils.Utils.isBlank; public final class PicassoHelper { + public static final String PLAYER_THUMBNAIL_TAG = "PICASSO_PLAYER_THUMBNAIL_TAG"; + private static final String PLAYER_THUMBNAIL_TRANSFORMATION_KEY + = "PICASSO_PLAYER_THUMBNAIL_TRANSFORMATION_KEY"; private PicassoHelper() { } @@ -63,7 +67,10 @@ public static void terminate() { public static void clearCache(final Context context) throws IOException { picassoInstance.shutdown(); picassoCache.clear(); // clear memory cache - picassoDownloaderClient.cache().delete(); // clear disk cache + final okhttp3.Cache diskCache = picassoDownloaderClient.cache(); + if (diskCache != null) { + diskCache.delete(); // clear disk cache + } init(context); } @@ -105,6 +112,50 @@ public static RequestCreator loadSeekbarThumbnailPreview(final String url) { } + public static RequestCreator loadScaledDownThumbnail(final Context context, final String url) { + // scale down the notification thumbnail for performance + return PicassoHelper.loadThumbnail(url) + .tag(PLAYER_THUMBNAIL_TAG) + .transform(new Transformation() { + @Override + public Bitmap transform(final Bitmap source) { + final float notificationThumbnailWidth = Math.min( + context.getResources() + .getDimension(R.dimen.player_notification_thumbnail_width), + source.getWidth()); + + final Bitmap result = Bitmap.createScaledBitmap( + source, + (int) notificationThumbnailWidth, + (int) (source.getHeight() + / (source.getWidth() / notificationThumbnailWidth)), + true); + + if (result == source) { + // create a new mutable bitmap to prevent strange crashes on some + // devices (see #4638) + final Bitmap copied = Bitmap.createScaledBitmap( + source, + (int) notificationThumbnailWidth - 1, + (int) (source.getHeight() / (source.getWidth() + / (notificationThumbnailWidth - 1))), + true); + source.recycle(); + return copied; + } else { + source.recycle(); + return result; + } + } + + @Override + public String key() { + return PLAYER_THUMBNAIL_TRANSFORMATION_KEY; + } + }); + } + + private static RequestCreator loadImageDefault(final String url, final int placeholderResId) { return picassoInstance .load((!shouldLoadImages || isBlank(url)) ? null : url) From 44128f91457a08aaea8ef1a76a7d188d4fe57366 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 10 Aug 2021 09:46:32 +0200 Subject: [PATCH 7/7] Remove placeholder image while loading thumbnails --- .../org/schabi/newpipe/util/PicassoHelper.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index 173b4577625..e15ecd277f0 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -157,9 +157,15 @@ public String key() { private static RequestCreator loadImageDefault(final String url, final int placeholderResId) { - return picassoInstance - .load((!shouldLoadImages || isBlank(url)) ? null : url) - .placeholder(placeholderResId) - .error(placeholderResId); + if (!shouldLoadImages || isBlank(url)) { + return picassoInstance + .load((String) null) + .placeholder(placeholderResId) // show placeholder when no image should load + .error(placeholderResId); + } else { + return picassoInstance + .load(url) + .error(placeholderResId); // don't show placeholder while loading, only on error + } } }