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 | |
parent | 9cd870c6eebf558d789055e16da4e44dff5d0f0d (diff) | |
download | AntennaPod-a6b6022626b26acd970588ca0bfd0c1c3d641825.zip |
PlaybackService now works with the 'Playable' interface
Diffstat (limited to 'src/de/danoeh/antennapod')
19 files changed, 724 insertions, 323 deletions
diff --git a/src/de/danoeh/antennapod/activity/AudioplayerActivity.java b/src/de/danoeh/antennapod/activity/AudioplayerActivity.java index 6f98777af..469acc9fb 100644 --- a/src/de/danoeh/antennapod/activity/AudioplayerActivity.java +++ b/src/de/danoeh/antennapod/activity/AudioplayerActivity.java @@ -22,11 +22,11 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.ChapterListAdapter; import de.danoeh.antennapod.asynctask.ImageLoader; import de.danoeh.antennapod.feed.Chapter; -import de.danoeh.antennapod.feed.FeedMedia; import de.danoeh.antennapod.feed.SimpleChapter; import de.danoeh.antennapod.fragment.CoverFragment; import de.danoeh.antennapod.fragment.ItemDescriptionFragment; import de.danoeh.antennapod.service.PlaybackService; +import de.danoeh.antennapod.util.Playable; /** Activity for playing audio files. */ public class AudioplayerActivity extends MediaplayerActivity { @@ -101,7 +101,7 @@ public class AudioplayerActivity extends MediaplayerActivity { if (AppConfig.DEBUG) Log.d(TAG, "Switching contentView to position " + pos); if (currentlyShownPosition != pos) { - FeedMedia media = controller.getMedia(); + Playable media = controller.getMedia(); if (media != null) { FragmentTransaction ft = getSupportFragmentManager() .beginTransaction(); @@ -113,15 +113,14 @@ public class AudioplayerActivity extends MediaplayerActivity { case POS_COVER: if (coverFragment == null) { Log.i(TAG, "Using new coverfragment"); - coverFragment = CoverFragment.newInstance(media - .getItem()); + coverFragment = CoverFragment.newInstance(media); } currentlyShownFragment = coverFragment; break; case POS_DESCR: if (descriptionFragment == null) { descriptionFragment = ItemDescriptionFragment - .newInstance(media.getItem()); + .newInstance(media); } currentlyShownFragment = descriptionFragment; break; @@ -140,7 +139,7 @@ public class AudioplayerActivity extends MediaplayerActivity { }; chapterFragment.setListAdapter(new ChapterListAdapter( - AudioplayerActivity.this, 0, media.getItem() + AudioplayerActivity.this, 0, media .getChapters(), media)); } currentlyShownFragment = chapterFragment; @@ -167,7 +166,7 @@ public class AudioplayerActivity extends MediaplayerActivity { private void updateNavButtonDrawable() { TypedArray drawables = obtainStyledAttributes(new int[] { R.attr.navigation_shownotes, R.attr.navigation_chapters }); - final FeedMedia media = controller.getMedia(); + final Playable media = controller.getMedia(); if (butNavLeft != null && butNavRight != null && media != null) { switch (currentlyShownPosition) { case POS_COVER: @@ -182,8 +181,7 @@ public class AudioplayerActivity extends MediaplayerActivity { @Override public void run() { ImageLoader.getInstance().loadThumbnailBitmap( - media.getItem().getFeed().getImage(), - butNavLeft); + media.getImageFileUrl(), butNavLeft); } }); butNavRight.setImageDrawable(drawables.getDrawable(1)); @@ -195,7 +193,7 @@ public class AudioplayerActivity extends MediaplayerActivity { @Override public void run() { ImageLoader.getInstance().loadThumbnailBitmap( - media.getItem().getFeed().getImage(), + media.getImageFileUrl(), butNavLeft); } }); @@ -251,11 +249,11 @@ public class AudioplayerActivity extends MediaplayerActivity { @Override protected void loadMediaInfo() { super.loadMediaInfo(); - final FeedMedia media = controller.getMedia(); + final Playable media = controller.getMedia(); if (media != null) { - txtvTitle.setText(media.getItem().getTitle()); - txtvFeed.setText(media.getItem().getFeed().getTitle()); - if (media.getItem().getChapters() != null) { + txtvTitle.setText(media.getEpisodeTitle()); + txtvFeed.setText(media.getFeedTitle()); + if (media.getChapters() != null) { butNavRight.setVisibility(View.VISIBLE); } else { butNavRight.setVisibility(View.GONE); @@ -302,7 +300,7 @@ public class AudioplayerActivity extends MediaplayerActivity { } public interface AudioplayerContentFragment { - public void onDataSetChanged(FeedMedia media); + public void onDataSetChanged(Playable media); } } diff --git a/src/de/danoeh/antennapod/activity/MediaplayerActivity.java b/src/de/danoeh/antennapod/activity/MediaplayerActivity.java index 7344c2b9d..09d3c5a3f 100644 --- a/src/de/danoeh/antennapod/activity/MediaplayerActivity.java +++ b/src/de/danoeh/antennapod/activity/MediaplayerActivity.java @@ -5,6 +5,7 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.res.Configuration; import android.graphics.PixelFormat; +import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.widget.ImageButton; @@ -19,18 +20,17 @@ import com.actionbarsherlock.view.MenuItem; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator; +import de.danoeh.antennapod.asynctask.FlattrClickWorker; import de.danoeh.antennapod.dialog.TimeDialog; import de.danoeh.antennapod.feed.FeedManager; -import de.danoeh.antennapod.feed.FeedMedia; import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.service.PlaybackService; -import de.danoeh.antennapod.storage.DownloadRequestException; import de.danoeh.antennapod.util.Converter; import de.danoeh.antennapod.util.MediaPlayerError; +import de.danoeh.antennapod.util.Playable; import de.danoeh.antennapod.util.PlaybackController; +import de.danoeh.antennapod.util.ShareUtils; import de.danoeh.antennapod.util.StorageUtils; -import de.danoeh.antennapod.util.menuhandler.FeedItemMenuHandler; /** * Provides general features which are both needed for playing audio and video @@ -91,7 +91,7 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity @Override public void onSleepTimerUpdate() { - invalidateOptionsMenu(); + supportInvalidateOptionsMenu(); } @Override @@ -133,7 +133,7 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity } protected void onServiceQueried() { - invalidateOptionsMenu(); + supportInvalidateOptionsMenu(); } @Override @@ -219,14 +219,14 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity @Override public boolean onPrepareOptionsMenu(Menu menu) { - FeedMedia media = controller.getMedia(); + Playable media = controller.getMedia(); menu.findItem(R.id.support_item).setVisible( - media != null && media.getItem().getPaymentLink() != null); + media != null && media.getPaymentLink() != null); menu.findItem(R.id.share_link_item).setVisible( - media != null && media.getItem().getLink() != null); + media != null && media.getWebsiteLink() != null); menu.findItem(R.id.visit_website_item).setVisible( - media != null && media.getItem().getLink() != null); + media != null && media.getWebsiteLink() != null); boolean sleepTimerSet = controller.sleepTimerActive(); boolean sleepTimerNotSet = controller.sleepTimerNotActive(); @@ -237,6 +237,11 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity @Override public boolean onOptionsItemSelected(MenuItem item) { + Playable media = controller.getMedia(); + if (media == null) { + return false; + } + switch (item.getItemId()) { case android.R.id.home: Intent intent = new Intent(MediaplayerActivity.this, @@ -289,15 +294,20 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity break; } - default: - try { - return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), - controller.getMedia().getItem()); - } catch (DownloadRequestException e) { - e.printStackTrace(); - DownloadRequestErrorDialogCreator.newRequestErrorDialog(this, - e.getMessage()); - } + case R.id.visit_website_item: + Uri uri = Uri.parse(media.getWebsiteLink()); + startActivity(new Intent(Intent.ACTION_VIEW, uri)); + break; + case R.id.support_item: + new FlattrClickWorker(this, media.getPaymentLink()) + .executeAsync(); + break; + case R.id.share_link_item: + ShareUtils.shareLink(this, media.getWebsiteLink()); + break; + default: + return false; + } return true; } @@ -363,7 +373,7 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity protected void loadMediaInfo() { if (AppConfig.DEBUG) Log.d(TAG, "Loading media info"); - FeedMedia media = controller.getMedia(); + Playable media = controller.getMedia(); if (media != null) { txtvPosition.setText(Converter.getDurationStringLong((media .getPosition()))); diff --git a/src/de/danoeh/antennapod/activity/VideoplayerActivity.java b/src/de/danoeh/antennapod/activity/VideoplayerActivity.java index 7ab5df572..004ab85ab 100644 --- a/src/de/danoeh/antennapod/activity/VideoplayerActivity.java +++ b/src/de/danoeh/antennapod/activity/VideoplayerActivity.java @@ -19,10 +19,10 @@ import com.actionbarsherlock.view.Window; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.feed.FeedMedia; import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.service.PlaybackService; import de.danoeh.antennapod.service.PlayerStatus; +import de.danoeh.antennapod.util.Playable; /** Activity for playing audio files. */ public class VideoplayerActivity extends MediaplayerActivity implements @@ -57,11 +57,11 @@ public class VideoplayerActivity extends MediaplayerActivity implements @Override protected void loadMediaInfo() { super.loadMediaInfo(); - FeedMedia media = controller.getMedia(); + Playable media = controller.getMedia(); if (media != null) { - getSupportActionBar().setSubtitle(media.getItem().getTitle()); + getSupportActionBar().setSubtitle(media.getEpisodeTitle()); getSupportActionBar() - .setTitle(media.getItem().getFeed().getTitle()); + .setTitle(media.getFeedTitle()); } } diff --git a/src/de/danoeh/antennapod/adapter/ChapterListAdapter.java b/src/de/danoeh/antennapod/adapter/ChapterListAdapter.java index 145ca4230..e60b28189 100644 --- a/src/de/danoeh/antennapod/adapter/ChapterListAdapter.java +++ b/src/de/danoeh/antennapod/adapter/ChapterListAdapter.java @@ -20,20 +20,21 @@ import android.widget.ArrayAdapter; import android.widget.TextView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.feed.Chapter; -import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.util.ChapterUtils; import de.danoeh.antennapod.util.Converter; +import de.danoeh.antennapod.util.Playable; public class ChapterListAdapter extends ArrayAdapter<Chapter> { private static final String TAG = "ChapterListAdapter"; private List<Chapter> chapters; - private FeedMedia media; + private Playable media; private int defaultTextColor; public ChapterListAdapter(Context context, int textViewResourceId, - List<Chapter> objects, FeedMedia media) { + List<Chapter> objects, Playable media) { super(context, textViewResourceId, objects); this.chapters = objects; this.media = media; @@ -122,7 +123,7 @@ public class ChapterListAdapter extends ArrayAdapter<Chapter> { } }); - Chapter current = sc.getItem().getCurrentChapter(); + Chapter current = ChapterUtils.getCurrentChapter(media); if (current != null) { if (current == sc) { holder.title.setTextColor(convertView.getResources().getColor( diff --git a/src/de/danoeh/antennapod/feed/Chapter.java b/src/de/danoeh/antennapod/feed/Chapter.java index 10575e03d..ebf8ed44f 100644 --- a/src/de/danoeh/antennapod/feed/Chapter.java +++ b/src/de/danoeh/antennapod/feed/Chapter.java @@ -5,7 +5,6 @@ public abstract class Chapter extends FeedComponent { /** Defines starting point in milliseconds. */ protected long start; protected String title; - protected FeedItem item; protected String link; public Chapter() { @@ -20,7 +19,6 @@ public abstract class Chapter extends FeedComponent { super(); this.start = start; this.title = title; - this.item = item; this.link = link; } @@ -34,10 +32,6 @@ public abstract class Chapter extends FeedComponent { return title; } - public FeedItem getItem() { - return item; - } - public String getLink() { return link; } @@ -50,10 +44,6 @@ public abstract class Chapter extends FeedComponent { this.title = title; } - public void setItem(FeedItem item) { - this.item = item; - } - public void setLink(String link) { this.link = link; } diff --git a/src/de/danoeh/antennapod/feed/FeedItem.java b/src/de/danoeh/antennapod/feed/FeedItem.java index c9449fadd..2887b62af 100644 --- a/src/de/danoeh/antennapod/feed/FeedItem.java +++ b/src/de/danoeh/antennapod/feed/FeedItem.java @@ -92,27 +92,6 @@ public class FeedItem extends FeedComponent { contentEncoded = null; } - /** Get the chapter that fits the position. */ - public Chapter getCurrentChapter(int position) { - Chapter current = null; - if (chapters != null) { - current = chapters.get(0); - for (Chapter sc : chapters) { - if (sc.getStart() > position) { - break; - } else { - current = sc; - } - } - } - return current; - } - - /** Calls getCurrentChapter with current position. */ - public Chapter getCurrentChapter() { - return getCurrentChapter(media.getPosition()); - } - /** * Returns the value that uniquely identifies this FeedItem. If the * itemIdentifier attribute is not null, it will be returned. Else it will diff --git a/src/de/danoeh/antennapod/feed/FeedManager.java b/src/de/danoeh/antennapod/feed/FeedManager.java index 6a18113e3..16f5937ca 100644 --- a/src/de/danoeh/antennapod/feed/FeedManager.java +++ b/src/de/danoeh/antennapod/feed/FeedManager.java @@ -136,10 +136,7 @@ public class FeedManager { } // Start playback Service Intent launchIntent = new Intent(context, PlaybackService.class); - launchIntent - .putExtra(PlaybackService.EXTRA_MEDIA_ID, media.getId()); - launchIntent.putExtra(PlaybackService.EXTRA_FEED_ID, media - .getItem().getFeed().getId()); + launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media); launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED, startWhenPrepared); launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, diff --git a/src/de/danoeh/antennapod/feed/FeedMedia.java b/src/de/danoeh/antennapod/feed/FeedMedia.java index a96649a62..5d139f01a 100644 --- a/src/de/danoeh/antennapod/feed/FeedMedia.java +++ b/src/de/danoeh/antennapod/feed/FeedMedia.java @@ -1,10 +1,23 @@ package de.danoeh.antennapod.feed; import java.util.Date; +import java.util.List; -public class FeedMedia extends FeedFile { +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.os.Parcel; +import android.os.Parcelable; +import de.danoeh.antennapod.PodcastApp; +import de.danoeh.antennapod.util.ChapterUtils; +import de.danoeh.antennapod.util.Playable; + +public class FeedMedia extends FeedFile implements Playable { public static final int FEEDFILETYPE_FEEDMEDIA = 2; + public static final int PLAYABLE_TYPE_FEEDMEDIA = 1; + + public static final String PREF_MEDIA_ID = "FeedMedia.PrefMediaId"; + public static final String PREF_FEED_ID = "FeedMedia.PrefFeedId"; private int duration; private int position; // Current position in file @@ -146,7 +159,7 @@ public class FeedMedia extends FeedFile { public boolean isInProgress() { return (this.position > 0); } - + public FeedImage getImage() { if (item != null && item.getFeed() != null) { return item.getFeed().getImage(); @@ -154,4 +167,159 @@ public class FeedMedia extends FeedFile { return null; } + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(item.getFeed().getId()); + dest.writeLong(item.getId()); + } + + @Override + public void writeToPreferences(Editor prefEditor) { + prefEditor.putLong(PREF_FEED_ID, item.getFeed().getId()); + prefEditor.putLong(PREF_MEDIA_ID, id); + } + + @Override + public void loadMetadata() throws PlayableException { + if (getChapters() == null) { + ChapterUtils.loadChapters(this); + } + } + + @Override + public String getEpisodeTitle() { + if (getItem().getTitle() != null) { + return getItem().getTitle(); + } else { + return getItem().getIdentifyingValue(); + } + } + + @Override + public List<Chapter> getChapters() { + return getItem().getChapters(); + } + + @Override + public String getWebsiteLink() { + return getItem().getLink(); + } + + @Override + public String getFeedTitle() { + return getItem().getFeed().getTitle(); + } + + @Override + public String getImageFileUrl() { + if (getItem().getFeed().getImage() != null) { + return getItem().getFeed().getImage().getFile_url(); + } else { + return null; + } + } + + @Override + public Object getIdentifier() { + return id; + } + + @Override + public String getFileUrl() { + return file_url; + } + + @Override + public String getStreamUrl() { + return download_url; + } + + @Override + public boolean localFileAvailable() { + return isDownloaded() && file_url != null; + } + + @Override + public boolean streamAvailable() { + return download_url != null; + } + + @Override + public void saveCurrentPosition(SharedPreferences pref, int newPosition) { + position = newPosition; + FeedManager.getInstance().setFeedMedia(PodcastApp.getInstance(), this); + } + + @Override + public void onPlaybackStart() { + if (getItem().isRead() == false) { + FeedManager.getInstance().markItemRead(PodcastApp.getInstance(), + getItem(), true, false); + } + } + + @Override + public void onPlaybackCompleted() { + + } + + @Override + public int getPlayableType() { + return PLAYABLE_TYPE_FEEDMEDIA; + } + + @Override + public void setChapters(List<Chapter> chapters) { + getItem().setChapters(chapters); + } + + @Override + public String getPaymentLink() { + return getItem().getPaymentLink(); + } + + @Override + public void loadShownotes(final ShownoteLoaderCallback callback) { + if (item.getDescription() == null || item.getContentEncoded() == null) { + FeedManager.getInstance().loadExtraInformationOfItem( + PodcastApp.getInstance(), item, + new FeedManager.TaskCallback<String[]>() { + @Override + public void onCompletion(String[] result) { + if (result[1] != null) { + callback.onShownotesLoaded(result[1]); + } else { + callback.onShownotesLoaded(result[0]); + + } + + } + }); + } else { + callback.onShownotesLoaded(item.getContentEncoded()); + } + } + + public static final Parcelable.Creator<FeedMedia> CREATOR = new Parcelable.Creator<FeedMedia>() { + public FeedMedia createFromParcel(Parcel in) { + long feedId = in.readLong(); + long itemId = in.readLong(); + FeedItem item = FeedManager.getInstance().getFeedItem(itemId, + feedId); + if (item != null) { + return item.getMedia(); + } else { + return null; + } + } + + public FeedMedia[] newArray(int size) { + return new FeedMedia[size]; + } + }; } diff --git a/src/de/danoeh/antennapod/fragment/CoverFragment.java b/src/de/danoeh/antennapod/fragment/CoverFragment.java index 0ef19f818..29a160960 100644 --- a/src/de/danoeh/antennapod/fragment/CoverFragment.java +++ b/src/de/danoeh/antennapod/fragment/CoverFragment.java @@ -13,30 +13,25 @@ import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.AudioplayerActivity.AudioplayerContentFragment; import de.danoeh.antennapod.asynctask.ImageLoader; -import de.danoeh.antennapod.feed.Feed; -import de.danoeh.antennapod.feed.FeedItem; -import de.danoeh.antennapod.feed.FeedManager; -import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.util.Playable; /** Displays the cover and the title of a FeedItem. */ public class CoverFragment extends SherlockFragment implements AudioplayerContentFragment { private static final String TAG = "CoverFragment"; - private static final String ARG_FEED_ID = "arg.feedId"; - private static final String ARG_FEEDITEM_ID = "arg.feedItem"; + private static final String ARG_PLAYABLE = "arg.playable"; - private FeedMedia media; + private Playable media; private ImageView imgvCover; private boolean viewCreated = false; - public static CoverFragment newInstance(FeedItem item) { + public static CoverFragment newInstance(Playable item) { CoverFragment f = new CoverFragment(); if (item != null) { Bundle args = new Bundle(); - args.putLong(ARG_FEED_ID, item.getFeed().getId()); - args.putLong(ARG_FEEDITEM_ID, item.getId()); + args.putParcelable(ARG_PLAYABLE, item); f.setArguments(args); } return f; @@ -46,21 +41,11 @@ public class CoverFragment extends SherlockFragment implements public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); - FeedManager manager = FeedManager.getInstance(); - FeedItem item = null; Bundle args = getArguments(); if (args != null) { - long feedId = args.getLong(ARG_FEED_ID, -1); - long itemId = args.getLong(ARG_FEEDITEM_ID, -1); - if (feedId != -1 && itemId != -1) { - Feed feed = manager.getFeed(feedId); - item = manager.getFeedItem(itemId, feed); - if (item != null) { - media = item.getMedia(); - } - } else { - Log.e(TAG, TAG + " was called with invalid arguments"); - } + media = args.getParcelable(ARG_PLAYABLE); + } else { + Log.e(TAG, TAG + " was called with invalid arguments"); } } @@ -80,7 +65,7 @@ public class CoverFragment extends SherlockFragment implements @Override public void run() { ImageLoader.getInstance().loadCoverBitmap( - media.getItem().getFeed().getImage(), imgvCover); + media.getImageFileUrl(), imgvCover); } }); } else { @@ -103,7 +88,7 @@ public class CoverFragment extends SherlockFragment implements } @Override - public void onDataSetChanged(FeedMedia media) { + public void onDataSetChanged(Playable media) { this.media = media; if (viewCreated) { loadMediaInfo(); diff --git a/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java b/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java index 0eefd3103..bc4f7bdec 100644 --- a/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java +++ b/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java @@ -15,9 +15,9 @@ import com.actionbarsherlock.app.SherlockFragment; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.R; import de.danoeh.antennapod.asynctask.ImageLoader; -import de.danoeh.antennapod.feed.FeedMedia; import de.danoeh.antennapod.service.PlaybackService; import de.danoeh.antennapod.util.Converter; +import de.danoeh.antennapod.util.Playable; import de.danoeh.antennapod.util.PlaybackController; /** @@ -193,11 +193,11 @@ public class ExternalPlayerFragment extends SherlockFragment { if (AppConfig.DEBUG) Log.d(TAG, "Loading media info"); if (controller.serviceAvailable()) { - FeedMedia media = controller.getMedia(); + Playable media = controller.getMedia(); if (media != null) { - txtvTitle.setText(media.getItem().getTitle()); + txtvTitle.setText(media.getEpisodeTitle()); ImageLoader.getInstance().loadThumbnailBitmap( - media.getItem().getFeed().getImage(), + media.getImageFileUrl(), imgvCover, (int) getActivity().getResources().getDimension( R.dimen.external_player_height)); diff --git a/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java b/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java index 5f1ce0092..5b1edd777 100644 --- a/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java +++ b/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java @@ -28,36 +28,48 @@ import android.widget.Toast; import com.actionbarsherlock.app.SherlockFragment; import de.danoeh.antennapod.AppConfig; +import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.feed.Feed; import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.feed.FeedManager; import de.danoeh.antennapod.preferences.UserPreferences; +import de.danoeh.antennapod.util.Playable; import de.danoeh.antennapod.util.ShareUtils; -/** Displays the description of a FeedItem in a Webview. */ +/** Displays the description of a Playable object in a Webview. */ public class ItemDescriptionFragment extends SherlockFragment { private static final String TAG = "ItemDescriptionFragment"; + private static final String ARG_PLAYABLE = "arg.playable"; + private static final String ARG_FEED_ID = "arg.feedId"; - private static final String ARG_FEEDITEM_ID = "arg.feedItemId"; + private static final String ARG_FEED_ITEM_ID = "arg.feeditemId"; private WebView webvDescription; + private Playable media; + private FeedItem item; private AsyncTask<Void, Void, Void> webViewLoader; - private String descriptionRef; - private String contentEncodedRef; + private String shownotes; /** URL that was selected via long-press. */ private String selectedURL; + public static ItemDescriptionFragment newInstance(Playable media) { + ItemDescriptionFragment f = new ItemDescriptionFragment(); + Bundle args = new Bundle(); + args.putParcelable(ARG_PLAYABLE, media); + f.setArguments(args); + return f; + } + public static ItemDescriptionFragment newInstance(FeedItem item) { ItemDescriptionFragment f = new ItemDescriptionFragment(); Bundle args = new Bundle(); args.putLong(ARG_FEED_ID, item.getFeed().getId()); - args.putLong(ARG_FEEDITEM_ID, item.getId()); + args.putLong(ARG_FEED_ITEM_ID, item.getId()); f.setArguments(args); return f; } @@ -75,7 +87,8 @@ public class ItemDescriptionFragment extends SherlockFragment { && Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { webvDescription.setLayerType(View.LAYER_TYPE_SOFTWARE, null); } - webvDescription.setBackgroundColor(getResources().getColor(R.color.black)); + webvDescription.setBackgroundColor(getResources().getColor( + R.color.black)); } webvDescription.getSettings().setUseWideViewPort(false); webvDescription.getSettings().setLayoutAlgorithm( @@ -124,50 +137,58 @@ public class ItemDescriptionFragment extends SherlockFragment { super.onCreate(savedInstanceState); if (AppConfig.DEBUG) Log.d(TAG, "Creating fragment"); - FeedManager manager = FeedManager.getInstance(); Bundle args = getArguments(); - long feedId = args.getLong(ARG_FEED_ID, -1); - long itemId = args.getLong(ARG_FEEDITEM_ID, -1); - if (feedId != -1 && itemId != -1) { - Feed feed = manager.getFeed(feedId); - item = manager.getFeedItem(itemId, feed); - - } else { - Log.e(TAG, TAG + " was called with invalid arguments"); + if (args.containsKey(ARG_PLAYABLE)) { + media = args.getParcelable(ARG_PLAYABLE); + } else if (args.containsKey(ARG_FEED_ID) + && args.containsKey(ARG_FEED_ITEM_ID)) { + long feedId = args.getLong(ARG_FEED_ID); + long itemId = args.getLong(ARG_FEED_ITEM_ID); + FeedItem f = FeedManager.getInstance().getFeedItem(itemId, feedId); + if (f != null) { + item = f; + } } } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - if (item != null) { + if (media != null) { + media.loadShownotes(new Playable.ShownoteLoaderCallback() { + + @Override + public void onShownotesLoaded(String shownotes) { + ItemDescriptionFragment.this.shownotes = shownotes; + if (ItemDescriptionFragment.this.shownotes != null) { + startLoader(); + } + } + }); + } else if (item != null) { if (item.getDescription() == null || item.getContentEncoded() == null) { - Log.i(TAG, "Loading data"); FeedManager.getInstance().loadExtraInformationOfItem( - getActivity(), item, + PodcastApp.getInstance(), item, new FeedManager.TaskCallback<String[]>() { @Override public void onCompletion(String[] result) { - if (result == null || result.length != 2) { - Log.e(TAG, "No description found"); + if (result[1] != null) { + shownotes = result[1]; } else { - descriptionRef = result[0]; - contentEncodedRef = result[1]; + shownotes = result[0]; + } + if (shownotes != null) { + startLoader(); } - startLoader(); } }); } else { - contentEncodedRef = item.getContentEncoded(); - descriptionRef = item.getDescription(); - if (AppConfig.DEBUG) - Log.d(TAG, "Using cached data"); - startLoader(); + shownotes = item.getContentEncoded(); } } else { - Log.e(TAG, "Error in onViewCreated: Item was null"); + Log.e(TAG, "Error in onViewCreated: Item and media were null"); } } @@ -195,8 +216,11 @@ public class ItemDescriptionFragment extends SherlockFragment { * */ private String applyWebviewStyle(String textColor, String data) { final String WEBVIEW_STYLE = "<html><head><style type=\"text/css\"> * { color: %s; font-family: Helvetica; line-height: 1.5em; font-size: 11pt; } a { font-style: normal; text-decoration: none; font-weight: normal; color: #00A8DF; } img { display: block; margin: 10 auto; max-width: %s; height: auto; } body { margin: %dpx %dpx %dpx %dpx; }</style></head><body>%s</body></html>"; - final int pageMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics()); - return String.format(WEBVIEW_STYLE, textColor, "100%", pageMargin, pageMargin, pageMargin, pageMargin, data); + final int pageMargin = (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, 8, getResources() + .getDisplayMetrics()); + return String.format(WEBVIEW_STYLE, textColor, "100%", pageMargin, + pageMargin, pageMargin, pageMargin, data); } private View.OnLongClickListener webViewLongClickListener = new View.OnLongClickListener() { @@ -245,7 +269,8 @@ public class ItemDescriptionFragment extends SherlockFragment { .getSystemService(Context.CLIPBOARD_SERVICE); cm.setText(selectedURL); } - Toast t = Toast.makeText(getActivity(), R.string.copied_url_msg, Toast.LENGTH_SHORT); + Toast t = Toast.makeText(getActivity(), + R.string.copied_url_msg, Toast.LENGTH_SHORT); t.show(); break; default: @@ -317,11 +342,7 @@ public class ItemDescriptionFragment extends SherlockFragment { if (AppConfig.DEBUG) Log.d(TAG, "Loading Webview"); data = ""; - if (contentEncodedRef == null && descriptionRef != null) { - data = descriptionRef; - } else { - data = StringEscapeUtils.unescapeHtml4(contentEncodedRef); - } + data = StringEscapeUtils.unescapeHtml4(shownotes); Activity activity = getActivity(); if (activity != null) { TypedArray res = getActivity() diff --git a/src/de/danoeh/antennapod/preferences/PlaybackPreferences.java b/src/de/danoeh/antennapod/preferences/PlaybackPreferences.java index 22167c62d..3187c0622 100644 --- a/src/de/danoeh/antennapod/preferences/PlaybackPreferences.java +++ b/src/de/danoeh/antennapod/preferences/PlaybackPreferences.java @@ -17,14 +17,14 @@ public class PlaybackPreferences implements SharedPreferences.OnSharedPreferenceChangeListener { private static final String TAG = "PlaybackPreferences"; - /** Contains the id of the media that was played last. */ + /** Contains the type of the media that was played last. */ public static final String PREF_LAST_PLAYED_ID = "de.danoeh.antennapod.preferences.lastPlayedId"; /** Contains the feed id of the last played item. */ public static final String PREF_LAST_PLAYED_FEED_ID = "de.danoeh.antennapod.preferences.lastPlayedFeedId"; /** - * ID of the media object that is currently being played. This preference is + * Type of the media object that is currently being played. This preference is * set to NO_MEDIA_PLAYING after playback has been completed and is set as * soon as the 'play' button is pressed. */ diff --git a/src/de/danoeh/antennapod/service/PlaybackService.java b/src/de/danoeh/antennapod/service/PlaybackService.java index 7eaff0578..7068a35d2 100644 --- a/src/de/danoeh/antennapod/service/PlaybackService.java +++ b/src/de/danoeh/antennapod/service/PlaybackService.java @@ -28,6 +28,7 @@ import android.media.MediaMetadataRetriever; import android.media.MediaPlayer; import android.media.RemoteControlClient; import android.media.RemoteControlClient.MetadataEditor; +import android.os.AsyncTask; import android.os.Binder; import android.os.IBinder; import android.preference.PreferenceManager; @@ -41,6 +42,7 @@ import de.danoeh.antennapod.activity.AudioplayerActivity; import de.danoeh.antennapod.activity.VideoplayerActivity; import de.danoeh.antennapod.feed.Chapter; import de.danoeh.antennapod.feed.Feed; +import de.danoeh.antennapod.feed.FeedComponent; import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.feed.FeedManager; import de.danoeh.antennapod.feed.FeedMedia; @@ -50,17 +52,17 @@ import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.receiver.MediaButtonReceiver; import de.danoeh.antennapod.receiver.PlayerWidget; import de.danoeh.antennapod.util.BitmapDecoder; -import de.danoeh.antennapod.util.ChapterUtils; +import de.danoeh.antennapod.util.Playable; +import de.danoeh.antennapod.util.Playable.PlayableException; +import de.danoeh.antennapod.util.flattr.FlattrUtils; /** Controls the MediaPlayer that plays a FeedMedia-file */ public class PlaybackService extends Service { /** Logging tag */ private static final String TAG = "PlaybackService"; - /** Contains the id of the FeedMedia object. */ - public static final String EXTRA_MEDIA_ID = "extra.de.danoeh.antennapod.service.mediaId"; - /** Contains the id of the Feed object of the FeedMedia. */ - public static final String EXTRA_FEED_ID = "extra.de.danoeh.antennapod.service.feedId"; + /** Parcelable of type Playable. */ + public static final String EXTRA_PLAYABLE = "PlaybackService.PlayableExtra"; /** True if media should be streamed. */ public static final String EXTRA_SHOULD_STREAM = "extra.de.danoeh.antennapod.service.shouldStream"; /** @@ -113,8 +115,8 @@ public class PlaybackService extends Service { private MediaPlayer player; private RemoteControlClient remoteControlClient; - private FeedMedia media; - private Feed feed; + private Playable media; + /** True if media should be streamed (Extracted from Intent Extra) . */ private boolean shouldStream; @@ -133,8 +135,6 @@ public class PlaybackService extends Service { private SleepTimer sleepTimer; private Future sleepTimerFuture; - private Thread chapterLoader; - private static final int SCHED_EX_POOL_SIZE = 3; private ScheduledThreadPoolExecutor schedExecutor; @@ -186,7 +186,7 @@ public class PlaybackService extends Service { * depends on the FeedMedia that is provided as an argument. */ public static Intent getPlayerActivityIntent(Context context, - FeedMedia media) { + Playable media) { MediaType mt = media.getMediaType(); if (mt == MediaType.VIDEO) { return new Intent(context, VideoplayerActivity.class); @@ -221,7 +221,6 @@ public class PlaybackService extends Service { PlaybackPreferences.PREF_AUTO_DELETE_MEDIA_PLAYBACK_COMPLETED, false); } - editor.putLong(PlaybackPreferences.PREF_LAST_PLAYED_ID, mediaId); editor.commit(); } @@ -366,26 +365,24 @@ public class PlaybackService extends Service { handleKeycode(keycode); } else { - long mediaId = intent.getLongExtra(EXTRA_MEDIA_ID, -1); - long feedId = intent.getLongExtra(EXTRA_FEED_ID, -1); + Playable playable = intent.getParcelableExtra(EXTRA_PLAYABLE); boolean playbackType = intent.getBooleanExtra(EXTRA_SHOULD_STREAM, true); - if (mediaId == -1 || feedId == -1) { - Log.e(TAG, - "Media ID or Feed ID wasn't provided to the Service."); - if (media == null || feed == null) { + if (playable == null) { + Log.e(TAG, "Playable extra wasn't sent to the service"); + if (media == null) { stopSelf(); } // Intent values appear to be valid // check if already playing and playbackType is the same - } else if (media == null || mediaId != media.getId() + } else if (media == null || playable != media || playbackType != shouldStream) { pause(true, false); player.reset(); sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0); - if (media == null || mediaId != media.getId()) { - feed = manager.getFeed(feedId); - media = manager.getFeedMedia(mediaId, feed); + if (media == null + || playable.getIdentifier() != media.getIdentifier()) { + media = playable; } if (media != null) { @@ -460,23 +457,44 @@ public class PlaybackService extends Service { if (status == PlayerStatus.STOPPED || status == PlayerStatus.AWAITING_VIDEO_SURFACE) { try { - if (shouldStream) { - player.setDataSource(media.getDownload_url()); - setStatus(PlayerStatus.PREPARING); - player.prepareAsync(); - } else { - player.setDataSource(media.getFile_url()); - setStatus(PlayerStatus.PREPARING); - player.prepare(); - } + InitTask initTask = new InitTask() { + + @Override + protected void onPostExecute(Playable result) { + if (result != null) { + try { + if (shouldStream) { + player.setDataSource(media.getStreamUrl()); + setStatus(PlayerStatus.PREPARING); + player.prepareAsync(); + } else { + player.setDataSource(media.getFileUrl()); + setStatus(PlayerStatus.PREPARING); + player.prepareAsync(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } else { + setStatus(PlayerStatus.ERROR); + sendBroadcast(new Intent( + ACTION_SHUTDOWN_PLAYBACK_SERVICE)); + } + } + + @Override + protected void onPreExecute() { + setStatus(PlayerStatus.INITIALIZING); + } + + }; + initTask.executeAsync(media); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalStateException e) { e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); } } @@ -508,18 +526,43 @@ public class PlaybackService extends Service { if (mediaType == MediaType.AUDIO) { if (AppConfig.DEBUG) Log.d(TAG, "Mime type is audio"); - playingVideo = false; - if (shouldStream) { - player.setDataSource(media.getDownload_url()); - } else if (media.getFile_url() != null) { - player.setDataSource(media.getFile_url()); - } - if (prepareImmediately) { - setStatus(PlayerStatus.PREPARING); - player.prepareAsync(); - } else { - setStatus(PlayerStatus.INITIALIZED); - } + + InitTask initTask = new InitTask() { + + @Override + protected void onPostExecute(Playable result) { + if (result != null) { + playingVideo = false; + try { + if (shouldStream) { + player.setDataSource(media.getStreamUrl()); + } else if (media.localFileAvailable()) { + player.setDataSource(media.getFileUrl()); + } + } catch (IOException e) { + e.printStackTrace(); + } + if (prepareImmediately) { + setStatus(PlayerStatus.PREPARING); + player.prepareAsync(); + } else { + setStatus(PlayerStatus.INITIALIZED); + } + } else { + Log.e(TAG, "InitTask could not load metadata"); + setStatus(PlayerStatus.ERROR); + sendBroadcast(new Intent( + ACTION_SHUTDOWN_PLAYBACK_SERVICE)); + } + } + + @Override + protected void onPreExecute() { + setStatus(PlayerStatus.INITIALIZING); + } + + }; + initTask.executeAsync(media); } else if (mediaType == MediaType.VIDEO) { if (AppConfig.DEBUG) Log.d(TAG, "Mime type is video"); @@ -534,8 +577,6 @@ public class PlaybackService extends Service { e.printStackTrace(); } catch (IllegalStateException e) { e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); } } @@ -574,39 +615,6 @@ public class PlaybackService extends Service { if (startWhenPrepared) { play(); } - if (shouldStream && media.getItem().getChapters() == null) { - // load chapters if available - if (chapterLoader != null) { - chapterLoader.interrupt(); - } - chapterLoader = new Thread() { - - @Override - public void run() { - if (AppConfig.DEBUG) - Log.d(TAG, "Starting chapterLoader thread"); - ChapterUtils - .readID3ChaptersFromFeedMediaDownloadUrl(media - .getItem()); - if (media.getItem().getChapters() == null) { - ChapterUtils - .readOggChaptersFromMediaDownloadUrl(media - .getItem()); - } - if (media.getItem().getChapters() != null - && !interrupted()) { - sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, - 0); - manager.setFeedItem(PlaybackService.this, - media.getItem()); - } - if (AppConfig.DEBUG) - Log.d(TAG, "ChapterLoaderThread has finished"); - } - - }; - chapterLoader.start(); - } } }; @@ -663,31 +671,35 @@ public class PlaybackService extends Service { audioManager.abandonAudioFocus(audioFocusChangeListener); SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(getApplicationContext()); + SharedPreferences.Editor editor = prefs.edit(); + // Save state cancelPositionSaver(); - media.setPlaybackCompletionDate(new Date()); - manager.markItemRead(PlaybackService.this, media.getItem(), true, - true); - FeedItem nextItem = manager - .getQueueSuccessorOfItem(media.getItem()); - boolean isInQueue = manager.isInQueue(media.getItem()); - if (isInQueue) { - manager.removeQueueItem(PlaybackService.this, media.getItem()); - } - manager.addItemToPlaybackHistory(PlaybackService.this, - media.getItem()); - manager.setFeedMedia(PlaybackService.this, media); - - long autoDeleteMediaId = media.getId(); - if (shouldStream) { - autoDeleteMediaId = -1; + boolean isInQueue = false; + FeedItem nextItem = null; + + if (media instanceof FeedMedia) { + FeedItem item = ((FeedMedia) media).getItem(); + ((FeedMedia) media).setPlaybackCompletionDate(new Date()); + manager.markItemRead(PlaybackService.this, item, true, true); + nextItem = manager.getQueueSuccessorOfItem(item); + isInQueue = media instanceof FeedMedia + && manager.isInQueue(((FeedMedia) media).getItem()); + if (isInQueue) { + manager.removeQueueItem(PlaybackService.this, item); + } + manager.addItemToPlaybackHistory(PlaybackService.this, item); + manager.setFeedMedia(PlaybackService.this, (FeedMedia) media); + long autoDeleteMediaId = ((FeedComponent) media).getId(); + if (shouldStream) { + autoDeleteMediaId = -1; + } + editor.putLong(PlaybackPreferences.PREF_AUTODELETE_MEDIA_ID, + autoDeleteMediaId); } - SharedPreferences.Editor editor = prefs.edit(); editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong(PlaybackPreferences.PREF_AUTODELETE_MEDIA_ID, - autoDeleteMediaId); editor.putBoolean( PlaybackPreferences.PREF_AUTO_DELETE_MEDIA_PLAYBACK_COMPLETED, true); @@ -700,8 +712,7 @@ public class PlaybackService extends Service { if (AppConfig.DEBUG) Log.d(TAG, "Loading next item in queue"); media = nextItem.getMedia(); - feed = nextItem.getFeed(); - shouldStream = !media.isDownloaded(); + shouldStream = !media.localFileAvailable(); prepareImmediately = startWhenPrepared = true; } else { if (AppConfig.DEBUG) @@ -832,12 +843,21 @@ public class PlaybackService extends Service { SharedPreferences.Editor editor = PreferenceManager .getDefaultSharedPreferences(getApplicationContext()) .edit(); - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, media.getId()); - editor.putLong(PlaybackPreferences.PREF_LAST_PLAYED_FEED_ID, feed.getId()); - editor.putBoolean(PlaybackPreferences.PREF_LAST_IS_STREAM, shouldStream); - editor.putBoolean(PlaybackPreferences.PREF_LAST_IS_VIDEO, playingVideo); + editor.putLong( + PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, + media.getPlayableType()); + editor.putBoolean(PlaybackPreferences.PREF_LAST_IS_STREAM, + shouldStream); + editor.putBoolean(PlaybackPreferences.PREF_LAST_IS_VIDEO, + playingVideo); + editor.putLong(PlaybackPreferences.PREF_LAST_PLAYED_ID, + media.getPlayableType()); + media.writeToPreferences(editor); + editor.commit(); - setLastPlayedMediaId(media.getId()); + if (media instanceof FeedMedia) { + setLastPlayedMediaId(((FeedMedia) media).getId()); + } player.start(); if (status != PlayerStatus.PAUSED) { player.seekTo((int) media.getPosition()); @@ -853,9 +873,7 @@ public class PlaybackService extends Service { } audioManager .registerMediaButtonEventReceiver(mediaButtonReceiver); - if (media.getItem().isRead() == false) { - manager.markItemRead(this, media.getItem(), true, false); - } + media.onPlaybackStart(); } else { if (AppConfig.DEBUG) Log.d(TAG, "Failed to request Audiofocus"); @@ -893,12 +911,11 @@ public class PlaybackService extends Service { Bitmap icon = null; if (android.os.Build.VERSION.SDK_INT >= 11) { - if (media != null && media.getImage() != null - && media.getImage().getFile_url() != null) { + if (media != null && media.getImageFileUrl() != null) { int iconSize = getResources().getDimensionPixelSize( android.R.dimen.notification_large_icon_width); - icon = BitmapDecoder.decodeBitmap(iconSize, media.getImage() - .getFile_url()); + icon = BitmapDecoder.decodeBitmap(iconSize, + media.getImageFileUrl()); } } if (icon == null) { @@ -906,8 +923,8 @@ public class PlaybackService extends Service { R.drawable.ic_stat_antenna); } - String contentText = media.getItem().getFeed().getTitle(); - String contentTitle = media.getItem().getTitle(); + String contentText = media.getFeedTitle(); + String contentTitle = media.getEpisodeTitle(); Notification notification = null; if (android.os.Build.VERSION.SDK_INT >= 16) { Intent pauseButtonIntent = new Intent(this, PlaybackService.class); @@ -983,8 +1000,9 @@ public class PlaybackService extends Service { if (position != INVALID_TIME) { if (AppConfig.DEBUG) Log.d(TAG, "Saving current position to " + position); - media.setPosition(position); - manager.setFeedMedia(this, media); + media.saveCurrentPosition(PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()), + position); } } @@ -1078,10 +1096,10 @@ public class PlaybackService extends Service { MetadataEditor editor = remoteControlClient .editMetadata(false); editor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, - media.getItem().getTitle()); + media.getEpisodeTitle()); editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, - media.getItem().getFeed().getTitle()); + media.getFeedTitle()); editor.apply(); } @@ -1144,12 +1162,8 @@ public class PlaybackService extends Service { public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(ACTION_SHUTDOWN_PLAYBACK_SERVICE)) { schedExecutor.shutdownNow(); - if (chapterLoader != null) { - chapterLoader.interrupt(); - } stop(); media = null; - feed = null; } } @@ -1251,7 +1265,7 @@ public class PlaybackService extends Service { return status; } - public FeedMedia getMedia() { + public Playable getMedia() { return media; } @@ -1321,4 +1335,37 @@ public class PlaybackService extends Service { editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, id); editor.commit(); } + + private static class InitTask extends AsyncTask<Playable, Void, Playable> { + private Playable playable; + public PlayableException exception; + + @Override + protected Playable doInBackground(Playable... params) { + if (params[0] == null) { + throw new IllegalArgumentException("Playable must not be null"); + } + playable = params[0]; + + try { + playable.loadMetadata(); + } catch (PlayableException e) { + e.printStackTrace(); + exception = e; + return null; + } + return playable; + } + + @SuppressLint("NewApi") + public void executeAsync(Playable playable) { + FlattrUtils.hasToken(); + if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { + executeOnExecutor(THREAD_POOL_EXECUTOR, playable); + } else { + execute(playable); + } + } + + } } diff --git a/src/de/danoeh/antennapod/service/PlayerStatus.java b/src/de/danoeh/antennapod/service/PlayerStatus.java index 17e42f847..fbf5b1505 100644 --- a/src/de/danoeh/antennapod/service/PlayerStatus.java +++ b/src/de/danoeh/antennapod/service/PlayerStatus.java @@ -9,5 +9,6 @@ public enum PlayerStatus { PREPARED, SEEKING, AWAITING_VIDEO_SURFACE, // player has been initialized and the media type to be played is a video. + INITIALIZING, // playback service is loading the Playable's metadata INITIALIZED // playback service was started, data source of media player was set. } diff --git a/src/de/danoeh/antennapod/service/PlayerWidgetService.java b/src/de/danoeh/antennapod/service/PlayerWidgetService.java index dd4598e9c..1a33806ba 100644 --- a/src/de/danoeh/antennapod/service/PlayerWidgetService.java +++ b/src/de/danoeh/antennapod/service/PlayerWidgetService.java @@ -13,10 +13,10 @@ import android.view.View; import android.widget.RemoteViews; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.feed.FeedMedia; import de.danoeh.antennapod.receiver.MediaButtonReceiver; import de.danoeh.antennapod.receiver.PlayerWidget; import de.danoeh.antennapod.util.Converter; +import de.danoeh.antennapod.util.Playable; /** Updates the state of the player widget */ public class PlayerWidgetService extends Service { @@ -84,10 +84,10 @@ public class PlayerWidgetService extends Service { views.setOnClickPendingIntent(R.id.layout_left, startMediaplayer); if (playbackService != null) { - FeedMedia media = playbackService.getMedia(); + Playable media = playbackService.getMedia(); PlayerStatus status = playbackService.getStatus(); - views.setTextViewText(R.id.txtvTitle, media.getItem().getTitle()); + views.setTextViewText(R.id.txtvTitle, media.getEpisodeTitle()); if (status == PlayerStatus.PLAYING) { String progressString = getProgressString(playbackService); diff --git a/src/de/danoeh/antennapod/service/download/DownloadService.java b/src/de/danoeh/antennapod/service/download/DownloadService.java index 712c15a77..a48e24748 100644 --- a/src/de/danoeh/antennapod/service/download/DownloadService.java +++ b/src/de/danoeh/antennapod/service/download/DownloadService.java @@ -833,11 +833,9 @@ public class DownloadService extends Service { } if (media.getItem().getChapters() == null) { - ChapterUtils.readID3ChaptersFromFeedMediaFileUrl(media - .getItem()); + ChapterUtils.readID3ChaptersFromPlayableFileUrl(media); if (media.getItem().getChapters() == null) { - ChapterUtils.readOggChaptersFromMediaFileUrl(media - .getItem()); + ChapterUtils.readOggChaptersFromPlayableFileUrl(media); } if (media.getItem().getChapters() != null) { chaptersRead = true; 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; } |