diff options
-rw-r--r-- | app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistDescriptionAdapter.java | 55 | ||||
-rw-r--r-- | app/src/main/java/de/danoeh/antennapod/dialog/StreamingConfirmationDialog.java | 10 | ||||
-rw-r--r-- | app/src/main/res/layout/itemdescription_listitem.xml | 10 | ||||
-rw-r--r-- | core/src/main/java/de/danoeh/antennapod/core/util/playback/RemoteMedia.java (renamed from core/src/play/java/de/danoeh/antennapod/core/cast/RemoteMedia.java) | 136 | ||||
-rw-r--r-- | core/src/main/res/values/strings.xml | 1 | ||||
-rw-r--r-- | core/src/play/java/de/danoeh/antennapod/core/cast/CastUtils.java | 1 | ||||
-rw-r--r-- | core/src/play/java/de/danoeh/antennapod/core/cast/MediaInfoCreator.java | 60 | ||||
-rw-r--r-- | core/src/play/java/de/danoeh/antennapod/core/feed/FeedMediaFlavorHelper.java | 2 | ||||
-rw-r--r-- | core/src/play/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java | 5 |
9 files changed, 182 insertions, 98 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistDescriptionAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistDescriptionAdapter.java index 8d469c7a6..f49892b63 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistDescriptionAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistDescriptionAdapter.java @@ -1,23 +1,29 @@ package de.danoeh.antennapod.adapter; import android.content.Context; -import android.os.Build; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; +import android.widget.Button; import android.widget.TextView; - -import java.util.List; - import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.util.NetworkUtils; +import de.danoeh.antennapod.core.util.playback.RemoteMedia; import de.danoeh.antennapod.core.feed.FeedItem; +import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.util.DateUtils; +import de.danoeh.antennapod.core.util.playback.Playable; +import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter; +import de.danoeh.antennapod.dialog.StreamingConfirmationDialog; + +import java.util.List; /** * List adapter for showing a list of FeedItems with their title and description. */ public class FeedItemlistDescriptionAdapter extends ArrayAdapter<FeedItem> { + private static final int MAX_LINES_COLLAPSED = 3; public FeedItemlistDescriptionAdapter(Context context, int resource, List<FeedItem> objects) { super(context, resource, objects); @@ -32,12 +38,12 @@ public class FeedItemlistDescriptionAdapter extends ArrayAdapter<FeedItem> { // Inflate layout if (convertView == null) { holder = new Holder(); - LayoutInflater inflater = (LayoutInflater) getContext() - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(R.layout.itemdescription_listitem, parent, false); holder.title = convertView.findViewById(R.id.txtvTitle); holder.pubDate = convertView.findViewById(R.id.txtvPubDate); holder.description = convertView.findViewById(R.id.txtvDescription); + holder.preview = convertView.findViewById(R.id.butPreview); convertView.setTag(holder); } else { @@ -52,18 +58,34 @@ public class FeedItemlistDescriptionAdapter extends ArrayAdapter<FeedItem> { .replaceAll("\\s+", " ") .trim(); holder.description.setText(description); - - final int MAX_LINES_COLLAPSED = 3; holder.description.setMaxLines(MAX_LINES_COLLAPSED); - holder.description.setOnClickListener(v -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN - && holder.description.getMaxLines() > MAX_LINES_COLLAPSED) { - holder.description.setMaxLines(MAX_LINES_COLLAPSED); - } else { - holder.description.setMaxLines(2000); - } - }); } + holder.description.setTag(Boolean.FALSE); // not expanded + holder.preview.setVisibility(View.GONE); + holder.preview.setOnClickListener(v -> { + Playable playable = new RemoteMedia(item); + if (!NetworkUtils.isStreamingAllowed()) { + new StreamingConfirmationDialog(getContext(), playable).show(); + return; + } + new PlaybackServiceStarter(getContext(), playable) + .shouldStream(true) + .startWhenPrepared(true) + .callEvenIfRunning(true) + .start(); + getContext().startActivity(PlaybackService.getPlayerActivityIntent(getContext(), playable)); + }); + convertView.setOnClickListener(v -> { + if (holder.description.getTag() == Boolean.TRUE) { + holder.description.setMaxLines(MAX_LINES_COLLAPSED); + holder.preview.setVisibility(View.GONE); + holder.description.setTag(Boolean.FALSE); + } else { + holder.description.setMaxLines(2000); + holder.preview.setVisibility(View.VISIBLE); + holder.description.setTag(Boolean.TRUE); + } + }); return convertView; } @@ -71,5 +93,6 @@ public class FeedItemlistDescriptionAdapter extends ArrayAdapter<FeedItem> { TextView title; TextView pubDate; TextView description; + Button preview; } } diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/StreamingConfirmationDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/StreamingConfirmationDialog.java index 5b77fd319..81e86e217 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/StreamingConfirmationDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/StreamingConfirmationDialog.java @@ -5,17 +5,17 @@ import android.view.View; import android.widget.CheckBox; import androidx.appcompat.app.AlertDialog; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter; public class StreamingConfirmationDialog { private final Context context; - private final FeedMedia media; + private final Playable playable; - public StreamingConfirmationDialog(Context context, FeedMedia media) { + public StreamingConfirmationDialog(Context context, Playable playable) { this.context = context; - this.media = media; + this.playable = playable; } public void show() { @@ -30,7 +30,7 @@ public class StreamingConfirmationDialog { if (checkDoNotShowAgain.isChecked()) { UserPreferences.setAllowMobileStreaming(true); } - new PlaybackServiceStarter(context, media) + new PlaybackServiceStarter(context, playable) .callEvenIfRunning(true) .startWhenPrepared(true) .shouldStream(true) diff --git a/app/src/main/res/layout/itemdescription_listitem.xml b/app/src/main/res/layout/itemdescription_listitem.xml index 9d03e30a3..15c23c915 100644 --- a/app/src/main/res/layout/itemdescription_listitem.xml +++ b/app/src/main/res/layout/itemdescription_listitem.xml @@ -56,4 +56,14 @@ tools:text="Feed item description" tools:background="@android:color/holo_green_dark" /> + <Button + android:id="@+id/butPreview" + android:layout_below="@id/txtvDescription" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:layout_alignParentLeft="true" + style="?android:attr/buttonBarButtonStyle" + android:text="@string/preview_episode"/> + </RelativeLayout> diff --git a/core/src/play/java/de/danoeh/antennapod/core/cast/RemoteMedia.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/RemoteMedia.java index 0614e5952..c39121564 100644 --- a/core/src/play/java/de/danoeh/antennapod/core/cast/RemoteMedia.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/RemoteMedia.java @@ -1,35 +1,26 @@ -package de.danoeh.antennapod.core.cast; +package de.danoeh.antennapod.core.util.playback; import android.content.Context; import android.content.SharedPreferences; -import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; -import androidx.annotation.Nullable; import android.text.TextUtils; - -import com.google.android.gms.cast.MediaInfo; -import com.google.android.gms.cast.MediaMetadata; -import com.google.android.gms.common.images.WebImage; - -import org.apache.commons.lang3.builder.HashCodeBuilder; - -import java.util.Calendar; -import java.util.Date; -import java.util.List; -import java.util.concurrent.Callable; - +import androidx.annotation.Nullable; import de.danoeh.antennapod.core.feed.Chapter; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.feed.MediaType; import de.danoeh.antennapod.core.util.ChapterUtils; -import de.danoeh.antennapod.core.util.playback.Playable; +import java.util.Date; +import java.util.List; +import java.util.concurrent.Callable; +import org.apache.commons.lang3.builder.HashCodeBuilder; /** - * Playable implementation for media on a Cast Device for which a local version of + * Playable implementation for media for which a local version of * {@link de.danoeh.antennapod.core.feed.FeedMedia} hasn't been found. + * Used for Casting and for previewing unsubscribed feeds. */ public class RemoteMedia implements Playable { public static final String TAG = "RemoteMedia"; @@ -45,7 +36,7 @@ public class RemoteMedia implements Playable { private String feedAuthor; private String imageUrl; private String feedLink; - private String mime_type; + private String mimeType; private Date pubDate; private String notes; private List<Chapter> chapters; @@ -55,7 +46,7 @@ public class RemoteMedia implements Playable { public RemoteMedia(String downloadUrl, String itemId, String feedUrl, String feedTitle, String episodeTitle, String episodeLink, String feedAuthor, - String imageUrl, String feedLink, String mime_type, Date pubDate) { + String imageUrl, String feedLink, String mimeType, Date pubDate) { this.downloadUrl = downloadUrl; this.itemIdentifier = itemId; this.feedUrl = feedUrl; @@ -65,61 +56,26 @@ public class RemoteMedia implements Playable { this.feedAuthor = feedAuthor; this.imageUrl = imageUrl; this.feedLink = feedLink; - this.mime_type = mime_type; + this.mimeType = mimeType; this.pubDate = pubDate; } - public void setNotes(String notes) { - this.notes = notes; + public RemoteMedia(FeedItem item) { + this.downloadUrl = item.getMedia().getDownload_url(); + this.itemIdentifier = item.getItemIdentifier(); + this.feedUrl = item.getFeed().getDownload_url(); + this.feedTitle = item.getFeed().getTitle(); + this.episodeTitle = item.getTitle(); + this.episodeLink = item.getLink(); + this.feedAuthor = item.getFeed().getAuthor(); + this.imageUrl = item.getImageUrl(); + this.feedLink = item.getFeed().getLink(); + this.mimeType = item.getMedia().getMime_type(); + this.pubDate = item.getPubDate(); } - public MediaInfo extractMediaInfo() { - MediaMetadata metadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_GENERIC); - - metadata.putString(MediaMetadata.KEY_TITLE, episodeTitle); - metadata.putString(MediaMetadata.KEY_SUBTITLE, feedTitle); - if (!TextUtils.isEmpty(imageUrl)) { - metadata.addImage(new WebImage(Uri.parse(imageUrl))); - } - Calendar calendar = Calendar.getInstance(); - calendar.setTime(pubDate); - metadata.putDate(MediaMetadata.KEY_RELEASE_DATE, calendar); - if (!TextUtils.isEmpty(feedAuthor)) { - metadata.putString(MediaMetadata.KEY_ARTIST, feedAuthor); - } - if (!TextUtils.isEmpty(feedUrl)) { - metadata.putString(CastUtils.KEY_FEED_URL, feedUrl); - } - if (!TextUtils.isEmpty(feedLink)) { - metadata.putString(CastUtils.KEY_FEED_WEBSITE, feedLink); - } - if (!TextUtils.isEmpty(itemIdentifier)) { - metadata.putString(CastUtils.KEY_EPISODE_IDENTIFIER, itemIdentifier); - } else { - metadata.putString(CastUtils.KEY_EPISODE_IDENTIFIER, downloadUrl); - } - if (!TextUtils.isEmpty(episodeLink)) { - metadata.putString(CastUtils.KEY_EPISODE_LINK, episodeLink); - } - String notes = this.notes; - if (notes != null) { - if (notes.length() > CastUtils.EPISODE_NOTES_MAX_LENGTH) { - notes = notes.substring(0, CastUtils.EPISODE_NOTES_MAX_LENGTH); - } - metadata.putString(CastUtils.KEY_EPISODE_NOTES, notes); - } - // Default id value - metadata.putInt(CastUtils.KEY_MEDIA_ID, 0); - metadata.putInt(CastUtils.KEY_FORMAT_VERSION, CastUtils.FORMAT_VERSION_VALUE); - - MediaInfo.Builder builder = new MediaInfo.Builder(downloadUrl) - .setContentType(mime_type) - .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) - .setMetadata(metadata); - if (duration > 0) { - builder.setStreamDuration(duration); - } - return builder.build(); + public void setNotes(String notes) { + this.notes = notes; } public String getEpisodeIdentifier() { @@ -130,6 +86,38 @@ public class RemoteMedia implements Playable { return feedUrl; } + public String getDownloadUrl() { + return downloadUrl; + } + + public String getEpisodeLink() { + return episodeLink; + } + + public String getFeedAuthor() { + return feedAuthor; + } + + public String getImageUrl() { + return imageUrl; + } + + public String getFeedLink() { + return feedLink; + } + + public String getMimeType() { + return mimeType; + } + + public Date getPubDate() { + return pubDate; + } + + public String getNotes() { + return notes; + } + @Override public void writeToPreferences(SharedPreferences.Editor prefEditor) { //it seems pointless to do it, since the session should be kept by the remote device. @@ -196,7 +184,7 @@ public class RemoteMedia implements Playable { @Override public MediaType getMediaType() { - return MediaType.fromMimeType(mime_type); + return MediaType.fromMimeType(mimeType); } @Override @@ -293,7 +281,7 @@ public class RemoteMedia implements Playable { dest.writeString(feedAuthor); dest.writeString(imageUrl); dest.writeString(feedLink); - dest.writeString(mime_type); + dest.writeString(mimeType); dest.writeLong(pubDate.getTime()); dest.writeString(notes); dest.writeInt(duration); @@ -324,9 +312,9 @@ public class RemoteMedia implements Playable { public boolean equals(Object other) { if (other instanceof RemoteMedia) { RemoteMedia rm = (RemoteMedia) other; - return TextUtils.equals(downloadUrl, rm.downloadUrl) && - TextUtils.equals(feedUrl, rm.feedUrl) && - TextUtils.equals(itemIdentifier, rm.itemIdentifier); + return TextUtils.equals(downloadUrl, rm.downloadUrl) + && TextUtils.equals(feedUrl, rm.feedUrl) + && TextUtils.equals(itemIdentifier, rm.itemIdentifier); } if (other instanceof FeedMedia) { FeedMedia fm = (FeedMedia) other; diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 7dee73805..57d0ca38d 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -658,6 +658,7 @@ <!-- Online feed view --> <string name="subscribe_label">Subscribe</string> <string name="subscribing_label">Subscribing…</string> + <string name="preview_episode">Preview</string> <!-- Content descriptions for image buttons --> <string name="rewind_label">Rewind</string> diff --git a/core/src/play/java/de/danoeh/antennapod/core/cast/CastUtils.java b/core/src/play/java/de/danoeh/antennapod/core/cast/CastUtils.java index 88da6a0ec..484904a9a 100644 --- a/core/src/play/java/de/danoeh/antennapod/core/cast/CastUtils.java +++ b/core/src/play/java/de/danoeh/antennapod/core/cast/CastUtils.java @@ -9,6 +9,7 @@ import com.google.android.gms.cast.MediaInfo; import com.google.android.gms.cast.MediaMetadata; import com.google.android.gms.common.images.WebImage; +import de.danoeh.antennapod.core.util.playback.RemoteMedia; import java.util.Calendar; import java.util.List; diff --git a/core/src/play/java/de/danoeh/antennapod/core/cast/MediaInfoCreator.java b/core/src/play/java/de/danoeh/antennapod/core/cast/MediaInfoCreator.java new file mode 100644 index 000000000..91de5418d --- /dev/null +++ b/core/src/play/java/de/danoeh/antennapod/core/cast/MediaInfoCreator.java @@ -0,0 +1,60 @@ +package de.danoeh.antennapod.core.cast; + +import android.net.Uri; +import android.text.TextUtils; +import com.google.android.gms.cast.MediaInfo; +import com.google.android.gms.cast.MediaMetadata; +import com.google.android.gms.common.images.WebImage; +import de.danoeh.antennapod.core.util.playback.RemoteMedia; +import java.util.Calendar; + +public class MediaInfoCreator { + public static MediaInfo from(RemoteMedia media) { + MediaMetadata metadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_GENERIC); + + metadata.putString(MediaMetadata.KEY_TITLE, media.getEpisodeTitle()); + metadata.putString(MediaMetadata.KEY_SUBTITLE, media.getFeedTitle()); + if (!TextUtils.isEmpty(media.getImageLocation())) { + metadata.addImage(new WebImage(Uri.parse(media.getImageLocation()))); + } + Calendar calendar = Calendar.getInstance(); + calendar.setTime(media.getPubDate()); + metadata.putDate(MediaMetadata.KEY_RELEASE_DATE, calendar); + if (!TextUtils.isEmpty(media.getFeedAuthor())) { + metadata.putString(MediaMetadata.KEY_ARTIST, media.getFeedAuthor()); + } + if (!TextUtils.isEmpty(media.getFeedUrl())) { + metadata.putString(CastUtils.KEY_FEED_URL, media.getFeedUrl()); + } + if (!TextUtils.isEmpty(media.getFeedLink())) { + metadata.putString(CastUtils.KEY_FEED_WEBSITE, media.getFeedLink()); + } + if (!TextUtils.isEmpty(media.getEpisodeIdentifier())) { + metadata.putString(CastUtils.KEY_EPISODE_IDENTIFIER, media.getEpisodeIdentifier()); + } else { + metadata.putString(CastUtils.KEY_EPISODE_IDENTIFIER, media.getDownloadUrl()); + } + if (!TextUtils.isEmpty(media.getEpisodeLink())) { + metadata.putString(CastUtils.KEY_EPISODE_LINK, media.getEpisodeLink()); + } + String notes = media.getNotes(); + if (notes != null) { + if (notes.length() > CastUtils.EPISODE_NOTES_MAX_LENGTH) { + notes = notes.substring(0, CastUtils.EPISODE_NOTES_MAX_LENGTH); + } + metadata.putString(CastUtils.KEY_EPISODE_NOTES, notes); + } + // Default id value + metadata.putInt(CastUtils.KEY_MEDIA_ID, 0); + metadata.putInt(CastUtils.KEY_FORMAT_VERSION, CastUtils.FORMAT_VERSION_VALUE); + + MediaInfo.Builder builder = new MediaInfo.Builder(media.getDownloadUrl()) + .setContentType(media.getMimeType()) + .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) + .setMetadata(metadata); + if (media.getDuration() > 0) { + builder.setStreamDuration(media.getDuration()); + } + return builder.build(); + } +} diff --git a/core/src/play/java/de/danoeh/antennapod/core/feed/FeedMediaFlavorHelper.java b/core/src/play/java/de/danoeh/antennapod/core/feed/FeedMediaFlavorHelper.java index fe0f771d6..2502f17c0 100644 --- a/core/src/play/java/de/danoeh/antennapod/core/feed/FeedMediaFlavorHelper.java +++ b/core/src/play/java/de/danoeh/antennapod/core/feed/FeedMediaFlavorHelper.java @@ -1,6 +1,6 @@ package de.danoeh.antennapod.core.feed; -import de.danoeh.antennapod.core.cast.RemoteMedia; +import de.danoeh.antennapod.core.util.playback.RemoteMedia; /** * Implements methods for FeedMedia that are flavor dependent. diff --git a/core/src/play/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java b/core/src/play/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java index 7c84437e1..2a75f30a2 100644 --- a/core/src/play/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java +++ b/core/src/play/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java @@ -15,6 +15,7 @@ import com.google.android.libraries.cast.companionlibrary.cast.exceptions.CastEx import com.google.android.libraries.cast.companionlibrary.cast.exceptions.NoConnectionException; import com.google.android.libraries.cast.companionlibrary.cast.exceptions.TransientNetworkDisconnectionException; +import de.danoeh.antennapod.core.cast.MediaInfoCreator; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.atomic.AtomicBoolean; @@ -24,7 +25,7 @@ import de.danoeh.antennapod.core.cast.CastConsumer; import de.danoeh.antennapod.core.cast.CastManager; import de.danoeh.antennapod.core.cast.CastUtils; import de.danoeh.antennapod.core.cast.DefaultCastConsumer; -import de.danoeh.antennapod.core.cast.RemoteMedia; +import de.danoeh.antennapod.core.util.playback.RemoteMedia; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.feed.MediaType; import de.danoeh.antennapod.core.preferences.UserPreferences; @@ -165,7 +166,7 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer { return CastUtils.convertFromFeedMedia((FeedMedia) playable); } if (playable instanceof RemoteMedia) { - return ((RemoteMedia) playable).extractMediaInfo(); + return MediaInfoCreator.from((RemoteMedia) playable); } return null; } |