PlaybackService now works with the 'Playable' interface

This commit is contained in:
daniel oeh 2013-02-27 11:47:12 +01:00
parent 9cd870c6ee
commit a6b6022626
19 changed files with 723 additions and 322 deletions

View File

@ -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);
}
}

View File

@ -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())));

View File

@ -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());
}
}

View File

@ -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(

View File

@ -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;
}

View File

@ -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

View File

@ -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,

View File

@ -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];
}
};
}

View File

@ -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();

View File

@ -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));

View File

@ -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()

View File

@ -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.
*/

View File

@ -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();
boolean isInQueue = false;
FeedItem nextItem = null;
if (shouldStream) {
autoDeleteMediaId = -1;
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);
}
}
}
}

View File

@ -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.
}

View File

@ -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);

View File

@ -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;

View File

@ -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");
}
}

View File

@ -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);
}
}

View File

@ -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;
}