Support obtaining multiple images from the extractor

This commit is contained in:
Stypox 2023-05-01 23:11:48 +02:00
parent e2de83188a
commit af2375948d
No known key found for this signature in database
GPG Key ID: 4BDF1B40A49FDD23
30 changed files with 190 additions and 77 deletions

View File

@ -197,7 +197,7 @@ dependencies {
// name and the commit hash with the commit hash of the (pushed) commit you want to test // name and the commit hash with the commit hash of the (pushed) commit you want to test
// This works thanks to JitPack: https://jitpack.io/ // This works thanks to JitPack: https://jitpack.io/
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
implementation 'com.github.TeamNewPipe:NewPipeExtractor:95a3cc0a173bba28c179f9f9503b1010ec6bff21' implementation 'com.github.TeamNewPipe:NewPipeExtractor:3be76a6406d59f1fd8eedf5fab6552e6c2a3da76'
implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0' implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0'
/** Checkstyle **/ /** Checkstyle **/

View File

@ -75,7 +75,7 @@ public final class QueueItemMenuUtil {
return true; return true;
case R.id.menu_item_share: case R.id.menu_item_share:
shareText(context, item.getTitle(), item.getUrl(), shareText(context, item.getTitle(), item.getUrl(),
item.getThumbnailUrl()); item.getThumbnails());
return true; return true;
case R.id.menu_item_download: case R.id.menu_item_download:
fetchStreamInfoAndSaveToDatabase(context, item.getServiceId(), item.getUrl(), fetchStreamInfoAndSaveToDatabase(context, item.getServiceId(), item.getUrl(),

View File

@ -7,6 +7,7 @@ import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity
import org.schabi.newpipe.database.stream.model.StreamEntity import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.stream.model.StreamStateEntity import org.schabi.newpipe.database.stream.model.StreamStateEntity
import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.util.PicassoHelper
data class PlaylistStreamEntry( data class PlaylistStreamEntry(
@Embedded @Embedded
@ -28,7 +29,7 @@ data class PlaylistStreamEntry(
item.duration = streamEntity.duration item.duration = streamEntity.duration
item.uploaderName = streamEntity.uploader item.uploaderName = streamEntity.uploader
item.uploaderUrl = streamEntity.uploaderUrl item.uploaderUrl = streamEntity.uploaderUrl
item.thumbnailUrl = streamEntity.thumbnailUrl item.thumbnails = PicassoHelper.urlToImageList(streamEntity.thumbnailUrl)
return item return item
} }

View File

@ -11,6 +11,7 @@ import androidx.room.PrimaryKey;
import org.schabi.newpipe.database.playlist.PlaylistLocalItem; import org.schabi.newpipe.database.playlist.PlaylistLocalItem;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.PicassoHelper;
import static org.schabi.newpipe.database.LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM; import static org.schabi.newpipe.database.LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM;
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_NAME; import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_NAME;
@ -69,8 +70,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
@Ignore @Ignore
public PlaylistRemoteEntity(final PlaylistInfo info) { public PlaylistRemoteEntity(final PlaylistInfo info) {
this(info.getServiceId(), info.getName(), info.getUrl(), this(info.getServiceId(), info.getName(), info.getUrl(),
info.getThumbnailUrl() == null PicassoHelper.choosePreferredImage(info.getThumbnails()),
? info.getUploaderAvatarUrl() : info.getThumbnailUrl(),
info.getUploaderName(), info.getStreamCount()); info.getUploaderName(), info.getStreamCount());
} }
@ -84,7 +84,8 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
&& getStreamCount() == info.getStreamCount() && getStreamCount() == info.getStreamCount()
&& TextUtils.equals(getName(), info.getName()) && TextUtils.equals(getName(), info.getName())
&& TextUtils.equals(getUrl(), info.getUrl()) && TextUtils.equals(getUrl(), info.getUrl())
&& TextUtils.equals(getThumbnailUrl(), info.getThumbnailUrl()) && TextUtils.equals(getThumbnailUrl(),
PicassoHelper.choosePreferredImage(info.getThumbnails()))
&& TextUtils.equals(getUploader(), info.getUploaderName()); && TextUtils.equals(getUploader(), info.getUploaderName());
} }

View File

@ -7,6 +7,7 @@ import org.schabi.newpipe.database.history.model.StreamHistoryEntity
import org.schabi.newpipe.database.stream.model.StreamEntity import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_PROGRESS_MILLIS import org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_PROGRESS_MILLIS
import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.util.PicassoHelper
import java.time.OffsetDateTime import java.time.OffsetDateTime
class StreamStatisticsEntry( class StreamStatisticsEntry(
@ -30,7 +31,7 @@ class StreamStatisticsEntry(
item.duration = streamEntity.duration item.duration = streamEntity.duration
item.uploaderName = streamEntity.uploader item.uploaderName = streamEntity.uploader
item.uploaderUrl = streamEntity.uploaderUrl item.uploaderUrl = streamEntity.uploaderUrl
item.thumbnailUrl = streamEntity.thumbnailUrl item.thumbnails = PicassoHelper.urlToImageList(streamEntity.thumbnailUrl)
return item return item
} }

View File

@ -13,6 +13,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfo
import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.extractor.stream.StreamType import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.player.playqueue.PlayQueueItem import org.schabi.newpipe.player.playqueue.PlayQueueItem
import org.schabi.newpipe.util.PicassoHelper
import java.io.Serializable import java.io.Serializable
import java.time.OffsetDateTime import java.time.OffsetDateTime
@ -67,7 +68,7 @@ data class StreamEntity(
constructor(item: StreamInfoItem) : this( constructor(item: StreamInfoItem) : this(
serviceId = item.serviceId, url = item.url, title = item.name, serviceId = item.serviceId, url = item.url, title = item.name,
streamType = item.streamType, duration = item.duration, uploader = item.uploaderName, streamType = item.streamType, duration = item.duration, uploader = item.uploaderName,
uploaderUrl = item.uploaderUrl, thumbnailUrl = item.thumbnailUrl, viewCount = item.viewCount, uploaderUrl = item.uploaderUrl, thumbnailUrl = PicassoHelper.choosePreferredImage(item.thumbnails), viewCount = item.viewCount,
textualUploadDate = item.textualUploadDate, uploadDate = item.uploadDate?.offsetDateTime(), textualUploadDate = item.textualUploadDate, uploadDate = item.uploadDate?.offsetDateTime(),
isUploadDateApproximation = item.uploadDate?.isApproximation isUploadDateApproximation = item.uploadDate?.isApproximation
) )
@ -76,7 +77,7 @@ data class StreamEntity(
constructor(info: StreamInfo) : this( constructor(info: StreamInfo) : this(
serviceId = info.serviceId, url = info.url, title = info.name, serviceId = info.serviceId, url = info.url, title = info.name,
streamType = info.streamType, duration = info.duration, uploader = info.uploaderName, streamType = info.streamType, duration = info.duration, uploader = info.uploaderName,
uploaderUrl = info.uploaderUrl, thumbnailUrl = info.thumbnailUrl, viewCount = info.viewCount, uploaderUrl = info.uploaderUrl, thumbnailUrl = PicassoHelper.choosePreferredImage(info.thumbnails), viewCount = info.viewCount,
textualUploadDate = info.textualUploadDate, uploadDate = info.uploadDate?.offsetDateTime(), textualUploadDate = info.textualUploadDate, uploadDate = info.uploadDate?.offsetDateTime(),
isUploadDateApproximation = info.uploadDate?.isApproximation isUploadDateApproximation = info.uploadDate?.isApproximation
) )
@ -85,7 +86,8 @@ data class StreamEntity(
constructor(item: PlayQueueItem) : this( constructor(item: PlayQueueItem) : this(
serviceId = item.serviceId, url = item.url, title = item.title, serviceId = item.serviceId, url = item.url, title = item.title,
streamType = item.streamType, duration = item.duration, uploader = item.uploader, streamType = item.streamType, duration = item.duration, uploader = item.uploader,
uploaderUrl = item.uploaderUrl, thumbnailUrl = item.thumbnailUrl uploaderUrl = item.uploaderUrl,
thumbnailUrl = PicassoHelper.choosePreferredImage(item.thumbnails)
) )
fun toStreamInfoItem(): StreamInfoItem { fun toStreamInfoItem(): StreamInfoItem {
@ -93,7 +95,7 @@ data class StreamEntity(
item.duration = duration item.duration = duration
item.uploaderName = uploader item.uploaderName = uploader
item.uploaderUrl = uploaderUrl item.uploaderUrl = uploaderUrl
item.thumbnailUrl = thumbnailUrl item.thumbnails = PicassoHelper.urlToImageList(thumbnailUrl)
if (viewCount != null) item.viewCount = viewCount as Long if (viewCount != null) item.viewCount = viewCount as Long
item.textualUploadDate = textualUploadDate item.textualUploadDate = textualUploadDate

View File

@ -10,6 +10,7 @@ import androidx.room.PrimaryKey;
import org.schabi.newpipe.extractor.channel.ChannelInfo; import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem; import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.PicassoHelper;
import static org.schabi.newpipe.database.subscription.SubscriptionEntity.SUBSCRIPTION_SERVICE_ID; import static org.schabi.newpipe.database.subscription.SubscriptionEntity.SUBSCRIPTION_SERVICE_ID;
import static org.schabi.newpipe.database.subscription.SubscriptionEntity.SUBSCRIPTION_TABLE; import static org.schabi.newpipe.database.subscription.SubscriptionEntity.SUBSCRIPTION_TABLE;
@ -57,8 +58,8 @@ public class SubscriptionEntity {
final SubscriptionEntity result = new SubscriptionEntity(); final SubscriptionEntity result = new SubscriptionEntity();
result.setServiceId(info.getServiceId()); result.setServiceId(info.getServiceId());
result.setUrl(info.getUrl()); result.setUrl(info.getUrl());
result.setData(info.getName(), info.getAvatarUrl(), info.getDescription(), result.setData(info.getName(), PicassoHelper.choosePreferredImage(info.getAvatars()),
info.getSubscriberCount()); info.getDescription(), info.getSubscriberCount());
return result; return result;
} }
@ -138,7 +139,7 @@ public class SubscriptionEntity {
@Ignore @Ignore
public ChannelInfoItem toChannelInfoItem() { public ChannelInfoItem toChannelInfoItem() {
final ChannelInfoItem item = new ChannelInfoItem(getServiceId(), getUrl(), getName()); final ChannelInfoItem item = new ChannelInfoItem(getServiceId(), getUrl(), getName());
item.setThumbnailUrl(getAvatarUrl()); item.setThumbnails(PicassoHelper.urlToImageList(getAvatarUrl()));
item.setSubscriberCount(getSubscriberCount()); item.setSubscriberCount(getSubscriberCount());
item.setDescription(getDescription()); item.setDescription(getDescription());
return item; return item;

View File

@ -17,6 +17,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.Localization;
import java.util.List; import java.util.List;
import org.schabi.newpipe.util.PicassoHelper;
import icepick.State; import icepick.State;
@ -113,7 +114,7 @@ public class DescriptionFragment extends BaseDescriptionFragment {
addMetadataItem(inflater, layout, true, R.string.metadata_host, addMetadataItem(inflater, layout, true, R.string.metadata_host,
streamInfo.getHost()); streamInfo.getHost());
addMetadataItem(inflater, layout, true, R.string.metadata_thumbnail_url, addMetadataItem(inflater, layout, true, R.string.metadata_thumbnail_url,
streamInfo.getThumbnailUrl()); PicassoHelper.choosePreferredImage(streamInfo.getThumbnails()));
} }
private void addPrivacyMetadataItem(final LayoutInflater inflater, final LinearLayout layout) { private void addPrivacyMetadataItem(final LayoutInflater inflater, final LinearLayout layout) {

View File

@ -71,6 +71,7 @@ import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.ReCaptchaActivity; import org.schabi.newpipe.error.ReCaptchaActivity;
import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.Image;
import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException; import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
@ -483,7 +484,7 @@ public final class VideoDetailFragment
}); });
binding.detailControlsShare.setOnClickListener(makeOnClickListener(info -> binding.detailControlsShare.setOnClickListener(makeOnClickListener(info ->
ShareUtils.shareText(requireContext(), info.getName(), info.getUrl(), ShareUtils.shareText(requireContext(), info.getName(), info.getUrl(),
info.getThumbnailUrl()))); info.getThumbnails())));
binding.detailControlsOpenInBrowser.setOnClickListener(makeOnClickListener(info -> binding.detailControlsOpenInBrowser.setOnClickListener(makeOnClickListener(info ->
ShareUtils.openUrlInBrowser(requireContext(), info.getUrl()))); ShareUtils.openUrlInBrowser(requireContext(), info.getUrl())));
binding.detailControlsPlayWithKodi.setOnClickListener(makeOnClickListener(info -> binding.detailControlsPlayWithKodi.setOnClickListener(makeOnClickListener(info ->
@ -723,7 +724,7 @@ public final class VideoDetailFragment
final boolean isPlayerStopped = !isPlayerAvailable() || player.isStopped(); final boolean isPlayerStopped = !isPlayerAvailable() || player.isStopped();
if (playQueueItem != null && isPlayerStopped) { if (playQueueItem != null && isPlayerStopped) {
updateOverlayData(playQueueItem.getTitle(), updateOverlayData(playQueueItem.getTitle(),
playQueueItem.getUploader(), playQueueItem.getThumbnailUrl()); playQueueItem.getUploader(), playQueueItem.getThumbnails());
} }
} }
@ -1536,13 +1537,13 @@ public final class VideoDetailFragment
binding.detailSecondaryControlPanel.setVisibility(View.GONE); binding.detailSecondaryControlPanel.setVisibility(View.GONE);
checkUpdateProgressInfo(info); checkUpdateProgressInfo(info);
PicassoHelper.loadDetailsThumbnail(info.getThumbnailUrl()).tag(PICASSO_VIDEO_DETAILS_TAG) PicassoHelper.loadDetailsThumbnail(info.getThumbnails()).tag(PICASSO_VIDEO_DETAILS_TAG)
.into(binding.detailThumbnailImageView); .into(binding.detailThumbnailImageView);
showMetaInfoInTextView(info.getMetaInfo(), binding.detailMetaInfoTextView, showMetaInfoInTextView(info.getMetaInfo(), binding.detailMetaInfoTextView,
binding.detailMetaInfoSeparator, disposables); binding.detailMetaInfoSeparator, disposables);
if (!isPlayerAvailable() || player.isStopped()) { if (!isPlayerAvailable() || player.isStopped()) {
updateOverlayData(info.getName(), info.getUploaderName(), info.getThumbnailUrl()); updateOverlayData(info.getName(), info.getUploaderName(), info.getThumbnails());
} }
if (!info.getErrors().isEmpty()) { if (!info.getErrors().isEmpty()) {
@ -1587,7 +1588,7 @@ public final class VideoDetailFragment
binding.detailUploaderTextView.setVisibility(View.GONE); binding.detailUploaderTextView.setVisibility(View.GONE);
} }
PicassoHelper.loadAvatar(info.getUploaderAvatarUrl()).tag(PICASSO_VIDEO_DETAILS_TAG) PicassoHelper.loadAvatar(info.getUploaderAvatars()).tag(PICASSO_VIDEO_DETAILS_TAG)
.into(binding.detailSubChannelThumbnailView); .into(binding.detailSubChannelThumbnailView);
binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE); binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE);
binding.detailUploaderThumbnailView.setVisibility(View.GONE); binding.detailUploaderThumbnailView.setVisibility(View.GONE);
@ -1619,10 +1620,10 @@ public final class VideoDetailFragment
binding.detailUploaderTextView.setVisibility(View.GONE); binding.detailUploaderTextView.setVisibility(View.GONE);
} }
PicassoHelper.loadAvatar(info.getSubChannelAvatarUrl()).tag(PICASSO_VIDEO_DETAILS_TAG) PicassoHelper.loadAvatar(info.getSubChannelAvatars()).tag(PICASSO_VIDEO_DETAILS_TAG)
.into(binding.detailSubChannelThumbnailView); .into(binding.detailSubChannelThumbnailView);
binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE); binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE);
PicassoHelper.loadAvatar(info.getUploaderAvatarUrl()).tag(PICASSO_VIDEO_DETAILS_TAG) PicassoHelper.loadAvatar(info.getUploaderAvatars()).tag(PICASSO_VIDEO_DETAILS_TAG)
.into(binding.detailUploaderThumbnailView); .into(binding.detailUploaderThumbnailView);
binding.detailUploaderThumbnailView.setVisibility(View.VISIBLE); binding.detailUploaderThumbnailView.setVisibility(View.VISIBLE);
} }
@ -1797,7 +1798,7 @@ public final class VideoDetailFragment
return; return;
} }
updateOverlayData(info.getName(), info.getUploaderName(), info.getThumbnailUrl()); updateOverlayData(info.getName(), info.getUploaderName(), info.getThumbnails());
if (currentInfo != null && info.getUrl().equals(currentInfo.getUrl())) { if (currentInfo != null && info.getUrl().equals(currentInfo.getUrl())) {
return; return;
} }
@ -1826,7 +1827,7 @@ public final class VideoDetailFragment
if (currentInfo != null) { if (currentInfo != null) {
updateOverlayData(currentInfo.getName(), updateOverlayData(currentInfo.getName(),
currentInfo.getUploaderName(), currentInfo.getUploaderName(),
currentInfo.getThumbnailUrl()); currentInfo.getThumbnails());
} }
updateOverlayPlayQueueButtonVisibility(); updateOverlayPlayQueueButtonVisibility();
} }
@ -2191,7 +2192,7 @@ public final class VideoDetailFragment
playerHolder.stopService(); playerHolder.stopService();
setInitialData(0, null, "", null); setInitialData(0, null, "", null);
currentInfo = null; currentInfo = null;
updateOverlayData(null, null, null); updateOverlayData(null, null, List.of());
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -2373,11 +2374,11 @@ public final class VideoDetailFragment
private void updateOverlayData(@Nullable final String overlayTitle, private void updateOverlayData(@Nullable final String overlayTitle,
@Nullable final String uploader, @Nullable final String uploader,
@Nullable final String thumbnailUrl) { @NonNull final List<Image> thumbnails) {
binding.overlayTitleTextView.setText(isEmpty(overlayTitle) ? "" : overlayTitle); binding.overlayTitleTextView.setText(isEmpty(overlayTitle) ? "" : overlayTitle);
binding.overlayChannelTextView.setText(isEmpty(uploader) ? "" : uploader); binding.overlayChannelTextView.setText(isEmpty(uploader) ? "" : uploader);
binding.overlayThumbnail.setImageDrawable(null); binding.overlayThumbnail.setImageDrawable(null);
PicassoHelper.loadDetailsThumbnail(thumbnailUrl).tag(PICASSO_VIDEO_DETAILS_TAG) PicassoHelper.loadDetailsThumbnail(thumbnails).tag(PICASSO_VIDEO_DETAILS_TAG)
.into(binding.overlayThumbnail); .into(binding.overlayThumbnail);
} }

