diff options
Diffstat (limited to 'core/src/main')
4 files changed, 370 insertions, 74 deletions
diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java index 89ecf271b..4b14a72d2 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java +++ b/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java @@ -30,12 +30,16 @@ public class FeedItemEvent { return new FeedItemEvent(Action.DELETE_MEDIA, items); } + public static FeedItemEvent deletedMedia(FeedItem... items) { + return deletedMedia(Arrays.asList(items)); + } + public static FeedItemEvent updated(List<FeedItem> items) { return new FeedItemEvent(Action.UPDATE, items); } public static FeedItemEvent updated(FeedItem... items) { - return new FeedItemEvent(Action.UPDATE, Arrays.asList(items)); + return updated(Arrays.asList(items)); } @Override diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java index 8ebe18dc0..5b7e62964 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java @@ -6,9 +6,22 @@ import android.database.Cursor; import android.os.Looper; import android.text.TextUtils; import android.util.Log; - import androidx.annotation.VisibleForTesting; - +import de.danoeh.antennapod.core.ClientConfig; +import de.danoeh.antennapod.core.R; +import de.danoeh.antennapod.core.event.FeedItemEvent; +import de.danoeh.antennapod.core.event.FeedListUpdateEvent; +import de.danoeh.antennapod.core.event.MessageEvent; +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.FeedPreferences; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.service.GpodnetSyncService; +import de.danoeh.antennapod.core.service.download.DownloadStatus; +import de.danoeh.antennapod.core.util.DownloadError; +import de.danoeh.antennapod.core.util.LongList; +import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator; import org.greenrobot.eventbus.EventBus; import java.util.ArrayList; @@ -24,23 +37,6 @@ import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.atomic.AtomicBoolean; -import de.danoeh.antennapod.core.ClientConfig; -import de.danoeh.antennapod.core.event.FeedListUpdateEvent; -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.FeedPreferences; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.service.GpodnetSyncService; -import de.danoeh.antennapod.core.service.download.DownloadStatus; -import de.danoeh.antennapod.core.service.playback.PlaybackService; -import de.danoeh.antennapod.core.util.DownloadError; -import de.danoeh.antennapod.core.util.IntentUtils; -import de.danoeh.antennapod.core.util.LongList; -import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator; -import de.danoeh.antennapod.core.util.exception.MediaFileNotFoundException; -import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter; - import static android.content.Context.MODE_PRIVATE; /** @@ -100,53 +96,6 @@ public final class DBTasks { } } - /** - * Starts playback of a FeedMedia object's file. This method will build an Intent based on the given parameters to - * start the {@link PlaybackService}. - * - * @param context Used for sending starting Services and Activities. - * @param media The FeedMedia object. - * @param showPlayer If true, starts the appropriate player activity ({@link de.danoeh.antennapod.activity.AudioplayerActivity} - * or {@link de.danoeh.antennapod.activity.VideoplayerActivity} - * @param startWhenPrepared Parameter for the {@link PlaybackService} start intent. If true, playback will start as - * soon as the PlaybackService has finished loading the FeedMedia object's file. - * @param shouldStream Parameter for the {@link PlaybackService} start intent. If true, the FeedMedia object's file - * will be streamed, otherwise the downloaded file will be used. If the downloaded file cannot be - * found, the PlaybackService will shutdown and the database entry of the FeedMedia object will be - * corrected. - */ - public static void playMedia(final Context context, final FeedMedia media, - boolean showPlayer, boolean startWhenPrepared, boolean shouldStream) { - try { - if (!shouldStream) { - if (!media.fileExists()) { - throw new MediaFileNotFoundException( - "No episode was found at " + media.getFile_url(), - media); - } - } - - new PlaybackServiceStarter(context, media) - .callEvenIfRunning(true) - .startWhenPrepared(startWhenPrepared) - .shouldStream(shouldStream) - .start(); - - if (showPlayer) { - // Launch media player - context.startActivity(PlaybackService.getPlayerActivityIntent( - context, media)); - } - DBWriter.addQueueItemAt(context, media.getItem().getId(), 0, false); - } catch (MediaFileNotFoundException e) { - e.printStackTrace(); - if (media.isPlaying()) { - IntentUtils.sendLocalBroadcast(context, PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE); - } - notifyMissingFeedMediaFile(context, media); - } - } - private static final AtomicBoolean isRefreshing = new AtomicBoolean(false); /** @@ -293,17 +242,16 @@ public final class DBTasks { } /** - * Notifies the database about a missing FeedMedia file. This method will correct the FeedMedia object's values in the - * DB and send a FeedUpdateBroadcast. + * Notifies the database about a missing FeedMedia file. This method will correct the FeedMedia object's + * values in the DB and send a FeedItemEvent. */ - public static void notifyMissingFeedMediaFile(final Context context, - final FeedMedia media) { - Log.i(TAG, - "The feedmanager was notified about a missing episode. It will update its database now."); + public static void notifyMissingFeedMediaFile(final Context context, final FeedMedia media) { + Log.i(TAG, "The feedmanager was notified about a missing episode. It will update its database now."); media.setDownloaded(false); media.setFile_url(null); DBWriter.setFeedMedia(media); - EventBus.getDefault().post(new FeedListUpdateEvent(media.getItem().getFeed())); + EventBus.getDefault().post(FeedItemEvent.deletedMedia(media.getItem())); + EventBus.getDefault().post(new MessageEvent(context.getString(R.string.error_file_not_found))); } @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/RemoteMedia.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/RemoteMedia.java new file mode 100644 index 000000000..c39121564 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/RemoteMedia.java @@ -0,0 +1,342 @@ +package de.danoeh.antennapod.core.util.playback; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +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 java.util.Date; +import java.util.List; +import java.util.concurrent.Callable; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +/** + * 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"; + + public static final int PLAYABLE_TYPE_REMOTE_MEDIA = 3; + + private String downloadUrl; + private String itemIdentifier; + private String feedUrl; + private String feedTitle; + private String episodeTitle; + private String episodeLink; + private String feedAuthor; + private String imageUrl; + private String feedLink; + private String mimeType; + private Date pubDate; + private String notes; + private List<Chapter> chapters; + private int duration; + private int position; + private long lastPlayedTime; + + public RemoteMedia(String downloadUrl, String itemId, String feedUrl, String feedTitle, + String episodeTitle, String episodeLink, String feedAuthor, + String imageUrl, String feedLink, String mimeType, Date pubDate) { + this.downloadUrl = downloadUrl; + this.itemIdentifier = itemId; + this.feedUrl = feedUrl; + this.feedTitle = feedTitle; + this.episodeTitle = episodeTitle; + this.episodeLink = episodeLink; + this.feedAuthor = feedAuthor; + this.imageUrl = imageUrl; + this.feedLink = feedLink; + this.mimeType = mimeType; + this.pubDate = pubDate; + } + + 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 void setNotes(String notes) { + this.notes = notes; + } + + public String getEpisodeIdentifier() { + return itemIdentifier; + } + + public String getFeedUrl() { + 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. + } + + @Override + public void loadMetadata() throws PlayableException { + //Already loaded + } + + @Override + public void loadChapterMarks() { + ChapterUtils.loadChaptersFromStreamUrl(this); + } + + @Override + public String getEpisodeTitle() { + return episodeTitle; + } + + @Override + public List<Chapter> getChapters() { + return chapters; + } + + @Override + public String getWebsiteLink() { + if (episodeLink != null) { + return episodeLink; + } else { + return feedUrl; + } + } + + @Override + public String getPaymentLink() { + return null; + } + + @Override + public String getFeedTitle() { + return feedTitle; + } + + @Override + public Object getIdentifier() { + return itemIdentifier + "@" + feedUrl; + } + + @Override + public int getDuration() { + return duration; + } + + @Override + public int getPosition() { + return position; + } + + @Override + public long getLastPlayedTime() { + return lastPlayedTime; + } + + @Override + public MediaType getMediaType() { + return MediaType.fromMimeType(mimeType); + } + + @Override + public String getLocalMediaUrl() { + return null; + } + + @Override + public String getStreamUrl() { + return downloadUrl; + } + + @Override + public boolean localFileAvailable() { + return false; + } + + @Override + public boolean streamAvailable() { + return true; + } + + @Override + public void saveCurrentPosition(SharedPreferences pref, int newPosition, long timestamp) { + //we're not saving playback information for this kind of items on preferences + setPosition(newPosition); + setLastPlayedTime(timestamp); + } + + @Override + public void setPosition(int newPosition) { + position = newPosition; + } + + @Override + public void setDuration(int newDuration) { + duration = newDuration; + } + + @Override + public void setLastPlayedTime(long lastPlayedTimestamp) { + lastPlayedTime = lastPlayedTimestamp; + } + + @Override + public void onPlaybackStart() { + // no-op + } + + @Override + public void onPlaybackPause(Context context) { + // no-op + } + + @Override + public void onPlaybackCompleted(Context context) { + // no-op + } + + @Override + public int getPlayableType() { + return PLAYABLE_TYPE_REMOTE_MEDIA; + } + + @Override + public void setChapters(List<Chapter> chapters) { + this.chapters = chapters; + } + + @Override + @Nullable + public String getImageLocation() { + return imageUrl; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public Callable<String> loadShownotes() { + return () -> (notes != null) ? notes : ""; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(downloadUrl); + dest.writeString(itemIdentifier); + dest.writeString(feedUrl); + dest.writeString(feedTitle); + dest.writeString(episodeTitle); + dest.writeString(episodeLink); + dest.writeString(feedAuthor); + dest.writeString(imageUrl); + dest.writeString(feedLink); + dest.writeString(mimeType); + dest.writeLong(pubDate.getTime()); + dest.writeString(notes); + dest.writeInt(duration); + dest.writeInt(position); + dest.writeLong(lastPlayedTime); + } + + public static final Parcelable.Creator<RemoteMedia> CREATOR = new Parcelable.Creator<RemoteMedia>() { + @Override + public RemoteMedia createFromParcel(Parcel in) { + RemoteMedia result = new RemoteMedia(in.readString(), in.readString(), in.readString(), + in.readString(), in.readString(), in.readString(), in.readString(), in.readString(), + in.readString(), in.readString(), new Date(in.readLong())); + result.setNotes(in.readString()); + result.setDuration(in.readInt()); + result.setPosition(in.readInt()); + result.setLastPlayedTime(in.readLong()); + return result; + } + + @Override + public RemoteMedia[] newArray(int size) { + return new RemoteMedia[size]; + } + }; + + @Override + 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); + } + if (other instanceof FeedMedia) { + FeedMedia fm = (FeedMedia) other; + if (!TextUtils.equals(downloadUrl, fm.getStreamUrl())) { + return false; + } + FeedItem fi = fm.getItem(); + if (fi == null || !TextUtils.equals(itemIdentifier, fi.getItemIdentifier())) { + return false; + } + Feed feed = fi.getFeed(); + return feed != null && TextUtils.equals(feedUrl, feed.getDownload_url()); + } + return false; + } + + @Override + public int hashCode() { + return new HashCodeBuilder() + .append(downloadUrl) + .append(feedUrl) + .append(itemIdentifier) + .toHashCode(); + } +} diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index bbdcb912e..57d0ca38d 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -289,6 +289,7 @@ <string name="player_buffering_msg">Buffering</string> <string name="player_go_to_picture_in_picture">Picture-in-picture mode</string> <string name="unknown_media_key">AntennaPod - Unknown media key: %1$d</string> + <string name="error_file_not_found">File not found</string> <!-- Queue operations --> <string name="lock_queue">Lock Queue</string> @@ -657,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> |