summaryrefslogtreecommitdiff
path: root/src/de/danoeh/antennapod/util
diff options
context:
space:
mode:
authordaniel oeh <daniel.oeh@gmail.com>2013-02-27 11:47:12 +0100
committerdaniel oeh <daniel.oeh@gmail.com>2013-02-27 11:47:12 +0100
commita6b6022626b26acd970588ca0bfd0c1c3d641825 (patch)
tree6fcfb15697335cf12202faa1dc2b5b28138f291e /src/de/danoeh/antennapod/util
parent9cd870c6eebf558d789055e16da4e44dff5d0f0d (diff)
downloadAntennaPod-a6b6022626b26acd970588ca0bfd0c1c3d641825.zip
PlaybackService now works with the 'Playable' interface
Diffstat (limited to 'src/de/danoeh/antennapod/util')
-rw-r--r--src/de/danoeh/antennapod/util/ChapterUtils.java90
-rw-r--r--src/de/danoeh/antennapod/util/Playable.java183
-rw-r--r--src/de/danoeh/antennapod/util/PlaybackController.java21
3 files changed, 250 insertions, 44 deletions
diff --git a/src/de/danoeh/antennapod/util/ChapterUtils.java b/src/de/danoeh/antennapod/util/ChapterUtils.java
index 3131b9e9a..5590707dc 100644
--- a/src/de/danoeh/antennapod/util/ChapterUtils.java
+++ b/src/de/danoeh/antennapod/util/ChapterUtils.java
@@ -16,8 +16,6 @@ import org.apache.commons.io.IOUtils;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.feed.Chapter;
-import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedMedia;
import de.danoeh.antennapod.util.comparator.ChapterStartTimeComparator;
import de.danoeh.antennapod.util.id3reader.ChapterReader;
import de.danoeh.antennapod.util.id3reader.ID3ReaderException;
@@ -35,14 +33,13 @@ public class ChapterUtils {
* Uses the download URL of a media object of a feeditem to read its ID3
* chapters.
*/
- public static void readID3ChaptersFromFeedMediaDownloadUrl(FeedItem item) {
+ public static void readID3ChaptersFromPlayableStreamUrl(Playable p) {
if (AppConfig.DEBUG)
- Log.d(TAG, "Reading id3 chapters from item " + item.getTitle());
- final FeedMedia media = item.getMedia();
- if (media != null && media.getDownload_url() != null) {
+ Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle());
+ if (p != null && p.getStreamUrl() != null) {
InputStream in = null;
try {
- URL url = new URL(media.getDownload_url());
+ URL url = new URL(p.getStreamUrl());
ChapterReader reader = new ChapterReader();
in = url.openStream();
@@ -52,9 +49,9 @@ public class ChapterUtils {
if (chapters != null) {
Collections
.sort(chapters, new ChapterStartTimeComparator());
- processChapters(chapters, item);
+ processChapters(chapters, p);
if (chaptersValid(chapters)) {
- item.setChapters(chapters);
+ p.setChapters(chapters);
Log.i(TAG, "Chapters loaded");
} else {
Log.e(TAG, "Chapter data was invalid");
@@ -87,13 +84,11 @@ public class ChapterUtils {
* Uses the file URL of a media object of a feeditem to read its ID3
* chapters.
*/
- public static void readID3ChaptersFromFeedMediaFileUrl(FeedItem item) {
+ public static void readID3ChaptersFromPlayableFileUrl(Playable p) {
if (AppConfig.DEBUG)
- Log.d(TAG, "Reading id3 chapters from item " + item.getTitle());
- final FeedMedia media = item.getMedia();
- if (media != null && media.isDownloaded()
- && media.getFile_url() != null) {
- File source = new File(media.getFile_url());
+ Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle());
+ if (p != null && p.localFileAvailable() && p.getFileUrl() != null) {
+ File source = new File(p.getFileUrl());
if (source.exists()) {
ChapterReader reader = new ChapterReader();
InputStream in = null;
@@ -106,9 +101,9 @@ public class ChapterUtils {
if (chapters != null) {
Collections.sort(chapters,
new ChapterStartTimeComparator());
- processChapters(chapters, item);
+ processChapters(chapters, p);
if (chaptersValid(chapters)) {
- item.setChapters(chapters);
+ p.setChapters(chapters);
Log.i(TAG, "Chapters loaded");
} else {
Log.e(TAG, "Chapter data was invalid");
@@ -136,15 +131,14 @@ public class ChapterUtils {
}
}
- public static void readOggChaptersFromMediaDownloadUrl(FeedItem item) {
- final FeedMedia media = item.getMedia();
- if (media != null && media.getDownload_url() != null) {
+ public static void readOggChaptersFromPlayableStreamUrl(Playable media) {
+ if (media != null && media.streamAvailable()) {
InputStream input = null;
try {
- URL url = new URL(media.getDownload_url());
+ URL url = new URL(media.getStreamUrl());
input = url.openStream();
if (input != null) {
- readOggChaptersFromInputStream(item, input);
+ readOggChaptersFromInputStream(media, input);
}
} catch (MalformedURLException e) {
e.printStackTrace();
@@ -156,15 +150,14 @@ public class ChapterUtils {
}
}
- public static void readOggChaptersFromMediaFileUrl(FeedItem item) {
- final FeedMedia media = item.getMedia();
- if (media != null && media.getFile_url() != null) {
- File source = new File(media.getFile_url());
+ public static void readOggChaptersFromPlayableFileUrl(Playable media) {
+ if (media != null && media.getFileUrl() != null) {
+ File source = new File(media.getFileUrl());
if (source.exists()) {
InputStream input = null;
try {
input = new BufferedInputStream(new FileInputStream(source));
- readOggChaptersFromInputStream(item, input);
+ readOggChaptersFromInputStream(media, input);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
@@ -174,21 +167,21 @@ public class ChapterUtils {
}
}
- private static void readOggChaptersFromInputStream(FeedItem item,
+ private static void readOggChaptersFromInputStream(Playable p,
InputStream input) {
if (AppConfig.DEBUG)
Log.d(TAG,
"Trying to read chapters from item with title "
- + item.getTitle());
+ + p.getEpisodeTitle());
try {
VorbisCommentChapterReader reader = new VorbisCommentChapterReader();
reader.readInputStream(input);
List<Chapter> chapters = reader.getChapters();
if (chapters != null) {
Collections.sort(chapters, new ChapterStartTimeComparator());
- processChapters(chapters, item);
+ processChapters(chapters, p);
if (chaptersValid(chapters)) {
- item.setChapters(chapters);
+ p.setChapters(chapters);
Log.i(TAG, "Chapters loaded");
} else {
Log.e(TAG, "Chapter data was invalid");
@@ -203,13 +196,12 @@ public class ChapterUtils {
}
/** Makes sure that chapter does a title and an item attribute. */
- private static void processChapters(List<Chapter> chapters, FeedItem item) {
+ private static void processChapters(List<Chapter> chapters, Playable p) {
for (int i = 0; i < chapters.size(); i++) {
Chapter c = chapters.get(i);
if (c.getTitle() == null) {
c.setTitle(Integer.toString(i));
}
- c.setItem(item);
}
}
@@ -228,4 +220,36 @@ public class ChapterUtils {
return true;
}
+ /** Calls getCurrentChapter with current position. */
+ public static Chapter getCurrentChapter(Playable media) {
+ if (media.getChapters() != null) {
+ List<Chapter> chapters = media.getChapters();
+ Chapter current = null;
+ if (chapters != null) {
+ current = chapters.get(0);
+ for (Chapter sc : chapters) {
+ if (sc.getStart() > media.getPosition()) {
+ break;
+ } else {
+ current = sc;
+ }
+ }
+ }
+ return current;
+ } else {
+ return null;
+ }
+ }
+
+ public static void loadChapters(Playable media) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Starting chapterLoader thread");
+ ChapterUtils.readID3ChaptersFromPlayableStreamUrl(media);
+ if (media.getChapters() == null) {
+ ChapterUtils.readOggChaptersFromPlayableStreamUrl(media);
+ }
+
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "ChapterLoaderThread has finished");
+ }
}
diff --git a/src/de/danoeh/antennapod/util/Playable.java b/src/de/danoeh/antennapod/util/Playable.java
new file mode 100644
index 000000000..360a31ac1
--- /dev/null
+++ b/src/de/danoeh/antennapod/util/Playable.java
@@ -0,0 +1,183 @@
+package de.danoeh.antennapod.util;
+
+import java.util.List;
+
+import android.content.SharedPreferences;
+import android.os.Parcelable;
+import android.util.Log;
+import de.danoeh.antennapod.feed.Chapter;
+import de.danoeh.antennapod.feed.Feed;
+import de.danoeh.antennapod.feed.FeedManager;
+import de.danoeh.antennapod.feed.FeedMedia;
+import de.danoeh.antennapod.feed.MediaType;
+
+/** Interface for objects that can be played by the PlaybackService. */
+public interface Playable extends Parcelable {
+
+ /**
+ * Save information about the playable in a preference so that it can be
+ * restored later via PlayableUtils.createInstanceFromPreferences.
+ * Implementations must NOT call commit() after they have written the values
+ * to the preferences file.
+ */
+ public void writeToPreferences(SharedPreferences.Editor prefEditor);
+
+ /**
+ * This method is called from a separate thread by the PlaybackService.
+ * Playable objects should load their metadata in this method (for example:
+ * chapter marks).
+ */
+ public void loadMetadata() throws PlayableException;
+
+ /** Returns the title of the episode that this playable represents */
+ public String getEpisodeTitle();
+
+ /**
+ * Loads shownotes. If the shownotes have to be loaded from a file or from a
+ * database, it should be done in a separate thread. After the shownotes
+ * have been loaded, callback.onShownotesLoaded should be called.
+ */
+ public void loadShownotes(ShownoteLoaderCallback callback);
+
+ /**
+ * Returns a list of chapter marks or null if this Playable has no chapters.
+ */
+ public List<Chapter> getChapters();
+
+ /** Returns a link to a website that is meant to be shown in a browser */
+ public String getWebsiteLink();
+
+ public String getPaymentLink();
+
+ /** Returns the title of the feed this Playable belongs to. */
+ public String getFeedTitle();
+
+ /** Returns a file url to an image or null if no such image exists. */
+ public String getImageFileUrl();
+
+ /**
+ * Returns a unique identifier, for example a file url or an ID from a
+ * database.
+ */
+ public Object getIdentifier();
+
+ /** Return duration of object or 0 if duration is unknown. */
+ public int getDuration();
+
+ /** Return position of object or 0 if position is unknown. */
+ public int getPosition();
+
+ /** Returns the type of media. */
+ public MediaType getMediaType();
+
+ /**
+ * Returns an url to a local file that can be played or null if this file
+ * does not exist.
+ */
+ public String getFileUrl();
+
+ /**
+ * Returns an url to a file that can be streamed by the player or null if
+ * this url is not known.
+ */
+ public String getStreamUrl();
+
+ /**
+ * Returns true if a local file that can be played is available. getFileUrl
+ * MUST return a non-null string if this method returns true.
+ */
+ public boolean localFileAvailable();
+
+ /**
+ * Returns true if a streamable file is available. getStreamUrl MUST return
+ * a non-null string if this method returns true.
+ */
+ public boolean streamAvailable();
+
+ /**
+ * Saves the current position of this object. Implementations can use the
+ * provided SharedPreference to save this information and retrieve it later
+ * via PlayableUtils.createInstanceFromPreferences.
+ */
+ public void saveCurrentPosition(SharedPreferences pref, int newPosition);
+
+ public void setPosition(int newPosition);
+
+ public void setDuration(int newDuration);
+
+ /** Is called by the PlaybackService when playback starts. */
+ public void onPlaybackStart();
+
+ /** Is called by the PlaybackService when playback is completed. */
+ public void onPlaybackCompleted();
+
+ /**
+ * Returns an integer that must be unique among all Playable classes. The
+ * return value is later used by PlayableUtils to determine the type of the
+ * Playable object that is restored.
+ */
+ public int getPlayableType();
+
+ public void setChapters(List<Chapter> chapters);
+
+ /** Provides utility methods for Playable objects. */
+ public static class PlayableUtils {
+ private static final String TAG = "PlayableUtils";
+
+ /**
+ * Restores a playable object from a sharedPreferences file.
+ *
+ * @param type
+ * An integer that represents the type of the Playable object
+ * that is restored.
+ * @param pref
+ * The SharedPreferences file from which the Playable object
+ * is restored
+ * @return The restored Playable object
+ */
+ public static Playable createInstanceFromPreferences(int type,
+ SharedPreferences pref) {
+ // ADD new Playable types here:
+ switch (type) {
+ case FeedMedia.PLAYABLE_TYPE_FEEDMEDIA:
+ long feedId = pref.getLong(FeedMedia.PREF_FEED_ID, -1);
+ long mediaId = pref.getLong(FeedMedia.PREF_MEDIA_ID, -1);
+ if (feedId != -1 && mediaId != -1) {
+ Feed feed = FeedManager.getInstance().getFeed(feedId);
+ if (feed != null) {
+ return FeedManager.getInstance().getFeedMedia(mediaId,
+ feed);
+ }
+ }
+ break;
+ }
+ Log.e(TAG, "Could not restore Playable object from preferences");
+ return null;
+ }
+ }
+
+ public static class PlayableException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public PlayableException() {
+ super();
+ }
+
+ public PlayableException(String detailMessage, Throwable throwable) {
+ super(detailMessage, throwable);
+ }
+
+ public PlayableException(String detailMessage) {
+ super(detailMessage);
+ }
+
+ public PlayableException(Throwable throwable) {
+ super(throwable);
+ }
+
+ }
+
+ public static interface ShownoteLoaderCallback {
+ void onShownotesLoaded(String shownotes);
+ }
+}
diff --git a/src/de/danoeh/antennapod/util/PlaybackController.java b/src/de/danoeh/antennapod/util/PlaybackController.java
index 465feba8e..cad5af92e 100644
--- a/src/de/danoeh/antennapod/util/PlaybackController.java
+++ b/src/de/danoeh/antennapod/util/PlaybackController.java
@@ -33,6 +33,7 @@ import de.danoeh.antennapod.feed.FeedMedia;
import de.danoeh.antennapod.preferences.PlaybackPreferences;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.service.PlayerStatus;
+import de.danoeh.antennapod.util.Playable.PlayableUtils;
/**
* Communicates with the playback service. GUI classes should use this class to
@@ -47,7 +48,7 @@ public abstract class PlaybackController {
private Activity activity;
private PlaybackService playbackService;
- private FeedMedia media;
+ private Playable media;
private PlayerStatus status;
private ScheduledThreadPoolExecutor schedExecutor;
@@ -186,24 +187,22 @@ public abstract class PlaybackController {
Log.d(TAG, "Trying to restore last played media");
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(activity.getApplicationContext());
- long mediaId = PlaybackPreferences.getLastPlayedId();
- long feedId = PlaybackPreferences.getLastPlayedFeedId();
- if (mediaId != -1 && feedId != -1) {
- FeedMedia media = FeedManager.getInstance().getFeedMedia(mediaId);
+ long lastPlayedId = PlaybackPreferences.getLastPlayedId();
+ if (lastPlayedId != PlaybackPreferences.NO_MEDIA_PLAYING) {
+ Playable media = PlayableUtils.createInstanceFromPreferences((int) lastPlayedId, prefs);
if (media != null) {
Intent serviceIntent = new Intent(activity,
PlaybackService.class);
- serviceIntent.putExtra(PlaybackService.EXTRA_FEED_ID, feedId);
- serviceIntent.putExtra(PlaybackService.EXTRA_MEDIA_ID, mediaId);
+ serviceIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
serviceIntent.putExtra(
PlaybackService.EXTRA_START_WHEN_PREPARED, false);
serviceIntent.putExtra(
PlaybackService.EXTRA_PREPARE_IMMEDIATELY, false);
- boolean fileExists = media.fileExists();
+ boolean fileExists = media.localFileAvailable();
boolean lastIsStream = PlaybackPreferences.isLastIsStream();
- if (!fileExists && !lastIsStream) {
+ if (!fileExists && !lastIsStream && media instanceof FeedMedia) {
FeedManager.getInstance().notifyMissingFeedMediaFile(
- activity, media);
+ activity, (FeedMedia) media);
}
serviceIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM,
lastIsStream || !fileExists);
@@ -585,7 +584,7 @@ public abstract class PlaybackController {
}
}
- public FeedMedia getMedia() {
+ public Playable getMedia() {
return media;
}