View File

@ -17,6 +17,7 @@ import org.schabi.newpipe.extractor.stream.Description;
import org.schabi.newpipe.fragments.detail.BaseDescriptionFragment; import org.schabi.newpipe.fragments.detail.BaseDescriptionFragment;
import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.PicassoHelper;
import java.util.List; import java.util.List;
@ -100,8 +101,8 @@ public class ChannelAboutFragment extends BaseDescriptionFragment {
} }
addMetadataItem(inflater, layout, true, R.string.metadata_avatar_url, addMetadataItem(inflater, layout, true, R.string.metadata_avatar_url,
channelInfo.getAvatarUrl()); PicassoHelper.choosePreferredImage(channelInfo.getAvatars()));
addMetadataItem(inflater, layout, true, R.string.metadata_banner_url, addMetadataItem(inflater, layout, true, R.string.metadata_banner_url,
channelInfo.getBannerUrl()); PicassoHelper.choosePreferredImage(channelInfo.getBanners()));
} }
} }

View File

@ -1,6 +1,5 @@
package org.schabi.newpipe.fragments.list.channel; package org.schabi.newpipe.fragments.list.channel;
import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
import static org.schabi.newpipe.ktx.TextViewUtils.animateTextColor; import static org.schabi.newpipe.ktx.TextViewUtils.animateTextColor;
import static org.schabi.newpipe.ktx.ViewUtils.animate; import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.ktx.ViewUtils.animateBackgroundColor; import static org.schabi.newpipe.ktx.ViewUtils.animateBackgroundColor;
@ -234,7 +233,7 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
case R.id.menu_item_share: case R.id.menu_item_share:
if (currentInfo != null) { if (currentInfo != null) {
ShareUtils.shareText(requireContext(), name, currentInfo.getOriginalUrl(), ShareUtils.shareText(requireContext(), name, currentInfo.getOriginalUrl(),
currentInfo.getAvatarUrl()); currentInfo.getAvatars());
} }
break; break;
default: default:
@ -355,7 +354,7 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
channel.setServiceId(info.getServiceId()); channel.setServiceId(info.getServiceId());
channel.setUrl(info.getUrl()); channel.setUrl(info.getUrl());
channel.setData(info.getName(), channel.setData(info.getName(),
info.getAvatarUrl(), PicassoHelper.choosePreferredImage(info.getAvatars()),
info.getDescription(), info.getDescription(),
info.getSubscriberCount()); info.getSubscriberCount());
channelSubscription = null; channelSubscription = null;
@ -579,17 +578,17 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
currentInfo = result; currentInfo = result;
setInitialData(result.getServiceId(), result.getOriginalUrl(), result.getName()); setInitialData(result.getServiceId(), result.getOriginalUrl(), result.getName());
if (PicassoHelper.getShouldLoadImages() && !isBlank(result.getBannerUrl())) { if (PicassoHelper.getShouldLoadImages() && !result.getBanners().isEmpty()) {
PicassoHelper.loadBanner(result.getBannerUrl()).tag(PICASSO_CHANNEL_TAG) PicassoHelper.loadBanner(result.getBanners()).tag(PICASSO_CHANNEL_TAG)
.into(binding.channelBannerImage); .into(binding.channelBannerImage);
} else { } else {
// do not waste space for the banner, if the user disabled images or there is not one // do not waste space for the banner, if the user disabled images or there is not one
binding.channelBannerImage.setImageDrawable(null); binding.channelBannerImage.setImageDrawable(null);
} }
PicassoHelper.loadAvatar(result.getAvatarUrl()).tag(PICASSO_CHANNEL_TAG) PicassoHelper.loadAvatar(result.getAvatars()).tag(PICASSO_CHANNEL_TAG)
.into(binding.channelAvatarView); .into(binding.channelAvatarView);
PicassoHelper.loadAvatar(result.getParentChannelAvatarUrl()).tag(PICASSO_CHANNEL_TAG) PicassoHelper.loadAvatar(result.getParentChannelAvatars()).tag(PICASSO_CHANNEL_TAG)
.into(binding.subChannelAvatarView); .into(binding.subChannelAvatarView);
binding.channelTitleView.setText(result.getName()); binding.channelTitleView.setText(result.getName());

