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
// This works thanks to JitPack: https://jitpack.io/
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'
/** Checkstyle **/

View file

@ -75,7 +75,7 @@ public final class QueueItemMenuUtil {
return true;
case R.id.menu_item_share:
shareText(context, item.getTitle(), item.getUrl(),
item.getThumbnailUrl());
item.getThumbnails());
return true;
case R.id.menu_item_download:
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.StreamStateEntity
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.util.PicassoHelper
data class PlaylistStreamEntry(
@Embedded
@ -28,7 +29,7 @@ data class PlaylistStreamEntry(
item.duration = streamEntity.duration
item.uploaderName = streamEntity.uploader
item.uploaderUrl = streamEntity.uploaderUrl
item.thumbnailUrl = streamEntity.thumbnailUrl
item.thumbnails = PicassoHelper.urlToImageList(streamEntity.thumbnailUrl)
return item
}

View file

@ -11,6 +11,7 @@ import androidx.room.PrimaryKey;
import org.schabi.newpipe.database.playlist.PlaylistLocalItem;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
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.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_NAME;
@ -69,8 +70,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
@Ignore
public PlaylistRemoteEntity(final PlaylistInfo info) {
this(info.getServiceId(), info.getName(), info.getUrl(),
info.getThumbnailUrl() == null
? info.getUploaderAvatarUrl() : info.getThumbnailUrl(),
PicassoHelper.choosePreferredImage(info.getThumbnails()),
info.getUploaderName(), info.getStreamCount());
}
@ -84,7 +84,8 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
&& getStreamCount() == info.getStreamCount()
&& TextUtils.equals(getName(), info.getName())
&& TextUtils.equals(getUrl(), info.getUrl())
&& TextUtils.equals(getThumbnailUrl(), info.getThumbnailUrl())
&& TextUtils.equals(getThumbnailUrl(),
PicassoHelper.choosePreferredImage(info.getThumbnails()))
&& 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.StreamStateEntity.STREAM_PROGRESS_MILLIS
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.util.PicassoHelper
import java.time.OffsetDateTime
class StreamStatisticsEntry(
@ -30,7 +31,7 @@ class StreamStatisticsEntry(
item.duration = streamEntity.duration
item.uploaderName = streamEntity.uploader
item.uploaderUrl = streamEntity.uploaderUrl
item.thumbnailUrl = streamEntity.thumbnailUrl
item.thumbnails = PicassoHelper.urlToImageList(streamEntity.thumbnailUrl)
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.StreamType
import org.schabi.newpipe.player.playqueue.PlayQueueItem
import org.schabi.newpipe.util.PicassoHelper
import java.io.Serializable
import java.time.OffsetDateTime
@ -67,7 +68,7 @@ data class StreamEntity(
constructor(item: StreamInfoItem) : this(
serviceId = item.serviceId, url = item.url, title = item.name,
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(),
isUploadDateApproximation = item.uploadDate?.isApproximation
)
@ -76,7 +77,7 @@ data class StreamEntity(
constructor(info: StreamInfo) : this(
serviceId = info.serviceId, url = info.url, title = info.name,
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(),
isUploadDateApproximation = info.uploadDate?.isApproximation
)
@ -85,7 +86,8 @@ data class StreamEntity(
constructor(item: PlayQueueItem) : this(
serviceId = item.serviceId, url = item.url, title = item.title,
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 {
@ -93,7 +95,7 @@ data class StreamEntity(
item.duration = duration
item.uploaderName = uploader
item.uploaderUrl = uploaderUrl
item.thumbnailUrl = thumbnailUrl
item.thumbnails = PicassoHelper.urlToImageList(thumbnailUrl)
if (viewCount != null) item.viewCount = viewCount as Long
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.ChannelInfoItem;
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_TABLE;
@ -57,8 +58,8 @@ public class SubscriptionEntity {
final SubscriptionEntity result = new SubscriptionEntity();
result.setServiceId(info.getServiceId());
result.setUrl(info.getUrl());
result.setData(info.getName(), info.getAvatarUrl(), info.getDescription(),
info.getSubscriberCount());
result.setData(info.getName(), PicassoHelper.choosePreferredImage(info.getAvatars()),
info.getDescription(), info.getSubscriberCount());
return result;
}
@ -138,7 +139,7 @@ public class SubscriptionEntity {
@Ignore
public ChannelInfoItem toChannelInfoItem() {
final ChannelInfoItem item = new ChannelInfoItem(getServiceId(), getUrl(), getName());
item.setThumbnailUrl(getAvatarUrl());
item.setThumbnails(PicassoHelper.urlToImageList(getAvatarUrl()));
item.setSubscriberCount(getSubscriberCount());
item.setDescription(getDescription());
return item;

View file

@ -17,6 +17,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.util.Localization;
import java.util.List;
import org.schabi.newpipe.util.PicassoHelper;
import icepick.State;
@ -113,7 +114,7 @@ public class DescriptionFragment extends BaseDescriptionFragment {
addMetadataItem(inflater, layout, true, R.string.metadata_host,
streamInfo.getHost());
addMetadataItem(inflater, layout, true, R.string.metadata_thumbnail_url,
streamInfo.getThumbnailUrl());
PicassoHelper.choosePreferredImage(streamInfo.getThumbnails()));
}
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.ReCaptchaActivity;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.Image;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
@ -483,7 +484,7 @@ public final class VideoDetailFragment
});
binding.detailControlsShare.setOnClickListener(makeOnClickListener(info ->
ShareUtils.shareText(requireContext(), info.getName(), info.getUrl(),
info.getThumbnailUrl())));
info.getThumbnails())));
binding.detailControlsOpenInBrowser.setOnClickListener(makeOnClickListener(info ->
ShareUtils.openUrlInBrowser(requireContext(), info.getUrl())));
binding.detailControlsPlayWithKodi.setOnClickListener(makeOnClickListener(info ->
@ -723,7 +724,7 @@ public final class VideoDetailFragment
final boolean isPlayerStopped = !isPlayerAvailable() || player.isStopped();
if (playQueueItem != null && isPlayerStopped) {
updateOverlayData(playQueueItem.getTitle(),
playQueueItem.getUploader(), playQueueItem.getThumbnailUrl());
playQueueItem.getUploader(), playQueueItem.getThumbnails());
}
}
@ -1536,13 +1537,13 @@ public final class VideoDetailFragment
binding.detailSecondaryControlPanel.setVisibility(View.GONE);
checkUpdateProgressInfo(info);
PicassoHelper.loadDetailsThumbnail(info.getThumbnailUrl()).tag(PICASSO_VIDEO_DETAILS_TAG)
PicassoHelper.loadDetailsThumbnail(info.getThumbnails()).tag(PICASSO_VIDEO_DETAILS_TAG)
.into(binding.detailThumbnailImageView);
showMetaInfoInTextView(info.getMetaInfo(), binding.detailMetaInfoTextView,
binding.detailMetaInfoSeparator, disposables);
if (!isPlayerAvailable() || player.isStopped()) {
updateOverlayData(info.getName(), info.getUploaderName(), info.getThumbnailUrl());
updateOverlayData(info.getName(), info.getUploaderName(), info.getThumbnails());
}
if (!info.getErrors().isEmpty()) {
@ -1587,7 +1588,7 @@ public final class VideoDetailFragment
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);
binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE);
binding.detailUploaderThumbnailView.setVisibility(View.GONE);
@ -1619,10 +1620,10 @@ public final class VideoDetailFragment
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);
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);
binding.detailUploaderThumbnailView.setVisibility(View.VISIBLE);
}
@ -1797,7 +1798,7 @@ public final class VideoDetailFragment
return;
}
updateOverlayData(info.getName(), info.getUploaderName(), info.getThumbnailUrl());
updateOverlayData(info.getName(), info.getUploaderName(), info.getThumbnails());
if (currentInfo != null && info.getUrl().equals(currentInfo.getUrl())) {
return;
}
@ -1826,7 +1827,7 @@ public final class VideoDetailFragment
if (currentInfo != null) {
updateOverlayData(currentInfo.getName(),
currentInfo.getUploaderName(),
currentInfo.getThumbnailUrl());
currentInfo.getThumbnails());
}
updateOverlayPlayQueueButtonVisibility();
}
@ -2191,7 +2192,7 @@ public final class VideoDetailFragment
playerHolder.stopService();
setInitialData(0, null, "", 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,
@Nullable final String uploader,
@Nullable final String thumbnailUrl) {
@NonNull final List<Image> thumbnails) {
binding.overlayTitleTextView.setText(isEmpty(overlayTitle) ? "" : overlayTitle);
binding.overlayChannelTextView.setText(isEmpty(uploader) ? "" : uploader);
binding.overlayThumbnail.setImageDrawable(null);
PicassoHelper.loadDetailsThumbnail(thumbnailUrl).tag(PICASSO_VIDEO_DETAILS_TAG)
PicassoHelper.loadDetailsThumbnail(thumbnails).tag(PICASSO_VIDEO_DETAILS_TAG)
.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.util.DeviceUtils;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.PicassoHelper;
import java.util.List;
@ -100,8 +101,8 @@ public class ChannelAboutFragment extends BaseDescriptionFragment {
}
addMetadataItem(inflater, layout, true, R.string.metadata_avatar_url,
channelInfo.getAvatarUrl());
PicassoHelper.choosePreferredImage(channelInfo.getAvatars()));
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;
import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
import static org.schabi.newpipe.ktx.TextViewUtils.animateTextColor;
import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.ktx.ViewUtils.animateBackgroundColor;
@ -234,7 +233,7 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
case R.id.menu_item_share:
if (currentInfo != null) {
ShareUtils.shareText(requireContext(), name, currentInfo.getOriginalUrl(),
currentInfo.getAvatarUrl());
currentInfo.getAvatars());
}
break;
default:
@ -355,7 +354,7 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
channel.setServiceId(info.getServiceId());
channel.setUrl(info.getUrl());
channel.setData(info.getName(),
info.getAvatarUrl(),
PicassoHelper.choosePreferredImage(info.getAvatars()),
info.getDescription(),
info.getSubscriberCount());
channelSubscription = null;
@ -579,17 +578,17 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
currentInfo = result;
setInitialData(result.getServiceId(), result.getOriginalUrl(), result.getName());
if (PicassoHelper.getShouldLoadImages() && !isBlank(result.getBannerUrl())) {
PicassoHelper.loadBanner(result.getBannerUrl()).tag(PICASSO_CHANNEL_TAG)
if (PicassoHelper.getShouldLoadImages() && !result.getBanners().isEmpty()) {
PicassoHelper.loadBanner(result.getBanners()).tag(PICASSO_CHANNEL_TAG)
.into(binding.channelBannerImage);
} else {
// do not waste space for the banner, if the user disabled images or there is not one
binding.channelBannerImage.setImageDrawable(null);
}
PicassoHelper.loadAvatar(result.getAvatarUrl()).tag(PICASSO_CHANNEL_TAG)
PicassoHelper.loadAvatar(result.getAvatars()).tag(PICASSO_CHANNEL_TAG)
.into(binding.channelAvatarView);
PicassoHelper.loadAvatar(result.getParentChannelAvatarUrl()).tag(PICASSO_CHANNEL_TAG)
PicassoHelper.loadAvatar(result.getParentChannelAvatars()).tag(PICASSO_CHANNEL_TAG)
.into(binding.subChannelAvatarView);
binding.channelTitleView.setText(result.getName());

View file

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

View file

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

View file

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

View file

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

View file

@ -46,7 +46,7 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
.localizeStreamCountMini(itemStreamCountView.getContext(), item.getStreamCount()));
itemUploaderView.setText(item.getUploaderName());
PicassoHelper.loadPlaylistThumbnail(item.getThumbnailUrl()).into(itemThumbnailView);
PicassoHelper.loadPlaylistThumbnail(item.getThumbnails()).into(itemThumbnailView);
itemView.setOnClickListener(view -> {
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
PicassoHelper.loadThumbnail(item.getThumbnailUrl()).into(itemThumbnailView);
PicassoHelper.loadThumbnail(item.getThumbnails()).into(itemThumbnailView);
itemView.setOnClickListener(view -> {
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.util.NavigationHelper
import org.schabi.newpipe.util.OnClickGesture
import org.schabi.newpipe.util.PicassoHelper
import org.schabi.newpipe.util.ServiceHelper
import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountChannels
import org.schabi.newpipe.util.external_communication.ShareUtils
@ -342,7 +343,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
when (i) {
0 -> ShareUtils.shareText(
requireContext(), selectedItem.name, selectedItem.url,
selectedItem.thumbnailUrl
PicassoHelper.choosePreferredImage(selectedItem.thumbnails)
)
1 -> ShareUtils.openUrlInBrowser(requireContext(), selectedItem.url)
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.local.feed.FeedDatabaseManager
import org.schabi.newpipe.util.ExtractorHelper
import org.schabi.newpipe.util.PicassoHelper
class SubscriptionManager(context: Context) {
private val database = NewPipeDatabase.getInstance(context)
@ -71,7 +72,12 @@ class SubscriptionManager(context: Context) {
subscriptionTable.getSubscription(info.serviceId, info.url)
.flatMapCompletable {
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)
}
}
@ -99,7 +105,7 @@ class SubscriptionManager(context: Context) {
} else if (info is ChannelInfo) {
subscriptionEntity.setData(
info.name,
info.avatarUrl,
PicassoHelper.choosePreferredImage(info.avatars),
info.description,
info.subscriberCount
)

View file

@ -39,7 +39,7 @@ class ChannelItem(
itemChannelDescriptionView.text = infoItem.description
}
PicassoHelper.loadAvatar(infoItem.thumbnailUrl).into(itemThumbnailView)
PicassoHelper.loadAvatar(infoItem.thumbnails).into(itemThumbnailView)
gesturesListener?.run {
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.UserAction;
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.StreamType;
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) {
Log.d(TAG, "Thumbnail - loadCurrentThumbnail() called with url = ["
+ (url == null ? "null" : url) + "]");
Log.d(TAG, "Thumbnail - loadCurrentThumbnail() called with thumbnails = ["
+ thumbnails.size() + "]");
}
// 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
// session metadata while the new thumbnail is being loaded by Picasso.
onThumbnailLoaded(null);
if (isNullOrEmpty(url)) {
if (thumbnails.isEmpty()) {
return;
}
// scale down the notification thumbnail for performance
PicassoHelper.loadScaledDownThumbnail(context, url)
PicassoHelper.loadScaledDownThumbnail(context, thumbnails)
.tag(PICASSO_PLAYER_THUMBNAIL_TAG)
.into(currentThumbnailTarget);
}
@ -1792,7 +1793,7 @@ public final class Player implements PlaybackListener, Listener {
maybeAutoQueueNextStream(info);
loadCurrentThumbnail(info.getThumbnailUrl());
loadCurrentThumbnail(info.getThumbnails());
registerStreamViewed();
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.StreamType;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.util.PicassoHelper;
import java.util.List;
import java.util.Optional;
@ -74,7 +75,7 @@ public final class ExceptionTag implements MediaItemTag {
@Override
public String getThumbnailUrl() {
return item.getThumbnailUrl();
return PicassoHelper.choosePreferredImage(item.getThumbnails());
}
@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.StreamType;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.util.PicassoHelper;
import java.util.Collections;
import java.util.List;
@ -95,7 +96,7 @@ public final class StreamInfoTag implements MediaItemTag {
@Override
public String getThumbnailUrl() {
return streamInfo.getThumbnailUrl();
return PicassoHelper.choosePreferredImage(streamInfo.getThumbnails());
}
@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.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.util.PicassoHelper;
import java.util.ArrayList;
import java.util.Collections;
@ -137,7 +138,8 @@ public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator
.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, player.getPlayQueue().size());
descBuilder.setExtras(additionalMetadata);
final Uri thumbnailUri = Uri.parse(item.getThumbnailUrl());
final Uri thumbnailUri = Uri.parse(
PicassoHelper.choosePreferredImage(item.getThumbnails()));
if (thumbnailUri != null) {
descBuilder.setIconUri(thumbnailUri);
}

View file

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

View file

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

View file

@ -740,7 +740,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh
String videoUrl = player.getVideoUrl();
videoUrl += ("&t=" + seconds);
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();
if (currentItem != null) {
ShareUtils.shareText(context, currentItem.getTitle(),
player.getVideoUrlAtCurrentTime(), currentItem.getThumbnailUrl());
player.getVideoUrlAtCurrentTime(), currentItem.getThumbnails());
}
}));
binding.share.setOnLongClickListener(v -> {

View file

@ -1,13 +1,14 @@
package org.schabi.newpipe.util;
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.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.BitmapCompat;
@ -19,9 +20,13 @@ import com.squareup.picasso.RequestCreator;
import com.squareup.picasso.Transformation;
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.IOException;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
@ -42,6 +47,7 @@ public final class PicassoHelper {
private static Picasso picassoInstance;
private static boolean shouldLoadImages;
private static ResolutionLevel preferredResolutionLevel = ResolutionLevel.HIGH;
public static void init(final Context context) {
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) {
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) {
return loadImageDefault(url, R.drawable.placeholder_thumbnail_video);
}
public static RequestCreator loadDetailsThumbnail(final String url) {
return loadImageDefault(url, R.drawable.placeholder_thumbnail_video, false);
public static RequestCreator loadDetailsThumbnail(final List<Image> images) {
return loadImageDefault(choosePreferredImage(images),
R.drawable.placeholder_thumbnail_video, false);
}
public static RequestCreator loadBanner(final String url) {
return loadImageDefault(url, R.drawable.placeholder_channel_banner);
public static RequestCreator loadBanner(final List<Image> images) {
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) {
@ -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
return PicassoHelper.loadThumbnail(url)
return PicassoHelper.loadThumbnail(images)
.transform(new Transformation() {
@Override
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);
}
private static RequestCreator loadImageDefault(final String url, final int placeholderResId,
private static RequestCreator loadImageDefault(@Nullable final String url,
final int placeholderResId,
final boolean showPlaceholderWhileLoading) {
if (!shouldLoadImages || isBlank(url)) {
if (isNullOrEmpty(url)) {
return picassoInstance
.load((String) null)
.placeholder(placeholderResId) // show placeholder when no image should load
@ -201,4 +228,44 @@ public final class PicassoHelper {
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.R;
import org.schabi.newpipe.extractor.Image;
import org.schabi.newpipe.util.PicassoHelper;
import java.io.File;
import java.io.FileOutputStream;
import java.util.List;
public final class ShareUtils {
private static final String TAG = ShareUtils.class.getSimpleName();
@ -261,6 +263,29 @@ public final class ShareUtils {
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.
*