Add the casting feature to PlaybackService
This commit is contained in:
parent
3a3b4bb57c
commit
2057a92a19
@ -207,6 +207,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
||||
|
||||
Log.d(TAG, "onCreate()");
|
||||
StorageUtils.checkStorageAvailability(this);
|
||||
//TODO we should most likely change this if casting
|
||||
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||
|
||||
orientation = getResources().getConfiguration().orientation;
|
||||
|
@ -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 =
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user