View File

@ -234,7 +234,7 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
break; break;
case R.id.menu_item_share: case R.id.menu_item_share:
ShareUtils.shareText(requireContext(), name, url, ShareUtils.shareText(requireContext(), name, url,
currentInfo == null ? null : currentInfo.getThumbnailUrl()); currentInfo == null ? List.of() : currentInfo.getThumbnails());
break; break;
case R.id.menu_item_bookmark: case R.id.menu_item_bookmark:
onBookmarkClicked(); onBookmarkClicked();
@ -299,7 +299,6 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
playlistControlBinding.getRoot().setVisibility(View.VISIBLE); playlistControlBinding.getRoot().setVisibility(View.VISIBLE);
final String avatarUrl = result.getUploaderAvatarUrl();
if (result.getServiceId() == ServiceList.YouTube.getServiceId() if (result.getServiceId() == ServiceList.YouTube.getServiceId()
&& (YoutubeParsingHelper.isYoutubeMixId(result.getId()) && (YoutubeParsingHelper.isYoutubeMixId(result.getId())
|| YoutubeParsingHelper.isYoutubeMusicMixId(result.getId()))) { || YoutubeParsingHelper.isYoutubeMusicMixId(result.getId()))) {
@ -315,7 +314,7 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
R.drawable.ic_radio) R.drawable.ic_radio)
); );
} else { } else {
PicassoHelper.loadAvatar(avatarUrl).tag(PICASSO_PLAYLIST_TAG) PicassoHelper.loadAvatar(result.getUploaderAvatars()).tag(PICASSO_PLAYLIST_TAG)
.into(headerBinding.uploaderAvatarView); .into(headerBinding.uploaderAvatarView);
} }

