summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
authorDomingos Lopes <domingos86lopes@gmail.com>2016-03-21 22:08:58 -0400
committerDomingos Lopes <domingos86lopes+github@gmail.com>2016-04-23 21:39:53 -0400
commit2057a92a19e76cdbf903f238f8faaa12001138b7 (patch)
tree7e82b839d93e4be49b0c3a26547b816b2e323f45 /core
parent3a3b4bb57cf663f15efcdf18c40618cc9cdd389d (diff)
downloadAntennaPod-2057a92a19e76cdbf903f238f8faaa12001138b7.zip
Add the casting feature to PlaybackService
Diffstat (limited to 'core')
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java16
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java120
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java16
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java12
4 files changed, 144 insertions, 20 deletions
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java
index 79b1537b0..373d587fb 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java
@@ -41,7 +41,6 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
private final AudioManager audioManager;
- private volatile PlayerStatus playerStatus;
private volatile PlayerStatus statusBeforeSeeking;
private volatile IPlayer mediaPlayer;
private volatile Playable media;
@@ -647,6 +646,15 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
releaseWifiLockIfNecessary();
}
+ /**
+ * Releases internally used resources. This method should only be called when the object is not used anymore.
+ * This method is executed on an internal executor service.
+ */
+ @Override
+ public void shutdownAsync() {
+ executor.submit(this::shutdown);
+ }
+
@Override
public void setVideoSurface(final SurfaceHolder surface) {
executor.submit(() -> {
@@ -780,7 +788,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
@Override
- public void endPlayback(final boolean wasSkipped) {
+ public void endPlayback(final boolean wasSkipped, boolean switchingPlayers) {
executor.submit(() -> {
playerLock.lock();
releaseWifiLockIfNecessary();
@@ -795,7 +803,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
}
audioManager.abandonAudioFocus(audioFocusChangeListener);
- callback.endPlayback(isPlaying, wasSkipped);
+ callback.endPlayback(isPlaying, wasSkipped, switchingPlayers);
playerLock.unlock();
});
@@ -873,7 +881,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
mp -> genericOnCompletion();
private void genericOnCompletion() {
- endPlayback(false);
+ endPlayback(false, false);
}
private final MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener =
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
index 9595877bf..1c70c67ea 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
@@ -23,6 +23,7 @@ import android.preference.PreferenceManager;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
+import android.support.annotation.NonNull;
import android.support.v7.app.NotificationCompat;
import android.text.TextUtils;
import android.util.Log;
@@ -35,6 +36,11 @@ import android.view.WindowManager;
import android.widget.Toast;
import com.bumptech.glide.Glide;
+import com.google.android.gms.cast.ApplicationMetadata;
+import com.google.android.libraries.cast.companionlibrary.cast.BaseCastManager;
+import com.google.android.libraries.cast.companionlibrary.cast.VideoCastManager;
+import com.google.android.libraries.cast.companionlibrary.cast.callbacks.VideoCastConsumer;
+import com.google.android.libraries.cast.companionlibrary.cast.callbacks.VideoCastConsumerImpl;
import java.util.List;
@@ -56,6 +62,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;
/**
@@ -123,6 +130,7 @@ public class PlaybackService extends Service implements SharedPreferences.OnShar
*/
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;
@@ -171,6 +179,14 @@ public class PlaybackService extends Service implements SharedPreferences.OnShar
* 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 static final int NOTIFICATION_ID = 1;
@@ -199,6 +215,7 @@ public class PlaybackService extends Service implements SharedPreferences.OnShar
return super.onUnbind(intent);
}
+ //TODO review the general intent handling and how a casting activity can be introduced
/**
* Returns an intent which starts an audio- or videoplayer, depending on the
* type of media that is being played. If the playbackservice is not
@@ -231,6 +248,10 @@ public class PlaybackService extends Service implements SharedPreferences.OnShar
Log.d(TAG, "Service created.");
isRunning = true;
+ VideoCastManager castMgr = VideoCastManager.getInstance();
+ castMgr.addVideoCastConsumer(castConsumer);
+ isCasting = castMgr.isConnected();
+
registerReceiver(headsetDisconnected, new IntentFilter(
Intent.ACTION_HEADSET_PLUG));
registerReceiver(shutdownReceiver, new IntentFilter(
@@ -296,6 +317,7 @@ public class PlaybackService extends Service implements SharedPreferences.OnShar
unregisterReceiver(skipCurrentEpisodeReceiver);
unregisterReceiver(pausePlayCurrentEpisodeReceiver);
unregisterReceiver(pauseResumeCurrentEpisodeReceiver);
+ VideoCastManager.getInstance().removeVideoCastConsumer(castConsumer);
mediaPlayer.shutdown();
taskManager.shutdown();
}
@@ -323,6 +345,7 @@ public class PlaybackService extends Service implements SharedPreferences.OnShar
if (keycode == -1 && playable == null) {
Log.e(TAG, "PlaybackService was started with no arguments");
stopSelf();
+ return Service.START_REDELIVER_INTENT;
}
if ((flags & Service.START_FLAG_REDELIVERY) != 0) {
@@ -341,6 +364,10 @@ public class PlaybackService extends Service implements SharedPreferences.OnShar
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) {
+ VideoCastManager.getInstance().disconnect();
+ }
mediaPlayer.playMediaObject(playable, stream, startWhenPrepared, prepareImmediately);
}
}
@@ -397,7 +424,7 @@ public class PlaybackService extends Service implements SharedPreferences.OnShar
UserPreferences.shouldHardwareButtonSkip()) {
// assume the skip command comes from a notification or the lockscreen
// a >| skip button should actually skip
- mediaPlayer.endPlayback(true);
+ mediaPlayer.endPlayback(true, false);
} else {
// assume skip command comes from a (bluetooth) media button
// user actually wants to fast-forward
@@ -619,13 +646,13 @@ public class PlaybackService extends Service implements SharedPreferences.OnShar
}
@Override
- public boolean endPlayback(boolean playNextEpisode, boolean wasSkipped) {
- PlaybackService.this.endPlayback(playNextEpisode, wasSkipped);
+ public boolean endPlayback(boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
+ PlaybackService.this.endPlayback(playNextEpisode, wasSkipped, switchingPlayers);
return true;
}
};
- private void endPlayback(boolean playNextEpisode, boolean wasSkipped) {
+ private void endPlayback(boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
Log.d(TAG, "Playback ended");
final Playable playable = mediaPlayer.getPlayable();
@@ -724,9 +751,12 @@ public class PlaybackService extends Service implements SharedPreferences.OnShar
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);
+ if (!switchingPlayers) {
+ sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0);
+ }
mediaPlayer.stop();
//stopSelf();
}
@@ -1274,7 +1304,7 @@ public class PlaybackService extends Service implements SharedPreferences.OnShar
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);
+ mediaPlayer.endPlayback(true, false);
}
}
};
@@ -1487,7 +1517,7 @@ public class PlaybackService extends Service implements SharedPreferences.OnShar
public void onSkipToNext() {
Log.d(TAG, "onSkipToNext()");
if(UserPreferences.shouldHardwareButtonSkip()) {
- mediaPlayer.endPlayback(true);
+ mediaPlayer.endPlayback(true, false);
} else {
seekDelta(UserPreferences.getFastFowardSecs() * 1000);
}
@@ -1514,4 +1544,80 @@ public class PlaybackService extends Service implements SharedPreferences.OnShar
return false;
}
};
+
+ private VideoCastConsumer castConsumer = new VideoCastConsumerImpl() {
+ @Override
+ public void onApplicationConnected(ApplicationMetadata appMetadata, String sessionId, boolean wasLaunched) {
+ Log.d(TAG, "A cast device application was connected");
+ isCasting = true;
+ if (mediaPlayer != null) {
+ PlaybackServiceMediaPlayer.PSMPInfo 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
+ saveCurrentPosition(false, 0);
+ }
+ }
+ switchMediaPlayer(new RemotePSMP(PlaybackService.this, mediaPlayerCallback),
+ (mediaPlayer != null) ? mediaPlayer.getPSMPInfo() :
+ new PlaybackServiceMediaPlayer.PSMPInfo(PlayerStatus.STOPPED, null));
+ sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, EXTRA_CODE_CAST);
+ }
+
+ @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);
+ 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);
+ }
+ }
+ };
+
+ private void switchMediaPlayer(@NonNull PlaybackServiceMediaPlayer newPlayer,
+ @NonNull PlaybackServiceMediaPlayer.PSMPInfo info) {
+ if (mediaPlayer != null) {
+ mediaPlayer.endPlayback(true, true);
+ mediaPlayer.shutdownAsync();
+ }
+ mediaPlayer = newPlayer;
+ Log.d(TAG, "switched to " + mediaPlayer.getClass().getSimpleName());
+ if (info.playable != null) {
+ mediaPlayer.playMediaObject(info.playable,
+ !info.playable.localFileAvailable(),
+ info.playerStatus == PlayerStatus.PLAYING,
+ info.playerStatus.isAtLeast(PlayerStatus.PREPARING));
+ }
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java
index 736afbe3b..8ac808164 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java
@@ -176,6 +176,12 @@ public abstract class PlaybackServiceMediaPlayer {
*/
public abstract void shutdown();
+ /**
+ * Releases internally used resources. This method should only be called when the object is not used anymore.
+ * This method is executed on an internal executor service.
+ */
+ public abstract void shutdownAsync();
+
public abstract void setVideoSurface(SurfaceHolder surface);
public abstract void resetVideoSurface();
@@ -218,10 +224,10 @@ public abstract class PlaybackServiceMediaPlayer {
protected abstract void setPlayable(Playable playable);
- public abstract void endPlayback(boolean wasSkipped);
+ public abstract void endPlayback(boolean wasSkipped, boolean switchingPlayers);
/**
- * Moves the LocalPSMP into STOPPED state. This call is only valid if the player is currently in
+ * Moves the PSMP into STOPPED state. This call is only valid if the player is currently in
* INDETERMINATE state, for example after a call to endPlayback.
* This method will only take care of changing the PlayerStatus of this object! Other tasks like
* abandoning audio focus have to be done with other methods.
@@ -238,7 +244,7 @@ public abstract class PlaybackServiceMediaPlayer {
* @param newStatus The new PlayerStatus. This must not be null.
* @param newMedia The new playable object of the PSMP object. This can be null.
*/
- protected synchronized void setPlayerStatus(@NonNull PlayerStatus newStatus, Playable newMedia) {
+ protected synchronized final void setPlayerStatus(@NonNull PlayerStatus newStatus, Playable newMedia) {
Log.d(TAG, "Setting player status to " + newStatus);
this.playerStatus = newStatus;
@@ -268,13 +274,13 @@ public abstract class PlaybackServiceMediaPlayer {
boolean onMediaPlayerError(Object inObj, int what, int extra);
- boolean endPlayback(boolean playNextEpisode, boolean wasSkipped);
+ boolean endPlayback(boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers);
}
/**
* Holds information about a PSMP object.
*/
- public class PSMPInfo {
+ public static class PSMPInfo {
public PlayerStatus playerStatus;
public Playable playable;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java
index 1dd19d3d7..d3b6c96cc 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java
@@ -124,14 +124,18 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
@Override
public boolean isStreaming() {
- //TODO
- return false;
+ return true;
}
@Override
public void shutdown() {
//TODO
- super.shutdown();
+ }
+
+ @Override
+ public void shutdownAsync() {
+ //TODO
+ this.shutdown();
}
@Override
@@ -162,7 +166,7 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
}
@Override
- public void endPlayback(boolean wasSkipped) {
+ public void endPlayback(boolean wasSkipped, boolean switchingPlayers) {
//TODO
}