diff options
author | Martin Fietz <Martin.Fietz@gmail.com> | 2016-06-09 00:00:47 +0200 |
---|---|---|
committer | Martin Fietz <Martin.Fietz@gmail.com> | 2016-06-09 00:00:47 +0200 |
commit | 405630ee502819f7214f77ab49c484f4c7fd918c (patch) | |
tree | 8ae0c8917711da636d9625a71bcd1b044fd1e80a /core/src | |
parent | 89a1851f57f7a3357ceeef20fa18538febbd2760 (diff) | |
parent | 186791b22bd850133b52e9f544c03f5282d477c8 (diff) | |
download | AntennaPod-405630ee502819f7214f77ab49c484f4c7fd918c.zip |
Merge pull request #2001 from domingos86/flavors-optimization
Flavors code optimization
Diffstat (limited to 'core/src')
15 files changed, 438 insertions, 3391 deletions
diff --git a/core/src/free/java/de/danoeh/antennapod/core/feed/FeedMedia.java b/core/src/free/java/de/danoeh/antennapod/core/feed/FeedMedia.java deleted file mode 100644 index cde66835a..000000000 --- a/core/src/free/java/de/danoeh/antennapod/core/feed/FeedMedia.java +++ /dev/null @@ -1,567 +0,0 @@ -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.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.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() { - 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 String getImageLocation() { - if (hasEmbeddedPicture()) { - return getLocalMediaUrl(); - } else if(item != null) { - return item.getImageLocation(); - } 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); - } - - public 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); -// } -} diff --git a/core/src/free/java/de/danoeh/antennapod/core/feed/FeedMediaFlavorHelper.java b/core/src/free/java/de/danoeh/antennapod/core/feed/FeedMediaFlavorHelper.java new file mode 100644 index 000000000..7595d9d0b --- /dev/null +++ b/core/src/free/java/de/danoeh/antennapod/core/feed/FeedMediaFlavorHelper.java @@ -0,0 +1,10 @@ +package de.danoeh.antennapod.core.feed; + +/** + * Implements methods for FeedMedia that are flavor dependent. + */ +public class FeedMediaFlavorHelper { + static boolean instanceOfRemoteMedia(Object o) { + return false; + } +} diff --git a/core/src/free/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java b/core/src/free/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java new file mode 100644 index 000000000..6bc3ed7c5 --- /dev/null +++ b/core/src/free/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java @@ -0,0 +1,44 @@ +package de.danoeh.antennapod.core.service.playback; + +import android.content.Context; +import android.support.annotation.StringRes; + +/** + * Class intended to work along PlaybackService and provide support for different flavors. + */ +public class PlaybackServiceFlavorHelper { + + private PlaybackService.FlavorHelperCallback callback; + + PlaybackServiceFlavorHelper(Context context, PlaybackService.FlavorHelperCallback callback) { + this.callback = callback; + } + + void initializeMediaPlayer(Context context) { + callback.setMediaPlayer(new LocalPSMP(context, callback.getMediaPlayerCallback())); + } + + void removeCastConsumer() { + // no-op + } + + boolean castDisconnect(boolean castDisconnect) { + return false; + } + + boolean onMediaPlayerInfo(Context context, int code, @StringRes int resourceId) { + return false; + } + + void registerWifiBroadcastReceiver() { + // no-op + } + + void unregisterWifiBroadcastReceiver() { + // no-op + } + + boolean onSharedPreference(String key) { + return false; + } +} diff --git a/core/src/free/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java b/core/src/free/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java deleted file mode 100644 index abf787ce8..000000000 --- a/core/src/free/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java +++ /dev/null @@ -1,592 +0,0 @@ -//package de.danoeh.antennapod.core.service.playback; -// -//import android.content.Context; -//import android.media.MediaPlayer; -//import android.support.annotation.NonNull; -//import android.util.Log; -//import android.util.Pair; -//import android.view.SurfaceHolder; -// -//import com.google.android.gms.cast.Cast; -//import com.google.android.gms.cast.CastStatusCodes; -//import com.google.android.gms.cast.MediaInfo; -//import com.google.android.gms.cast.MediaStatus; -//import com.google.android.libraries.cast.companionlibrary.cast.exceptions.CastException; -//import com.google.android.libraries.cast.companionlibrary.cast.exceptions.NoConnectionException; -//import com.google.android.libraries.cast.companionlibrary.cast.exceptions.TransientNetworkDisconnectionException; -// -//import java.util.concurrent.atomic.AtomicBoolean; -// -//import de.danoeh.antennapod.core.R; -//import de.danoeh.antennapod.core.cast.CastConsumer; -//import de.danoeh.antennapod.core.cast.CastManager; -//import de.danoeh.antennapod.core.cast.CastUtils; -//import de.danoeh.antennapod.core.cast.DefaultCastConsumer; -//import de.danoeh.antennapod.core.cast.RemoteMedia; -//import de.danoeh.antennapod.core.feed.FeedMedia; -//import de.danoeh.antennapod.core.feed.MediaType; -//import de.danoeh.antennapod.core.util.RewindAfterPauseUtils; -//import de.danoeh.antennapod.core.util.playback.Playable; -// -///** -// * Implementation of PlaybackServiceMediaPlayer suitable for remote playback on Cast Devices. -// */ -//public class RemotePSMP extends PlaybackServiceMediaPlayer { -// -// public static final String TAG = "RemotePSMP"; -// -// public static final int CAST_ERROR = 3001; -// -// public static final int CAST_ERROR_PRIORITY_HIGH = 3005; -// -// private final CastManager castMgr; -// -// private volatile Playable media; -// private volatile MediaInfo remoteMedia; -// private volatile MediaType mediaType; -// -// private final AtomicBoolean isBuffering; -// -// private final AtomicBoolean startWhenPrepared; -// -// public RemotePSMP(@NonNull Context context, @NonNull PSMPCallback callback) { -// super(context, callback); -// -// castMgr = CastManager.getInstance(); -// media = null; -// mediaType = null; -// startWhenPrepared = new AtomicBoolean(false); -// isBuffering = new AtomicBoolean(false); -// -// try { -// if (castMgr.isConnected() && castMgr.isRemoteMediaLoaded()) { -// // updates the state, but does not start playing new media if it was going to -// onRemoteMediaPlayerStatusUpdated( -// ((p, playNextEpisode, wasSkipped, switchingPlayers) -> -// this.callback.endPlayback(p, false, wasSkipped, switchingPlayers))); -// } -// } catch (TransientNetworkDisconnectionException | NoConnectionException e) { -// Log.e(TAG, "Unable to do initial check for loaded media", e); -// } -// -// castMgr.addCastConsumer(castConsumer); -// //TODO -// } -// -// private CastConsumer castConsumer = new DefaultCastConsumer() { -// @Override -// public void onRemoteMediaPlayerMetadataUpdated() { -// RemotePSMP.this.onRemoteMediaPlayerStatusUpdated(callback::endPlayback); -// } -// -// @Override -// public void onRemoteMediaPlayerStatusUpdated() { -// RemotePSMP.this.onRemoteMediaPlayerStatusUpdated(callback::endPlayback); -// } -// -// @Override -// public void onMediaLoadResult(int statusCode) { -// if (playerStatus == PlayerStatus.PREPARING) { -// if (statusCode == CastStatusCodes.SUCCESS) { -// setPlayerStatus(PlayerStatus.PREPARED, media); -// if (media.getDuration() == 0) { -// Log.d(TAG, "Setting duration of media"); -// try { -// media.setDuration((int) castMgr.getMediaDuration()); -// } catch (TransientNetworkDisconnectionException | NoConnectionException e) { -// Log.e(TAG, "Unable to get remote media's duration"); -// } -// } -// } else if (statusCode != CastStatusCodes.REPLACED){ -// Log.d(TAG, "Remote media failed to load"); -// setPlayerStatus(PlayerStatus.INITIALIZED, media); -// } -// } else { -// Log.d(TAG, "onMediaLoadResult called, but Player Status wasn't in preparing state, so we ignore the result"); -// } -// } -// -// @Override -// public void onApplicationStatusChanged(String appStatus) { -// if (playerStatus != PlayerStatus.PLAYING) { -// Log.d(TAG, "onApplicationStatusChanged, but no media was playing"); -// return; -// } -// boolean playbackEnded = false; -// try { -// int standbyState = castMgr.getApplicationStandbyState(); -// Log.d(TAG, "standbyState: " + standbyState); -// playbackEnded = standbyState == Cast.STANDBY_STATE_YES; -// } catch (IllegalStateException e) { -// Log.d(TAG, "unable to get standbyState on onApplicationStatusChanged()"); -// } -// if (playbackEnded) { -// setPlayerStatus(PlayerStatus.INDETERMINATE, media); -// callback.endPlayback(media, true, false, false); -// } -// } -// -// @Override -// public void onFailed(int resourceId, int statusCode) { -// callback.onMediaPlayerInfo(CAST_ERROR, resourceId); -// } -// }; -// -// private void setBuffering(boolean buffering) { -// if (buffering && isBuffering.compareAndSet(false, true)) { -// callback.onMediaPlayerInfo(MediaPlayer.MEDIA_INFO_BUFFERING_START, 0); -// } else if (!buffering && isBuffering.compareAndSet(true, false)) { -// callback.onMediaPlayerInfo(MediaPlayer.MEDIA_INFO_BUFFERING_END, 0); -// } -// } -// -// private Playable localVersion(MediaInfo info){ -// if (info == null) { -// return null; -// } -// if (CastUtils.matches(info, media)) { -// return media; -// } -// return CastUtils.getPlayable(info, true); -// } -// -// private MediaInfo remoteVersion(Playable playable) { -// if (playable == null) { -// return null; -// } -// if (CastUtils.matches(remoteMedia, playable)) { -// return remoteMedia; -// } -// if (playable instanceof FeedMedia) { -// return CastUtils.convertFromFeedMedia((FeedMedia) playable); -// } -// if (playable instanceof RemoteMedia) { -// return ((RemoteMedia) playable).extractMediaInfo(); -// } -// return null; -// } -// -// private void onRemoteMediaPlayerStatusUpdated(@NonNull EndPlaybackCall endPlaybackCall) { -// MediaStatus status = castMgr.getMediaStatus(); -// if (status == null) { -// Log.d(TAG, "Received null MediaStatus"); -// //setBuffering(false); -// //setPlayerStatus(PlayerStatus.INDETERMINATE, null); -// return; -// } else { -// Log.d(TAG, "Received remote status/media update. New state=" + status.getPlayerState()); -// } -// Playable currentMedia = localVersion(status.getMediaInfo()); -// boolean updateUI = currentMedia != media; -// if (currentMedia != null) { -// long position = status.getStreamPosition(); -// if (position > 0 && currentMedia.getPosition() == 0) { -// currentMedia.setPosition((int) position); -// } -// } -// int state = status.getPlayerState(); -// setBuffering(state == MediaStatus.PLAYER_STATE_BUFFERING); -// switch (state) { -// case MediaStatus.PLAYER_STATE_PLAYING: -// setPlayerStatus(PlayerStatus.PLAYING, currentMedia); -// break; -// case MediaStatus.PLAYER_STATE_PAUSED: -// setPlayerStatus(PlayerStatus.PAUSED, currentMedia); -// break; -// case MediaStatus.PLAYER_STATE_BUFFERING: -// setPlayerStatus(playerStatus, currentMedia); -// break; -// case MediaStatus.PLAYER_STATE_IDLE: -// int reason = status.getIdleReason(); -// switch (reason) { -// case MediaStatus.IDLE_REASON_CANCELED: -// // check if we're already loading something else -// if (!updateUI || media == null) { -// setPlayerStatus(PlayerStatus.STOPPED, currentMedia); -// } else { -// updateUI = false; -// } -// break; -// case MediaStatus.IDLE_REASON_INTERRUPTED: -// // check if we're already loading something else -// if (!updateUI || media == null) { -// setPlayerStatus(PlayerStatus.PREPARING, currentMedia); -// } else { -// updateUI = false; -// } -// break; -// case MediaStatus.IDLE_REASON_NONE: -// setPlayerStatus(PlayerStatus.INITIALIZED, currentMedia); -// break; -// case MediaStatus.IDLE_REASON_FINISHED: -// boolean playing = playerStatus == PlayerStatus.PLAYING; -// setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia); -// endPlaybackCall.endPlayback(currentMedia,playing, false, false); -// // endPlayback already updates the UI, so no need to trigger it ourselves -// updateUI = false; -// break; -// case MediaStatus.IDLE_REASON_ERROR: -// Log.w(TAG, "Got an error status from the Chromecast. Skipping, if possible, to the next episode..."); -// setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia); -// callback.onMediaPlayerInfo(CAST_ERROR_PRIORITY_HIGH, -// R.string.cast_failed_media_error_skipping); -// endPlaybackCall.endPlayback(currentMedia, startWhenPrepared.get(), true, false); -// // endPlayback already updates the UI, so no need to trigger it ourselves -// updateUI = false; -// } -// break; -// case MediaStatus.PLAYER_STATE_UNKNOWN: -// //is this right? -// setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia); -// break; -// default: -// Log.e(TAG, "Remote media state undetermined!"); -// setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia); -// } -// if (updateUI) { -// callback.onMediaChanged(true); -// } -// } -// -// @Override -// public void playMediaObject(@NonNull final Playable playable, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) { -// Log.d(TAG, "playMediaObject() called"); -// playMediaObject(playable, false, stream, startWhenPrepared, prepareImmediately); -// } -// -// /** -// * Internal implementation of playMediaObject. This method has an additional parameter that allows the caller to force a media player reset even if -// * the given playable parameter is the same object as the currently playing media. -// * -// * @see #playMediaObject(de.danoeh.antennapod.core.util.playback.Playable, boolean, boolean, boolean) -// */ -// private void playMediaObject(@NonNull final Playable playable, final boolean forceReset, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) { -// if (!CastUtils.isCastable(playable)) { -// Log.d(TAG, "media provided is not compatible with cast device"); -// callback.onMediaPlayerInfo(CAST_ERROR_PRIORITY_HIGH, R.string.cast_not_castable); -// try { -// playable.loadMetadata(); -// } catch (Playable.PlayableException e) { -// Log.e(TAG, "Unable to load metadata of playable", e); -// } -// callback.endPlayback(playable, startWhenPrepared, true, false); -// return; -// } -// -// if (media != null) { -// if (!forceReset && media.getIdentifier().equals(playable.getIdentifier()) -// && playerStatus == PlayerStatus.PLAYING) { -// // episode is already playing -> ignore method call -// Log.d(TAG, "Method call to playMediaObject was ignored: media file already playing."); -// return; -// } else { -// // set temporarily to pause in order to update list with current position -// try { -// if (castMgr.isRemoteMediaPlaying()) { -// setPlayerStatus(PlayerStatus.PAUSED, media); -// } -// } catch (TransientNetworkDisconnectionException | NoConnectionException e) { -// Log.e(TAG, "Unable to determine whether media was playing, falling back to stored player status", e); -// // this might end up just being pointless if we need to query the remote device for the position -// if (playerStatus == PlayerStatus.PLAYING) { -// setPlayerStatus(PlayerStatus.PAUSED, media); -// } -// } -// smartMarkAsPlayed(media); -// -// -// setPlayerStatus(PlayerStatus.INDETERMINATE, null); -// } -// } -// -// this.media = playable; -// remoteMedia = remoteVersion(playable); -// //this.stream = stream; -// this.mediaType = media.getMediaType(); -// this.startWhenPrepared.set(startWhenPrepared); -// setPlayerStatus(PlayerStatus.INITIALIZING, media); -// try { -// media.loadMetadata(); -// callback.onMediaChanged(true); -// setPlayerStatus(PlayerStatus.INITIALIZED, media); -// if (prepareImmediately) { -// prepare(); -// } -// } catch (Playable.PlayableException e) { -// Log.e(TAG, "Error while loading media metadata", e); -// setPlayerStatus(PlayerStatus.STOPPED, null); -// } -// } -// -// @Override -// public void resume() { -// try { -// // TODO see comment on prepare() -// // setVolume(UserPreferences.getLeftVolume(), UserPreferences.getRightVolume()); -// if (playerStatus == PlayerStatus.PREPARED && media.getPosition() > 0) { -// int newPosition = RewindAfterPauseUtils.calculatePositionWithRewind( -// media.getPosition(), -// media.getLastPlayedTime()); -// castMgr.play(newPosition); -// } -// castMgr.play(); -// } catch (CastException | TransientNetworkDisconnectionException | NoConnectionException e) { -// Log.e(TAG, "Unable to resume remote playback", e); -// } -// } -// -// @Override -// public void pause(boolean abandonFocus, boolean reinit) { -// try { -// if (castMgr.isRemoteMediaPlaying()) { -// castMgr.pause(); -// } -// } catch (CastException | TransientNetworkDisconnectionException | NoConnectionException e) { -// Log.e(TAG, "Unable to pause", e); -// } -// } -// -// @Override -// public void prepare() { -// if (playerStatus == PlayerStatus.INITIALIZED) { -// Log.d(TAG, "Preparing media player"); -// setPlayerStatus(PlayerStatus.PREPARING, media); -// try { -// int position = media.getPosition(); -// if (position > 0) { -// position = RewindAfterPauseUtils.calculatePositionWithRewind( -// position, -// media.getLastPlayedTime()); -// } -// // TODO We're not supporting user set stream volume yet, as we need to make a UI -// // that doesn't allow changing playback speed or have different values for left/right -// //setVolume(UserPreferences.getLeftVolume(), UserPreferences.getRightVolume()); -// castMgr.loadMedia(remoteMedia, startWhenPrepared.get(), position); -// } catch (TransientNetworkDisconnectionException | NoConnectionException e) { -// Log.e(TAG, "Error loading media", e); -// setPlayerStatus(PlayerStatus.INITIALIZED, media); -// } -// } -// } -// -// @Override -// public void reinit() { -// Log.d(TAG, "reinit() called"); -// if (media != null) { -// playMediaObject(media, true, false, startWhenPrepared.get(), false); -// } else { -// Log.d(TAG, "Call to reinit was ignored: media was null"); -// } -// } -// -// @Override -// public void seekTo(int t) { -// //TODO check other seek implementations and see if there's no issue with sending too many seek commands to the remote media player -// try { -// if (castMgr.isRemoteMediaLoaded()) { -// setPlayerStatus(PlayerStatus.SEEKING, media); -// castMgr.seek(t); -// } else if (media != null && playerStatus == PlayerStatus.INITIALIZED){ -// media.setPosition(t); -// startWhenPrepared.set(false); -// prepare(); -// } -// } catch (TransientNetworkDisconnectionException | NoConnectionException e) { -// Log.e(TAG, "Unable to seek", e); -// } -// } -// -// @Override -// public void seekDelta(int d) { -// int position = getPosition(); -// if (position != INVALID_TIME) { -// seekTo(position + d); -// } else { -// Log.e(TAG, "getPosition() returned INVALID_TIME in seekDelta"); -// } -// } -// -// @Override -// public int getDuration() { -// int retVal = INVALID_TIME; -// boolean prepared; -// try { -// prepared = castMgr.isRemoteMediaLoaded(); -// } catch (TransientNetworkDisconnectionException | NoConnectionException e) { -// Log.e(TAG, "Unable to check if remote media is loaded", e); -// prepared = playerStatus.isAtLeast(PlayerStatus.PREPARED); -// } -// if (prepared) { -// try { -// retVal = (int) castMgr.getMediaDuration(); -// } catch (TransientNetworkDisconnectionException | NoConnectionException e) { -// Log.e(TAG, "Unable to determine remote media's duration", e); -// } -// } -// if(retVal == INVALID_TIME && media != null && media.getDuration() > 0) { -// retVal = media.getDuration(); -// } -// Log.d(TAG, "getDuration() -> " + retVal); -// return retVal; -// } -// -// @Override -// public int getPosition() { -// int retVal = INVALID_TIME; -// boolean prepared; -// try { -// prepared = castMgr.isRemoteMediaLoaded(); -// } catch (TransientNetworkDisconnectionException | NoConnectionException e) { -// Log.e(TAG, "Unable to check if remote media is loaded", e); -// prepared = playerStatus.isAtLeast(PlayerStatus.PREPARED); -// } -// if (prepared) { -// try { -// retVal = (int) castMgr.getCurrentMediaPosition(); -// } catch (TransientNetworkDisconnectionException | NoConnectionException e) { -// Log.e(TAG, "Unable to determine remote media's position", e); -// } -// } -// if(retVal <= 0 && media != null && media.getPosition() >= 0) { -// retVal = media.getPosition(); -// } -// Log.d(TAG, "getPosition() -> " + retVal); -// return retVal; -// } -// -// @Override -// public boolean isStartWhenPrepared() { -// return startWhenPrepared.get(); -// } -// -// @Override -// public void setStartWhenPrepared(boolean startWhenPrepared) { -// this.startWhenPrepared.set(startWhenPrepared); -// } -// -// //TODO I believe some parts of the code make the same decision skipping this check, so that -// //should be changed as well -// @Override -// public boolean canSetSpeed() { -// return false; -// } -// -// @Override -// public void setSpeed(float speed) { -// throw new UnsupportedOperationException("Setting playback speed unsupported for Remote Playback"); -// } -// -// @Override -// public float getPlaybackSpeed() { -// return 1; -// } -// -// @Override -// public void setVolume(float volumeLeft, float volumeRight) { -// Log.d(TAG, "Setting the Stream volume on Remote Media Player"); -// double volume = (volumeLeft+volumeRight)/2; -// if (volume > 1.0) { -// volume = 1.0; -// } -// if (volume < 0.0) { -// volume = 0.0; -// } -// try { -// castMgr.setStreamVolume(volume); -// } catch (TransientNetworkDisconnectionException | NoConnectionException | CastException e) { -// Log.e(TAG, "Unable to set the volume", e); -// } -// } -// -// @Override -// public boolean canDownmix() { -// return false; -// } -// -// @Override -// public void setDownmix(boolean enable) { -// throw new UnsupportedOperationException("Setting downmix unsupported in Remote Media Player"); -// } -// -// @Override -// public MediaType getCurrentMediaType() { -// return mediaType; -// } -// -// @Override -// public boolean isStreaming() { -// return true; -// } -// -// @Override -// public void shutdown() { -// castMgr.removeCastConsumer(castConsumer); -// } -// -// @Override -// public void shutdownQuietly() { -// shutdown(); -// } -// -// @Override -// public void setVideoSurface(SurfaceHolder surface) { -// throw new UnsupportedOperationException("Setting Video Surface unsupported in Remote Media Player"); -// } -// -// @Override -// public void resetVideoSurface() { -// Log.e(TAG, "Resetting Video Surface unsupported in Remote Media Player"); -// } -// -// @Override -// public Pair<Integer, Integer> getVideoSize() { -// return null; -// } -// -// @Override -// public Playable getPlayable() { -// return media; -// } -// -// @Override -// protected void setPlayable(Playable playable) { -// if (playable != media) { -// media = playable; -// remoteMedia = remoteVersion(playable); -// } -// } -// -// @Override -// public void endPlayback(boolean wasSkipped, boolean switchingPlayers) { -// Log.d(TAG, "endPlayback() called"); -// boolean isPlaying = playerStatus == PlayerStatus.PLAYING; -// try { -// isPlaying = castMgr.isRemoteMediaPlaying(); -// } catch (TransientNetworkDisconnectionException | NoConnectionException e) { -// Log.e(TAG, "Could not determine if media is playing", e); -// } -// // TODO make sure we stop playback whenever there's no next episode. -// if (playerStatus != PlayerStatus.INDETERMINATE) { -// setPlayerStatus(PlayerStatus.INDETERMINATE, media); -// } -// callback.endPlayback(media, isPlaying, wasSkipped, switchingPlayers); -// } -// -// @Override -// public void stop() { -// if (playerStatus == PlayerStatus.INDETERMINATE) { -// setPlayerStatus(PlayerStatus.STOPPED, null); -// } else { -// Log.d(TAG, "Ignored call to stop: Current player state is: " + playerStatus); -// } -// } -// -// @Override -// protected boolean shouldLockWifi() { -// return false; -// } -// -// private interface EndPlaybackCall { -// boolean endPlayback(Playable media, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers); -// } -//} diff --git a/core/src/free/res/values/strings.xml b/core/src/free/res/values/strings.xml new file mode 100644 index 000000000..579b12e12 --- /dev/null +++ b/core/src/free/res/values/strings.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="pref_cast_message">@string/pref_cast_message_free_flavor</string> +</resources> diff --git a/core/src/play/java/de/danoeh/antennapod/core/feed/FeedMedia.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java index 068669af9..b8b154ff0 100644 --- a/core/src/play/java/de/danoeh/antennapod/core/feed/FeedMedia.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java @@ -12,7 +12,6 @@ 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; @@ -560,7 +559,7 @@ public class FeedMedia extends FeedFile implements Playable { @Override public boolean equals(Object o) { - if (o instanceof RemoteMedia) { + if (FeedMediaFlavorHelper.instanceOfRemoteMedia(o)) { return o.equals(this); } return super.equals(o); diff --git a/core/src/free/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java index 01b803d80..76c960607 100644 --- a/core/src/free/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java @@ -15,14 +15,11 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.AudioManager; import android.media.MediaPlayer; -import android.net.NetworkInfo; -import android.net.wifi.WifiManager; import android.os.Binder; import android.os.Build; import android.os.IBinder; import android.os.Vibrator; import android.preference.PreferenceManager; -import android.support.annotation.NonNull; import android.support.annotation.StringRes; import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaSessionCompat; @@ -61,6 +58,7 @@ import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.util.IntList; import de.danoeh.antennapod.core.util.QueueAccess; import de.danoeh.antennapod.core.util.flattr.FlattrUtils; +import de.danoeh.antennapod.core.util.playback.ExternalMedia; import de.danoeh.antennapod.core.util.playback.Playable; /** @@ -175,12 +173,6 @@ public class PlaybackService extends Service { public static final int INVALID_TIME = -1; /** - * Time in seconds during which the CastManager will try to reconnect to the Cast Device after - * the Wifi Connection is regained. - */ - private static final int RECONNECTION_ATTEMPT_PERIOD_S = 15; - - /** * Is true if service is running. */ public static boolean isRunning = false; @@ -196,21 +188,13 @@ public class PlaybackService extends Service { * Is true if a Cast Device is connected to the service. */ private static volatile boolean isCasting = false; - /** - * Stores the state of the cast playback just before it disconnects. - */ - private volatile PlaybackServiceMediaPlayer.PSMPInfo infoBeforeCastDisconnection; - - private boolean wifiConnectivity = true; - private BroadcastReceiver wifiBroadcastReceiver; private static final int NOTIFICATION_ID = 1; private PlaybackServiceMediaPlayer mediaPlayer; private PlaybackServiceTaskManager taskManager; + private PlaybackServiceFlavorHelper flavorHelper; -// private CastManager castManager; -// private MediaRouter mediaRouter; /** * Only used for Lollipop notifications. */ @@ -284,7 +268,7 @@ public class PlaybackService extends Service { ACTION_RESUME_PLAY_CURRENT_EPISODE)); taskManager = new PlaybackServiceTaskManager(this, taskManagerCallback); -// mediaRouter = MediaRouter.getInstance(getApplicationContext()); + flavorHelper = new PlaybackServiceFlavorHelper(PlaybackService.this, flavorHelperCallback); PreferenceManager.getDefaultSharedPreferences(this) .registerOnSharedPreferenceChangeListener(prefListener); @@ -308,18 +292,7 @@ public class PlaybackService extends Service { npe.printStackTrace(); } -// castManager = CastManager.getInstance(); -// castManager.addCastConsumer(castConsumer); -// isCasting = castManager.isConnected(); -// if (isCasting) { -// if (UserPreferences.isCastEnabled()) { -// onCastAppConnected(false); -// } else { -// castManager.disconnect(); -// } -// } else { - mediaPlayer = new LocalPSMP(this, mediaPlayerCallback); -// } + flavorHelper.initializeMediaPlayer(PlaybackService.this); mediaSession.setActive(true); } @@ -346,8 +319,8 @@ public class PlaybackService extends Service { unregisterReceiver(skipCurrentEpisodeReceiver); unregisterReceiver(pausePlayCurrentEpisodeReceiver); unregisterReceiver(pauseResumeCurrentEpisodeReceiver); -// castManager.removeCastConsumer(castConsumer); - unregisterWifiBroadcastReceiver(); + flavorHelper.removeCastConsumer(); + flavorHelper.unregisterWifiBroadcastReceiver(); mediaPlayer.shutdown(); taskManager.shutdown(); } @@ -381,9 +354,7 @@ public class PlaybackService extends Service { Log.d(TAG, "Received media button event"); handleKeycode(keycode, intent.getIntExtra(MediaButtonReceiver.EXTRA_SOURCE, InputDevice.SOURCE_CLASS_NONE)); -// } else if (castDisconnect) { -// castManager.disconnect(); - } else { + } else if (!flavorHelper.castDisconnect(castDisconnect)) { started = true; boolean stream = intent.getBooleanExtra(EXTRA_SHOULD_STREAM, true); @@ -391,9 +362,7 @@ public class PlaybackService extends Service { boolean prepareImmediately = intent.getBooleanExtra(EXTRA_PREPARE_IMMEDIATELY, false); sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0); //If the user asks to play External Media, the casting session, if on, should end. -// if (playable instanceof ExternalMedia) { -// castManager.disconnect(); -// } + flavorHelper.castDisconnect(playable instanceof ExternalMedia); mediaPlayer.playMediaObject(playable, stream, startWhenPrepared, prepareImmediately); } } @@ -649,14 +618,8 @@ public class PlaybackService extends Service { case MediaPlayer.MEDIA_INFO_BUFFERING_END: sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_END, 0); return true; -// case RemotePSMP.CAST_ERROR: -// sendNotificationBroadcast(NOTIFICATION_TYPE_SHOW_TOAST, resourceId); -// return true; -// case RemotePSMP.CAST_ERROR_PRIORITY_HIGH: -// Toast.makeText(PlaybackService.this, resourceId, Toast.LENGTH_SHORT).show(); -// return true; default: - return false; + return flavorHelper.onMediaPlayerInfo(PlaybackService.this, code, resourceId); } } @@ -1527,67 +1490,6 @@ public class PlaybackService extends Service { } } -// private CastConsumer castConsumer = new DefaultCastConsumer() { -// @Override -// public void onApplicationConnected(ApplicationMetadata appMetadata, String sessionId, boolean wasLaunched) { -// PlaybackService.this.onCastAppConnected(wasLaunched); -// } -// -// @Override -// public void onDisconnectionReason(int reason) { -// Log.d(TAG, "onDisconnectionReason() with code " + reason); -// // This is our final chance to update the underlying stream position -// // In onDisconnected(), the underlying CastPlayback#mVideoCastConsumer -// // is disconnected and hence we update our local value of stream position -// // to the latest position. -// if (mediaPlayer != null) { -// saveCurrentPosition(false, 0); -// infoBeforeCastDisconnection = mediaPlayer.getPSMPInfo(); -// if (reason != BaseCastManager.DISCONNECT_REASON_EXPLICIT && -// infoBeforeCastDisconnection.playerStatus == PlayerStatus.PLAYING) { -// // If it's NOT based on user action, we shouldn't automatically resume local playback -// infoBeforeCastDisconnection.playerStatus = PlayerStatus.PAUSED; -// } -// } -// } -// -// @Override -// public void onDisconnected() { -// Log.d(TAG, "onDisconnected()"); -// isCasting = false; -// PlaybackServiceMediaPlayer.PSMPInfo info = infoBeforeCastDisconnection; -// infoBeforeCastDisconnection = null; -// if (info == null && mediaPlayer != null) { -// info = mediaPlayer.getPSMPInfo(); -// } -// if (info == null) { -// info = new PlaybackServiceMediaPlayer.PSMPInfo(PlayerStatus.STOPPED, null); -// } -// switchMediaPlayer(new LocalPSMP(PlaybackService.this, mediaPlayerCallback), -// info, true); -// if (info.playable != null) { -// sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, -// info.playable.getMediaType() == MediaType.AUDIO ? EXTRA_CODE_AUDIO : EXTRA_CODE_VIDEO); -// } else { -// Log.d(TAG, "Cast session disconnected, but no current media"); -// sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0); -// } -// // hardware volume buttons control the local device volume -// mediaRouter.setMediaSessionCompat(null); -// unregisterWifiBroadcastReceiver(); -// PlayerStatus status = info.playerStatus; -// if ((status == PlayerStatus.PLAYING || -// status == PlayerStatus.SEEKING || -// status == PlayerStatus.PREPARING || -// UserPreferences.isPersistNotify()) && -// android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { -// setupNotification(info); -// } else if (!UserPreferences.isPersistNotify()){ -// stopForeground(true); -// } -// } -// }; - private final MediaSessionCompat.Callback sessionCallback = new MediaSessionCompat.Callback() { private static final String TAG = "MediaSessionCompat"; @@ -1673,101 +1575,90 @@ public class PlaybackService extends Service { } }; -// private void onCastAppConnected(boolean wasLaunched) { -// Log.d(TAG, "A cast device application was " + (wasLaunched ? "launched" : "joined")); -// isCasting = true; -// PlaybackServiceMediaPlayer.PSMPInfo info = null; -// if (mediaPlayer != null) { -// info = mediaPlayer.getPSMPInfo(); -// if (info.playerStatus == PlayerStatus.PLAYING) { -// // could be pause, but this way we make sure the new player will get the correct position, -// // since pause runs asynchronously and we could be directing the new player to play even before -// // the old player gives us back the position. -// saveCurrentPosition(false, 0); -// } -// } -// if (info == null) { -// info = new PlaybackServiceMediaPlayer.PSMPInfo(PlayerStatus.STOPPED, null); -// } -// sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, EXTRA_CODE_CAST); -// switchMediaPlayer(new RemotePSMP(PlaybackService.this, mediaPlayerCallback), -// info, -// wasLaunched); -// // hardware volume buttons control the remote device volume -// mediaRouter.setMediaSessionCompat(mediaSession); -// registerWifiBroadcastReceiver(); -// setupNotification(info); -// } - - private void switchMediaPlayer(@NonNull PlaybackServiceMediaPlayer newPlayer, - @NonNull PlaybackServiceMediaPlayer.PSMPInfo info, - boolean wasLaunched) { - if (mediaPlayer != null) { - mediaPlayer.endPlayback(true, true); - mediaPlayer.shutdownQuietly(); - } - mediaPlayer = newPlayer; - Log.d(TAG, "switched to " + mediaPlayer.getClass().getSimpleName()); - if (!wasLaunched) { - PlaybackServiceMediaPlayer.PSMPInfo candidate = mediaPlayer.getPSMPInfo(); - if (candidate.playable != null && - candidate.playerStatus.isAtLeast(PlayerStatus.PREPARING)) { - // do not automatically send new media to cast device - info.playable = null; - } + private SharedPreferences.OnSharedPreferenceChangeListener prefListener = + (sharedPreferences, key) -> { + if (UserPreferences.PREF_LOCKSCREEN_BACKGROUND.equals(key)) { + updateMediaSessionMetadata(getPlayable()); + } else { + flavorHelper.onSharedPreference(key); + } + }; + + interface FlavorHelperCallback { + PlaybackServiceMediaPlayer.PSMPCallback getMediaPlayerCallback(); + void setMediaPlayer(PlaybackServiceMediaPlayer mediaPlayer); + PlaybackServiceMediaPlayer getMediaPlayer(); + void setIsCasting(boolean isCasting); + void sendNotificationBroadcast(int type, int code); + void saveCurrentPosition(boolean updatePlayedDuration, int deltaPlayedDuration); + void setupNotification(boolean connected, PlaybackServiceMediaPlayer.PSMPInfo info); + MediaSessionCompat getMediaSession(); + Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter); + void unregisterReceiver(BroadcastReceiver receiver); + } + + private FlavorHelperCallback flavorHelperCallback = new FlavorHelperCallback() { + @Override + public PlaybackServiceMediaPlayer.PSMPCallback getMediaPlayerCallback() { + return PlaybackService.this.mediaPlayerCallback; } - if (info.playable != null) { - mediaPlayer.playMediaObject(info.playable, - !info.playable.localFileAvailable(), - info.playerStatus == PlayerStatus.PLAYING, - info.playerStatus.isAtLeast(PlayerStatus.PREPARING)); + + @Override + public void setMediaPlayer(PlaybackServiceMediaPlayer mediaPlayer) { + PlaybackService.this.mediaPlayer = mediaPlayer; } - } - private void registerWifiBroadcastReceiver() { - if (wifiBroadcastReceiver != null) { - return; + @Override + public PlaybackServiceMediaPlayer getMediaPlayer() { + return PlaybackService.this.mediaPlayer; } - wifiBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { - NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); - boolean isConnected = info.isConnected(); - //apparently this method gets called twice when a change happens, but one run is enough. - if (isConnected && !wifiConnectivity) { - wifiConnectivity = true; -// castManager.startCastDiscovery(); -// castManager.reconnectSessionIfPossible(RECONNECTION_ATTEMPT_PERIOD_S, NetworkUtils.getWifiSsid()); - } else { - wifiConnectivity = isConnected; - } + + @Override + public void setIsCasting(boolean isCasting) { + PlaybackService.isCasting = isCasting; + } + + @Override + public void sendNotificationBroadcast(int type, int code) { + PlaybackService.this.sendNotificationBroadcast(type, code); + } + + @Override + public void saveCurrentPosition(boolean updatePlayedDuration, int deltaPlayedDuration) { + PlaybackService.this.saveCurrentPosition(updatePlayedDuration, deltaPlayedDuration); + } + + @Override + public void setupNotification(boolean connected, PlaybackServiceMediaPlayer.PSMPInfo info) { + if (connected) { + PlaybackService.this.setupNotification(info); + } else { + PlayerStatus status = info.playerStatus; + if ((status == PlayerStatus.PLAYING || + status == PlayerStatus.SEEKING || + status == PlayerStatus.PREPARING || + UserPreferences.isPersistNotify()) && + android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + PlaybackService.this.setupNotification(info); + } else if (!UserPreferences.isPersistNotify()){ + PlaybackService.this.stopForeground(true); } } - }; - registerReceiver(wifiBroadcastReceiver, - new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION)); - } + } - private void unregisterWifiBroadcastReceiver() { - if (wifiBroadcastReceiver != null) { - unregisterReceiver(wifiBroadcastReceiver); - wifiBroadcastReceiver = null; + @Override + public MediaSessionCompat getMediaSession() { + return PlaybackService.this.mediaSession; } - } - private SharedPreferences.OnSharedPreferenceChangeListener prefListener = - (sharedPreferences, key) -> { -// if (UserPreferences.PREF_CAST_ENABLED.equals(key)) { -// if (!UserPreferences.isCastEnabled()) { -// if (castManager.isConnecting() || castManager.isConnected()) { -// Log.d(TAG, "Disconnecting cast device due to a change in user preferences"); -// castManager.disconnect(); -// } -// } -// } else - if (UserPreferences.PREF_LOCKSCREEN_BACKGROUND.equals(key)) { - updateMediaSessionMetadata(getPlayable()); - } + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { + return PlaybackService.this.registerReceiver(receiver, filter); + } + + @Override + public void unregisterReceiver(BroadcastReceiver receiver) { + PlaybackService.this.unregisterReceiver(receiver); + } }; } diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/Flavors.java b/core/src/main/java/de/danoeh/antennapod/core/util/Flavors.java new file mode 100644 index 000000000..5feb232e7 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/util/Flavors.java @@ -0,0 +1,24 @@ +package de.danoeh.antennapod.core.util; + +import de.danoeh.antennapod.core.BuildConfig; + +/** + * Helper class to handle the different build flavors. + */ +public enum Flavors { + FREE, + PLAY, + UNKNOWN; + + public static final Flavors FLAVOR; + + static { + if (BuildConfig.FLAVOR.equals("free")) { + FLAVOR = FREE; + } else if (BuildConfig.FLAVOR.equals("play")) { + FLAVOR = PLAY; + } else { + FLAVOR = UNKNOWN; + } + } +} diff --git a/core/src/free/java/de/danoeh/antennapod/core/util/playback/Playable.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java index bc22e063c..6459d86ed 100644 --- a/core/src/free/java/de/danoeh/antennapod/core/util/playback/Playable.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java @@ -183,9 +183,6 @@ public interface Playable extends Parcelable, case ExternalMedia.PLAYABLE_TYPE_EXTERNAL_MEDIA: result = createExternalMediaInstance(pref); break; -// case RemoteMedia.PLAYABLE_TYPE_REMOTE_MEDIA: -// result = createRemoteMediaInstance(pref); -// break; } if (result == null) { Log.e(TAG, "Could not restore Playable object from preferences"); @@ -214,12 +211,6 @@ public interface Playable extends Parcelable, } return result; } - - private static Playable createRemoteMediaInstance(SharedPreferences pref) { - //TODO there's probably no point in restoring RemoteMedia from preferences, because we - //only care about it while it's playing on the cast device. - return null; - } } class PlayableException extends Exception { diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index f4ed79937..d775c6fe5 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -403,7 +403,8 @@ <string name="pref_known_issues">Known issues</string> <string name="pref_no_browser_found">No web browser found.</string> <string name="pref_cast_title">Chromecast support</string> - <string name="pref_cast_message">Enable support for remote media playback on Cast devices (such as Chromecast, Audio Speakers or Android TV)</string> + <string name="pref_cast_message_play_flavor">Enable support for remote media playback on Cast devices (such as Chromecast, Audio Speakers or Android TV)</string> + <string name="pref_cast_message_free_flavor">Chromecast requires third party proprietary libraries that are disabled in this version of AntennaPod</string> <!-- Auto-Flattr dialog --> <string name="auto_flattr_enable">Enable automatic flattring</string> diff --git a/core/src/play/java/de/danoeh/antennapod/core/feed/FeedMediaFlavorHelper.java b/core/src/play/java/de/danoeh/antennapod/core/feed/FeedMediaFlavorHelper.java new file mode 100644 index 000000000..b89904af9 --- /dev/null +++ b/core/src/play/java/de/danoeh/antennapod/core/feed/FeedMediaFlavorHelper.java @@ -0,0 +1,12 @@ +package de.danoeh.antennapod.core.feed; + +import de.danoeh.antennapod.core.cast.RemoteMedia; + +/** + * Implements methods for FeedMedia that are flavor dependent. + */ +public class FeedMediaFlavorHelper { + static boolean instanceOfRemoteMedia(Object o) { + return o instanceof RemoteMedia; + } +} diff --git a/core/src/play/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/play/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java deleted file mode 100644 index e2d63a385..000000000 --- a/core/src/play/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java +++ /dev/null @@ -1,1780 +0,0 @@ -package de.danoeh.antennapod.core.service.playback; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.bluetooth.BluetoothA2dp; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.media.AudioManager; -import android.media.MediaPlayer; -import android.net.NetworkInfo; -import android.net.wifi.WifiManager; -import android.os.Binder; -import android.os.Build; -import android.os.IBinder; -import android.os.Vibrator; -import android.preference.PreferenceManager; -import android.support.annotation.NonNull; -import android.support.annotation.StringRes; -import android.support.v4.media.MediaMetadataCompat; -import android.support.v4.media.session.MediaSessionCompat; -import android.support.v4.media.session.PlaybackStateCompat; -import android.support.v7.app.NotificationCompat; -import android.support.v7.media.MediaRouter; -import android.text.TextUtils; -import android.util.Log; -import android.util.Pair; -import android.view.Display; -import android.view.InputDevice; -import android.view.KeyEvent; -import android.view.SurfaceHolder; -import android.view.WindowManager; -import android.widget.Toast; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.request.target.Target; -import com.google.android.gms.cast.ApplicationMetadata; -import com.google.android.libraries.cast.companionlibrary.cast.BaseCastManager; - -import java.util.List; - -import de.danoeh.antennapod.core.ClientConfig; -import de.danoeh.antennapod.core.R; -import de.danoeh.antennapod.core.cast.CastConsumer; -import de.danoeh.antennapod.core.cast.CastManager; -import de.danoeh.antennapod.core.cast.DefaultCastConsumer; -import de.danoeh.antennapod.core.feed.Chapter; -import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.feed.MediaType; -import de.danoeh.antennapod.core.glide.ApGlideSettings; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction.Action; -import de.danoeh.antennapod.core.preferences.GpodnetPreferences; -import de.danoeh.antennapod.core.preferences.PlaybackPreferences; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.receiver.MediaButtonReceiver; -import de.danoeh.antennapod.core.storage.DBTasks; -import de.danoeh.antennapod.core.storage.DBWriter; -import de.danoeh.antennapod.core.util.IntList; -import de.danoeh.antennapod.core.util.NetworkUtils; -import de.danoeh.antennapod.core.util.QueueAccess; -import de.danoeh.antennapod.core.util.flattr.FlattrUtils; -import de.danoeh.antennapod.core.util.playback.ExternalMedia; -import de.danoeh.antennapod.core.util.playback.Playable; - -/** - * Controls the MediaPlayer that plays a FeedMedia-file - */ -public class PlaybackService extends Service { - public static final String FORCE_WIDGET_UPDATE = "de.danoeh.antennapod.FORCE_WIDGET_UPDATE"; - public static final String STOP_WIDGET_UPDATE = "de.danoeh.antennapod.STOP_WIDGET_UPDATE"; - /** - * Logging tag - */ - private static final String TAG = "PlaybackService"; - - /** - * Parcelable of type Playable. - */ - public static final String EXTRA_PLAYABLE = "PlaybackService.PlayableExtra"; - /** - * True if cast session should disconnect. - */ - public static final String EXTRA_CAST_DISCONNECT = "extra.de.danoeh.antennapod.core.service.castDisconnect"; - /** - * True if media should be streamed. - */ - public static final String EXTRA_SHOULD_STREAM = "extra.de.danoeh.antennapod.core.service.shouldStream"; - /** - * True if playback should be started immediately after media has been - * prepared. - */ - public static final String EXTRA_START_WHEN_PREPARED = "extra.de.danoeh.antennapod.core.service.startWhenPrepared"; - - public static final String EXTRA_PREPARE_IMMEDIATELY = "extra.de.danoeh.antennapod.core.service.prepareImmediately"; - - public static final String ACTION_PLAYER_STATUS_CHANGED = "action.de.danoeh.antennapod.core.service.playerStatusChanged"; - public static final String EXTRA_NEW_PLAYER_STATUS = "extra.de.danoeh.antennapod.service.playerStatusChanged.newStatus"; - private static final String AVRCP_ACTION_PLAYER_STATUS_CHANGED = "com.android.music.playstatechanged"; - private static final String AVRCP_ACTION_META_CHANGED = "com.android.music.metachanged"; - - public static final String ACTION_PLAYER_NOTIFICATION = "action.de.danoeh.antennapod.core.service.playerNotification"; - public static final String EXTRA_NOTIFICATION_CODE = "extra.de.danoeh.antennapod.core.service.notificationCode"; - public static final String EXTRA_NOTIFICATION_TYPE = "extra.de.danoeh.antennapod.core.service.notificationType"; - - /** - * If the PlaybackService receives this action, it will stop playback and - * try to shutdown. - */ - public static final String ACTION_SHUTDOWN_PLAYBACK_SERVICE = "action.de.danoeh.antennapod.core.service.actionShutdownPlaybackService"; - - /** - * If the PlaybackService receives this action, it will end playback of the - * current episode and load the next episode if there is one available. - */ - public static final String ACTION_SKIP_CURRENT_EPISODE = "action.de.danoeh.antennapod.core.service.skipCurrentEpisode"; - - /** - * If the PlaybackService receives this action, it will pause playback. - */ - public static final String ACTION_PAUSE_PLAY_CURRENT_EPISODE = "action.de.danoeh.antennapod.core.service.pausePlayCurrentEpisode"; - - - /** - * If the PlaybackService receives this action, it will resume playback. - */ - public static final String ACTION_RESUME_PLAY_CURRENT_EPISODE = "action.de.danoeh.antennapod.core.service.resumePlayCurrentEpisode"; - - - /** - * Used in NOTIFICATION_TYPE_RELOAD. - */ - public static final int EXTRA_CODE_AUDIO = 1; - public static final int EXTRA_CODE_VIDEO = 2; - public static final int EXTRA_CODE_CAST = 3; - - public static final int NOTIFICATION_TYPE_ERROR = 0; - public static final int NOTIFICATION_TYPE_INFO = 1; - public static final int NOTIFICATION_TYPE_BUFFER_UPDATE = 2; - - /** - * Receivers of this intent should update their information about the curently playing media - */ - public static final int NOTIFICATION_TYPE_RELOAD = 3; - /** - * The state of the sleeptimer changed. - */ - public static final int NOTIFICATION_TYPE_SLEEPTIMER_UPDATE = 4; - public static final int NOTIFICATION_TYPE_BUFFER_START = 5; - public static final int NOTIFICATION_TYPE_BUFFER_END = 6; - /** - * No more episodes are going to be played. - */ - public static final int NOTIFICATION_TYPE_PLAYBACK_END = 7; - - /** - * Playback speed has changed - */ - public static final int NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE = 8; - - /** - * Ability to set the playback speed has changed - */ - public static final int NOTIFICATION_TYPE_SET_SPEED_ABILITY_CHANGED = 9; - - /** - * Send a message to the user (with provided String resource id) - */ - public static final int NOTIFICATION_TYPE_SHOW_TOAST = 10; - - /** - * Returned by getPositionSafe() or getDurationSafe() if the playbackService - * is in an invalid state. - */ - public static final int INVALID_TIME = -1; - - /** - * Time in seconds during which the CastManager will try to reconnect to the Cast Device after - * the Wifi Connection is regained. - */ - private static final int RECONNECTION_ATTEMPT_PERIOD_S = 15; - - /** - * Is true if service is running. - */ - public static boolean isRunning = false; - /** - * Is true if service has received a valid start command. - */ - public static boolean started = false; - /** - * Is true if the service was running, but paused due to headphone disconnect - */ - public static boolean transientPause = false; - /** - * Is true if a Cast Device is connected to the service. - */ - private static volatile boolean isCasting = false; - /** - * Stores the state of the cast playback just before it disconnects. - */ - private volatile PlaybackServiceMediaPlayer.PSMPInfo infoBeforeCastDisconnection; - - private boolean wifiConnectivity = true; - private BroadcastReceiver wifiBroadcastReceiver; - - private static final int NOTIFICATION_ID = 1; - - private PlaybackServiceMediaPlayer mediaPlayer; - private PlaybackServiceTaskManager taskManager; - - private CastManager castManager; - private MediaRouter mediaRouter; - /** - * Only used for Lollipop notifications. - */ - private MediaSessionCompat mediaSession; - - private int startPosition; - - private static volatile MediaType currentMediaType = MediaType.UNKNOWN; - - private final IBinder mBinder = new LocalBinder(); - - public class LocalBinder extends Binder { - public PlaybackService getService() { - return PlaybackService.this; - } - } - - @Override - public boolean onUnbind(Intent intent) { - Log.d(TAG, "Received onUnbind event"); - return super.onUnbind(intent); - } - - /** - * Returns an intent which starts an audio- or videoplayer, depending on the - * type of media that is being played. If the playbackservice is not - * running, the type of the last played media will be looked up. - */ - public static Intent getPlayerActivityIntent(Context context) { - if (isRunning) { - return ClientConfig.playbackServiceCallbacks.getPlayerActivityIntent(context, currentMediaType, isCasting); - } else { - if (PlaybackPreferences.getCurrentEpisodeIsVideo()) { - return ClientConfig.playbackServiceCallbacks.getPlayerActivityIntent(context, MediaType.VIDEO, isCasting); - } else { - return ClientConfig.playbackServiceCallbacks.getPlayerActivityIntent(context, MediaType.AUDIO, isCasting); - } - } - } - - /** - * Same as getPlayerActivityIntent(context), but here the type of activity - * depends on the FeedMedia that is provided as an argument. - */ - public static Intent getPlayerActivityIntent(Context context, Playable media) { - MediaType mt = media.getMediaType(); - return ClientConfig.playbackServiceCallbacks.getPlayerActivityIntent(context, mt, isCasting); - } - - @Override - public void onCreate() { - super.onCreate(); - Log.d(TAG, "Service created."); - isRunning = true; - - registerReceiver(headsetDisconnected, new IntentFilter( - Intent.ACTION_HEADSET_PLUG)); - registerReceiver(shutdownReceiver, new IntentFilter( - ACTION_SHUTDOWN_PLAYBACK_SERVICE)); - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - registerReceiver(bluetoothStateUpdated, new IntentFilter( - BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)); - } - registerReceiver(audioBecomingNoisy, new IntentFilter( - AudioManager.ACTION_AUDIO_BECOMING_NOISY)); - registerReceiver(skipCurrentEpisodeReceiver, new IntentFilter( - ACTION_SKIP_CURRENT_EPISODE)); - registerReceiver(pausePlayCurrentEpisodeReceiver, new IntentFilter( - ACTION_PAUSE_PLAY_CURRENT_EPISODE)); - registerReceiver(pauseResumeCurrentEpisodeReceiver, new IntentFilter( - ACTION_RESUME_PLAY_CURRENT_EPISODE)); - taskManager = new PlaybackServiceTaskManager(this, taskManagerCallback); - - mediaRouter = MediaRouter.getInstance(getApplicationContext()); - PreferenceManager.getDefaultSharedPreferences(this) - .registerOnSharedPreferenceChangeListener(prefListener); - - ComponentName eventReceiver = new ComponentName(getApplicationContext(), - MediaButtonReceiver.class); - Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); - mediaButtonIntent.setComponent(eventReceiver); - PendingIntent buttonReceiverIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT); - - mediaSession = new MediaSessionCompat(getApplicationContext(), TAG, eventReceiver, buttonReceiverIntent); - - try { - mediaSession.setCallback(sessionCallback); - mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); - } catch (NullPointerException npe) { - // on some devices (Huawei) setting active can cause a NullPointerException - // even with correct use of the api. - // See http://stackoverflow.com/questions/31556679/android-huawei-mediassessioncompat - // and https://plus.google.com/+IanLake/posts/YgdTkKFxz7d - Log.e(TAG, "NullPointerException while setting up MediaSession"); - npe.printStackTrace(); - } - - castManager = CastManager.getInstance(); - castManager.addCastConsumer(castConsumer); - isCasting = castManager.isConnected(); - if (isCasting) { - if (UserPreferences.isCastEnabled()) { - onCastAppConnected(false); - } else { - castManager.disconnect(); - } - } else { - mediaPlayer = new LocalPSMP(this, mediaPlayerCallback); - } - - mediaSession.setActive(true); - } - - @Override - public void onDestroy() { - super.onDestroy(); - Log.d(TAG, "Service is about to be destroyed"); - isRunning = false; - started = false; - currentMediaType = MediaType.UNKNOWN; - - PreferenceManager.getDefaultSharedPreferences(this) - .unregisterOnSharedPreferenceChangeListener(prefListener); - if (mediaSession != null) { - mediaSession.release(); - } - unregisterReceiver(headsetDisconnected); - unregisterReceiver(shutdownReceiver); - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - unregisterReceiver(bluetoothStateUpdated); - } - unregisterReceiver(audioBecomingNoisy); - unregisterReceiver(skipCurrentEpisodeReceiver); - unregisterReceiver(pausePlayCurrentEpisodeReceiver); - unregisterReceiver(pauseResumeCurrentEpisodeReceiver); - castManager.removeCastConsumer(castConsumer); - unregisterWifiBroadcastReceiver(); - mediaPlayer.shutdown(); - taskManager.shutdown(); - } - - @Override - public IBinder onBind(Intent intent) { - Log.d(TAG, "Received onBind event"); - return mBinder; - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - super.onStartCommand(intent, flags, startId); - - Log.d(TAG, "OnStartCommand called"); - final int keycode = intent.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1); - final boolean castDisconnect = intent.getBooleanExtra(EXTRA_CAST_DISCONNECT, false); - final Playable playable = intent.getParcelableExtra(EXTRA_PLAYABLE); - if (keycode == -1 && playable == null && !castDisconnect) { - Log.e(TAG, "PlaybackService was started with no arguments"); - stopSelf(); - return Service.START_REDELIVER_INTENT; - } - - if ((flags & Service.START_FLAG_REDELIVERY) != 0) { - Log.d(TAG, "onStartCommand is a redelivered intent, calling stopForeground now."); - stopForeground(true); - } else { - - if (keycode != -1) { - Log.d(TAG, "Received media button event"); - handleKeycode(keycode, intent.getIntExtra(MediaButtonReceiver.EXTRA_SOURCE, - InputDevice.SOURCE_CLASS_NONE)); - } else if (castDisconnect) { - castManager.disconnect(); - } else { - started = true; - boolean stream = intent.getBooleanExtra(EXTRA_SHOULD_STREAM, - true); - boolean startWhenPrepared = intent.getBooleanExtra(EXTRA_START_WHEN_PREPARED, false); - boolean prepareImmediately = intent.getBooleanExtra(EXTRA_PREPARE_IMMEDIATELY, false); - sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0); - //If the user asks to play External Media, the casting session, if on, should end. - if (playable instanceof ExternalMedia) { - castManager.disconnect(); - } - mediaPlayer.playMediaObject(playable, stream, startWhenPrepared, prepareImmediately); - } - } - - return Service.START_REDELIVER_INTENT; - } - - /** - * Handles media button events - */ - private void handleKeycode(int keycode, int source) { - Log.d(TAG, "Handling keycode: " + keycode); - final PlaybackServiceMediaPlayer.PSMPInfo info = mediaPlayer.getPSMPInfo(); - final PlayerStatus status = info.playerStatus; - switch (keycode) { - case KeyEvent.KEYCODE_HEADSETHOOK: - case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: - if (status == PlayerStatus.PLAYING) { - mediaPlayer.pause(!UserPreferences.isPersistNotify(), true); - } else if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED) { - mediaPlayer.resume(); - } else if (status == PlayerStatus.PREPARING) { - mediaPlayer.setStartWhenPrepared(!mediaPlayer.isStartWhenPrepared()); - } else if (status == PlayerStatus.INITIALIZED) { - mediaPlayer.setStartWhenPrepared(true); - mediaPlayer.prepare(); - } - break; - case KeyEvent.KEYCODE_MEDIA_PLAY: - if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED) { - mediaPlayer.resume(); - } else if (status == PlayerStatus.INITIALIZED) { - mediaPlayer.setStartWhenPrepared(true); - mediaPlayer.prepare(); - } - break; - case KeyEvent.KEYCODE_MEDIA_PAUSE: - if (status == PlayerStatus.PLAYING) { - mediaPlayer.pause(!UserPreferences.isPersistNotify(), true); - } - - break; - case KeyEvent.KEYCODE_MEDIA_NEXT: - if(source == InputDevice.SOURCE_CLASS_NONE || - UserPreferences.shouldHardwareButtonSkip()) { - // assume the skip command comes from a notification or the lockscreen - // a >| skip button should actually skip - mediaPlayer.endPlayback(true, false); - } else { - // assume skip command comes from a (bluetooth) media button - // user actually wants to fast-forward - seekDelta(UserPreferences.getFastFowardSecs() * 1000); - } - break; - case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: - mediaPlayer.seekDelta(UserPreferences.getFastFowardSecs() * 1000); - break; - case KeyEvent.KEYCODE_MEDIA_PREVIOUS: - case KeyEvent.KEYCODE_MEDIA_REWIND: - mediaPlayer.seekDelta(-UserPreferences.getRewindSecs() * 1000); - break; - case KeyEvent.KEYCODE_MEDIA_STOP: - if (status == PlayerStatus.PLAYING) { - mediaPlayer.pause(true, true); - started = false; - } - - stopForeground(true); // gets rid of persistent notification - break; - default: - Log.d(TAG, "Unhandled key code: " + keycode); - if (info.playable != null && info.playerStatus == PlayerStatus.PLAYING) { // only notify the user about an unknown key event if it is actually doing something - String message = String.format(getResources().getString(R.string.unknown_media_key), keycode); - Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); - } - break; - } - } - - /** - * Called by a mediaplayer Activity as soon as it has prepared its - * mediaplayer. - */ - public void setVideoSurface(SurfaceHolder sh) { - Log.d(TAG, "Setting display"); - mediaPlayer.setVideoSurface(sh); - } - - /** - * Called when the surface holder of the mediaplayer has to be changed. - */ - private void resetVideoSurface() { - taskManager.cancelPositionSaver(); - mediaPlayer.resetVideoSurface(); - } - - public void notifyVideoSurfaceAbandoned() { - stopForeground(!UserPreferences.isPersistNotify()); - mediaPlayer.resetVideoSurface(); - } - - private final PlaybackServiceTaskManager.PSTMCallback taskManagerCallback = new PlaybackServiceTaskManager.PSTMCallback() { - @Override - public void positionSaverTick() { - saveCurrentPosition(true, PlaybackServiceTaskManager.POSITION_SAVER_WAITING_INTERVAL); - } - - @Override - public void onSleepTimerAlmostExpired() { - float leftVolume = 0.1f * UserPreferences.getLeftVolume(); - float rightVolume = 0.1f * UserPreferences.getRightVolume(); - mediaPlayer.setVolume(leftVolume, rightVolume); - } - - @Override - public void onSleepTimerExpired() { - mediaPlayer.pause(true, true); - float leftVolume = UserPreferences.getLeftVolume(); - float rightVolume = UserPreferences.getRightVolume(); - mediaPlayer.setVolume(leftVolume, rightVolume); - sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); - } - - @Override - public void onSleepTimerReset() { - float leftVolume = UserPreferences.getLeftVolume(); - float rightVolume = UserPreferences.getRightVolume(); - mediaPlayer.setVolume(leftVolume, rightVolume); - } - - @Override - public void onWidgetUpdaterTick() { - updateWidget(); - } - - @Override - public void onChapterLoaded(Playable media) { - sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0); - } - }; - - private final PlaybackServiceMediaPlayer.PSMPCallback mediaPlayerCallback = new PlaybackServiceMediaPlayer.PSMPCallback() { - @Override - public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) { - currentMediaType = mediaPlayer.getCurrentMediaType(); - updateMediaSession(newInfo.playerStatus); - switch (newInfo.playerStatus) { - case INITIALIZED: - writePlaybackPreferences(); - break; - - case PREPARED: - taskManager.startChapterLoader(newInfo.playable); - break; - - case PAUSED: - taskManager.cancelPositionSaver(); - saveCurrentPosition(false, 0); - taskManager.cancelWidgetUpdater(); - if ((UserPreferences.isPersistNotify() || isCasting) && - android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - // do not remove notification on pause based on user pref and whether android version supports expanded notifications - // Change [Play] button to [Pause] - setupNotification(newInfo); - } else if (!UserPreferences.isPersistNotify() && !isCasting) { - // remove notification on pause - stopForeground(true); - } - writePlayerStatusPlaybackPreferences(); - - final Playable playable = newInfo.playable; - - // Gpodder: send play action - if(GpodnetPreferences.loggedIn() && playable instanceof FeedMedia) { - FeedMedia media = (FeedMedia) playable; - FeedItem item = media.getItem(); - GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, Action.PLAY) - .currentDeviceId() - .currentTimestamp() - .started(startPosition / 1000) - .position(getCurrentPosition() / 1000) - .total(getDuration() / 1000) - .build(); - GpodnetPreferences.enqueueEpisodeAction(action); - } - break; - - case STOPPED: - //setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING); - //stopSelf(); - break; - - case PLAYING: - Log.d(TAG, "Audiofocus successfully requested"); - Log.d(TAG, "Resuming/Starting playback"); - - taskManager.startPositionSaver(); - taskManager.startWidgetUpdater(); - writePlayerStatusPlaybackPreferences(); - setupNotification(newInfo); - started = true; - startPosition = mediaPlayer.getPosition(); - break; - - case ERROR: - writePlaybackPreferencesNoMediaPlaying(); - break; - - } - - Intent statusUpdate = new Intent(ACTION_PLAYER_STATUS_CHANGED); - // statusUpdate.putExtra(EXTRA_NEW_PLAYER_STATUS, newInfo.playerStatus.ordinal()); - sendBroadcast(statusUpdate); - updateWidget(); - bluetoothNotifyChange(newInfo, AVRCP_ACTION_PLAYER_STATUS_CHANGED); - bluetoothNotifyChange(newInfo, AVRCP_ACTION_META_CHANGED); - } - - @Override - public void shouldStop() { - stopSelf(); - } - - @Override - public void playbackSpeedChanged(float s) { - sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE, 0); - } - - public void setSpeedAbilityChanged() { - sendNotificationBroadcast(NOTIFICATION_TYPE_SET_SPEED_ABILITY_CHANGED, 0); - } - - @Override - public void onBufferingUpdate(int percent) { - sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_UPDATE, percent); - } - - @Override - public void onMediaChanged(boolean reloadUI) { - Log.d(TAG, "reloadUI callback reached"); - if (reloadUI) { - sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0); - } - PlaybackService.this.updateMediaSessionMetadata(getPlayable()); - } - - @Override - public boolean onMediaPlayerInfo(int code, @StringRes int resourceId) { - switch (code) { - case MediaPlayer.MEDIA_INFO_BUFFERING_START: - sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_START, 0); - return true; - case MediaPlayer.MEDIA_INFO_BUFFERING_END: - sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_END, 0); - return true; - case RemotePSMP.CAST_ERROR: - sendNotificationBroadcast(NOTIFICATION_TYPE_SHOW_TOAST, resourceId); - return true; - case RemotePSMP.CAST_ERROR_PRIORITY_HIGH: - Toast.makeText(PlaybackService.this, resourceId, Toast.LENGTH_SHORT).show(); - return true; - default: - return false; - } - } - - @Override - public boolean onMediaPlayerError(Object inObj, int what, int extra) { - final String TAG = "PlaybackSvc.onErrorLtsn"; - Log.w(TAG, "An error has occured: " + what + " " + extra); - if (mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING) { - mediaPlayer.pause(true, false); - } - sendNotificationBroadcast(NOTIFICATION_TYPE_ERROR, what); - writePlaybackPreferencesNoMediaPlaying(); - stopSelf(); - return true; - } - - @Override - public boolean endPlayback(Playable media, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { - PlaybackService.this.endPlayback(media, playNextEpisode, wasSkipped, switchingPlayers); - return true; - } - }; - - private void endPlayback(final Playable playable, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { - Log.d(TAG, "Playback ended" + (switchingPlayers ? " from switching players": "")); - - if (playable == null) { - Log.e(TAG, "Cannot end playback: media was null"); - return; - } - - taskManager.cancelPositionSaver(); - - boolean isInQueue = false; - FeedItem nextItem = null; - - if (playable instanceof FeedMedia && ((FeedMedia) playable).getItem() != null) { - FeedMedia media = (FeedMedia) playable; - FeedItem item = media.getItem(); - - if (!switchingPlayers) { - try { - final List<FeedItem> queue = taskManager.getQueue(); - isInQueue = QueueAccess.ItemListAccess(queue).contains(item.getId()); - nextItem = DBTasks.getQueueSuccessorOfItem(item.getId(), queue); - } catch (InterruptedException e) { - e.printStackTrace(); - // isInQueue remains false - } - - boolean shouldKeep = wasSkipped && UserPreferences.shouldSkipKeepEpisode(); - - if (!shouldKeep) { - // only mark the item as played if we're not keeping it anyways - DBWriter.markItemPlayed(item, FeedItem.PLAYED, true); - - if (isInQueue) { - DBWriter.removeQueueItem(PlaybackService.this, item, true); - } - - // Delete episode if enabled - if (item.getFeed().getPreferences().getCurrentAutoDelete()) { - DBWriter.deleteFeedMediaOfItem(PlaybackService.this, media.getId()); - Log.d(TAG, "Episode Deleted"); - } - } - } - - - DBWriter.addItemToPlaybackHistory(media); - - // auto-flattr if enabled - if (isAutoFlattrable(media) && UserPreferences.getAutoFlattrPlayedDurationThreshold() == 1.0f) { - DBTasks.flattrItemIfLoggedIn(PlaybackService.this, item); - } - - // gpodder play action - if(GpodnetPreferences.loggedIn()) { - GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, Action.PLAY) - .currentDeviceId() - .currentTimestamp() - .started(startPosition / 1000) - .position(getDuration() / 1000) - .total(getDuration() / 1000) - .build(); - GpodnetPreferences.enqueueEpisodeAction(action); - } - } - - if (!switchingPlayers) { - // Load next episode if previous episode was in the queue and if there - // is an episode in the queue left. - // Start playback immediately if continuous playback is enabled - Playable nextMedia = null; - boolean loadNextItem = ClientConfig.playbackServiceCallbacks.useQueue() && - isInQueue && - nextItem != null; - - playNextEpisode = playNextEpisode && - loadNextItem && - UserPreferences.isFollowQueue(); - - if (loadNextItem) { - Log.d(TAG, "Loading next item in queue"); - nextMedia = nextItem.getMedia(); - } - final boolean prepareImmediately; - final boolean startWhenPrepared; - final boolean stream; - - if (playNextEpisode) { - Log.d(TAG, "Playback of next episode will start immediately."); - prepareImmediately = startWhenPrepared = true; - } else { - Log.d(TAG, "No more episodes available to play"); - prepareImmediately = startWhenPrepared = false; - stopForeground(true); - stopWidgetUpdater(); - } - - writePlaybackPreferencesNoMediaPlaying(); - if (nextMedia != null) { - stream = !nextMedia.localFileAvailable(); - mediaPlayer.playMediaObject(nextMedia, stream, startWhenPrepared, prepareImmediately); - sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, - isCasting ? EXTRA_CODE_CAST : - (nextMedia.getMediaType() == MediaType.VIDEO) ? EXTRA_CODE_VIDEO : EXTRA_CODE_AUDIO); - } else { - sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0); - mediaPlayer.stop(); - //stopSelf(); - } - } - } - - public void setSleepTimer(long waitingTime, boolean shakeToReset, boolean vibrate) { - Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime) + " milliseconds"); - taskManager.setSleepTimer(waitingTime, shakeToReset, vibrate); - sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); - } - - public void disableSleepTimer() { - taskManager.disableSleepTimer(); - sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); - } - - private void writePlaybackPreferencesNoMediaPlaying() { - SharedPreferences.Editor editor = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()).edit(); - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putInt( - PlaybackPreferences.PREF_CURRENT_PLAYER_STATUS, - PlaybackPreferences.PLAYER_STATUS_OTHER); - editor.commit(); - } - - private int getCurrentPlayerStatusAsInt(PlayerStatus playerStatus) { - int playerStatusAsInt; - switch (playerStatus) { - case PLAYING: - playerStatusAsInt = PlaybackPreferences.PLAYER_STATUS_PLAYING; - break; - case PAUSED: - playerStatusAsInt = PlaybackPreferences.PLAYER_STATUS_PAUSED; - break; - default: - playerStatusAsInt = PlaybackPreferences.PLAYER_STATUS_OTHER; - } - return playerStatusAsInt; - } - - private void writePlaybackPreferences() { - Log.d(TAG, "Writing playback preferences"); - - SharedPreferences.Editor editor = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()).edit(); - PlaybackServiceMediaPlayer.PSMPInfo info = mediaPlayer.getPSMPInfo(); - MediaType mediaType = mediaPlayer.getCurrentMediaType(); - boolean stream = mediaPlayer.isStreaming(); - int playerStatus = getCurrentPlayerStatusAsInt(info.playerStatus); - - if (info.playable != null) { - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, - info.playable.getPlayableType()); - editor.putBoolean( - PlaybackPreferences.PREF_CURRENT_EPISODE_IS_STREAM, - stream); - editor.putBoolean( - PlaybackPreferences.PREF_CURRENT_EPISODE_IS_VIDEO, - mediaType == MediaType.VIDEO); - if (info.playable instanceof FeedMedia) { - FeedMedia fMedia = (FeedMedia) info.playable; - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - fMedia.getItem().getFeed().getId()); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, - fMedia.getId()); - } else { - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - } - info.playable.writeToPreferences(editor); - } else { - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - } - editor.putInt( - PlaybackPreferences.PREF_CURRENT_PLAYER_STATUS, playerStatus); - - editor.commit(); - } - - private void writePlayerStatusPlaybackPreferences() { - Log.d(TAG, "Writing player status playback preferences"); - - SharedPreferences.Editor editor = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()).edit(); - int playerStatus = getCurrentPlayerStatusAsInt(mediaPlayer.getPlayerStatus()); - - editor.putInt( - PlaybackPreferences.PREF_CURRENT_PLAYER_STATUS, playerStatus); - - editor.commit(); - } - - /** - * Send ACTION_PLAYER_STATUS_CHANGED without changing the status attribute. - */ - private void postStatusUpdateIntent() { - sendBroadcast(new Intent(ACTION_PLAYER_STATUS_CHANGED)); - } - - private void sendNotificationBroadcast(int type, int code) { - Intent intent = new Intent(ACTION_PLAYER_NOTIFICATION); - intent.putExtra(EXTRA_NOTIFICATION_TYPE, type); - intent.putExtra(EXTRA_NOTIFICATION_CODE, code); - sendBroadcast(intent); - } - - /** - * Updates the Media Session for the corresponding status. - * @param playerStatus the current {@link PlayerStatus} - */ - private void updateMediaSession(final PlayerStatus playerStatus) { - PlaybackStateCompat.Builder sessionState = new PlaybackStateCompat.Builder(); - - int state; - if (playerStatus != null) { - switch (playerStatus) { - case PLAYING: - state = PlaybackStateCompat.STATE_PLAYING; - break; - case PREPARED: - case PAUSED: - state = PlaybackStateCompat.STATE_PAUSED; - break; - case STOPPED: - state = PlaybackStateCompat.STATE_STOPPED; - break; - case SEEKING: - state = PlaybackStateCompat.STATE_FAST_FORWARDING; - break; - case PREPARING: - case INITIALIZING: - state = PlaybackStateCompat.STATE_CONNECTING; - break; - case INITIALIZED: - case INDETERMINATE: - state = PlaybackStateCompat.STATE_NONE; - break; - case ERROR: - state = PlaybackStateCompat.STATE_ERROR; - break; - default: - state = PlaybackStateCompat.STATE_NONE; - break; - } - } else { - state = PlaybackStateCompat.STATE_NONE; - } - sessionState.setState(state, mediaPlayer.getPosition(), mediaPlayer.getPlaybackSpeed()); - sessionState.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE - | PlaybackStateCompat.ACTION_REWIND - | PlaybackStateCompat.ACTION_FAST_FORWARD - | PlaybackStateCompat.ACTION_SKIP_TO_NEXT); - mediaSession.setPlaybackState(sessionState.build()); - } - - /** - * Used by updateMediaSessionMetadata to load notification data in another thread. - */ - private Thread mediaSessionSetupThread; - - private void updateMediaSessionMetadata(final Playable p) { - if (p == null || mediaSession == null) { - return; - } - if (mediaSessionSetupThread != null) { - mediaSessionSetupThread.interrupt(); - } - - Runnable mediaSessionSetupTask = () -> { - MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder(); - builder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, p.getFeedTitle()); - builder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, p.getEpisodeTitle()); - builder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, p.getDuration()); - builder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, p.getEpisodeTitle()); - builder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, p.getFeedTitle()); - - if (p.getImageLocation() != null && UserPreferences.setLockscreenBackground()) { - builder.putString(MediaMetadataCompat.METADATA_KEY_ART_URI, p.getImageLocation().toString()); - try { - if (isCasting) { - Bitmap art = Glide.with(this) - .load(p.getImageLocation()) - .asBitmap() - .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) - .into(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) - .get(); - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, art); - } else { - WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); - Display display = wm.getDefaultDisplay(); - Bitmap art = Glide.with(this) - .load(p.getImageLocation()) - .asBitmap() - .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) - .centerCrop() - .into(display.getWidth(), display.getHeight()) - .get(); - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, art); - } - } catch (Throwable tr) { - Log.e(TAG, Log.getStackTraceString(tr)); - } - } - if (!Thread.currentThread().isInterrupted() && started) { - mediaSession.setMetadata(builder.build()); - } - }; - - mediaSessionSetupThread = new Thread(mediaSessionSetupTask); - mediaSessionSetupThread.start(); - } - - /** - * Used by setupNotification to load notification data in another thread. - */ - private Thread notificationSetupThread; - - /** - * Prepares notification and starts the service in the foreground. - */ - private void setupNotification(final PlaybackServiceMediaPlayer.PSMPInfo info) { - final PendingIntent pIntent = PendingIntent.getActivity(this, 0, - PlaybackService.getPlayerActivityIntent(this), - PendingIntent.FLAG_UPDATE_CURRENT); - - if (notificationSetupThread != null) { - notificationSetupThread.interrupt(); - } - Runnable notificationSetupTask = new Runnable() { - Bitmap icon = null; - - @Override - public void run() { - Log.d(TAG, "Starting background work"); - if (android.os.Build.VERSION.SDK_INT >= 11) { - if (info.playable != null) { - int iconSize = getResources().getDimensionPixelSize( - android.R.dimen.notification_large_icon_width); - try { - icon = Glide.with(PlaybackService.this) - .load(info.playable.getImageLocation()) - .asBitmap() - .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) - .centerCrop() - .into(iconSize, iconSize) - .get(); - } catch (Throwable tr) { - Log.e(TAG, "Error loading the media icon for the notification", tr); - } - } - } - if (icon == null) { - icon = BitmapFactory.decodeResource(getApplicationContext().getResources(), - ClientConfig.playbackServiceCallbacks.getNotificationIconResource(getApplicationContext())); - } - - if (mediaPlayer == null) { - return; - } - PlayerStatus playerStatus = mediaPlayer.getPlayerStatus(); - final int smallIcon = ClientConfig.playbackServiceCallbacks.getNotificationIconResource(getApplicationContext()); - - if (!Thread.currentThread().isInterrupted() && started && info.playable != null) { - String contentText = info.playable.getEpisodeTitle(); - String contentTitle = info.playable.getFeedTitle(); - Notification notification; - - // Builder is v7, even if some not overwritten methods return its parent's v4 interface - NotificationCompat.Builder notificationBuilder = (NotificationCompat.Builder) new NotificationCompat.Builder( - PlaybackService.this) - .setContentTitle(contentTitle) - .setContentText(contentText) - .setOngoing(false) - .setContentIntent(pIntent) - .setLargeIcon(icon) - .setSmallIcon(smallIcon) - .setWhen(0) // we don't need the time - .setPriority(UserPreferences.getNotifyPriority()); // set notification priority - IntList compactActionList = new IntList(); - - int numActions = 0; // we start and 0 and then increment by 1 for each call to addAction - - if (isCasting) { - Intent stopCastingIntent = new Intent(PlaybackService.this, PlaybackService.class); - stopCastingIntent.putExtra(EXTRA_CAST_DISCONNECT, true); - PendingIntent stopCastingPendingIntent = PendingIntent.getService(PlaybackService.this, - numActions, stopCastingIntent, PendingIntent.FLAG_UPDATE_CURRENT); - notificationBuilder.addAction(R.drawable.ic_media_cast_disconnect, - getString(R.string.cast_disconnect_label), - stopCastingPendingIntent); - numActions++; - } - - // always let them rewind - PendingIntent rewindButtonPendingIntent = getPendingIntentForMediaAction( - KeyEvent.KEYCODE_MEDIA_REWIND, numActions); - notificationBuilder.addAction(android.R.drawable.ic_media_rew, - getString(R.string.rewind_label), - rewindButtonPendingIntent); - if(UserPreferences.showRewindOnCompactNotification()) { - compactActionList.add(numActions); - } - numActions++; - - if (playerStatus == PlayerStatus.PLAYING) { - PendingIntent pauseButtonPendingIntent = getPendingIntentForMediaAction( - KeyEvent.KEYCODE_MEDIA_PAUSE, numActions); - notificationBuilder.addAction(android.R.drawable.ic_media_pause, //pause action - getString(R.string.pause_label), - pauseButtonPendingIntent); - compactActionList.add(numActions++); - } else { - PendingIntent playButtonPendingIntent = getPendingIntentForMediaAction( - KeyEvent.KEYCODE_MEDIA_PLAY, numActions); - notificationBuilder.addAction(android.R.drawable.ic_media_play, //play action - getString(R.string.play_label), - playButtonPendingIntent); - compactActionList.add(numActions++); - } - - // ff follows play, then we have skip (if it's present) - PendingIntent ffButtonPendingIntent = getPendingIntentForMediaAction( - KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, numActions); - notificationBuilder.addAction(android.R.drawable.ic_media_ff, - getString(R.string.fast_forward_label), - ffButtonPendingIntent); - if(UserPreferences.showFastForwardOnCompactNotification()) { - compactActionList.add(numActions); - } - numActions++; - - if (UserPreferences.isFollowQueue()) { - PendingIntent skipButtonPendingIntent = getPendingIntentForMediaAction( - KeyEvent.KEYCODE_MEDIA_NEXT, numActions); - notificationBuilder.addAction(android.R.drawable.ic_media_next, - getString(R.string.skip_episode_label), - skipButtonPendingIntent); - if(UserPreferences.showSkipOnCompactNotification()) { - compactActionList.add(numActions); - } - numActions++; - } - - PendingIntent stopButtonPendingIntent = getPendingIntentForMediaAction( - KeyEvent.KEYCODE_MEDIA_STOP, numActions); - notificationBuilder.setStyle(new android.support.v7.app.NotificationCompat.MediaStyle() - .setMediaSession(mediaSession.getSessionToken()) - .setShowActionsInCompactView(compactActionList.toArray()) - .setShowCancelButton(true) - .setCancelButtonIntent(stopButtonPendingIntent)) - .setVisibility(Notification.VISIBILITY_PUBLIC) - .setColor(Notification.COLOR_DEFAULT); - - notification = notificationBuilder.build(); - - if (playerStatus == PlayerStatus.PLAYING || - playerStatus == PlayerStatus.PREPARING || - playerStatus == PlayerStatus.SEEKING || - isCasting) { - startForeground(NOTIFICATION_ID, notification); - } else { - stopForeground(false); - NotificationManager mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - mNotificationManager.notify(NOTIFICATION_ID, notification); - } - Log.d(TAG, "Notification set up"); - } - } - }; - notificationSetupThread = new Thread(notificationSetupTask); - notificationSetupThread.start(); - } - - private PendingIntent getPendingIntentForMediaAction(int keycodeValue, int requestCode) { - Intent intent = new Intent( - PlaybackService.this, PlaybackService.class); - intent.putExtra( - MediaButtonReceiver.EXTRA_KEYCODE, - keycodeValue); - return PendingIntent - .getService(PlaybackService.this, requestCode, - intent, - PendingIntent.FLAG_UPDATE_CURRENT); - } - - /** - * Persists the current position and last played time of the media file. - * - * @param updatePlayedDuration true if played_duration should be updated. This applies only to FeedMedia objects - * @param deltaPlayedDuration value by which played_duration should be increased. - */ - private synchronized void saveCurrentPosition(boolean updatePlayedDuration, int deltaPlayedDuration) { - int position = getCurrentPosition(); - int duration = getDuration(); - float playbackSpeed = getCurrentPlaybackSpeed(); - final Playable playable = mediaPlayer.getPlayable(); - if (position != INVALID_TIME && duration != INVALID_TIME && playable != null) { - Log.d(TAG, "Saving current position to " + position); - if (updatePlayedDuration && playable instanceof FeedMedia) { - FeedMedia media = (FeedMedia) playable; - FeedItem item = media.getItem(); - media.setPlayedDuration(media.getPlayedDuration() + ((int) (deltaPlayedDuration * playbackSpeed))); - // Auto flattr - if (isAutoFlattrable(media) && - (media.getPlayedDuration() > UserPreferences.getAutoFlattrPlayedDurationThreshold() * duration)) { - Log.d(TAG, "saveCurrentPosition: performing auto flattr since played duration " + Integer.toString(media.getPlayedDuration()) - + " is " + UserPreferences.getAutoFlattrPlayedDurationThreshold() * 100 + "% of file duration " + Integer.toString(duration)); - DBTasks.flattrItemIfLoggedIn(this, item); - } - } - playable.saveCurrentPosition( - PreferenceManager.getDefaultSharedPreferences(getApplicationContext()), - position, - System.currentTimeMillis()); - } - } - - private void stopWidgetUpdater() { - taskManager.cancelWidgetUpdater(); - sendBroadcast(new Intent(STOP_WIDGET_UPDATE)); - } - - private void updateWidget() { - PlaybackService.this.sendBroadcast(new Intent( - FORCE_WIDGET_UPDATE)); - } - - public boolean sleepTimerActive() { - return taskManager.isSleepTimerActive(); - } - - public long getSleepTimerTimeLeft() { - return taskManager.getSleepTimerTimeLeft(); - } - - private void bluetoothNotifyChange(PlaybackServiceMediaPlayer.PSMPInfo info, String whatChanged) { - boolean isPlaying = false; - - if (info.playerStatus == PlayerStatus.PLAYING) { - isPlaying = true; - } - - if (info.playable != null) { - Intent i = new Intent(whatChanged); - i.putExtra("id", 1); - i.putExtra("artist", ""); - i.putExtra("album", info.playable.getFeedTitle()); - i.putExtra("track", info.playable.getEpisodeTitle()); - i.putExtra("playing", isPlaying); - final List<FeedItem> queue = taskManager.getQueueIfLoaded(); - if (queue != null) { - i.putExtra("ListSize", queue.size()); - } - i.putExtra("duration", info.playable.getDuration()); - i.putExtra("position", info.playable.getPosition()); - sendBroadcast(i); - } - } - - /** - * Pauses playback when the headset is disconnected and the preference is - * set - */ - private final BroadcastReceiver headsetDisconnected = new BroadcastReceiver() { - private static final String TAG = "headsetDisconnected"; - private static final int UNPLUGGED = 0; - private static final int PLUGGED = 1; - - @Override - public void onReceive(Context context, Intent intent) { - if (TextUtils.equals(intent.getAction(), Intent.ACTION_HEADSET_PLUG)) { - int state = intent.getIntExtra("state", -1); - if (state != -1) { - Log.d(TAG, "Headset plug event. State is " + state); - if (state == UNPLUGGED) { - Log.d(TAG, "Headset was unplugged during playback."); - pauseIfPauseOnDisconnect(); - } else if (state == PLUGGED) { - Log.d(TAG, "Headset was plugged in during playback."); - unpauseIfPauseOnDisconnect(false); - } - } else { - Log.e(TAG, "Received invalid ACTION_HEADSET_PLUG intent"); - } - } - } - }; - - private final BroadcastReceiver bluetoothStateUpdated = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - if (TextUtils.equals(intent.getAction(), BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) { - int state = intent.getIntExtra(BluetoothA2dp.EXTRA_STATE, -1); - if (state == BluetoothA2dp.STATE_CONNECTED) { - Log.d(TAG, "Received bluetooth connection intent"); - unpauseIfPauseOnDisconnect(true); - } - } - } - } - }; - - private final BroadcastReceiver audioBecomingNoisy = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - // sound is about to change, eg. bluetooth -> speaker - Log.d(TAG, "Pausing playback because audio is becoming noisy"); - pauseIfPauseOnDisconnect(); - } - // android.media.AUDIO_BECOMING_NOISY - }; - - /** - * Pauses playback if PREF_PAUSE_ON_HEADSET_DISCONNECT was set to true. - */ - private void pauseIfPauseOnDisconnect() { - if (UserPreferences.isPauseOnHeadsetDisconnect()) { - if (mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING) { - transientPause = true; - } - mediaPlayer.pause(!UserPreferences.isPersistNotify(), true); - } - } - - /** - * @param bluetooth true if the event for unpausing came from bluetooth - */ - private void unpauseIfPauseOnDisconnect(boolean bluetooth) { - if (transientPause) { - transientPause = false; - if (!bluetooth && UserPreferences.isUnpauseOnHeadsetReconnect()) { - mediaPlayer.resume(); - } else if (bluetooth && UserPreferences.isUnpauseOnBluetoothReconnect()){ - // let the user know we've started playback again... - Vibrator v = (Vibrator) getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE); - if(v != null) { - v.vibrate(500); - } - mediaPlayer.resume(); - } - } - } - - private final BroadcastReceiver shutdownReceiver = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - if (TextUtils.equals(intent.getAction(), ACTION_SHUTDOWN_PLAYBACK_SERVICE)) { - stopSelf(); - } - } - - }; - - private final BroadcastReceiver skipCurrentEpisodeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (TextUtils.equals(intent.getAction(), ACTION_SKIP_CURRENT_EPISODE)) { - Log.d(TAG, "Received SKIP_CURRENT_EPISODE intent"); - mediaPlayer.endPlayback(true, false); - } - } - }; - - private final BroadcastReceiver pauseResumeCurrentEpisodeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (TextUtils.equals(intent.getAction(), ACTION_RESUME_PLAY_CURRENT_EPISODE)) { - Log.d(TAG, "Received RESUME_PLAY_CURRENT_EPISODE intent"); - mediaPlayer.resume(); - } - } - }; - - private final BroadcastReceiver pausePlayCurrentEpisodeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (TextUtils.equals(intent.getAction(), ACTION_PAUSE_PLAY_CURRENT_EPISODE)) { - Log.d(TAG, "Received PAUSE_PLAY_CURRENT_EPISODE intent"); - mediaPlayer.pause(false, false); - } - } - }; - - public static MediaType getCurrentMediaType() { - return currentMediaType; - } - - public static boolean isCasting() { - return isCasting; - } - - public void resume() { - mediaPlayer.resume(); - } - - public void prepare() { - mediaPlayer.prepare(); - } - - public void pause(boolean abandonAudioFocus, boolean reinit) { - mediaPlayer.pause(abandonAudioFocus, reinit); - } - - public void reinit() { - mediaPlayer.reinit(); - } - - public PlaybackServiceMediaPlayer.PSMPInfo getPSMPInfo() { - return mediaPlayer.getPSMPInfo(); - } - - public PlayerStatus getStatus() { - return mediaPlayer.getPlayerStatus(); - } - - public Playable getPlayable() { return mediaPlayer.getPlayable(); } - - public boolean canSetSpeed() { - return mediaPlayer.canSetSpeed(); - } - - public void setSpeed(float speed) { - mediaPlayer.setSpeed(speed); - } - - public void setVolume(float leftVolume, float rightVolume) { - mediaPlayer.setVolume(leftVolume, rightVolume); - } - - public float getCurrentPlaybackSpeed() { - return mediaPlayer.getPlaybackSpeed(); - } - - public boolean canDownmix() { - return mediaPlayer.canDownmix(); - } - - public void setDownmix(boolean enable) { - mediaPlayer.setDownmix(enable); - } - - public boolean isStartWhenPrepared() { - return mediaPlayer.isStartWhenPrepared(); - } - - public void setStartWhenPrepared(boolean s) { - mediaPlayer.setStartWhenPrepared(s); - } - - - public void seekTo(final int t) { - if(mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING - && GpodnetPreferences.loggedIn()) { - final Playable playable = mediaPlayer.getPlayable(); - if (playable instanceof FeedMedia) { - FeedMedia media = (FeedMedia) playable; - FeedItem item = media.getItem(); - GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, Action.PLAY) - .currentDeviceId() - .currentTimestamp() - .started(startPosition / 1000) - .position(getCurrentPosition() / 1000) - .total(getDuration() / 1000) - .build(); - GpodnetPreferences.enqueueEpisodeAction(action); - } - } - mediaPlayer.seekTo(t); - if(mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING ) { - startPosition = t; - } - } - - - public void seekDelta(final int d) { - mediaPlayer.seekDelta(d); - } - - /** - * @see LocalPSMP#seekToChapter(de.danoeh.antennapod.core.feed.Chapter) - */ - public void seekToChapter(Chapter c) { - mediaPlayer.seekToChapter(c); - } - - /** - * call getDuration() on mediaplayer or return INVALID_TIME if player is in - * an invalid state. - */ - public int getDuration() { - return mediaPlayer.getDuration(); - } - - /** - * call getCurrentPosition() on mediaplayer or return INVALID_TIME if player - * is in an invalid state. - */ - public int getCurrentPosition() { - return mediaPlayer.getPosition(); - } - - public boolean isStreaming() { - return mediaPlayer.isStreaming(); - } - - public Pair<Integer, Integer> getVideoSize() { - return mediaPlayer.getVideoSize(); - } - - private boolean isAutoFlattrable(FeedMedia media) { - if (media != null) { - FeedItem item = media.getItem(); - return item != null && FlattrUtils.hasToken() && UserPreferences.isAutoFlattr() && item.getPaymentLink() != null && item.getFlattrStatus().getUnflattred(); - } else { - return false; - } - } - - private CastConsumer castConsumer = new DefaultCastConsumer() { - @Override - public void onApplicationConnected(ApplicationMetadata appMetadata, String sessionId, boolean wasLaunched) { - PlaybackService.this.onCastAppConnected(wasLaunched); - } - - @Override - public void onDisconnectionReason(int reason) { - Log.d(TAG, "onDisconnectionReason() with code " + reason); - // This is our final chance to update the underlying stream position - // In onDisconnected(), the underlying CastPlayback#mVideoCastConsumer - // is disconnected and hence we update our local value of stream position - // to the latest position. - if (mediaPlayer != null) { - saveCurrentPosition(false, 0); - infoBeforeCastDisconnection = mediaPlayer.getPSMPInfo(); - if (reason != BaseCastManager.DISCONNECT_REASON_EXPLICIT && - infoBeforeCastDisconnection.playerStatus == PlayerStatus.PLAYING) { - // If it's NOT based on user action, we shouldn't automatically resume local playback - infoBeforeCastDisconnection.playerStatus = PlayerStatus.PAUSED; - } - } - } - - @Override - public void onDisconnected() { - Log.d(TAG, "onDisconnected()"); - isCasting = false; - PlaybackServiceMediaPlayer.PSMPInfo info = infoBeforeCastDisconnection; - infoBeforeCastDisconnection = null; - if (info == null && mediaPlayer != null) { - info = mediaPlayer.getPSMPInfo(); - } - if (info == null) { - info = new PlaybackServiceMediaPlayer.PSMPInfo(PlayerStatus.STOPPED, null); - } - switchMediaPlayer(new LocalPSMP(PlaybackService.this, mediaPlayerCallback), - info, true); - if (info.playable != null) { - sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, - info.playable.getMediaType() == MediaType.AUDIO ? EXTRA_CODE_AUDIO : EXTRA_CODE_VIDEO); - } else { - Log.d(TAG, "Cast session disconnected, but no current media"); - sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0); - } - // hardware volume buttons control the local device volume - mediaRouter.setMediaSessionCompat(null); - unregisterWifiBroadcastReceiver(); - PlayerStatus status = info.playerStatus; - if ((status == PlayerStatus.PLAYING || - status == PlayerStatus.SEEKING || - status == PlayerStatus.PREPARING || - UserPreferences.isPersistNotify()) && - android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - setupNotification(info); - } else if (!UserPreferences.isPersistNotify()){ - stopForeground(true); - } - } - }; - - private final MediaSessionCompat.Callback sessionCallback = new MediaSessionCompat.Callback() { - - private static final String TAG = "MediaSessionCompat"; - - @Override - public void onPlay() { - Log.d(TAG, "onPlay()"); - PlayerStatus status = getStatus(); - if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED) { - resume(); - } else if (status == PlayerStatus.INITIALIZED) { - setStartWhenPrepared(true); - prepare(); - } - } - - @Override - public void onPause() { - Log.d(TAG, "onPause()"); - if (getStatus() == PlayerStatus.PLAYING) { - pause(false, true); - } - if (UserPreferences.isPersistNotify()) { - pause(false, true); - } else { - pause(true, true); - } - } - - @Override - public void onStop() { - Log.d(TAG, "onStop()"); - mediaPlayer.stop(); - } - - @Override - public void onSkipToPrevious() { - Log.d(TAG, "onSkipToPrevious()"); - seekDelta(-UserPreferences.getRewindSecs() * 1000); - } - - @Override - public void onRewind() { - Log.d(TAG, "onRewind()"); - seekDelta(-UserPreferences.getRewindSecs() * 1000); - } - - @Override - public void onFastForward() { - Log.d(TAG, "onFastForward()"); - seekDelta(UserPreferences.getFastFowardSecs() * 1000); - } - - @Override - public void onSkipToNext() { - Log.d(TAG, "onSkipToNext()"); - if(UserPreferences.shouldHardwareButtonSkip()) { - mediaPlayer.endPlayback(true, false); - } else { - seekDelta(UserPreferences.getFastFowardSecs() * 1000); - } - } - - - @Override - public void onSeekTo(long pos) { - Log.d(TAG, "onSeekTo()"); - seekTo((int) pos); - } - - @Override - public boolean onMediaButtonEvent(final Intent mediaButton) { - Log.d(TAG, "onMediaButtonEvent(" + mediaButton + ")"); - if (mediaButton != null) { - KeyEvent keyEvent = (KeyEvent) mediaButton.getExtras().get(Intent.EXTRA_KEY_EVENT); - if (keyEvent != null && - keyEvent.getAction() == KeyEvent.ACTION_DOWN && - keyEvent.getRepeatCount() == 0){ - handleKeycode(keyEvent.getKeyCode(), keyEvent.getSource()); - } - } - return false; - } - }; - - private void onCastAppConnected(boolean wasLaunched) { - Log.d(TAG, "A cast device application was " + (wasLaunched ? "launched" : "joined")); - isCasting = true; - PlaybackServiceMediaPlayer.PSMPInfo info = null; - if (mediaPlayer != null) { - info = mediaPlayer.getPSMPInfo(); - if (info.playerStatus == PlayerStatus.PLAYING) { - // could be pause, but this way we make sure the new player will get the correct position, - // since pause runs asynchronously and we could be directing the new player to play even before - // the old player gives us back the position. - saveCurrentPosition(false, 0); - } - } - if (info == null) { - info = new PlaybackServiceMediaPlayer.PSMPInfo(PlayerStatus.STOPPED, null); - } - sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, EXTRA_CODE_CAST); - switchMediaPlayer(new RemotePSMP(PlaybackService.this, mediaPlayerCallback), - info, - wasLaunched); - // hardware volume buttons control the remote device volume - mediaRouter.setMediaSessionCompat(mediaSession); - registerWifiBroadcastReceiver(); - setupNotification(info); - } - - private void switchMediaPlayer(@NonNull PlaybackServiceMediaPlayer newPlayer, - @NonNull PlaybackServiceMediaPlayer.PSMPInfo info, - boolean wasLaunched) { - if (mediaPlayer != null) { - mediaPlayer.endPlayback(true, true); - mediaPlayer.shutdownQuietly(); - } - mediaPlayer = newPlayer; - Log.d(TAG, "switched to " + mediaPlayer.getClass().getSimpleName()); - if (!wasLaunched) { - PlaybackServiceMediaPlayer.PSMPInfo candidate = mediaPlayer.getPSMPInfo(); - if (candidate.playable != null && - candidate.playerStatus.isAtLeast(PlayerStatus.PREPARING)) { - // do not automatically send new media to cast device - info.playable = null; - } - } - if (info.playable != null) { - mediaPlayer.playMediaObject(info.playable, - !info.playable.localFileAvailable(), - info.playerStatus == PlayerStatus.PLAYING, - info.playerStatus.isAtLeast(PlayerStatus.PREPARING)); - } - } - - private void registerWifiBroadcastReceiver() { - if (wifiBroadcastReceiver != null) { - return; - } - wifiBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { - NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); - boolean isConnected = info.isConnected(); - //apparently this method gets called twice when a change happens, but one run is enough. - if (isConnected && !wifiConnectivity) { - wifiConnectivity = true; - castManager.startCastDiscovery(); - castManager.reconnectSessionIfPossible(RECONNECTION_ATTEMPT_PERIOD_S, NetworkUtils.getWifiSsid()); - } else { - wifiConnectivity = isConnected; - } - } - } - }; - registerReceiver(wifiBroadcastReceiver, - new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION)); - } - - private void unregisterWifiBroadcastReceiver() { - if (wifiBroadcastReceiver != null) { - unregisterReceiver(wifiBroadcastReceiver); - wifiBroadcastReceiver = null; - } - } - - private SharedPreferences.OnSharedPreferenceChangeListener prefListener = - (sharedPreferences, key) -> { - if (UserPreferences.PREF_CAST_ENABLED.equals(key)) { - if (!UserPreferences.isCastEnabled()) { - if (castManager.isConnecting() || castManager.isConnected()) { - Log.d(TAG, "Disconnecting cast device due to a change in user preferences"); - castManager.disconnect(); - } - } - } else if (UserPreferences.PREF_LOCKSCREEN_BACKGROUND.equals(key)) { - updateMediaSessionMetadata(getPlayable()); - } - }; -} diff --git a/core/src/play/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java b/core/src/play/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java new file mode 100644 index 000000000..aef3e3c2b --- /dev/null +++ b/core/src/play/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java @@ -0,0 +1,252 @@ +package de.danoeh.antennapod.core.service.playback; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.NetworkInfo; +import android.net.wifi.WifiManager; +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; +import android.support.v7.media.MediaRouter; +import android.util.Log; +import android.widget.Toast; + +import com.google.android.gms.cast.ApplicationMetadata; +import com.google.android.libraries.cast.companionlibrary.cast.BaseCastManager; + +import de.danoeh.antennapod.core.cast.CastConsumer; +import de.danoeh.antennapod.core.cast.CastManager; +import de.danoeh.antennapod.core.cast.DefaultCastConsumer; +import de.danoeh.antennapod.core.feed.MediaType; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.util.NetworkUtils; + +/** + * Class intended to work along PlaybackService and provide support for different flavors. + */ +public class PlaybackServiceFlavorHelper { + public static final String TAG = "PlaybackSrvFlavorHelper"; + + /** + * Time in seconds during which the CastManager will try to reconnect to the Cast Device after + * the Wifi Connection is regained. + */ + private static final int RECONNECTION_ATTEMPT_PERIOD_S = 15; + /** + * Stores the state of the cast playback just before it disconnects. + */ + private volatile PlaybackServiceMediaPlayer.PSMPInfo infoBeforeCastDisconnection; + + private boolean wifiConnectivity = true; + private BroadcastReceiver wifiBroadcastReceiver; + + private CastManager castManager; + private MediaRouter mediaRouter; + private PlaybackService.FlavorHelperCallback callback; + private CastConsumer castConsumer; + + PlaybackServiceFlavorHelper(Context context, PlaybackService.FlavorHelperCallback callback) { + this.callback = callback; + mediaRouter = MediaRouter.getInstance(context.getApplicationContext()); + setCastConsumer(context); + } + + void initializeMediaPlayer(Context context) { + castManager = CastManager.getInstance(); + castManager.addCastConsumer(castConsumer); + boolean isCasting = castManager.isConnected(); + callback.setIsCasting(isCasting); + if (isCasting) { + if (UserPreferences.isCastEnabled()) { + onCastAppConnected(context, false); + } else { + castManager.disconnect(); + } + } else { + callback.setMediaPlayer(new LocalPSMP(context, callback.getMediaPlayerCallback())); + } + } + + void removeCastConsumer() { + castManager.removeCastConsumer(castConsumer); + } + + boolean castDisconnect(boolean castDisconnect) { + if (castDisconnect) { + castManager.disconnect(); + return true; + } + return false; + } + + boolean onMediaPlayerInfo(Context context, int code, @StringRes int resourceId) { + switch (code) { + case RemotePSMP.CAST_ERROR: + callback.sendNotificationBroadcast(PlaybackService.NOTIFICATION_TYPE_SHOW_TOAST, resourceId); + return true; + case RemotePSMP.CAST_ERROR_PRIORITY_HIGH: + Toast.makeText(context, resourceId, Toast.LENGTH_SHORT).show(); + return true; + default: + return false; + } + } + + private void setCastConsumer(Context context) { + castConsumer = new DefaultCastConsumer() { + @Override + public void onApplicationConnected(ApplicationMetadata appMetadata, String sessionId, boolean wasLaunched) { + onCastAppConnected(context, wasLaunched); + } + + @Override + public void onDisconnectionReason(int reason) { + Log.d(TAG, "onDisconnectionReason() with code " + reason); + // This is our final chance to update the underlying stream position + // In onDisconnected(), the underlying CastPlayback#mVideoCastConsumer + // is disconnected and hence we update our local value of stream position + // to the latest position. + PlaybackServiceMediaPlayer mediaPlayer = callback.getMediaPlayer(); + if (mediaPlayer != null) { + callback.saveCurrentPosition(false, 0); + infoBeforeCastDisconnection = mediaPlayer.getPSMPInfo(); + if (reason != BaseCastManager.DISCONNECT_REASON_EXPLICIT && + infoBeforeCastDisconnection.playerStatus == PlayerStatus.PLAYING) { + // If it's NOT based on user action, we shouldn't automatically resume local playback + infoBeforeCastDisconnection.playerStatus = PlayerStatus.PAUSED; + } + } + } + + @Override + public void onDisconnected() { + Log.d(TAG, "onDisconnected()"); + callback.setIsCasting(false); + PlaybackServiceMediaPlayer.PSMPInfo info = infoBeforeCastDisconnection; + infoBeforeCastDisconnection = null; + PlaybackServiceMediaPlayer mediaPlayer = callback.getMediaPlayer(); + if (info == null && mediaPlayer != null) { + info = mediaPlayer.getPSMPInfo(); + } + if (info == null) { + info = new PlaybackServiceMediaPlayer.PSMPInfo(PlayerStatus.STOPPED, null); + } + switchMediaPlayer(new LocalPSMP(context, callback.getMediaPlayerCallback()), + info, true); + if (info.playable != null) { + callback.sendNotificationBroadcast(PlaybackService.NOTIFICATION_TYPE_RELOAD, + info.playable.getMediaType() == MediaType.AUDIO ? + PlaybackService.EXTRA_CODE_AUDIO : PlaybackService.EXTRA_CODE_VIDEO); + } else { + Log.d(TAG, "Cast session disconnected, but no current media"); + callback.sendNotificationBroadcast(PlaybackService.NOTIFICATION_TYPE_PLAYBACK_END, 0); + } + // hardware volume buttons control the local device volume + mediaRouter.setMediaSessionCompat(null); + unregisterWifiBroadcastReceiver(); + callback.setupNotification(false, info); + } + }; + } + + private void onCastAppConnected(Context context, boolean wasLaunched) { + Log.d(TAG, "A cast device application was " + (wasLaunched ? "launched" : "joined")); + callback.setIsCasting(true); + PlaybackServiceMediaPlayer.PSMPInfo info = null; + PlaybackServiceMediaPlayer mediaPlayer = callback.getMediaPlayer(); + if (mediaPlayer != null) { + info = mediaPlayer.getPSMPInfo(); + if (info.playerStatus == PlayerStatus.PLAYING) { + // could be pause, but this way we make sure the new player will get the correct position, + // since pause runs asynchronously and we could be directing the new player to play even before + // the old player gives us back the position. + callback.saveCurrentPosition(false, 0); + } + } + if (info == null) { + info = new PlaybackServiceMediaPlayer.PSMPInfo(PlayerStatus.STOPPED, null); + } + callback.sendNotificationBroadcast(PlaybackService.NOTIFICATION_TYPE_RELOAD, + PlaybackService.EXTRA_CODE_CAST); + switchMediaPlayer(new RemotePSMP(context, callback.getMediaPlayerCallback()), + info, + wasLaunched); + // hardware volume buttons control the remote device volume + mediaRouter.setMediaSessionCompat(callback.getMediaSession()); + registerWifiBroadcastReceiver(); + callback.setupNotification(true, info); + } + + private void switchMediaPlayer(@NonNull PlaybackServiceMediaPlayer newPlayer, + @NonNull PlaybackServiceMediaPlayer.PSMPInfo info, + boolean wasLaunched) { + PlaybackServiceMediaPlayer mediaPlayer = callback.getMediaPlayer(); + if (mediaPlayer != null) { + mediaPlayer.endPlayback(true, true); + mediaPlayer.shutdownQuietly(); + } + mediaPlayer = newPlayer; + callback.setMediaPlayer(mediaPlayer); + Log.d(TAG, "switched to " + mediaPlayer.getClass().getSimpleName()); + if (!wasLaunched) { + PlaybackServiceMediaPlayer.PSMPInfo candidate = mediaPlayer.getPSMPInfo(); + if (candidate.playable != null && + candidate.playerStatus.isAtLeast(PlayerStatus.PREPARING)) { + // do not automatically send new media to cast device + info.playable = null; + } + } + if (info.playable != null) { + mediaPlayer.playMediaObject(info.playable, + !info.playable.localFileAvailable(), + info.playerStatus == PlayerStatus.PLAYING, + info.playerStatus.isAtLeast(PlayerStatus.PREPARING)); + } + } + + void registerWifiBroadcastReceiver() { + if (wifiBroadcastReceiver != null) { + return; + } + wifiBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { + NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); + boolean isConnected = info.isConnected(); + //apparently this method gets called twice when a change happens, but one run is enough. + if (isConnected && !wifiConnectivity) { + wifiConnectivity = true; + castManager.startCastDiscovery(); + castManager.reconnectSessionIfPossible(RECONNECTION_ATTEMPT_PERIOD_S, NetworkUtils.getWifiSsid()); + } else { + wifiConnectivity = isConnected; + } + } + } + }; + callback.registerReceiver(wifiBroadcastReceiver, + new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION)); + } + + void unregisterWifiBroadcastReceiver() { + if (wifiBroadcastReceiver != null) { + callback.unregisterReceiver(wifiBroadcastReceiver); + wifiBroadcastReceiver = null; + } + } + + boolean onSharedPreference(String key) { + if (UserPreferences.PREF_CAST_ENABLED.equals(key)) { + if (!UserPreferences.isCastEnabled()) { + if (castManager.isConnecting() || castManager.isConnected()) { + Log.d(TAG, "Disconnecting cast device due to a change in user preferences"); + castManager.disconnect(); + } + } + return true; + } + return false; + } +} diff --git a/core/src/play/java/de/danoeh/antennapod/core/util/playback/Playable.java b/core/src/play/java/de/danoeh/antennapod/core/util/playback/Playable.java deleted file mode 100644 index 201efbc81..000000000 --- a/core/src/play/java/de/danoeh/antennapod/core/util/playback/Playable.java +++ /dev/null @@ -1,246 +0,0 @@ -package de.danoeh.antennapod.core.util.playback; - -import android.content.Context; -import android.content.SharedPreferences; -import android.os.Parcelable; -import android.util.Log; - -import java.util.List; - -import de.danoeh.antennapod.core.asynctask.ImageResource; -import de.danoeh.antennapod.core.cast.RemoteMedia; -import de.danoeh.antennapod.core.feed.Chapter; -import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.feed.MediaType; -import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.util.ShownotesProvider; - -/** - * Interface for objects that can be played by the PlaybackService. - */ -public interface Playable extends Parcelable, - ShownotesProvider, ImageResource { - - /** - * 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. - */ - 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. - */ - 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. - */ - void loadChapterMarks(); - - /** - * Returns the title of the episode that this playable represents - */ - String getEpisodeTitle(); - - /** - * Returns a list of chapter marks or null if this Playable has no chapters. - */ - List<Chapter> getChapters(); - - /** - * Returns a link to a website that is meant to be shown in a browser - */ - String getWebsiteLink(); - - String getPaymentLink(); - - /** - * Returns the title of the feed this Playable belongs to. - */ - String getFeedTitle(); - - /** - * Returns a unique identifier, for example a file url or an ID from a - * database. - */ - Object getIdentifier(); - - /** - * Return duration of object or 0 if duration is unknown. - */ - int getDuration(); - - /** - * Return position of object or 0 if position is unknown. - */ - int getPosition(); - - /** - * Returns last time (in ms) when this playable was played or 0 - * if last played time is unknown. - */ - long getLastPlayedTime(); - - /** - * Returns the type of media. This method should return the correct value - * BEFORE loadMetadata() is called. - */ - MediaType getMediaType(); - - /** - * Returns an url to a local file that can be played or null if this file - * does not exist. - */ - String getLocalMediaUrl(); - - /** - * Returns an url to a file that can be streamed by the player or null if - * this url is not known. - */ - 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. - */ - boolean localFileAvailable(); - - /** - * Returns true if a streamable file is available. getStreamUrl MUST return - * a non-null string if this method returns true. - */ - 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. - * - * @param pref shared prefs that might be used to store this object - * @param newPosition new playback position in ms - * @param timestamp current time in ms - */ - void saveCurrentPosition(SharedPreferences pref, int newPosition, long timestamp); - - void setPosition(int newPosition); - - void setDuration(int newDuration); - - /** - * @param lastPlayedTimestamp timestamp in ms - */ - void setLastPlayedTime(long lastPlayedTimestamp); - - /** - * Is called by the PlaybackService when playback starts. - */ - void onPlaybackStart(); - - /** - * Is called by the PlaybackService when playback is completed. - */ - 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. - */ - int getPlayableType(); - - void setChapters(List<Chapter> chapters); - - /** - * Provides utility methods for Playable objects. - */ - 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) { - Playable result = null; - // ADD new Playable types here: - switch (type) { - case FeedMedia.PLAYABLE_TYPE_FEEDMEDIA: - result = createFeedMediaInstance(pref); - break; - case ExternalMedia.PLAYABLE_TYPE_EXTERNAL_MEDIA: - result = createExternalMediaInstance(pref); - break; - case RemoteMedia.PLAYABLE_TYPE_REMOTE_MEDIA: - result = createRemoteMediaInstance(pref); - break; - } - if (result == null) { - Log.e(TAG, "Could not restore Playable object from preferences"); - } - return result; - } - - private static Playable createFeedMediaInstance(SharedPreferences pref) { - Playable result = null; - long mediaId = pref.getLong(FeedMedia.PREF_MEDIA_ID, -1); - if (mediaId != -1) { - result = DBReader.getFeedMedia(mediaId); - } - return result; - } - - private static Playable createExternalMediaInstance(SharedPreferences pref) { - Playable result = null; - String source = pref.getString(ExternalMedia.PREF_SOURCE_URL, null); - String mediaType = pref.getString(ExternalMedia.PREF_MEDIA_TYPE, null); - if (source != null && mediaType != null) { - int position = pref.getInt(ExternalMedia.PREF_POSITION, 0); - long lastPlayedTime = pref.getLong(ExternalMedia.PREF_LAST_PLAYED_TIME, 0); - result = new ExternalMedia(source, MediaType.valueOf(mediaType), - position, lastPlayedTime); - } - return result; - } - - private static Playable createRemoteMediaInstance(SharedPreferences pref) { - //TODO there's probably no point in restoring RemoteMedia from preferences, because we - //only care about it while it's playing on the cast device. - return null; - } - } - - 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); - } - - } -} diff --git a/core/src/play/res/values/strings.xml b/core/src/play/res/values/strings.xml new file mode 100644 index 000000000..4fd13d2de --- /dev/null +++ b/core/src/play/res/values/strings.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="pref_cast_message">@string/pref_cast_message_play_flavor</string> +</resources> |