View File

@ -104,7 +104,7 @@ public enum StreamDialogDefaultEntry {
SHARE(R.string.share, (fragment, item) -> SHARE(R.string.share, (fragment, item) ->
ShareUtils.shareText(fragment.requireContext(), item.getName(), item.getUrl(), ShareUtils.shareText(fragment.requireContext(), item.getName(), item.getUrl(),
item.getThumbnailUrl())), item.getThumbnails())),
/** /**
* Opens a {@link DownloadDialog} after fetching some stream info. * Opens a {@link DownloadDialog} after fetching some stream info.

View File

@ -56,7 +56,7 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
itemAdditionalDetailView.setText(getDetailLine(item)); itemAdditionalDetailView.setText(getDetailLine(item));
} }
PicassoHelper.loadAvatar(item.getThumbnailUrl()).into(itemThumbnailView); PicassoHelper.loadAvatar(item.getThumbnails()).into(itemThumbnailView);
itemView.setOnClickListener(view -> { itemView.setOnClickListener(view -> {
if (itemBuilder.getOnChannelSelectedListener() != null) { if (itemBuilder.getOnChannelSelectedListener() != null) {

View File

@ -97,7 +97,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
} }
final CommentsInfoItem item = (CommentsInfoItem) infoItem; final CommentsInfoItem item = (CommentsInfoItem) infoItem;
PicassoHelper.loadAvatar(item.getUploaderAvatarUrl()).into(itemThumbnailView); PicassoHelper.loadAvatar(item.getUploaderAvatars()).into(itemThumbnailView);
if (PicassoHelper.getShouldLoadImages()) { if (PicassoHelper.getShouldLoadImages()) {
itemThumbnailView.setVisibility(View.VISIBLE); itemThumbnailView.setVisibility(View.VISIBLE);
itemRoot.setPadding(commentVerticalPadding, commentVerticalPadding, itemRoot.setPadding(commentVerticalPadding, commentVerticalPadding,

View File

@ -46,7 +46,7 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
.localizeStreamCountMini(itemStreamCountView.getContext(), item.getStreamCount())); .localizeStreamCountMini(itemStreamCountView.getContext(), item.getStreamCount()));
itemUploaderView.setText(item.getUploaderName()); itemUploaderView.setText(item.getUploaderName());
PicassoHelper.loadPlaylistThumbnail(item.getThumbnailUrl()).into(itemThumbnailView); PicassoHelper.loadPlaylistThumbnail(item.getThumbnails()).into(itemThumbnailView);
itemView.setOnClickListener(view -> { itemView.setOnClickListener(view -> {
if (itemBuilder.getOnPlaylistSelectedListener() != null) { if (itemBuilder.getOnPlaylistSelectedListener() != null) {

View File

@ -87,7 +87,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
} }
// Default thumbnail is shown on error, while loading and if the url is empty // Default thumbnail is shown on error, while loading and if the url is empty
PicassoHelper.loadThumbnail(item.getThumbnailUrl()).into(itemThumbnailView); PicassoHelper.loadThumbnail(item.getThumbnails()).into(itemThumbnailView);
itemView.setOnClickListener(view -> { itemView.setOnClickListener(view -> {
if (itemBuilder.getOnStreamSelectedListener() != null) { if (itemBuilder.getOnStreamSelectedListener() != null) {

View File

@ -58,6 +58,7 @@ import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard
import org.schabi.newpipe.streams.io.StoredFileHelper import org.schabi.newpipe.streams.io.StoredFileHelper
import org.schabi.newpipe.util.NavigationHelper import org.schabi.newpipe.util.NavigationHelper
import org.schabi.newpipe.util.OnClickGesture import org.schabi.newpipe.util.OnClickGesture
import org.schabi.newpipe.util.PicassoHelper
import org.schabi.newpipe.util.ServiceHelper import org.schabi.newpipe.util.ServiceHelper
import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountChannels import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountChannels
import org.schabi.newpipe.util.external_communication.ShareUtils import org.schabi.newpipe.util.external_communication.ShareUtils
@ -342,7 +343,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
when (i) { when (i) {
0 -> ShareUtils.shareText( 0 -> ShareUtils.shareText(
requireContext(), selectedItem.name, selectedItem.url, requireContext(), selectedItem.name, selectedItem.url,
selectedItem.thumbnailUrl PicassoHelper.choosePreferredImage(selectedItem.thumbnails)
) )
1 -> ShareUtils.openUrlInBrowser(requireContext(), selectedItem.url) 1 -> ShareUtils.openUrlInBrowser(requireContext(), selectedItem.url)
2 -> deleteChannel(selectedItem) 2 -> deleteChannel(selectedItem)

View File

@ -19,6 +19,7 @@ import org.schabi.newpipe.extractor.feed.FeedInfo
import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.local.feed.FeedDatabaseManager import org.schabi.newpipe.local.feed.FeedDatabaseManager
import org.schabi.newpipe.util.ExtractorHelper import org.schabi.newpipe.util.ExtractorHelper
import org.schabi.newpipe.util.PicassoHelper
class SubscriptionManager(context: Context) { class SubscriptionManager(context: Context) {
private val database = NewPipeDatabase.getInstance(context) private val database = NewPipeDatabase.getInstance(context)
@ -71,7 +72,12 @@ class SubscriptionManager(context: Context) {
subscriptionTable.getSubscription(info.serviceId, info.url) subscriptionTable.getSubscription(info.serviceId, info.url)
.flatMapCompletable { .flatMapCompletable {
Completable.fromRunnable { Completable.fromRunnable {
it.setData(info.name, info.avatarUrl, info.description, info.subscriberCount) it.setData(
info.name,
PicassoHelper.choosePreferredImage(info.avatars),
info.description,
info.subscriberCount
)
subscriptionTable.update(it) subscriptionTable.update(it)
} }
} }
@ -99,7 +105,7 @@ class SubscriptionManager(context: Context) {
} else if (info is ChannelInfo) { } else if (info is ChannelInfo) {
subscriptionEntity.setData( subscriptionEntity.setData(
info.name, info.name,
info.avatarUrl, PicassoHelper.choosePreferredImage(info.avatars),
info.description, info.description,
info.subscriberCount info.subscriberCount
) )

View File

@ -39,7 +39,7 @@ class ChannelItem(
itemChannelDescriptionView.text = infoItem.description itemChannelDescriptionView.text = infoItem.description
} }
PicassoHelper.loadAvatar(infoItem.thumbnailUrl).into(itemThumbnailView) PicassoHelper.loadAvatar(infoItem.thumbnails).into(itemThumbnailView)
gesturesListener?.run { gesturesListener?.run {
viewHolder.root.setOnClickListener { selected(infoItem) } viewHolder.root.setOnClickListener { selected(infoItem) }

View File

@ -87,6 +87,7 @@ import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.Image;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.extractor.stream.VideoStream;
@ -805,10 +806,10 @@ public final class Player implements PlaybackListener, Listener {
}; };
} }
private void loadCurrentThumbnail(final String url) { private void loadCurrentThumbnail(final List<Image> thumbnails) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "Thumbnail - loadCurrentThumbnail() called with url = [" Log.d(TAG, "Thumbnail - loadCurrentThumbnail() called with thumbnails = ["
+ (url == null ? "null" : url) + "]"); + thumbnails.size() + "]");
} }
// first cancel any previous loading // first cancel any previous loading
@ -817,12 +818,12 @@ public final class Player implements PlaybackListener, Listener {
// Unset currentThumbnail, since it is now outdated. This ensures it is not used in media // Unset currentThumbnail, since it is now outdated. This ensures it is not used in media
// session metadata while the new thumbnail is being loaded by Picasso. // session metadata while the new thumbnail is being loaded by Picasso.
onThumbnailLoaded(null); onThumbnailLoaded(null);
if (isNullOrEmpty(url)) { if (thumbnails.isEmpty()) {
return; return;
} }
// scale down the notification thumbnail for performance // scale down the notification thumbnail for performance
PicassoHelper.loadScaledDownThumbnail(context, url) PicassoHelper.loadScaledDownThumbnail(context, thumbnails)
.tag(PICASSO_PLAYER_THUMBNAIL_TAG) .tag(PICASSO_PLAYER_THUMBNAIL_TAG)
.into(currentThumbnailTarget); .into(currentThumbnailTarget);
} }
@ -1792,7 +1793,7 @@ public final class Player implements PlaybackListener, Listener {
maybeAutoQueueNextStream(info); maybeAutoQueueNextStream(info);
loadCurrentThumbnail(info.getThumbnailUrl()); loadCurrentThumbnail(info.getThumbnails());
registerStreamViewed(); registerStreamViewed();
notifyMetadataUpdateToListeners(); notifyMetadataUpdateToListeners();

View File

@ -3,6 +3,7 @@ package org.schabi.newpipe.player.mediaitem;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.util.PicassoHelper;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -74,7 +75,7 @@ public final class ExceptionTag implements MediaItemTag {
@Override @Override
public String getThumbnailUrl() { public String getThumbnailUrl() {
return item.getThumbnailUrl(); return PicassoHelper.choosePreferredImage(item.getThumbnails());
} }
@Override @Override

View File

@ -6,6 +6,7 @@ import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.util.PicassoHelper;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -95,7 +96,7 @@ public final class StreamInfoTag implements MediaItemTag {
@Override @Override
public String getThumbnailUrl() { public String getThumbnailUrl() {
return streamInfo.getThumbnailUrl(); return PicassoHelper.choosePreferredImage(streamInfo.getThumbnails());
} }
@Override @Override

View File

@ -20,6 +20,7 @@ import com.google.android.exoplayer2.util.Util;
import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.util.PicassoHelper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -137,7 +138,8 @@ public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator
.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, player.getPlayQueue().size()); .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, player.getPlayQueue().size());
descBuilder.setExtras(additionalMetadata); descBuilder.setExtras(additionalMetadata);
final Uri thumbnailUri = Uri.parse(item.getThumbnailUrl()); final Uri thumbnailUri = Uri.parse(
PicassoHelper.choosePreferredImage(item.getThumbnails()));
if (thumbnailUri != null) { if (thumbnailUri != null) {
descBuilder.setIconUri(thumbnailUri); descBuilder.setIconUri(thumbnailUri);
} }

View File

@ -3,12 +3,14 @@ package org.schabi.newpipe.player.playqueue;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.schabi.newpipe.extractor.Image;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ExtractorHelper;
import java.io.Serializable; import java.io.Serializable;
import java.util.List;
import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
@ -24,7 +26,7 @@ public class PlayQueueItem implements Serializable {
private final int serviceId; private final int serviceId;
private final long duration; private final long duration;
@NonNull @NonNull
private final String thumbnailUrl; private final List<Image> thumbnails;
@NonNull @NonNull
private final String uploader; private final String uploader;
private final String uploaderUrl; private final String uploaderUrl;
@ -38,7 +40,7 @@ public class PlayQueueItem implements Serializable {
PlayQueueItem(@NonNull final StreamInfo info) { PlayQueueItem(@NonNull final StreamInfo info) {
this(info.getName(), info.getUrl(), info.getServiceId(), info.getDuration(), this(info.getName(), info.getUrl(), info.getServiceId(), info.getDuration(),
info.getThumbnailUrl(), info.getUploaderName(), info.getThumbnails(), info.getUploaderName(),
info.getUploaderUrl(), info.getStreamType()); info.getUploaderUrl(), info.getStreamType());
if (info.getStartPosition() > 0) { if (info.getStartPosition() > 0) {
@ -48,20 +50,20 @@ public class PlayQueueItem implements Serializable {
PlayQueueItem(@NonNull final StreamInfoItem item) { PlayQueueItem(@NonNull final StreamInfoItem item) {
this(item.getName(), item.getUrl(), item.getServiceId(), item.getDuration(), this(item.getName(), item.getUrl(), item.getServiceId(), item.getDuration(),
item.getThumbnailUrl(), item.getUploaderName(), item.getThumbnails(), item.getUploaderName(),
item.getUploaderUrl(), item.getStreamType()); item.getUploaderUrl(), item.getStreamType());
} }
@SuppressWarnings("ParameterNumber") @SuppressWarnings("ParameterNumber")
private PlayQueueItem(@Nullable final String name, @Nullable final String url, private PlayQueueItem(@Nullable final String name, @Nullable final String url,
final int serviceId, final long duration, final int serviceId, final long duration,
@Nullable final String thumbnailUrl, @Nullable final String uploader, final List<Image> thumbnails, @Nullable final String uploader,
final String uploaderUrl, @NonNull final StreamType streamType) { final String uploaderUrl, @NonNull final StreamType streamType) {
this.title = name != null ? name : EMPTY_STRING; this.title = name != null ? name : EMPTY_STRING;
this.url = url != null ? url : EMPTY_STRING; this.url = url != null ? url : EMPTY_STRING;
this.serviceId = serviceId; this.serviceId = serviceId;
this.duration = duration; this.duration = duration;
this.thumbnailUrl = thumbnailUrl != null ? thumbnailUrl : EMPTY_STRING; this.thumbnails = thumbnails;
this.uploader = uploader != null ? uploader : EMPTY_STRING; this.uploader = uploader != null ? uploader : EMPTY_STRING;
this.uploaderUrl = uploaderUrl; this.uploaderUrl = uploaderUrl;
this.streamType = streamType; this.streamType = streamType;
@ -88,8 +90,8 @@ public class PlayQueueItem implements Serializable {
} }
@NonNull @NonNull
public String getThumbnailUrl() { public List<Image> getThumbnails() {
return thumbnailUrl; return thumbnails;
} }
@NonNull @NonNull

View File

@ -33,7 +33,7 @@ public class PlayQueueItemBuilder {
holder.itemDurationView.setVisibility(View.GONE); holder.itemDurationView.setVisibility(View.GONE);
} }
PicassoHelper.loadThumbnail(item.getThumbnailUrl()).into(holder.itemThumbnailView); PicassoHelper.loadThumbnail(item.getThumbnails()).into(holder.itemThumbnailView);
holder.itemRoot.setOnClickListener(view -> { holder.itemRoot.setOnClickListener(view -> {
if (onItemClickListener != null) { if (onItemClickListener != null) {

View File

@ -740,7 +740,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh
String videoUrl = player.getVideoUrl(); String videoUrl = player.getVideoUrl();
videoUrl += ("&t=" + seconds); videoUrl += ("&t=" + seconds);
ShareUtils.shareText(context, currentItem.getTitle(), ShareUtils.shareText(context, currentItem.getTitle(),
videoUrl, currentItem.getThumbnailUrl()); videoUrl, currentItem.getThumbnails());
} }
} }
}; };

View File

@ -226,7 +226,7 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
final PlayQueueItem currentItem = player.getCurrentItem(); final PlayQueueItem currentItem = player.getCurrentItem();
if (currentItem != null) { if (currentItem != null) {
ShareUtils.shareText(context, currentItem.getTitle(), ShareUtils.shareText(context, currentItem.getTitle(),
player.getVideoUrlAtCurrentTime(), currentItem.getThumbnailUrl()); player.getVideoUrlAtCurrentTime(), currentItem.getThumbnails());
} }
})); }));
binding.share.setOnLongClickListener(v -> { binding.share.setOnLongClickListener(v -> {

View File

@ -1,13 +1,14 @@
package org.schabi.newpipe.util; package org.schabi.newpipe.util;
import static org.schabi.newpipe.MainActivity.DEBUG; import static org.schabi.newpipe.MainActivity.DEBUG;
import static org.schabi.newpipe.extractor.utils.Utils.isBlank; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.graphics.BitmapCompat; import androidx.core.graphics.BitmapCompat;
@ -19,9 +20,13 @@ import com.squareup.picasso.RequestCreator;
import com.squareup.picasso.Transformation; import com.squareup.picasso.Transformation;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.Image;
import org.schabi.newpipe.extractor.Image.ResolutionLevel;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
@ -42,6 +47,7 @@ public final class PicassoHelper {
private static Picasso picassoInstance; private static Picasso picassoInstance;
private static boolean shouldLoadImages; private static boolean shouldLoadImages;
private static ResolutionLevel preferredResolutionLevel = ResolutionLevel.HIGH;
public static void init(final Context context) { public static void init(final Context context) {
picassoCache = new LruCache(10 * 1024 * 1024); picassoCache = new LruCache(10 * 1024 * 1024);
@ -96,20 +102,33 @@ public final class PicassoHelper {
} }
public static RequestCreator loadAvatar(final List<Image> images) {
return loadImageDefault(images, R.drawable.placeholder_person);
}
public static RequestCreator loadAvatar(final String url) { public static RequestCreator loadAvatar(final String url) {
return loadImageDefault(url, R.drawable.placeholder_person); return loadImageDefault(url, R.drawable.placeholder_person);
} }
public static RequestCreator loadThumbnail(final List<Image> images) {
return loadImageDefault(images, R.drawable.placeholder_thumbnail_video);
}
public static RequestCreator loadThumbnail(final String url) { public static RequestCreator loadThumbnail(final String url) {
return loadImageDefault(url, R.drawable.placeholder_thumbnail_video); return loadImageDefault(url, R.drawable.placeholder_thumbnail_video);
} }
public static RequestCreator loadDetailsThumbnail(final String url) { public static RequestCreator loadDetailsThumbnail(final List<Image> images) {
return loadImageDefault(url, R.drawable.placeholder_thumbnail_video, false); return loadImageDefault(choosePreferredImage(images),
R.drawable.placeholder_thumbnail_video, false);
} }
public static RequestCreator loadBanner(final String url) { public static RequestCreator loadBanner(final List<Image> images) {
return loadImageDefault(url, R.drawable.placeholder_channel_banner); return loadImageDefault(images, R.drawable.placeholder_channel_banner);
}
public static RequestCreator loadPlaylistThumbnail(final List<Image> images) {
return loadImageDefault(images, R.drawable.placeholder_thumbnail_playlist);
} }
public static RequestCreator loadPlaylistThumbnail(final String url) { public static RequestCreator loadPlaylistThumbnail(final String url) {
@ -125,9 +144,10 @@ public final class PicassoHelper {
} }
public static RequestCreator loadScaledDownThumbnail(final Context context, final String url) { public static RequestCreator loadScaledDownThumbnail(final Context context,
final List<Image> images) {
// scale down the notification thumbnail for performance // scale down the notification thumbnail for performance
return PicassoHelper.loadThumbnail(url) return PicassoHelper.loadThumbnail(images)
.transform(new Transformation() { .transform(new Transformation() {
@Override @Override
public Bitmap transform(final Bitmap source) { public Bitmap transform(final Bitmap source) {
@ -180,13 +200,20 @@ public final class PicassoHelper {
} }
private static RequestCreator loadImageDefault(final String url, final int placeholderResId) { private static RequestCreator loadImageDefault(final List<Image> images,
final int placeholderResId) {
return loadImageDefault(choosePreferredImage(images), placeholderResId);
}
private static RequestCreator loadImageDefault(final String url,
final int placeholderResId) {
return loadImageDefault(url, placeholderResId, true); return loadImageDefault(url, placeholderResId, true);
} }
private static RequestCreator loadImageDefault(final String url, final int placeholderResId, private static RequestCreator loadImageDefault(@Nullable final String url,
final int placeholderResId,
final boolean showPlaceholderWhileLoading) { final boolean showPlaceholderWhileLoading) {
if (!shouldLoadImages || isBlank(url)) { if (isNullOrEmpty(url)) {
return picassoInstance return picassoInstance
.load((String) null) .load((String) null)
.placeholder(placeholderResId) // show placeholder when no image should load .placeholder(placeholderResId) // show placeholder when no image should load
@ -201,4 +228,44 @@ public final class PicassoHelper {
return requestCreator; return requestCreator;
} }
} }
@Nullable
public static String choosePreferredImage(final List<Image> images) {
if (!shouldLoadImages) {
return null;
}
final Comparator<Image> comparator;
switch (preferredResolutionLevel) {
case HIGH:
comparator = Comparator.comparingInt(Image::getHeight).reversed();
break;
default:
case UNKNOWN:
case MEDIUM:
comparator = Comparator.comparingInt(image -> Math.abs(image.getHeight() - 450));
break;
case LOW:
comparator = Comparator.comparingInt(Image::getHeight);
break;
}
return images.stream()
.filter(image -> image.getEstimatedResolutionLevel() != ResolutionLevel.UNKNOWN)
.min(comparator)
.map(Image::getUrl)
.orElseGet(() -> images.stream()
.findAny()
.map(Image::getUrl)
.orElse(null));
}
@NonNull
public static List<Image> urlToImageList(@Nullable final String url) {
if (url == null) {
return List.of();
} else {
return List.of(new Image(url, -1, -1, ResolutionLevel.UNKNOWN));
}
}
} }

View File

@ -23,10 +23,12 @@ import androidx.core.content.FileProvider;
import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.Image;
import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.PicassoHelper;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.util.List;
public final class ShareUtils { public final class ShareUtils {
private static final String TAG = ShareUtils.class.getSimpleName(); private static final String TAG = ShareUtils.class.getSimpleName();
@ -261,6 +263,29 @@ public final class ShareUtils {
openAppChooser(context, shareIntent, false); openAppChooser(context, shareIntent, false);
} }
/**
* Open the android share sheet to share a content.
*
* <p>
* For Android 10+ users, a content preview is shown, which includes the title of the shared
* content and an image preview the content, if its URL is not null or empty and its
* corresponding image is in the image cache.
* </p>
*
* @param context the context to use
* @param title the title of the content
* @param content the content to share
* @param images a set of possible {@link Image}s of the subject, among which to choose with
* {@link PicassoHelper#choosePreferredImage(List)} since that's likely to
* provide an image that is in Picasso's cache
*/
public static void shareText(@NonNull final Context context,
@NonNull final String title,
final String content,
final List<Image> images) {
shareText(context, title, content, PicassoHelper.choosePreferredImage(images));
}
/** /**
* Open the android share sheet to share a content. * Open the android share sheet to share a content.
* *