diff options
author | daniel oeh <daniel.oeh@gmail.com> | 2013-02-27 11:47:12 +0100 |
---|---|---|
committer | daniel oeh <daniel.oeh@gmail.com> | 2013-02-27 11:47:12 +0100 |
commit | a6b6022626b26acd970588ca0bfd0c1c3d641825 (patch) | |
tree | 6fcfb15697335cf12202faa1dc2b5b28138f291e /src/de/danoeh/antennapod/util | |
parent | 9cd870c6eebf558d789055e16da4e44dff5d0f0d (diff) | |
download | AntennaPod-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.java | 90 | ||||
-rw-r--r-- | src/de/danoeh/antennapod/util/Playable.java | 183 | ||||
-rw-r--r-- | src/de/danoeh/antennapod/util/PlaybackController.java | 21 |
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; } |