package de.danoeh.antennapod.core.feed;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.database.Cursor;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import de.danoeh.antennapod.core.cast.RemoteMedia;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.core.util.ChapterUtils;
import de.danoeh.antennapod.core.util.playback.Playable;
public class FeedMedia extends FeedFile implements Playable {
private static final String TAG = "FeedMedia";
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";
/**
* Indicates we've checked on the size of the item via the network
* and got an invalid response. Using Integer.MIN_VALUE because
* 1) we'll still check on it in case it gets downloaded (it's <= 0)
* 2) By default all FeedMedia have a size of 0 if we don't know it,
* so this won't conflict with existing practice.
*/
private static final int CHECKED_ON_SIZE_BUT_UNKNOWN = Integer.MIN_VALUE;
private int duration;
private int position; // Current position in file
private long lastPlayedTime; // Last time this media was played (in ms)
private int played_duration; // How many ms of this file have been played (for autoflattring)
private long size; // File size in Byte
private String mime_type;
@Nullable private volatile FeedItem item;
private Date playbackCompletionDate;
// if null: unknown, will be checked
private Boolean hasEmbeddedPicture;
/* Used for loading item when restoring from parcel. */
private long itemID;
public FeedMedia(FeedItem i, String download_url, long size,
String mime_type) {
super(null, download_url, false);
this.item = i;
this.size = size;
this.mime_type = mime_type;
}
public FeedMedia(long id, FeedItem item, int duration, int position,
long size, String mime_type, String file_url, String download_url,
boolean downloaded, Date playbackCompletionDate, int played_duration,
long lastPlayedTime) {
super(file_url, download_url, downloaded);
this.id = id;
this.item = item;
this.duration = duration;
this.position = position;
this.played_duration = played_duration;
this.size = size;
this.mime_type = mime_type;
this.playbackCompletionDate = playbackCompletionDate == null
? null : (Date) playbackCompletionDate.clone();
this.lastPlayedTime = lastPlayedTime;
}
public FeedMedia(long id, FeedItem item, int duration, int position,
long size, String mime_type, String file_url, String download_url,
boolean downloaded, Date playbackCompletionDate, int played_duration,
Boolean hasEmbeddedPicture, long lastPlayedTime) {
this(id, item, duration, position, size, mime_type, file_url, download_url, downloaded,
playbackCompletionDate, played_duration, lastPlayedTime);
this.hasEmbeddedPicture = hasEmbeddedPicture;
}
public static FeedMedia fromCursor(Cursor cursor) {
int indexId = cursor.getColumnIndex(PodDBAdapter.KEY_ID);
int indexPlaybackCompletionDate = cursor.getColumnIndex(PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE);
int indexDuration = cursor.getColumnIndex(PodDBAdapter.KEY_DURATION);
int indexPosition = cursor.getColumnIndex(PodDBAdapter.KEY_POSITION);
int indexSize = cursor.getColumnIndex(PodDBAdapter.KEY_SIZE);
int indexMimeType = cursor.getColumnIndex(PodDBAdapter.KEY_MIME_TYPE);
int indexFileUrl = cursor.getColumnIndex(PodDBAdapter.KEY_FILE_URL);
int indexDownloadUrl = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOAD_URL);
int indexDownloaded = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOADED);
int indexPlayedDuration = cursor.getColumnIndex(PodDBAdapter.KEY_PLAYED_DURATION);
int indexLastPlayedTime = cursor.getColumnIndex(PodDBAdapter.KEY_LAST_PLAYED_TIME);
long mediaId = cursor.getLong(indexId);
Date playbackCompletionDate = null;
long playbackCompletionTime = cursor.getLong(indexPlaybackCompletionDate);
if (playbackCompletionTime > 0) {
playbackCompletionDate = new Date(playbackCompletionTime);
}
Boolean hasEmbeddedPicture;
switch(cursor.getInt(cursor.getColumnIndex(PodDBAdapter.KEY_HAS_EMBEDDED_PICTURE))) {
case 1:
hasEmbeddedPicture = Boolean.TRUE;
break;
case 0:
hasEmbeddedPicture = Boolean.FALSE;
break;
default:
hasEmbeddedPicture = null;
break;
}
return new FeedMedia(
mediaId,
null,
cursor.getInt(indexDuration),
cursor.getInt(indexPosition),
cursor.getLong(indexSize),
cursor.getString(indexMimeType),
cursor.getString(indexFileUrl),
cursor.getString(indexDownloadUrl),
cursor.getInt(indexDownloaded) > 0,
playbackCompletionDate,
cursor.getInt(indexPlayedDuration),
hasEmbeddedPicture,
cursor.getLong(indexLastPlayedTime)
);
}
@Override
public String getHumanReadableIdentifier() {
if (item != null && item.getTitle() != null) {
return item.getTitle();
} else {
return download_url;
}
}
/**
* Uses mimetype to determine the type of media.
*/
public MediaType getMediaType() {
return MediaType.fromMimeType(mime_type);
}
public void updateFromOther(FeedMedia other) {
super.updateFromOther(other);
if (other.size > 0) {
size = other.size;
}
if (other.mime_type != null) {
mime_type = other.mime_type;
}
}
public boolean compareWithOther(FeedMedia other) {
if (super.compareWithOther(other)) {
return true;
}
if (other.mime_type != null) {
if (mime_type == null || !mime_type.equals(other.mime_type)) {
return true;
}
}
if (other.size > 0 && other.size != size) {
return true;
}
return false;
}
/**
* Reads playback preferences to determine whether this FeedMedia object is
* currently being played.
*/
public boolean isPlaying() {
return PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA
&& PlaybackPreferences.getCurrentlyPlayingFeedMediaId() == id;
}
/**
* Reads playback preferences to determine whether this FeedMedia object is
* currently being played and the current player status is playing.
*/
public boolean isCurrentlyPlaying() {
return isPlaying() &&
((PlaybackPreferences.getCurrentPlayerStatus() == PlaybackPreferences.PLAYER_STATUS_PLAYING));
}
/**
* Reads playback preferences to determine whether this FeedMedia object is
* currently being played and the current player status is paused.
*/
public boolean isCurrentlyPaused() {
return isPlaying() &&
((PlaybackPreferences.getCurrentPlayerStatus() == PlaybackPreferences.PLAYER_STATUS_PAUSED));
}
public boolean hasAlmostEnded() {
int smartMarkAsPlayedSecs = UserPreferences.getSmartMarkAsPlayedSecs();
return this.position >= this.duration - smartMarkAsPlayedSecs * 1000;
}
@Override
public int getTypeAsInt() {
return FEEDFILETYPE_FEEDMEDIA;
}
public int getDuration() {
return duration;
}
public void setDuration(int duration) {
this.duration = duration;
}
@Override
public void setLastPlayedTime(long lastPlayedTime) {
this.lastPlayedTime = lastPlayedTime;
}
public int getPlayedDuration() {
return played_duration;
}
public void setPlayedDuration(int played_duration) {
this.played_duration = played_duration;
}
public int getPosition() {
return position;
}
@Override
public long getLastPlayedTime() {
return lastPlayedTime;
}
public void setPosition(int position) {
this.position = position;
if(position > 0 && item != null && item.isNew()) {
this.item.setPlayed(false);
}
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
/**
* Indicates we asked the service what the size was, but didn't
* get a valid answer and we shoudln't check using the network again.
*/
public void setCheckedOnSizeButUnknown() {
this.size = CHECKED_ON_SIZE_BUT_UNKNOWN;
}
public boolean checkedOnSizeButUnknown() {
return (CHECKED_ON_SIZE_BUT_UNKNOWN == this.size);
}
public String getMime_type() {
return mime_type;
}
public void setMime_type(String mime_type) {
this.mime_type = mime_type;
}
@Nullable
public FeedItem getItem() {
return item;
}
/**
* Sets the item object of this FeedMedia. If the given
* FeedItem object is not null, it's 'media'-attribute value
* will also be set to this media object.
*/
public void setItem(FeedItem item) {
this.item = item;
if (item != null && item.getMedia() != this) {
item.setMedia(this);
}
}
public Date getPlaybackCompletionDate() {
return playbackCompletionDate == null
? null : (Date) playbackCompletionDate.clone();
}
public void setPlaybackCompletionDate(Date playbackCompletionDate) {
this.playbackCompletionDate = playbackCompletionDate == null
? null : (Date) playbackCompletionDate.clone();
}
public boolean isInProgress() {
return (this.position > 0);
}
@Override
public int describeContents() {
return 0;
}
public boolean hasEmbeddedPicture() {
return false;
// TODO: reenable!
//if(hasEmbeddedPicture == null) {
// checkEmbeddedPicture();
//}
//return hasEmbeddedPicture;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id);
dest.writeLong(item != null ? item.getId() : 0L);
dest.writeInt(duration);
dest.writeInt(position);
dest.writeLong(size);
dest.writeString(mime_type);
dest.writeString(file_url);
dest.writeString(download_url);
dest.writeByte((byte) ((downloaded) ? 1 : 0));
dest.writeLong((playbackCompletionDate != null) ? playbackCompletionDate.getTime() : 0);
dest.writeInt(played_duration);
dest.writeLong(lastPlayedTime);
}
@Override
public void writeToPreferences(Editor prefEditor) {
if(item != null && item.getFeed() != null) {
prefEditor.putLong(PREF_FEED_ID, item.getFeed().getId());
} else {
prefEditor.putLong(PREF_FEED_ID, 0L);
}
prefEditor.putLong(PREF_MEDIA_ID, id);
}
@Override
public void loadMetadata() throws PlayableException {
if (item == null && itemID != 0) {
item = DBReader.getFeedItem(itemID);
}
}
@Override
public void loadChapterMarks() {
if (item == null && itemID != 0) {
item = DBReader.getFeedItem(itemID);
}
// check if chapters are stored in db and not loaded yet.
if (item != null && item.hasChapters() && item.getChapters() == null) {
DBReader.loadChaptersOfFeedItem(item);
} else if (item != null && item.getChapters() == null) {
if(localFileAvailable()) {
ChapterUtils.loadChaptersFromFileUrl(this);
} else {
ChapterUtils.loadChaptersFromStreamUrl(this);
}
if (getChapters() != null && item != null) {
DBWriter.setFeedItem(item);
}
}
}
@Override
public String getEpisodeTitle() {
if (item == null) {
return null;
}
if (item.getTitle() != null) {
return item.getTitle();
} else {
return item.getIdentifyingValue();
}
}
@Override
public List<Chapter> getChapters() {
if (item == null) {
return null;
}
return item.getChapters();
}
@Override
public String getWebsiteLink() {
if (item == null) {
return null;
}
return item.getLink();
}
@Override
public String getFeedTitle() {
if (item == null || item.getFeed() == null) {
return null;
}
return item.getFeed().getTitle();
}
@Override
public Object getIdentifier() {
return id;
}
@Override
public String getLocalMediaUrl() {
return file_url;
}
@Override
public String getStreamUrl() {
return download_url;
}
@Override
public String getPaymentLink() {
if (item == null) {
return null;
}
return item.getPaymentLink();
}
@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, long timeStamp) {
if(item != null && item.isNew()) {
DBWriter.markItemPlayed(FeedItem.UNPLAYED, item.getId());
}
setPosition(newPosition);
setLastPlayedTime(timeStamp);
DBWriter.setFeedMediaPlaybackInformation(this);
}
@Override
public void onPlaybackStart() {
}
@Override
public void onPlaybackCompleted() {
}
@Override
public int getPlayableType() {
return PLAYABLE_TYPE_FEEDMEDIA;
}
@Override
public void setChapters(List<Chapter> chapters) {
if(item != null) {
item.setChapters(chapters);
}
}
@Override
public Callable<String> loadShownotes() {
return () -> {
if (item == null) {
item = DBReader.getFeedItem(
itemID);
}
if (item.getContentEncoded() == null || item.getDescription() == null) {
DBReader.loadExtraInformationOfFeedItem(
item);
}
return (item.getContentEncoded() != null) ? item.getContentEncoded() : item.getDescription();
};
}
public static final Parcelable.Creator<FeedMedia> CREATOR = new Parcelable.Creator<FeedMedia>() {
public FeedMedia createFromParcel(Parcel in) {
final long id = in.readLong();
final long itemID = in.readLong();
FeedMedia result = new FeedMedia(id, null, in.readInt(), in.readInt(), in.readLong(), in.readString(), in.readString(),
in.readString(), in.readByte() != 0, new Date(in.readLong()), in.readInt(), in.readLong());
result.itemID = itemID;
return result;
}
public FeedMedia[] newArray(int size) {
return new FeedMedia[size];
}
};
@Override
public Uri getImageUri() {
if (hasEmbeddedPicture()) {
Uri.Builder builder = new Uri.Builder();
builder.scheme(SCHEME_MEDIA).encodedPath(getLocalMediaUrl());
if (item != null && item.getFeed() != null) {
final Uri feedImgUri = item.getFeed().getImageUri();
if (feedImgUri != null) {
builder.appendQueryParameter(PARAM_FALLBACK, feedImgUri.toString());
}
}
return builder.build();
} else if(item != null) {
return item.getImageUri();
} else {
return null;
}
}
public void setHasEmbeddedPicture(Boolean hasEmbeddedPicture) {
this.hasEmbeddedPicture = hasEmbeddedPicture;
}
@Override
public void setDownloaded(boolean downloaded) {
super.setDownloaded(downloaded);
if(item != null && downloaded) {
item.setPlayed(false);
}
}
@Override
public void setFile_url(String file_url) {
super.setFile_url(file_url);
}
private void checkEmbeddedPicture() {
if (!localFileAvailable()) {
hasEmbeddedPicture = Boolean.FALSE;
return;
}
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
try {
mmr.setDataSource(getLocalMediaUrl());
byte[] image = mmr.getEmbeddedPicture();
if(image != null) {
hasEmbeddedPicture = Boolean.TRUE;
} else {
hasEmbeddedPicture = Boolean.FALSE;
}
} catch (Exception e) {
e.printStackTrace();
hasEmbeddedPicture = Boolean.FALSE;
}
}
@Override
public boolean equals(Object o) {
if (o instanceof RemoteMedia) {
return o.equals(this);
}
return super.equals(o);
}
}