From 94a16bb9baf2832d8c4c1208d6713c09a237e59d Mon Sep 17 00:00:00 2001 From: Domingos Lopes Date: Sun, 27 Mar 2016 02:25:15 -0400 Subject: create RemoteMedia class, add more fields to remote mediametadata --- .../danoeh/antennapod/core/cast/RemoteMedia.java | 257 +++++++++++++++++++++ .../de/danoeh/antennapod/core/util/CastUtils.java | 72 +++++- .../antennapod/core/util/playback/Playable.java | 10 + 3 files changed, 333 insertions(+), 6 deletions(-) create mode 100644 core/src/main/java/de/danoeh/antennapod/core/cast/RemoteMedia.java (limited to 'core/src/main/java/de/danoeh') diff --git a/core/src/main/java/de/danoeh/antennapod/core/cast/RemoteMedia.java b/core/src/main/java/de/danoeh/antennapod/core/cast/RemoteMedia.java new file mode 100644 index 000000000..dc72897e4 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/cast/RemoteMedia.java @@ -0,0 +1,257 @@ +package de.danoeh.antennapod.core.cast; + +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import java.util.Date; +import java.util.List; +import java.util.concurrent.Callable; + +import de.danoeh.antennapod.core.feed.Chapter; +import de.danoeh.antennapod.core.feed.MediaType; +import de.danoeh.antennapod.core.util.ChapterUtils; +import de.danoeh.antennapod.core.util.playback.Playable; + +/** + * Playable implementation for media on a Cast Device for which a local version of + * {@link de.danoeh.antennapod.core.feed.FeedMedia} could not be found. + */ +public class RemoteMedia implements Playable { + + 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 mime_type; + private Date pubDate; + private String notes; + private List 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 mime_type, 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.mime_type = mime_type; + this.pubDate = pubDate; + } + + public void setNotes(String notes) { + this.notes = 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 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() { + if (TextUtils.isEmpty(mime_type)) { + return MediaType.UNKNOWN; + } else { + if (mime_type.startsWith("audio")) { + return MediaType.AUDIO; + } else if (mime_type.startsWith("video")) { + return MediaType.VIDEO; + } else if (mime_type.equals("application/ogg")) { + return MediaType.AUDIO; + } + } + return MediaType.UNKNOWN; + } + + @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 onPlaybackCompleted() { + // no-op + } + + @Override + public int getPlayableType() { + return PLAYABLE_TYPE_REMOTE_MEDIA; + } + + @Override + public void setChapters(List chapters) { + this.chapters = chapters; + } + + @Override + public Uri getImageUri() { + if (imageUrl != null) { + return Uri.parse(imageUrl); + } + return null; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public Callable 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(mime_type); + dest.writeLong(pubDate.getTime()); + dest.writeString(notes); + dest.writeInt(duration); + dest.writeInt(position); + dest.writeLong(lastPlayedTime); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @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(), 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]; + } + }; +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/CastUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/CastUtils.java index 9f41317da..9d665113a 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/CastUtils.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/CastUtils.java @@ -1,6 +1,7 @@ package de.danoeh.antennapod.core.util; import android.net.Uri; +import android.text.TextUtils; import android.util.Log; import com.google.android.gms.cast.CastDevice; @@ -11,6 +12,7 @@ import com.google.android.gms.common.images.WebImage; import java.util.Calendar; import de.danoeh.antennapod.core.cast.CastManager; +import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedImage; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; @@ -23,7 +25,27 @@ import de.danoeh.antennapod.core.util.playback.Playable; public class CastUtils { private static final String TAG = "CastUtils"; - public static final String KEY_MEDIA_ID = "CastUtils.Id"; + public static final String KEY_MEDIA_ID = "AntennaPod.MediaId"; + + public static final String KEY_EPISODE_IDENTIFIER = "AntennaPod.EpisodeId"; + public static final String KEY_EPISODE_LINK = "AntennaPod.EpisodeLink"; + public static final String KEY_FEED_URL = "AntennaPod.FeedUrl"; + public static final String KEY_FEED_WEBSITE = "AntennaPod.FeedWebsite"; + public static final String KEY_EPISODE_NOTES = "AntennaPod.EpisodeNotes"; + public static final int EPISODE_NOTES_MAX_LENGTH = Integer.MAX_VALUE; + + /** + * The field AntennaPod.FormatVersion specifies which version of MediaMetaData + * fields we're using. Future implementations should try to be backwards compatible with earlier + * versions, and earlier versions should be forward compatible until the version indicated by + * MAX_VERSION_FORWARD_COMPATIBILITY. If an update makes the format unreadable for + * an earlier version, then its version number should be greater than the + * MAX_VERSION_FORWARD_COMPATIBILITY value set on the earlier one, so that it + * doesn't try to parse the object. + */ + public static final String KEY_FORMAT_VERSION = "AntennaPod.FormatVersion"; + public static final int FORMAT_VERSION_VALUE = 1; + public static final int MAX_VERSION_FORWARD_COMPATIBILITY = 9999; public static boolean isCastable(Playable media){ if (media == null || media instanceof ExternalMedia) { @@ -75,17 +97,53 @@ public class CastUtils { metadata.putString(MediaMetadata.KEY_SUBTITLE, subtitle); } FeedImage image = feedItem.getImage(); - if (image != null && image.getDownload_url() != null && - !image.getDownload_url().isEmpty()) { + if (image != null && !TextUtils.isEmpty(image.getDownload_url())) { metadata.addImage(new WebImage(Uri.parse(image.getDownload_url()))); } Calendar calendar = Calendar.getInstance(); calendar.setTime(media.getItem().getPubDate()); metadata.putDate(MediaMetadata.KEY_RELEASE_DATE, calendar); - + Feed feed = feedItem.getFeed(); + if (feed != null) { + if (!TextUtils.isEmpty(feed.getAuthor())) { + metadata.putString(MediaMetadata.KEY_ARTIST, feed.getAuthor()); + } + if (!TextUtils.isEmpty(feed.getDownload_url())) { + metadata.putString(KEY_FEED_URL, feed.getDownload_url()); + } + if (!TextUtils.isEmpty(feed.getLink())) { + metadata.putString(KEY_FEED_WEBSITE, feed.getLink()); + } + } + if (!TextUtils.isEmpty(feedItem.getItemIdentifier())) { + metadata.putString(KEY_EPISODE_IDENTIFIER, feedItem.getItemIdentifier()); + } else { + metadata.putString(KEY_EPISODE_IDENTIFIER, media.getStreamUrl()); + } + if (!TextUtils.isEmpty(feedItem.getLink())) { + metadata.putString(KEY_EPISODE_LINK, feedItem.getLink()); + } + } + String notes = null; + try { + notes = media.loadShownotes().call(); + } catch (Exception e) { + Log.e(TAG, "Unable to load FeedMedia notes", e); + } + if (notes != null) { + if (notes.length() > EPISODE_NOTES_MAX_LENGTH) { + notes = notes.substring(0, EPISODE_NOTES_MAX_LENGTH); + } + metadata.putString(KEY_EPISODE_NOTES, notes); } - //metadata.putString(MediaMetadata.KEY_ARTIST, null); - metadata.putString(KEY_MEDIA_ID, media.getIdentifier().toString()); + // This field only identifies the id on the device that has the original version. + // Idea is to perhaps, on a first approach, check if the version on the local DB with the + // same id matches the remote object, and if not then search for episode and feed identifiers. + // This at least should make media recognition for a single device much quicker. + metadata.putInt(KEY_MEDIA_ID, ((Long) media.getIdentifier()).intValue()); + // A way to identify different casting media formats in case we change it in the future and + // senders with different versions share a casting device. + metadata.putInt(KEY_FORMAT_VERSION, FORMAT_VERSION_VALUE); return new MediaInfo.Builder(media.getStreamUrl()) .setContentType(media.getMime_type()) @@ -94,5 +152,7 @@ public class CastUtils { .build(); } + + //TODO Queue handling perhaps } diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java index 6459d86ed..201efbc81 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java @@ -8,6 +8,7 @@ import android.util.Log; import java.util.List; import de.danoeh.antennapod.core.asynctask.ImageResource; +import de.danoeh.antennapod.core.cast.RemoteMedia; import de.danoeh.antennapod.core.feed.Chapter; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.feed.MediaType; @@ -183,6 +184,9 @@ public interface Playable extends Parcelable, case ExternalMedia.PLAYABLE_TYPE_EXTERNAL_MEDIA: result = createExternalMediaInstance(pref); break; + case RemoteMedia.PLAYABLE_TYPE_REMOTE_MEDIA: + result = createRemoteMediaInstance(pref); + break; } if (result == null) { Log.e(TAG, "Could not restore Playable object from preferences"); @@ -211,6 +215,12 @@ public interface Playable extends Parcelable, } return result; } + + private static Playable createRemoteMediaInstance(SharedPreferences pref) { + //TODO there's probably no point in restoring RemoteMedia from preferences, because we + //only care about it while it's playing on the cast device. + return null; + } } class PlayableException extends Exception { -- cgit v1.2.3