package de.danoeh.antennapodsp.util.playback; import android.content.Context; import android.content.SharedPreferences; import android.media.MediaMetadataRetriever; import android.os.Parcelable; import android.util.Log; import de.danoeh.antennapodsp.asynctask.ImageLoader; import de.danoeh.antennapodsp.feed.Chapter; import de.danoeh.antennapodsp.feed.FeedMedia; import de.danoeh.antennapodsp.feed.MediaType; import de.danoeh.antennapodsp.storage.DBReader; import de.danoeh.antennapodsp.util.ShownotesProvider; import org.apache.commons.io.IOUtils; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.List; /** * Interface for objects that can be played by the PlaybackService. */ public interface Playable extends Parcelable, ImageLoader.ImageWorkerTaskResource, ShownotesProvider { /** * 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. This method * should execute as quickly as possible and NOT load chapter marks if no * local file is available. */ public void loadMetadata() throws PlayableException; /** * This method is called from a separate thread by the PlaybackService. * Playable objects should load their chapter marks in this method if no * local file was available when loadMetadata() was called. */ public void loadChapterMarks(); /** * Returns the title of the episode that this playable represents */ public String getEpisodeTitle(); /** * 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 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. This method should return the correct value * BEFORE loadMetadata() is called. */ public MediaType getMediaType(); /** * Returns an url to a local file that can be played or null if this file * does not exist. */ public String getLocalMediaUrl(); /** * 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. This method might load data from the database, * depending on the type of playable that was restored. * * @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(Context context, int type, SharedPreferences pref) { // ADD new Playable types here: switch (type) { case FeedMedia.PLAYABLE_TYPE_FEEDMEDIA: long mediaId = pref.getLong(FeedMedia.PREF_MEDIA_ID, -1); if (mediaId != -1) { return DBReader.getFeedMedia(context, mediaId); } 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); } } /** * Uses local file as image resource if it is available. */ public static class DefaultPlayableImageLoader implements ImageLoader.ImageWorkerTaskResource { private Playable playable; public DefaultPlayableImageLoader(Playable playable) { if (playable == null) { throw new IllegalArgumentException("Playable must not be null"); } this.playable = playable; } @Override public InputStream openImageInputStream() { if (playable.localFileAvailable() && playable.getLocalMediaUrl() != null) { MediaMetadataRetriever mmr = new MediaMetadataRetriever(); try { mmr.setDataSource(playable.getLocalMediaUrl()); } catch (IllegalArgumentException e) { e.printStackTrace(); return null; } byte[] imgData = mmr.getEmbeddedPicture(); if (imgData != null) { return new PublicByteArrayInputStream(imgData); } } return null; } @Override public String getImageLoaderCacheKey() { return playable.getLocalMediaUrl(); } @Override public InputStream reopenImageInputStream(InputStream input) { if (input instanceof PublicByteArrayInputStream) { IOUtils.closeQuietly(input); byte[] imgData = ((PublicByteArrayInputStream) input) .getByteArray(); if (imgData != null) { ByteArrayInputStream out = new ByteArrayInputStream(imgData); return out; } } return null; } private static class PublicByteArrayInputStream extends ByteArrayInputStream { public PublicByteArrayInputStream(byte[] buf) { super(buf); } public byte[] getByteArray() { return buf; } } } }