summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/de/danoeh/antennapod/activity/AudioplayerActivity.java28
-rw-r--r--src/de/danoeh/antennapod/activity/MediaplayerActivity.java50
-rw-r--r--src/de/danoeh/antennapod/activity/VideoplayerActivity.java8
-rw-r--r--src/de/danoeh/antennapod/adapter/ChapterListAdapter.java9
-rw-r--r--src/de/danoeh/antennapod/feed/Chapter.java10
-rw-r--r--src/de/danoeh/antennapod/feed/FeedItem.java21
-rw-r--r--src/de/danoeh/antennapod/feed/FeedManager.java5
-rw-r--r--src/de/danoeh/antennapod/feed/FeedMedia.java172
-rw-r--r--src/de/danoeh/antennapod/fragment/CoverFragment.java35
-rw-r--r--src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java8
-rw-r--r--src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java97
-rw-r--r--src/de/danoeh/antennapod/preferences/PlaybackPreferences.java4
-rw-r--r--src/de/danoeh/antennapod/service/PlaybackService.java293
-rw-r--r--src/de/danoeh/antennapod/service/PlayerStatus.java1
-rw-r--r--src/de/danoeh/antennapod/service/PlayerWidgetService.java6
-rw-r--r--src/de/danoeh/antennapod/service/download/DownloadService.java6
-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
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;
}