summaryrefslogtreecommitdiff
path: root/core/src/main
diff options
context:
space:
mode:
authorMartin Fietz <Martin.Fietz@gmail.com>2016-06-04 01:36:25 +0200
committerMartin Fietz <Martin.Fietz@gmail.com>2016-06-04 01:36:25 +0200
commit3c033cc0fbaa4b1418cda330ac0287d735c621f2 (patch)
tree44d563b702ef5ebe466ca3e4953e654e220ff328 /core/src/main
parent0aaa14923c9b99c6bda8e4a72c0dd61202ae9680 (diff)
downloadAntennaPod-3c033cc0fbaa4b1418cda330ac0287d735c621f2.zip
Create one flavor with Google Cast support and one (free) without
Diffstat (limited to 'core/src/main')
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/ClientConfig.java49
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/cast/CastConsumer.java11
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/cast/CastManager.java1766
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/cast/CastUtils.java317
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/cast/DefaultCastConsumer.java10
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/cast/RemoteMedia.java357
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/cast/SwitchableMediaRouteActionProvider.java106
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java568
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java1780
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java592
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java246
11 files changed, 0 insertions, 5802 deletions
diff --git a/core/src/main/java/de/danoeh/antennapod/core/ClientConfig.java b/core/src/main/java/de/danoeh/antennapod/core/ClientConfig.java
deleted file mode 100644
index 9bbccbb82..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/ClientConfig.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package de.danoeh.antennapod.core;
-
-import android.content.Context;
-
-import de.danoeh.antennapod.core.cast.CastManager;
-import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
-import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.storage.PodDBAdapter;
-import de.danoeh.antennapod.core.util.NetworkUtils;
-
-/**
- * Stores callbacks for core classes like Services, DB classes etc. and other configuration variables.
- * Apps using the core module of AntennaPod should register implementations of all interfaces here.
- */
-public class ClientConfig {
-
- /**
- * Should be used when setting User-Agent header for HTTP-requests.
- */
- public static String USER_AGENT;
-
- public static ApplicationCallbacks applicationCallbacks;
-
- public static DownloadServiceCallbacks downloadServiceCallbacks;
-
- public static PlaybackServiceCallbacks playbackServiceCallbacks;
-
- public static GpodnetCallbacks gpodnetCallbacks;
-
- public static FlattrCallbacks flattrCallbacks;
-
- public static DBTasksCallbacks dbTasksCallbacks;
-
- private static boolean initialized = false;
-
- public static synchronized void initialize(Context context) {
- if(initialized) {
- return;
- }
- PodDBAdapter.init(context);
- UserPreferences.init(context);
- UpdateManager.init(context);
- PlaybackPreferences.init(context);
- NetworkUtils.init(context);
- CastManager.init(context);
- initialized = true;
- }
-
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/cast/CastConsumer.java b/core/src/main/java/de/danoeh/antennapod/core/cast/CastConsumer.java
deleted file mode 100644
index 213dd1875..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/cast/CastConsumer.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package de.danoeh.antennapod.core.cast;
-
-import com.google.android.libraries.cast.companionlibrary.cast.callbacks.VideoCastConsumer;
-
-public interface CastConsumer extends VideoCastConsumer{
-
- /**
- * Called when the stream's volume is changed.
- */
- void onStreamVolumeChanged(double value, boolean isMute);
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/cast/CastManager.java b/core/src/main/java/de/danoeh/antennapod/core/cast/CastManager.java
deleted file mode 100644
index 5b1fdab61..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/cast/CastManager.java
+++ /dev/null
@@ -1,1766 +0,0 @@
-/*
- * Copyright (C) 2015 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * ------------------------------------------------------------------------
- *
- * Changes made by Domingos Lopes <domingos86lopes@gmail.com>
- *
- * original can be found at http://www.github.com/googlecast/CastCompanionLibrary-android
- */
-
-package de.danoeh.antennapod.core.cast;
-
-import android.content.Context;
-import android.os.Build;
-import android.support.v4.view.MenuItemCompat;
-import android.support.v7.media.MediaRouter;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.MenuItem;
-
-import com.google.android.gms.cast.ApplicationMetadata;
-import com.google.android.gms.cast.Cast;
-import com.google.android.gms.cast.CastDevice;
-import com.google.android.gms.cast.CastMediaControlIntent;
-import com.google.android.gms.cast.CastStatusCodes;
-import com.google.android.gms.cast.MediaInfo;
-import com.google.android.gms.cast.MediaMetadata;
-import com.google.android.gms.cast.MediaQueueItem;
-import com.google.android.gms.cast.MediaStatus;
-import com.google.android.gms.cast.RemoteMediaPlayer;
-import com.google.android.gms.common.ConnectionResult;
-import com.google.android.gms.common.GoogleApiAvailability;
-import com.google.android.libraries.cast.companionlibrary.cast.BaseCastManager;
-import com.google.android.libraries.cast.companionlibrary.cast.CastConfiguration;
-import com.google.android.libraries.cast.companionlibrary.cast.MediaQueue;
-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.OnFailedListener;
-import com.google.android.libraries.cast.companionlibrary.cast.exceptions.TransientNetworkDisconnectionException;
-
-import org.json.JSONObject;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.CopyOnWriteArraySet;
-import java.util.concurrent.TimeUnit;
-
-import de.danoeh.antennapod.core.R;
-
-import static com.google.android.gms.cast.RemoteMediaPlayer.RESUME_STATE_PLAY;
-import static com.google.android.gms.cast.RemoteMediaPlayer.RESUME_STATE_UNCHANGED;
-
-/**
- * A subclass of {@link BaseCastManager} that is suitable for casting video contents (it
- * also provides a single custom data channel/namespace if an out-of-band communication is
- * needed).
- * <p>
- * Clients need to initialize this class by calling
- * {@link #init(android.content.Context)} in the Application's
- * {@code onCreate()} method. To access the (singleton) instance of this class, clients
- * need to call {@link #getInstance()}.
- * <p>This
- * class manages various states of the remote cast device. Client applications, however, can
- * complement the default behavior of this class by hooking into various callbacks that it provides
- * (see {@link CastConsumer}).
- * Since the number of these callbacks is usually much larger than what a single application might
- * be interested in, there is a no-op implementation of this interface (see
- * {@link DefaultCastConsumer}) that applications can subclass to override only those methods that
- * they are interested in. Since this library depends on the cast functionalities provided by the
- * Google Play services, the library checks to ensure that the right version of that service is
- * installed. It also provides a simple static method {@code checkGooglePlayServices()} that clients
- * can call at an early stage of their applications to provide a dialog for users if they need to
- * update/activate their Google Play Services library.
- *
- * @see CastConfiguration
- */
-public class CastManager extends BaseCastManager implements OnFailedListener {
- public static final String TAG = "CastManager";
-
- public static final String CAST_APP_ID = CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID;
-
- public static final double DEFAULT_VOLUME_STEP = 0.05;
- public static final long DEFAULT_LIVE_STREAM_DURATION_MS = TimeUnit.HOURS.toMillis(2);
- private double volumeStep = DEFAULT_VOLUME_STEP;
- private MediaQueue mediaQueue;
- private MediaStatus mediaStatus;
-
- private static CastManager INSTANCE;
- private RemoteMediaPlayer remoteMediaPlayer;
- private int state = MediaStatus.PLAYER_STATE_IDLE;
- private int idleReason;
- private final Set<CastConsumer> castConsumers = new CopyOnWriteArraySet<>();
- private long liveStreamDuration = DEFAULT_LIVE_STREAM_DURATION_MS;
- private MediaQueueItem preLoadingItem;
-
- public static final int QUEUE_OPERATION_LOAD = 1;
- public static final int QUEUE_OPERATION_INSERT_ITEMS = 2;
- public static final int QUEUE_OPERATION_UPDATE_ITEMS = 3;
- public static final int QUEUE_OPERATION_JUMP = 4;
- public static final int QUEUE_OPERATION_REMOVE_ITEM = 5;
- public static final int QUEUE_OPERATION_REMOVE_ITEMS = 6;
- public static final int QUEUE_OPERATION_REORDER = 7;
- public static final int QUEUE_OPERATION_MOVE = 8;
- public static final int QUEUE_OPERATION_APPEND = 9;
- public static final int QUEUE_OPERATION_NEXT = 10;
- public static final int QUEUE_OPERATION_PREV = 11;
- public static final int QUEUE_OPERATION_SET_REPEAT = 12;
-
- private CastManager(Context context, CastConfiguration castConfiguration) {
- super(context, castConfiguration);
- Log.d(TAG, "CastManager is instantiated");
- }
-
- public static synchronized CastManager init(Context context) {
- if (INSTANCE == null) {
- //TODO also setup dialog factory if necessary
- CastConfiguration castConfiguration = new CastConfiguration.Builder(CAST_APP_ID)
- .enableDebug()
- .enableAutoReconnect()
- .enableWifiReconnection()
- .setLaunchOptions(true, Locale.getDefault())
- .build();
- Log.d(TAG, "New instance of CastManager is created");
- if (ConnectionResult.SUCCESS != GoogleApiAvailability.getInstance()
- .isGooglePlayServicesAvailable(context)) {
- Log.e(TAG, "Couldn't find the appropriate version of Google Play Services");
- //TODO check whether creating an instance without google play services installed actually gives an exception
- }
- INSTANCE = new CastManager(context, castConfiguration);
- }
- return INSTANCE;
- }
-
- /**
- * Returns a (singleton) instance of this class. Clients should call this method in order to
- * get a hold of this singleton instance, only after it is initialized. If it is not initialized
- * yet, an {@link IllegalStateException} will be thrown.
- *
- */
- public static CastManager getInstance() {
- if (INSTANCE == null) {
- String msg = "No CastManager instance was found, did you forget to initialize it?";
- Log.e(TAG, msg);
- throw new IllegalStateException(msg);
- }
- return INSTANCE;
- }
-
- /**
- * Returns the active {@link RemoteMediaPlayer} instance. Since there are a number of media
- * control APIs that this library do not provide a wrapper for, client applications can call
- * those methods directly after obtaining an instance of the active {@link RemoteMediaPlayer}.
- */
- public final RemoteMediaPlayer getRemoteMediaPlayer() {
- return remoteMediaPlayer;
- }
-
- /**
- * Determines if the media that is loaded remotely is a live stream or not.
- *
- * @throws TransientNetworkDisconnectionException
- * @throws NoConnectionException
- */
- public final boolean isRemoteStreamLive() throws TransientNetworkDisconnectionException,
- NoConnectionException {
- checkConnectivity();
- MediaInfo info = getRemoteMediaInformation();
- return (info != null) && (info.getStreamType() == MediaInfo.STREAM_TYPE_LIVE);
- }
-
- /*
- * A simple check to make sure remoteMediaPlayer is not null
- */
- private void checkRemoteMediaPlayerAvailable() throws NoConnectionException {
- if (remoteMediaPlayer == null) {
- throw new NoConnectionException();
- }
- }
-
- /**
- * Returns the url for the media that is currently playing on the remote device. If there is no
- * connection, this will return <code>null</code>.
- *
- * @throws NoConnectionException If no connectivity to the device exists
- * @throws TransientNetworkDisconnectionException If framework is still trying to recover from
- * a possibly transient loss of network
- */
- public String getRemoteMediaUrl() throws TransientNetworkDisconnectionException,
- NoConnectionException {
- checkConnectivity();
- if (remoteMediaPlayer != null && remoteMediaPlayer.getMediaInfo() != null) {
- MediaInfo info = remoteMediaPlayer.getMediaInfo();
- remoteMediaPlayer.getMediaStatus().getPlayerState();
- return info.getContentId();
- }
- throw new NoConnectionException();
- }
-
- /**
- * Indicates if the remote media is currently playing (or buffering).
- *
- * @throws NoConnectionException
- * @throws TransientNetworkDisconnectionException
- */
- public boolean isRemoteMediaPlaying() throws TransientNetworkDisconnectionException,
- NoConnectionException {
- checkConnectivity();
- return state == MediaStatus.PLAYER_STATE_BUFFERING
- || state == MediaStatus.PLAYER_STATE_PLAYING;
- }
-
- /**
- * Returns <code>true</code> if the remote connected device is playing a movie.
- *
- * @throws NoConnectionException
- * @throws TransientNetworkDisconnectionException
- */
- public boolean isRemoteMediaPaused() throws TransientNetworkDisconnectionException,
- NoConnectionException {
- checkConnectivity();
- return state == MediaStatus.PLAYER_STATE_PAUSED;
- }
-
- /**
- * Returns <code>true</code> only if there is a media on the remote being played, paused or
- * buffered.
- *
- * @throws NoConnectionException
- * @throws TransientNetworkDisconnectionException
- */
- public boolean isRemoteMediaLoaded() throws TransientNetworkDisconnectionException,
- NoConnectionException {
- checkConnectivity();
- return isRemoteMediaPaused() || isRemoteMediaPlaying();
- }
-
- /**
- * Returns the {@link MediaInfo} for the current media
- *
- * @throws NoConnectionException If no connectivity to the device exists
- * @throws TransientNetworkDisconnectionException If framework is still trying to recover from
- * a possibly transient loss of network
- */
- public MediaInfo getRemoteMediaInformation() throws TransientNetworkDisconnectionException,
- NoConnectionException {
- checkConnectivity();
- checkRemoteMediaPlayerAvailable();
- return remoteMediaPlayer.getMediaInfo();
- }
-
- /**
- * Gets the remote's system volume. It internally detects what type of volume is used.
- *
- * @throws NoConnectionException If no connectivity to the device exists
- * @throws TransientNetworkDisconnectionException If framework is still trying to recover from
- * a possibly transient loss of network
- */
- public double getStreamVolume() throws TransientNetworkDisconnectionException, NoConnectionException {
- checkConnectivity();
- checkRemoteMediaPlayerAvailable();
- return remoteMediaPlayer.getMediaStatus().getStreamVolume();
- }
-
- /**
- * Sets the stream volume.
- *
- * @param volume Should be a value between 0 and 1, inclusive.
- * @throws NoConnectionException
- * @throws TransientNetworkDisconnectionException
- * @throws CastException If setting system volume fails
- */
- public void setStreamVolume(double volume) throws CastException,
- TransientNetworkDisconnectionException, NoConnectionException {
- checkConnectivity();
- if (volume > 1.0) {
- volume = 1.0;
- } else if (volume < 0) {
- volume = 0.0;
- }
-
- RemoteMediaPlayer mediaPlayer = getRemoteMediaPlayer();
- if (mediaPlayer == null) {
- throw new NoConnectionException();
- }
- mediaPlayer.setStreamVolume(mApiClient, volume).setResultCallback(
- (result) -> {
- if (!result.getStatus().isSuccess()) {
- onFailed(R.string.cast_failed_setting_volume,
- result.getStatus().getStatusCode());
- } else {
- CastManager.this.onStreamVolumeChanged();
- }
- });
- }
-
- /**
- * Returns <code>true</code> if remote Stream is muted.
- *
- * @throws NoConnectionException
- * @throws TransientNetworkDisconnectionException
- */
- public boolean isStreamMute() throws TransientNetworkDisconnectionException, NoConnectionException {
- checkConnectivity();
- checkRemoteMediaPlayerAvailable();
- return remoteMediaPlayer.getMediaStatus().isMute();
- }
-
- /**
- * Returns <code>true</code> if remote device is muted.
- *
- * @throws NoConnectionException
- * @throws TransientNetworkDisconnectionException
- */
- public boolean isMute() throws TransientNetworkDisconnectionException, NoConnectionException {
- return isStreamMute() || isDeviceMute();
- }
-
- /**
- * Mutes or un-mutes the stream volume.
- *
- * @throws CastException
- * @throws NoConnectionException
- * @throws TransientNetworkDisconnectionException
- */
- public void setStreamMute(boolean mute) throws CastException, TransientNetworkDisconnectionException,
- NoConnectionException {
- checkConnectivity();
- checkRemoteMediaPlayerAvailable();
- remoteMediaPlayer.setStreamMute(mApiClient, mute);
- }
-
- /**
- * Returns the duration of the media that is loaded, in milliseconds.
- *
- * @throws NoConnectionException
- * @throws TransientNetworkDisconnectionException
- */
- public long getMediaDuration() throws TransientNetworkDisconnectionException,
- NoConnectionException {
- checkConnectivity();
- checkRemoteMediaPlayerAvailable();
- return remoteMediaPlayer.getStreamDuration();
- }
-
- /**
- * Returns the time left (in milliseconds) of the current media. If there is no
- * {@code RemoteMediaPlayer}, it returns -1.
- *
- * @throws TransientNetworkDisconnectionException
- * @throws NoConnectionException
- */
- public long getMediaTimeRemaining()
- throws TransientNetworkDisconnectionException, NoConnectionException {
- checkConnectivity();
- if (remoteMediaPlayer == null) {
- return -1;
- }
- return isRemoteStreamLive() ? liveStreamDuration : remoteMediaPlayer.getStreamDuration()
- - remoteMediaPlayer.getApproximateStreamPosition();
- }
-
- /**
- * Returns the current (approximate) position of the current media, in milliseconds.
- *
- * @throws NoConnectionException
- * @throws TransientNetworkDisconnectionException
- */
- public long getCurrentMediaPosition() throws TransientNetworkDisconnectionException,
- NoConnectionException {
- checkConnectivity();
- checkRemoteMediaPlayerAvailable();
- return remoteMediaPlayer.getApproximateStreamPosition();
- }
-
- public int getApplicationStandbyState() throws IllegalStateException {
- Log.d(TAG, "getApplicationStandbyState()");
- return Cast.CastApi.getStandbyState(mApiClient);
- }
-
- private void onApplicationDisconnected(int errorCode) {
- Log.d(TAG, "onApplicationDisconnected() reached with error code: " + errorCode);
- mApplicationErrorCode = errorCode;
- for (CastConsumer consumer : castConsumers) {
- consumer.onApplicationDisconnected(errorCode);
- }
- if (mMediaRouter != null) {
- Log.d(TAG, "onApplicationDisconnected(): Cached RouteInfo: " + getRouteInfo());
- Log.d(TAG, "onApplicationDisconnected(): Selected RouteInfo: "
- + mMediaRouter.getSelectedRoute());
- if (getRouteInfo() == null || mMediaRouter.getSelectedRoute().equals(getRouteInfo())) {
- Log.d(TAG, "onApplicationDisconnected(): Setting route to default");
- mMediaRouter.selectRoute(mMediaRouter.getDefaultRoute());
- }
- }
- onDeviceSelected(null /* CastDevice */, null /* RouteInfo */);
- }
-
- private void onApplicationStatusChanged() {
- if (!isConnected()) {
- return;
- }
- try {
- String appStatus = Cast.CastApi.getApplicationStatus(mApiClient);
- Log.d(TAG, "onApplicationStatusChanged() reached: " + appStatus);
- for (CastConsumer consumer : castConsumers) {
- consumer.onApplicationStatusChanged(appStatus);
- }
- } catch (IllegalStateException e) {
- Log.e(TAG, "onApplicationStatusChanged()", e);
- }
- }
-
- private void onDeviceVolumeChanged() {
- Log.d(TAG, "onDeviceVolumeChanged() reached");
- double volume;
- try {
- volume = getDeviceVolume();
- boolean isMute = isDeviceMute();
- for (CastConsumer consumer : castConsumers) {
- consumer.onVolumeChanged(volume, isMute);
- }
- } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
- Log.e(TAG, "Failed to get volume", e);
- }
-
- }
-
- private void onStreamVolumeChanged() {
- Log.d(TAG, "onStreamVolumeChanged() reached");
- double volume;
- try {
- volume = getStreamVolume();
- boolean isMute = isStreamMute();
- for (CastConsumer consumer : castConsumers) {
- consumer.onStreamVolumeChanged(volume, isMute);
- }
- } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
- Log.e(TAG, "Failed to get volume", e);
- }
- }
-
- @Override
- protected void onApplicationConnected(ApplicationMetadata appMetadata,
- String applicationStatus, String sessionId, boolean wasLaunched) {
- Log.d(TAG, "onApplicationConnected() reached with sessionId: " + sessionId
- + ", and mReconnectionStatus=" + mReconnectionStatus);
- mApplicationErrorCode = NO_APPLICATION_ERROR;
- if (mReconnectionStatus == RECONNECTION_STATUS_IN_PROGRESS) {
- // we have tried to reconnect and successfully launched the app, so
- // it is time to select the route and make the cast icon happy :-)
- List<MediaRouter.RouteInfo> routes = mMediaRouter.getRoutes();
- if (routes != null) {
- String routeId = mPreferenceAccessor.getStringFromPreference(PREFS_KEY_ROUTE_ID);
- for (MediaRouter.RouteInfo routeInfo : routes) {
- if (routeId.equals(routeInfo.getId())) {
- // found the right route
- Log.d(TAG, "Found the correct route during reconnection attempt");
- mReconnectionStatus = RECONNECTION_STATUS_FINALIZED;
- mMediaRouter.selectRoute(routeInfo);
- break;
- }
- }
- }
- }
- try {
- //attachDataChannel();
- attachMediaChannel();
- mSessionId = sessionId;
- // saving device for future retrieval; we only save the last session info
- mPreferenceAccessor.saveStringToPreference(PREFS_KEY_SESSION_ID, mSessionId);
- remoteMediaPlayer.requestStatus(mApiClient).setResultCallback(result -> {
- if (!result.getStatus().isSuccess()) {
- onFailed(R.string.cast_failed_status_request,
- result.getStatus().getStatusCode());
- }
- });
- for (CastConsumer consumer : castConsumers) {
- consumer.onApplicationConnected(appMetadata, mSessionId, wasLaunched);
- }
- } catch (TransientNetworkDisconnectionException e) {
- Log.e(TAG, "Failed to attach media/data channel due to network issues", e);
- onFailed(R.string.cast_failed_no_connection_trans, NO_STATUS_CODE);
- } catch (NoConnectionException e) {
- Log.e(TAG, "Failed to attach media/data channel due to network issues", e);
- onFailed(R.string.cast_failed_no_connection, NO_STATUS_CODE);
- }
- }
-
- /*
- * (non-Javadoc)
- * @see com.google.android.libraries.cast.companionlibrary.cast.BaseCastManager
- * #onConnectivityRecovered()
- */
- @Override
- public void onConnectivityRecovered() {
- reattachMediaChannel();
- //reattachDataChannel();
- super.onConnectivityRecovered();
- }
-
- /*
- * (non-Javadoc)
- * @see com.google.android.gms.cast.CastClient.Listener#onApplicationStopFailed (int)
- */
- @Override
- public void onApplicationStopFailed(int errorCode) {
- for (CastConsumer consumer : castConsumers) {
- consumer.onApplicationStopFailed(errorCode);
- }
- }
-
- @Override
- public void onApplicationConnectionFailed(int errorCode) {
- Log.d(TAG, "onApplicationConnectionFailed() reached with errorCode: " + errorCode);
- mApplicationErrorCode = errorCode;
- if (mReconnectionStatus == RECONNECTION_STATUS_IN_PROGRESS) {
- if (errorCode == CastStatusCodes.APPLICATION_NOT_RUNNING) {
- // while trying to re-establish session, we found out that the app is not running
- // so we need to disconnect
- mReconnectionStatus = RECONNECTION_STATUS_INACTIVE;
- onDeviceSelected(null /* CastDevice */, null /* RouteInfo */);
- }
- } else {
- for (CastConsumer consumer : castConsumers) {
- consumer.onApplicationConnectionFailed(errorCode);
- }
- onDeviceSelected(null /* CastDevice */, null /* RouteInfo */);
- if (mMediaRouter != null) {
- Log.d(TAG, "onApplicationConnectionFailed(): Setting route to default");
- mMediaRouter.selectRoute(mMediaRouter.getDefaultRoute());
- }
- }
- }
-
- /**
- * Loads a media. For this to succeed, you need to have successfully launched the application.
- *
- * @param media The media to be loaded
- * @param autoPlay If <code>true</code>, playback starts after load
- * @param position Where to start the playback (only used if autoPlay is <code>true</code>.
- * Units is milliseconds.
- * @throws NoConnectionException
- * @throws TransientNetworkDisconnectionException
- */
- public void loadMedia(MediaInfo media, boolean autoPlay, int position)
- throws TransientNetworkDisconnectionException, NoConnectionException {
- loadMedia(media, autoPlay, position, null);
- }
-
- /**
- * Loads a media. For this to succeed, you need to have successfully launched the application.
- *
- * @param media The media to be loaded
- * @param autoPlay If <code>true</code>, playback starts after load
- * @param position Where to start the playback (only used if autoPlay is <code>true</code>).
- * Units is milliseconds.
- * @param customData Optional {@link JSONObject} data to be passed to the cast device
- * @throws NoConnectionException
- * @throws TransientNetworkDisconnectionException
- */
- public void loadMedia(MediaInfo media, boolean autoPlay, int position, JSONObject customData)
- throws TransientNetworkDisconnectionException, NoConnectionException {
- loadMedia(media, null, autoPlay, position, customData);
- }
-
- /**
- * Loads a media. For this to succeed, you need to have successfully launched the application.
- *
- * @param media The media to be loaded
- * @param activeTracks An array containing the list of track IDs to be set active for this
- * media upon a successful load
- * @param autoPlay If <code>true</code>, playback starts after load
- * @param position Where to start the playback (only used if autoPlay is <code>true</code>).
- * Units is milliseconds.
- * @param customData Optional {@link JSONObject} data to be passed to the cast device
- * @throws NoConnectionException
- * @throws TransientNetworkDisconnectionException
- */
- public void loadMedia(MediaInfo media, final long[] activeTracks, boolean autoPlay,
- int position, JSONObject customData)
- throws TransientNetworkDisconnectionException, NoConnectionException {
- Log.d(TAG, "loadMedia");
- checkConnectivity();
- if (media == null) {
- return;
- }
- if (remoteMediaPlayer == null) {
- Log.e(TAG, "Trying to load a video with no active media session");
- throw new NoConnectionException();
- }
-
- Log.d(TAG, "remoteMediaPlayer.load() with media=" + media.getMetadata().getString(MediaMetadata.KEY_TITLE)
- + ", position=" + position + ", autoplay=" + autoPlay);
- remoteMediaPlayer.load(mApiClient, media, autoPlay, position, activeTracks, customData)
- .setResultCallback(result -> {
- for (CastConsumer consumer : castConsumers) {
- consumer.onMediaLoadResult(result.getStatus().getStatusCode());
- }
- });
- }
-
- /**
- * Loads and optionally starts playback of a new queue of media items.
- *
- * @param items Array of items to load, in the order that they should be played. Must not be
- * {@code null} or empty.
- * @param startIndex The array index of the item in the {@code items} array that should be
- * played first (i.e., it will become the currentItem).If {@code repeatMode}
- * is {@link MediaStatus#REPEAT_MODE_REPEAT_OFF} playback will end when the
- * last item in the array is played.
- * <p>
- * This may be useful for continuation scenarios where the user was already
- * using the sender application and in the middle decides to cast. This lets
- * the sender application avoid mapping between the local and remote queue
- * positions and/or avoid issuing an extra request to update the queue.
- * <p>
- * This value must be less than the length of {@code items}.
- * @param repeatMode The repeat playback mode for the queue. One of
- * {@link MediaStatus#REPEAT_MODE_REPEAT_OFF},
- * {@link MediaStatus#REPEAT_MODE_REPEAT_ALL},
- * {@link MediaStatus#REPEAT_MODE_REPEAT_SINGLE} and
- * {@link MediaStatus#REPEAT_MODE_REPEAT_ALL_AND_SHUFFLE}.
- * @param customData Custom application-specific data to pass along with the request, may be
- * {@code null}.
- * @throws TransientNetworkDisconnectionException
- * @throws NoConnectionException
- */
- public void queueLoad(final MediaQueueItem[] items, final int startIndex, final int repeatMode,
- final JSONObject customData)
- throws TransientNetworkDisconnectionException, NoConnectionException {
- Log.d(TAG, "queueLoad");
- checkConnectivity();
- if (items == null || items.length == 0) {
- return;
- }
- if (remoteMediaPlayer == null) {
- Log.e(TAG, "Trying to queue one or more videos with no active media session");
- throw new NoConnectionException();
- }
- Log.d(TAG, "remoteMediaPlayer.queueLoad() with " + items.length + "items, starting at "
- + startIndex);
- remoteMediaPlayer
- .queueLoad(mApiClient, items, startIndex, repeatMode, customData)
- .setResultCallback(result -> {
- for (CastConsumer consumer : castConsumers) {
- consumer.onMediaQueueOperationResult(QUEUE_OPERATION_LOAD,
- result.getStatus().getStatusCode());
- }
- });
- }
-
- /**
- * Inserts a list of new media items into the queue.
- *
- * @param itemsToInsert List of items to insert into the queue, in the order that they should be
- * played. The itemId field of the items should be unassigned or the
- * request will fail with an INVALID_PARAMS error. Must not be {@code null}
- * or empty.
- * @param insertBeforeItemId ID of the item that will be located immediately after the inserted
- * list. If the value is {@link MediaQueueItem#INVALID_ITEM_ID} or
- * invalid, the inserted list will be appended to the end of the
- * queue.
- * @param customData Custom application-specific data to pass along with the request. May be
- * {@code null}.
- * @throws TransientNetworkDisconnectionException
- * @throws NoConnectionException
- * @throws IllegalArgumentException
- */
- public void queueInsertItems(final MediaQueueItem[] itemsToInsert, final int insertBeforeItemId,
- final JSONObject customData)
- throws TransientNetworkDisconnectionException, NoConnectionException {
- Log.d(TAG, "queueInsertItems");
- checkConnectivity();
- if (itemsToInsert == null || itemsToInsert.length == 0) {
- throw new IllegalArgumentException("items cannot be empty or null");
- }
- if (remoteMediaPlayer == null) {
- Log.e(TAG, "Trying to insert into queue with no active media session");
- throw new NoConnectionException();
- }
- remoteMediaPlayer
- .queueInsertItems(mApiClient, itemsToInsert, insertBeforeItemId, customData)
- .setResultCallback(
- result -> {
- for (CastConsumer consumer : castConsumers) {
- consumer.onMediaQueueOperationResult(
- QUEUE_OPERATION_INSERT_ITEMS,
- result.getStatus().getStatusCode());
- }
- });
- }
-
- /**
- * Updates properties of a subset of the existing items in the media queue.
- *
- * @param itemsToUpdate List of queue items to be updated. The items will retain the existing
- * order and will be fully replaced with the ones provided, including the
- * media information. Any other items currently in the queue will remain
- * unchanged. The tracks information can not change once the item is loaded
- * (if the item is the currentItem). If any of the items does not exist it
- * will be ignored.
- * @param customData Custom application-specific data to pass along with the request. May be
- * {@code null}.
- * @throws TransientNetworkDisconnectionException
- * @throws NoConnectionException
- */
- public void queueUpdateItems(final MediaQueueItem[] itemsToUpdate, final JSONObject customData)
- throws TransientNetworkDisconnectionException, NoConnectionException {
- checkConnectivity();
- if (remoteMediaPlayer == null) {
- Log.e(TAG, "Trying to update the queue with no active media session");
- throw new NoConnectionException();
- }
- remoteMediaPlayer
- .queueUpdateItems(mApiClient, itemsToUpdate, customData).setResultCallback(
- result -> {
- Log.d(TAG, "queueUpdateItems() " + result.getStatus() + result.getStatus()
- .isSuccess());
- for (CastConsumer consumer : castConsumers) {
- consumer.onMediaQueueOperationResult(QUEUE_OPERATION_UPDATE_ITEMS,
- result.getStatus().getStatusCode());
- }
- });
- }
-
- /**
- * Plays the item with {@code itemId} in the queue.
- * <p>
- * If {@code itemId} is not found in the queue, this method will report success without sending
- * a request to the receiver.
- *
- * @param itemId The ID of the item to which to jump.
- * @param customData Custom application-specific data to pass along with the request. May be
- * {@code null}.
- * @throws TransientNetworkDisconnectionException
- * @throws NoConnectionException
- * @throws IllegalArgumentException
- */
- public void queueJumpToItem(int itemId, final JSONObject customData)
- throws TransientNetworkDisconnectionException, NoConnectionException,
- IllegalArgumentException {
- checkConnectivity();
- if (itemId == MediaQueueItem.INVALID_ITEM_ID) {
- throw new IllegalArgumentException("itemId is not valid");
- }
- if (remoteMediaPlayer == null) {
- Log.e(TAG, "Trying to jump in a queue with no active media session");
- throw new NoConnectionException();
- }
- remoteMediaPlayer
- .queueJumpToItem(mApiClient, itemId, customData).setResultCallback(
- result -> {
- for (CastConsumer consumer : castConsumers) {
- consumer.onMediaQueueOperationResult(QUEUE_OPERATION_JUMP,
- result.getStatus().getStatusCode());
- }
- });
- }
-
- /**
- * Removes a list of items from the queue. If the remaining queue is empty, the media session
- * will be terminated.
- *
- * @param itemIdsToRemove The list of media item IDs to remove. Must not be {@code null} or
- * empty.
- * @param customData Custom application-specific data to pass along with the request. May be
- * {@code null}.
- * @throws TransientNetworkDisconnectionException
- * @throws NoConnectionException
- * @throws IllegalArgumentException
- */
- public void queueRemoveItems(final int[] itemIdsToRemove, final JSONObject customData)
- throws TransientNetworkDisconnectionException, NoConnectionException,
- IllegalArgumentException {
- Log.d(TAG, "queueRemoveItems");
- checkConnectivity();
- if (itemIdsToRemove == null || itemIdsToRemove.length == 0) {
- throw new IllegalArgumentException("itemIds cannot be empty or null");
- }
- if (remoteMediaPlayer == null) {
- Log.e(TAG, "Trying to remove items from queue with no active media session");
- throw new NoConnectionException();
- }
- remoteMediaPlayer
- .queueRemoveItems(mApiClient, itemIdsToRemove, customData).setResultCallback(
- result -> {
- for (CastConsumer consumer : castConsumers) {
- consumer.onMediaQueueOperationResult(QUEUE_OPERATION_REMOVE_ITEMS,
- result.getStatus().getStatusCode());
- }
- });
- }
-
- /**
- * Removes the item with {@code itemId} from the queue.
- * <p>
- * If {@code itemId} is not found in the queue, this method will silently return without sending
- * a request to the receiver. A {@code itemId} may not be in the queue because it wasn't
- * originally in the queue, or it was removed by another sender.
- *
- * @param itemId The ID of the item to be removed.
- * @param customData Custom application-specific data to pass along with the request. May be
- * {@code null}.
- * @throws TransientNetworkDisconnectionException
- * @throws NoConnectionException
- * @throws IllegalArgumentException
- */
- public void queueRemoveItem(final int itemId, final JSONObject customData)
- throws TransientNetworkDisconnectionException, NoConnectionException,
- IllegalArgumentException {
- Log.d(TAG, "queueRemoveItem");
- checkConnectivity();
- if (itemId == MediaQueueItem.INVALID_ITEM_ID) {
- throw new IllegalArgumentException("itemId is invalid");
- }
- if (remoteMediaPlayer == null) {
- Log.e(TAG, "Trying to remove an item from queue with no active media session");
- throw new NoConnectionException();
- }
- remoteMediaPlayer
- .queueRemoveItem(mApiClient, itemId, customData).setResultCallback(
- result -> {
- for (CastConsumer consumer : castConsumers) {
- consumer.onMediaQueueOperationResult(QUEUE_OPERATION_REMOVE_ITEM,
- result.getStatus().getStatusCode());
- }
- });
- }
-
- /**
- * Reorder a list of media items in the queue.
- *
- * @param itemIdsToReorder The list of media item IDs to reorder, in the new order. Any other
- * items currently in the queue will maintain their existing order. The
- * list will be inserted just before the item specified by
- * {@code insertBeforeItemId}, or at the end of the queue if
- * {@code insertBeforeItemId} is {@link MediaQueueItem#INVALID_ITEM_ID}.
- * <p>
- * For example:
- * <p>
- * If insertBeforeItemId is not specified <br>
- * Existing queue: "A","D","G","H","B","E" <br>
- * itemIds: "D","H","B" <br>
- * New Order: "A","G","E","D","H","B" <br>
- * <p>
- * If insertBeforeItemId is "A" <br>
- * Existing queue: "A","D","G","H","B" <br>
- * itemIds: "D","H","B" <br>
- * New Order: "D","H","B","A","G","E" <br>
- * <p>
- * If insertBeforeItemId is "G" <br>
- * Existing queue: "A","D","G","H","B" <br>
- * itemIds: "D","H","B" <br>
- * New Order: "A","D","H","B","G","E" <br>
- * <p>
- * If any of the items does not exist it will be ignored.
- * Must not be {@code null} or empty.
- * @param insertBeforeItemId ID of the item that will be located immediately after the reordered
- * list. If set to {@link MediaQueueItem#INVALID_ITEM_ID}, the
- * reordered list will be appended at the end of the queue.
- * @param customData Custom application-specific data to pass along with the request. May be
- * {@code null}.
- * @throws TransientNetworkDisconnectionException
- * @throws NoConnectionException
- */
- public void queueReorderItems(final int[] itemIdsToReorder, final int insertBeforeItemId,
- final JSONObject customData)
- throws TransientNetworkDisconnectionException, NoConnectionException,
- IllegalArgumentException {
- Log.d(TAG, "queueReorderItems");
- checkConnectivity();
- if (itemIdsToReorder == null || itemIdsToReorder.length == 0) {
- throw new IllegalArgumentException("itemIdsToReorder cannot be empty or null");
- }
- if (remoteMediaPlayer == null) {
- Log.e(TAG, "Trying to reorder items in a queue with no active media session");
- throw new NoConnectionException();
- }
- remoteMediaPlayer
- .queueReorderItems(mApiClient, itemIdsToReorder, insertBeforeItemId, customData)
- .setResultCallback(
- result -> {
- for (CastConsumer consumer : castConsumers) {
- consumer.onMediaQueueOperationResult(QUEUE_OPERATION_REORDER,
- result.getStatus().getStatusCode());
- }
- });
- }
-
- /**
- * Moves the item with {@code itemId} to a new position in the queue.
- * <p>
- * If {@code itemId} is not found in the queue, either because it wasn't there originally or it
- * was removed by another sender before calling this function, this function will silently
- * return without sending a request to the receiver.
- *
- * @param itemId The ID of the item to be moved.
- * @param newIndex The new index of the item. If the value is negative, an error will be
- * returned. If the value is out of bounds, or becomes out of bounds because the
- * queue was shortened by another sender while this request is in progress, the
- * item will be moved to the end of the queue.
- * @param customData Custom application-specific data to pass along with the request. May be
- * {@code null}.
- * @throws TransientNetworkDisconnectionException
- * @throws NoConnectionException
- */
- public void queueMoveItemToNewIndex(int itemId, int newIndex, final JSONObject customData)
- throws TransientNetworkDisconnectionException, NoConnectionException {
- Log.d(TAG, "queueMoveItemToNewIndex");
- checkConnectivity();
- if (remoteMediaPlayer == null) {
- Log.e(TAG, "Trying to mote item to new index with no active media session");
- throw new NoConnectionException();
- }
- remoteMediaPlayer
- .queueMoveItemToNewIndex(mApiClient, itemId, newIndex, customData)
- .setResultCallback(
- result -> {
- for (CastConsumer consumer : castConsumers) {
- consumer.onMediaQueueOperationResult(QUEUE_OPERATION_MOVE,
- result.getStatus().getStatusCode());
- }
- });
- }
-
- /**
- * Appends a new media item to the end of the queue.
- *
- * @param item The item to append. Must not be {@code null}.
- * @param customData Custom application-specific data to pass along with the request. May be
- * {@code null}.
- * @throws TransientNetworkDisconnectionException
- * @throws NoConnectionException
- */
- public void queueAppendItem(MediaQueueItem item, final JSONObject customData)
- throws TransientNetworkDisconnectionException, NoConnectionException {
- Log.d(TAG, "queueAppendItem");
- checkConnectivity();
- if (remoteMediaPlayer == null) {
- Log.e(TAG, "Trying to append item with no active media session");
- throw new NoConnectionException();
- }
- remoteMediaPlayer
- .queueAppendItem(mApiClient, item, customData)
- .setResultCallback(
- result -> {
- for (CastConsumer consumer : castConsumers) {
- consumer.onMediaQueueOperationResult(QUEUE_OPERATION_APPEND,
- result.getStatus().getStatusCode());
- }
- });
- }
-
- /**
- * Jumps to the next item in the queue.
- *
- * @param customData Custom application-specific data to pass along with the request. May be
- * {@code null}.
- * @throws TransientNetworkDisconnectionException
- * @throws NoConnectionException
- */
- public void queueNext(final JSONObject customData)
- throws TransientNetworkDisconnectionException, NoConnectionException {
- Log.d(TAG, "queueNext");
- checkConnectivity();
- if (remoteMediaPlayer == null) {
- Log.e(TAG, "Trying to update the queue with no active media session");
- throw new NoConnectionException();
- }
- remoteMediaPlayer
- .queueNext(mApiClient, customData).setResultCallback(
- result -> {
- for (CastConsumer consumer : castConsumers) {
- consumer.onMediaQueueOperationResult(QUEUE_OPERATION_NEXT,
- result.getStatus().getStatusCode());
- }
- });
- }
-
- /**
- * Jumps to the previous item in the queue.
- *
- * @param customData Custom application-specific data to pass along with the request. May be
- * {@code null}.
- * @throws TransientNetworkDisconnectionException
- * @throws NoConnectionException
- */
- public void queuePrev(final JSONObject customData)
- throws TransientNetworkDisconnectionException, NoConnectionException {
- Log.d(TAG, "queuePrev");
- checkConnectivity();
- if (remoteMediaPlayer == null) {
- Log.e(TAG, "Trying to update the queue with no active media session");
- throw new NoConnectionException();
- }
- remoteMediaPlayer
- .queuePrev(mApiClient, customData).setResultCallback(
- result -> {
- for (CastConsumer consumer : castConsumers) {
- consumer.onMediaQueueOperationResult(QUEUE_OPERATION_PREV,
- result.getStatus().getStatusCode());
- }
- });
- }
-
- /**
- * Inserts an item in the queue and starts the playback of that newly inserted item. It is
- * assumed that we are inserting before the "current item"
- *
- * @param item The item to be inserted
- * @param insertBeforeItemId ID of the item that will be located immediately after the inserted
- * and is assumed to be the "current item"
- * @param customData Custom application-specific data to pass along with the request. May be
- * {@code null}.
- * @throws TransientNetworkDisconnectionException
- * @throws NoConnectionException
- * @throws IllegalArgumentException
- */
- public void queueInsertBeforeCurrentAndPlay(MediaQueueItem item, int insertBeforeItemId,
- final JSONObject customData)
- throws TransientNetworkDisconnectionException, NoConnectionException {
- Log.d(TAG, "queueInsertBeforeCurrentAndPlay");
- checkConnectivity();
- if (remoteMediaPlayer == null) {
- Log.e(TAG, "Trying to insert into queue with no active media session");
- throw new NoConnectionException();
- }
- if (item == null || insertBeforeItemId == MediaQueueItem.INVALID_ITEM_ID) {
- throw new IllegalArgumentException(
- "item cannot be empty or insertBeforeItemId cannot be invalid");
- }
- remoteMediaPlayer.queueInsertItems(mApiClient, new MediaQueueItem[]{item},
- insertBeforeItemId, customData).setResultCallback(
- result -> {
- if (result.getStatus().isSuccess()) {
-
- try {
- queuePrev(customData);
- } catch (TransientNetworkDisconnectionException |
- NoConnectionException e) {
- Log.e(TAG, "queuePrev() Failed to skip to previous", e);
- }
- }
- for (CastConsumer consumer : castConsumers) {
- consumer.onMediaQueueOperationResult(QUEUE_OPERATION_INSERT_ITEMS,
- result.getStatus().getStatusCode());
- }
- });
- }
-
- /**
- * Sets the repeat mode of the queue.
- *
- * @param repeatMode The repeat playback mode for the queue.
- * @param customData Custom application-specific data to pass along with the request. May be
- * {@code null}.
- * @throws TransientNetworkDisconnectionException
- * @throws NoConnectionException
- */
- public void queueSetRepeatMode(final int repeatMode, final JSONObject customData)
- throws TransientNetworkDisconnectionException, NoConnectionException {
- Log.d(TAG, "queueSetRepeatMode");
- checkConnectivity();
- if (remoteMediaPlayer == null) {
- Log.e(TAG, "Trying to update the queue with no active media session");
- throw new NoConnectionException();
- }
- remoteMediaPlayer
- .queueSetRepeatMode(mApiClient, repeatMode, customData).setResultCallback(
- result -> {
- if (!result.getStatus().isSuccess()) {
- Log.d(TAG, "Failed with status: " + result.getStatus());
- }
- for (CastConsumer consumer : castConsumers) {
- consumer.onMediaQueueOperationResult(QUEUE_OPERATION_SET_REPEAT,
- result.getStatus().getStatusCode());
- }
- });
- }
-
- /**
- * Plays the loaded media.
- *
- * @param position Where to start the playback. Units is milliseconds.
- * @throws NoConnectionException
- * @throws TransientNetworkDisconnectionException
- */
- public void play(int position) throws TransientNetworkDisconnectionException,
- NoConnectionException {
- checkConnectivity();
- Log.d(TAG, "attempting to play media at position " + position + " seconds");
- if (remoteMediaPlayer == null) {
- Log.e(TAG, "Trying to play a video with no active media session");
- throw new NoConnectionException();
- }
- seekAndPlay(position);
- }
-
- /**
- * Resumes the playback from where it was left (can be the beginning).
- *
- * @param customData Optional {@link JSONObject} data to be passed to the cast device
- * @throws NoConnectionException
- * @throws TransientNetworkDisconnectionException
- */
- public void play(JSONObject customData) throws
- TransientNetworkDisconnectionException, NoConnectionException {
- Log.d(TAG, "play(customData)");
- checkConnectivity();
- if (remoteMediaPlayer == null) {
- Log.e(TAG, "Trying to play a video with no active media session");
- throw new NoConnectionException();
- }
- remoteMediaPlayer.play(mApiClient, customData)
- .setResultCallback(result -> {
- if (!result.getStatus().isSuccess()) {
- onFailed(R.string.cast_failed_to_play,
- result.getStatus().getStatusCode());
- }
- });
- }
-
- /**
- * Resumes the playback from where it was left (can be the beginning).
- *
- * @throws CastException
- * @throws NoConnectionException
- * @throws TransientNetworkDisconnectionException
- */
- public void play() throws CastException, TransientNetworkDisconnectionException,
- NoConnectionException {
- play(null);
- }
-
- /**
- * Stops the playback of media/stream
- *
- * @param customData Optional {@link JSONObject}
- * @throws TransientNetworkDisconnectionException
- * @throws NoConnectionException
- */
- public void stop(JSONObject customData) throws
- TransientNetworkDisconnectionException, NoConnectionException {
- Log.d(TAG, "stop()");
- checkConnectivity();
- if (remoteMediaPlayer == null) {
- Log.e(TAG, "Trying to stop a stream with no active media session");
- throw new NoConnectionException();
- }
- remoteMediaPlayer.stop(mApiClient, customData).setResultCallback(
- result -> {
- if (!result.getStatus().isSuccess()) {
- onFailed(R.string.cast_failed_to_stop,
- result.getStatus().getStatusCode());
- }
- }
- );
- }
-
- /**
- * Stops the playback of media/stream
- *
- * @throws CastException
- * @throws TransientNetworkDisconnectionException
- * @throws NoConnectionException
- */
- public void stop() throws CastException,
- TransientNetworkDisconnectionException, NoConnectionException {
- stop(null);
- }
-
- /**
- * Pauses the playback.
- *
- * @throws CastException
- * @throws NoConnectionException
- * @throws TransientNetworkDisconnectionException
- */
- public void pause() throws CastException, TransientNetworkDisconnectionException,
- NoConnectionException {
- pause(null);
- }
-
- /**
- * Pauses the playback.
- *
- * @param customData Optional {@link JSONObject} data to be passed to the cast device
- * @throws NoConnectionException
- * @throws TransientNetworkDisconnectionException
- */
- public void pause(JSONObject customData) throws
- TransientNetworkDisconnectionException, NoConnectionException {
- Log.d(TAG, "attempting to pause media");
- checkConnectivity();
- if (remoteMediaPlayer == null) {
- Log.e(TAG, "Trying to pause a video with no active media session");
- throw new NoConnectionException();
- }
- remoteMediaPlayer.pause(mApiClient, customData)
- .setResultCallback(result -> {
- if (!result.getStatus().isSuccess()) {
- onFailed(R.string.cast_failed_to_pause,
- result.getStatus().getStatusCode());
- }
- });
- }
-
- /**
- * Seeks to the given point without changing the state of the player, i.e. after seek is
- * completed, it resumes what it was doing before the start of seek.
- *
- * @param position in milliseconds
- * @throws NoConnectionException
- * @throws TransientNetworkDisconnectionException
- */
- public void seek(int position) throws TransientNetworkDisconnectionException,
- NoConnectionException {
- Log.d(TAG, "attempting to seek media");
- checkConnectivity();
- if (remoteMediaPlayer == null) {
- Log.e(TAG, "Trying to seek a video with no active media session");
- throw new NoConnectionException();
- }
- Log.d(TAG, "remoteMediaPlayer.seek() to position " + position);
- remoteMediaPlayer.seek(mApiClient,
- position,
- RESUME_STATE_UNCHANGED).
- setResultCallback(result -> {
- if (!result.getStatus().isSuccess()) {
- onFailed(R.string.cast_failed_seek, result.getStatus().getStatusCode());
- }
- });
- }
-
- /**
- * Fast forwards the media by the given amount. If {@code lengthInMillis} is negative, it
- * rewinds the media.
- *
- * @param lengthInMillis The amount to fast forward the media, given in milliseconds
- * @throws TransientNetworkDisconnectionException
- * @throws NoConnectionException
- */
- public void forward(int lengthInMillis) throws TransientNetworkDisconnectionException,
- NoConnectionException {
- Log.d(TAG, "forward(): attempting to forward media by " + lengthInMillis);
- checkConnectivity();
- if (remoteMediaPlayer == null) {
- Log.e(TAG, "Trying to seek a video with no active media session");
- throw new NoConnectionException();
- }
- long position = remoteMediaPlayer.getApproximateStreamPosition() + lengthInMillis;
- seek((int) position);
- }
-
- /**
- * Seeks to the given point and starts playback regardless of the starting state.
- *
- * @param position in milliseconds
- * @throws NoConnectionException
- * @throws TransientNetworkDisconnectionException
- */
- public void seekAndPlay(int position) throws TransientNetworkDisconnectionException,
- NoConnectionException {
- Log.d(TAG, "attempting to seek media");
- checkConnectivity();
- if (remoteMediaPlayer == null) {
- Log.e(TAG, "Trying to seekAndPlay a video with no active media session");
- throw new NoConnectionException();
- }
- Log.d(TAG, "remoteMediaPlayer.seek() to position " + position + "and play");
- remoteMediaPlayer.seek(mApiClient, position, RESUME_STATE_PLAY)
- .setResultCallback(result -> {
- if (!result.getStatus().isSuccess()) {
- onFailed(R.string.cast_failed_seek, result.getStatus().getStatusCode());
- }
- });
- }
-
- /**
- * Toggles the playback of the media.
- *
- * @throws CastException
- * @throws NoConnectionException
- * @throws TransientNetworkDisconnectionException
- */
- public void togglePlayback() throws CastException, TransientNetworkDisconnectionException,
- NoConnectionException {
- checkConnectivity();
- boolean isPlaying = isRemoteMediaPlaying();
- if (isPlaying) {
- pause();
- } else {
- if (state == MediaStatus.PLAYER_STATE_IDLE
- && idleReason == MediaStatus.IDLE_REASON_FINISHED) {
- loadMedia(getRemoteMediaInformation(), true, 0);
- } else {
- play();
- }
- }
- }
-
- private void attachMediaChannel() throws TransientNetworkDisconnectionException,
- NoConnectionException {
- Log.d(TAG, "attachMediaChannel()");
- checkConnectivity();
- if (remoteMediaPlayer == null) {
- remoteMediaPlayer = new RemoteMediaPlayer();
-
- remoteMediaPlayer.setOnStatusUpdatedListener(
- () -> {
- Log.d(TAG, "RemoteMediaPlayer::onStatusUpdated() is reached");
- CastManager.this.onRemoteMediaPlayerStatusUpdated();
- }
- );
-
- remoteMediaPlayer.setOnPreloadStatusUpdatedListener(
- () -> {
- Log.d(TAG, "RemoteMediaPlayer::onPreloadStatusUpdated() is reached");
- CastManager.this.onRemoteMediaPreloadStatusUpdated();
- });
-
-
- remoteMediaPlayer.setOnMetadataUpdatedListener(
- () -> {
- Log.d(TAG, "RemoteMediaPlayer::onMetadataUpdated() is reached");
- CastManager.this.onRemoteMediaPlayerMetadataUpdated();
- }
- );
-
- remoteMediaPlayer.setOnQueueStatusUpdatedListener(
- () -> {
- Log.d(TAG, "RemoteMediaPlayer::onQueueStatusUpdated() is reached");
- mediaStatus = remoteMediaPlayer.getMediaStatus();
- if (mediaStatus != null
- && mediaStatus.getQueueItems() != null) {
- List<MediaQueueItem> queueItems = mediaStatus
- .getQueueItems();
- int itemId = mediaStatus.getCurrentItemId();
- MediaQueueItem item = mediaStatus
- .getQueueItemById(itemId);
- int repeatMode = mediaStatus.getQueueRepeatMode();
- onQueueUpdated(queueItems, item, repeatMode, false);
- } else {
- onQueueUpdated(null, null,
- MediaStatus.REPEAT_MODE_REPEAT_OFF, false);
- }
- });
-
- }
- try {
- Log.d(TAG, "Registering MediaChannel namespace");
- Cast.CastApi.setMessageReceivedCallbacks(mApiClient, remoteMediaPlayer.getNamespace(),
- remoteMediaPlayer);
- } catch (IOException | IllegalStateException e) {
- Log.e(TAG, "attachMediaChannel()", e);
- }
- }
-
- private void reattachMediaChannel() {
- if (remoteMediaPlayer != null && mApiClient != null) {
- try {
- Log.d(TAG, "Registering MediaChannel namespace");
- Cast.CastApi.setMessageReceivedCallbacks(mApiClient,
- remoteMediaPlayer.getNamespace(), remoteMediaPlayer);
- } catch (IOException | IllegalStateException e) {
- Log.e(TAG, "reattachMediaChannel()", e);
- }
- }
- }
-
- private void detachMediaChannel() {
- Log.d(TAG, "trying to detach media channel");
- if (remoteMediaPlayer != null) {
- try {
- Cast.CastApi.removeMessageReceivedCallbacks(mApiClient,
- remoteMediaPlayer.getNamespace());
- } catch (IOException | IllegalStateException e) {
- Log.e(TAG, "detachMediaChannel()", e);
- }
- remoteMediaPlayer = null;
- }
- }
-
- /**
- * Returns the playback status of the remote device.
- *
- * @return Returns one of the values
- * <ul>
- * <li> <code>MediaStatus.PLAYER_STATE_UNKNOWN</code></li>
- * <li> <code>MediaStatus.PLAYER_STATE_IDLE</code></li>
- * <li> <code>MediaStatus.PLAYER_STATE_PLAYING</code></li>
- * <li> <code>MediaStatus.PLAYER_STATE_PAUSED</code></li>
- * <li> <code>MediaStatus.PLAYER_STATE_BUFFERING</code></li>
- * </ul>
- */
- public int getPlaybackStatus() {
- return state;
- }
-
- /**
- * Returns the latest retrieved value for the {@link MediaStatus}. This value is updated
- * whenever the onStatusUpdated callback is called.
- */
- public final MediaStatus getMediaStatus() {
- return mediaStatus;
- }
-
- /**
- * Returns the Idle reason, defined in <code>MediaStatus.IDLE_*</code>. Note that the returned
- * value is only meaningful if the status is truly <code>MediaStatus.PLAYER_STATE_IDLE
- * </code>
- *
- * <p>Possible values are:
- * <ul>
- * <li>IDLE_REASON_NONE</li>
- * <li>IDLE_REASON_FINISHED</li>
- * <li>IDLE_REASON_CANCELED</li>
- * <li>IDLE_REASON_INTERRUPTED</li>
- * <li>IDLE_REASON_ERROR</li>
- * </ul>
- */
- public int getIdleReason() {
- return idleReason;
- }
-
- private void onMessageSendFailed(int errorCode) {
- for (CastConsumer consumer : castConsumers) {
- consumer.onDataMessageSendFailed(errorCode);
- }
- }
-
- /*
- * This is called by onStatusUpdated() of the RemoteMediaPlayer
- */
- private void onRemoteMediaPlayerStatusUpdated() {
- Log.d(TAG, "onRemoteMediaPlayerStatusUpdated() reached");
- if (mApiClient == null || remoteMediaPlayer == null) {
- Log.d(TAG, "mApiClient or remoteMediaPlayer is null, so will not proceed");
- return;
- }
- mediaStatus = remoteMediaPlayer.getMediaStatus();
- if (mediaStatus == null) {
- Log.d(TAG, "MediaStatus is null, so will not proceed");
- return;
- } else {
- List<MediaQueueItem> queueItems = mediaStatus.getQueueItems();
- if (queueItems != null) {
- int itemId = mediaStatus.getCurrentItemId();
- MediaQueueItem item = mediaStatus.getQueueItemById(itemId);
- int repeatMode = mediaStatus.getQueueRepeatMode();
- onQueueUpdated(queueItems, item, repeatMode, false);
- } else {
- onQueueUpdated(null, null, MediaStatus.REPEAT_MODE_REPEAT_OFF, false);
- }
- state = mediaStatus.getPlayerState();
- idleReason = mediaStatus.getIdleReason();
-
- if (state == MediaStatus.PLAYER_STATE_PLAYING) {
- Log.d(TAG, "onRemoteMediaPlayerStatusUpdated(): Player status = playing");
- } else if (state == MediaStatus.PLAYER_STATE_PAUSED) {
- Log.d(TAG, "onRemoteMediaPlayerStatusUpdated(): Player status = paused");
- } else if (state == MediaStatus.PLAYER_STATE_IDLE) {
- Log.d(TAG, "onRemoteMediaPlayerStatusUpdated(): Player status = IDLE with reason: "
- + idleReason);
- if (idleReason == MediaStatus.IDLE_REASON_ERROR) {
- // something bad happened on the cast device
- Log.d(TAG, "onRemoteMediaPlayerStatusUpdated(): IDLE reason = ERROR");
- onFailed(R.string.cast_failed_receiver_player_error, NO_STATUS_CODE);
- }
- } else if (state == MediaStatus.PLAYER_STATE_BUFFERING) {
- Log.d(TAG, "onRemoteMediaPlayerStatusUpdated(): Player status = buffering");
- } else {
- Log.d(TAG, "onRemoteMediaPlayerStatusUpdated(): Player status = unknown");
- }
- }
- for (CastConsumer consumer : castConsumers) {
- consumer.onRemoteMediaPlayerStatusUpdated();
- }
- if (mediaStatus != null) {
- double volume = mediaStatus.getStreamVolume();
- boolean isMute = mediaStatus.isMute();
- for (CastConsumer consumer : castConsumers) {
- consumer.onStreamVolumeChanged(volume, isMute);
- }
- }
- }
-
- private void onRemoteMediaPreloadStatusUpdated() {
- MediaQueueItem item = null;
- mediaStatus = remoteMediaPlayer.getMediaStatus();
- if (mediaStatus != null) {
- item = mediaStatus.getQueueItemById(mediaStatus.getPreloadedItemId());
- }
- preLoadingItem = item;
- Log.d(TAG, "onRemoteMediaPreloadStatusUpdated() " + item);
- for (CastConsumer consumer : castConsumers) {
- consumer.onRemoteMediaPreloadStatusUpdated(item);
- }
- }
-
- public MediaQueueItem getPreLoadingItem() {
- return preLoadingItem;
- }
-
- /*
- * This is called by onQueueStatusUpdated() of RemoteMediaPlayer
- */
- private void onQueueUpdated(List<MediaQueueItem> queueItems, MediaQueueItem item,
- int repeatMode, boolean shuffle) {
- Log.d(TAG, "onQueueUpdated() reached");
- Log.d(TAG, String.format("Queue Items size: %d, Item: %s, Repeat Mode: %d, Shuffle: %s",
- queueItems == null ? 0 : queueItems.size(), item, repeatMode, shuffle));
- if (queueItems != null) {
- mediaQueue = new MediaQueue(new CopyOnWriteArrayList<>(queueItems), item, shuffle,
- repeatMode);
- } else {
- mediaQueue = new MediaQueue(new CopyOnWriteArrayList<>(), null, false,
- MediaStatus.REPEAT_MODE_REPEAT_OFF);
- }
- for (CastConsumer consumer : castConsumers) {
- consumer.onMediaQueueUpdated(queueItems, item, repeatMode, shuffle);
- }
- }
-
- /*
- * This is called by onMetadataUpdated() of RemoteMediaPlayer
- */
- public void onRemoteMediaPlayerMetadataUpdated() {
- Log.d(TAG, "onRemoteMediaPlayerMetadataUpdated() reached");
- for (CastConsumer consumer : castConsumers) {
- consumer.onRemoteMediaPlayerMetadataUpdated();
- }
- }
-
- /**
- * Registers a {@link CastConsumer} interface with this class.
- * Registered listeners will be notified of changes to a variety of
- * lifecycle and media status changes through the callbacks that the interface provides.
- *
- * @see DefaultCastConsumer
- */
- public synchronized void addCastConsumer(CastConsumer listener) {
- if (listener != null) {
- addBaseCastConsumer(listener);
- castConsumers.add(listener);
- Log.d(TAG, "Successfully added the new CastConsumer listener " + listener);
- }
- }
-
- /**
- * Unregisters a {@link CastConsumer}.
- */
- public synchronized void removeCastConsumer(CastConsumer listener) {
- if (listener != null) {
- removeBaseCastConsumer(listener);
- castConsumers.remove(listener);
- }
- }
-
- @Override
- protected void onDeviceUnselected() {
- detachMediaChannel();
- //removeDataChannel();
- state = MediaStatus.PLAYER_STATE_IDLE;
- mediaStatus = null;
- }
-
- @Override
- protected Cast.CastOptions.Builder getCastOptionBuilder(CastDevice device) {
- Cast.CastOptions.Builder builder = new Cast.CastOptions.Builder(mSelectedCastDevice, new CastListener());
- if (isFeatureEnabled(CastConfiguration.FEATURE_DEBUGGING)) {
- builder.setVerboseLoggingEnabled(true);
- }
- return builder;
- }
-
- @Override
- public void onConnectionFailed(ConnectionResult result) {
- super.onConnectionFailed(result);
- state = MediaStatus.PLAYER_STATE_IDLE;
- mediaStatus = null;
- }
-
- @Override
- public void onDisconnected(boolean stopAppOnExit, boolean clearPersistedConnectionData,
- boolean setDefaultRoute) {
- super.onDisconnected(stopAppOnExit, clearPersistedConnectionData, setDefaultRoute);
- state = MediaStatus.PLAYER_STATE_IDLE;
- mediaStatus = null;
- mediaQueue = null;
- }
-
- class CastListener extends Cast.Listener {
-
- /*
- * (non-Javadoc)
- * @see com.google.android.gms.cast.Cast.Listener#onApplicationDisconnected (int)
- */
- @Override
- public void onApplicationDisconnected(int statusCode) {
- CastManager.this.onApplicationDisconnected(statusCode);
- }
-
- /*
- * (non-Javadoc)
- * @see com.google.android.gms.cast.Cast.Listener#onApplicationStatusChanged ()
- */
- @Override
- public void onApplicationStatusChanged() {
- CastManager.this.onApplicationStatusChanged();
- }
-
- @Override
- public void onVolumeChanged() {
- CastManager.this.onDeviceVolumeChanged();
- }
- }
-
- @Override
- public void onFailed(int resourceId, int statusCode) {
- Log.d(TAG, "onFailed: " + mContext.getString(resourceId) + ", code: " + statusCode);
- super.onFailed(resourceId, statusCode);
- }
-
- /**
- * Clients can call this method to delegate handling of the volume. Clients should override
- * {@code dispatchEvent} and call this method:
- * <pre>
- public boolean dispatchKeyEvent(KeyEvent event) {
- if (mCastManager.onDispatchVolumeKeyEvent(event, VOLUME_DELTA)) {
- return true;
- }
- return super.dispatchKeyEvent(event);
- }
- * </pre>
- * @param event The dispatched event.
- * @param volumeDelta The amount by which volume should be increased or decreased in each step
- * @return <code>true</code> if volume is handled by the library, <code>false</code> otherwise.
- */
- public boolean onDispatchVolumeKeyEvent(KeyEvent event, double volumeDelta) {
- if (isConnected()) {
- boolean isKeyDown = event.getAction() == KeyEvent.ACTION_DOWN;
- switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_VOLUME_UP:
- return changeVolume(volumeDelta, isKeyDown);
- case KeyEvent.KEYCODE_VOLUME_DOWN:
- return changeVolume(-volumeDelta, isKeyDown);
- }
- }
- return false;
- }
-
- private boolean changeVolume(double volumeIncrement, boolean isKeyDown) {
- if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
- && getPlaybackStatus() == MediaStatus.PLAYER_STATE_PLAYING
- && isFeatureEnabled(CastConfiguration.FEATURE_LOCKSCREEN)) {
- return false;
- }
-
- if (isKeyDown) {
- try {
- adjustDeviceVolume(volumeIncrement);
- } catch (CastException | TransientNetworkDisconnectionException |
- NoConnectionException e) {
- Log.e(TAG, "Failed to change volume", e);
- }
- }
- return true;
- }
-
- /**
- * Sets the volume step, i.e. the fraction by which volume will increase or decrease each time
- * user presses the hard volume buttons on the device.
- *
- * @param volumeStep Should be a double between 0 and 1, inclusive.
- */
- public CastManager setVolumeStep(double volumeStep) {
- if ((volumeStep > 1) || (volumeStep < 0)) {
- throw new IllegalArgumentException("Volume Step should be between 0 and 1, inclusive");
- }
- this.volumeStep = volumeStep;
- return this;
- }
-
- /**
- * Returns the volume step. The default value is {@code DEFAULT_VOLUME_STEP}.
- */
- public double getVolumeStep() {
- return volumeStep;
- }
-
- public final MediaQueue getMediaQueue() {
- return mediaQueue;
- }
-
- /**
- * Checks whether the selected Cast Device has the specified audio or video capabilities.
- *
- * @param capability capability from:
- * <ul>
- * <li>{@link CastDevice#CAPABILITY_AUDIO_IN}</li>
- * <li>{@link CastDevice#CAPABILITY_AUDIO_OUT}</li>
- * <li>{@link CastDevice#CAPABILITY_VIDEO_IN}</li>
- * <li>{@link CastDevice#CAPABILITY_VIDEO_OUT}</li>
- * </ul>
- * @param defaultVal value to return whenever there's no device selected.
- * @return {@code true} if the selected device has the specified capability,
- * {@code false} otherwise.
- */
- public boolean hasCapability(final int capability, final boolean defaultVal) {
- if (mSelectedCastDevice != null) {
- return mSelectedCastDevice.hasCapability(capability);
- } else {
- return defaultVal;
- }
- }
-
- /**
- * Adds and wires up the Switchable Media Router cast button. It returns a reference to the
- * {@link SwitchableMediaRouteActionProvider} associated with the button if the caller needs
- * such reference. It is assumed that the enclosing
- * {@link android.app.Activity} inherits (directly or indirectly) from
- * {@link android.support.v7.app.AppCompatActivity}.
- *
- * @param menuItem MenuItem of the Media Router cast button.
- */
- public final SwitchableMediaRouteActionProvider addMediaRouterButton(MenuItem menuItem) {
- SwitchableMediaRouteActionProvider mediaRouteActionProvider = (SwitchableMediaRouteActionProvider)
- MenuItemCompat.getActionProvider(menuItem);
- mediaRouteActionProvider.setRouteSelector(mMediaRouteSelector);
- if (mCastConfiguration.getMediaRouteDialogFactory() != null) {
- mediaRouteActionProvider.setDialogFactory(mCastConfiguration.getMediaRouteDialogFactory());
- }
- return mediaRouteActionProvider;
- }
-
- /* (non-Javadoc)
- * These methods startReconnectionService and stopReconnectionService simply override the ones
- * from BaseCastManager with empty implementations because we handle the service ourselves, but
- * need to allow BaseCastManager to save current network information.
- */
- @Override
- protected void startReconnectionService(long mediaDurationLeft) {
- // Do nothing
- }
-
- @Override
- protected void stopReconnectionService() {
- // Do nothing
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/cast/CastUtils.java b/core/src/main/java/de/danoeh/antennapod/core/cast/CastUtils.java
deleted file mode 100644
index f0a7214c9..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/cast/CastUtils.java
+++ /dev/null
@@ -1,317 +0,0 @@
-package de.danoeh.antennapod.core.cast;
-
-import android.net.Uri;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.google.android.gms.cast.CastDevice;
-import com.google.android.gms.cast.MediaInfo;
-import com.google.android.gms.cast.MediaMetadata;
-import com.google.android.gms.common.images.WebImage;
-
-import java.util.Calendar;
-import java.util.List;
-
-import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.feed.FeedImage;
-import de.danoeh.antennapod.core.feed.FeedItem;
-import de.danoeh.antennapod.core.feed.FeedMedia;
-import de.danoeh.antennapod.core.storage.DBReader;
-import de.danoeh.antennapod.core.util.playback.ExternalMedia;
-import de.danoeh.antennapod.core.util.playback.Playable;
-
-/**
- * Helper functions for Cast support.
- */
-public class CastUtils {
- private static final String TAG = "CastUtils";
-
- public static final String KEY_MEDIA_ID = "de.danoeh.antennapod.core.cast.MediaId";
-
- public static final String KEY_EPISODE_IDENTIFIER = "de.danoeh.antennapod.core.cast.EpisodeId";
- public static final String KEY_EPISODE_LINK = "de.danoeh.antennapod.core.cast.EpisodeLink";
- public static final String KEY_FEED_URL = "de.danoeh.antennapod.core.cast.FeedUrl";
- public static final String KEY_FEED_WEBSITE = "de.danoeh.antennapod.core.cast.FeedWebsite";
- public static final String KEY_EPISODE_NOTES = "de.danoeh.antennapod.core.cast.EpisodeNotes";
- public static final int EPISODE_NOTES_MAX_LENGTH = Integer.MAX_VALUE;
-
- /**
- * The field <code>AntennaPod.FormatVersion</code> specifies which version of MediaMetaData
- * fields we're using. Future implementations should try to be backwards compatible with earlier
- * versions, and earlier versions should be forward compatible until the version indicated by
- * <code>MAX_VERSION_FORWARD_COMPATIBILITY</code>. If an update makes the format unreadable for
- * an earlier version, then its version number should be greater than the
- * <code>MAX_VERSION_FORWARD_COMPATIBILITY</code> value set on the earlier one, so that it
- * doesn't try to parse the object.
- */
- public static final String KEY_FORMAT_VERSION = "de.danoeh.antennapod.core.cast.FormatVersion";
- public static final int FORMAT_VERSION_VALUE = 1;
- public static final int MAX_VERSION_FORWARD_COMPATIBILITY = 9999;
-
- public static boolean isCastable(Playable media){
- if (media == null || media instanceof ExternalMedia) {
- return false;
- }
- if (media instanceof FeedMedia || media instanceof RemoteMedia){
- String url = media.getStreamUrl();
- if(url == null || url.isEmpty()){
- return false;
- }
- switch (media.getMediaType()) {
- case UNKNOWN:
- return false;
- case AUDIO:
- return CastManager.getInstance().hasCapability(CastDevice.CAPABILITY_AUDIO_OUT, true);
- case VIDEO:
- return CastManager.getInstance().hasCapability(CastDevice.CAPABILITY_VIDEO_OUT, true);
- }
- }
- return false;
- }
-
- /**
- * Converts {@link FeedMedia} objects into a format suitable for sending to a Cast Device.
- * Before using this method, one should make sure {@link #isCastable(Playable)} returns
- * {@code true}.
- *
- * Unless media.{@link FeedMedia#loadMetadata() loadMetadata()} has already been called,
- * this method should not run on the main thread.
- *
- * @param media The {@link FeedMedia} object to be converted.
- * @return {@link MediaInfo} object in a format proper for casting.
- */
- public static MediaInfo convertFromFeedMedia(FeedMedia media){
- if(media == null) {
- return null;
- }
- MediaMetadata metadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_GENERIC);
- try{
- media.loadMetadata();
- } catch (Playable.PlayableException e) {
- Log.e(TAG, "Unable to load FeedMedia metadata", e);
- }
- FeedItem feedItem = media.getItem();
- if (feedItem != null) {
- metadata.putString(MediaMetadata.KEY_TITLE, media.getEpisodeTitle());
- String subtitle = media.getFeedTitle();
- if (subtitle != null) {
- metadata.putString(MediaMetadata.KEY_SUBTITLE, subtitle);
- }
- FeedImage image = feedItem.getImage();
- if (image != null && !TextUtils.isEmpty(image.getDownload_url())) {
- metadata.addImage(new WebImage(Uri.parse(image.getDownload_url())));
- }
- Calendar calendar = Calendar.getInstance();
- calendar.setTime(media.getItem().getPubDate());
- metadata.putDate(MediaMetadata.KEY_RELEASE_DATE, calendar);
- Feed feed = feedItem.getFeed();
- if (feed != null) {
- if (!TextUtils.isEmpty(feed.getAuthor())) {
- metadata.putString(MediaMetadata.KEY_ARTIST, feed.getAuthor());
- }
- if (!TextUtils.isEmpty(feed.getDownload_url())) {
- metadata.putString(KEY_FEED_URL, feed.getDownload_url());
- }
- if (!TextUtils.isEmpty(feed.getLink())) {
- metadata.putString(KEY_FEED_WEBSITE, feed.getLink());
- }
- }
- if (!TextUtils.isEmpty(feedItem.getItemIdentifier())) {
- metadata.putString(KEY_EPISODE_IDENTIFIER, feedItem.getItemIdentifier());
- } else {
- metadata.putString(KEY_EPISODE_IDENTIFIER, media.getStreamUrl());
- }
- if (!TextUtils.isEmpty(feedItem.getLink())) {
- metadata.putString(KEY_EPISODE_LINK, feedItem.getLink());
- }
- }
- String notes = null;
- try {
- notes = media.loadShownotes().call();
- } catch (Exception e) {
- Log.e(TAG, "Unable to load FeedMedia notes", e);
- }
- if (notes != null) {
- if (notes.length() > EPISODE_NOTES_MAX_LENGTH) {
- notes = notes.substring(0, EPISODE_NOTES_MAX_LENGTH);
- }
- metadata.putString(KEY_EPISODE_NOTES, notes);
- }
- // This field only identifies the id on the device that has the original version.
- // Idea is to perhaps, on a first approach, check if the version on the local DB with the
- // same id matches the remote object, and if not then search for episode and feed identifiers.
- // This at least should make media recognition for a single device much quicker.
- metadata.putInt(KEY_MEDIA_ID, ((Long) media.getIdentifier()).intValue());
- // A way to identify different casting media formats in case we change it in the future and
- // senders with different versions share a casting device.
- metadata.putInt(KEY_FORMAT_VERSION, FORMAT_VERSION_VALUE);
-
- MediaInfo.Builder builder = new MediaInfo.Builder(media.getStreamUrl())
- .setContentType(media.getMime_type())
- .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
- .setMetadata(metadata);
- if (media.getDuration() > 0) {
- builder.setStreamDuration(media.getDuration());
- }
- return builder.build();
- }
-
- //TODO make unit tests for all the conversion methods
- /**
- * Converts {@link MediaInfo} objects into the appropriate implementation of {@link Playable}.
- *
- * Unless <code>searchFeedMedia</code> is set to <code>false</code>, this method should not run
- * on the GUI thread.
- *
- * @param media The {@link MediaInfo} object to be converted.
- * @param searchFeedMedia If set to <code>true</code>, the database will be queried to find a
- * {@link FeedMedia} instance that matches {@param media}.
- * @return {@link Playable} object in a format proper for casting.
- */
- public static Playable getPlayable(MediaInfo media, boolean searchFeedMedia) {
- Log.d(TAG, "getPlayable called with searchFeedMedia=" + searchFeedMedia);
- if (media == null) {
- Log.d(TAG, "MediaInfo object provided is null, not converting to any Playable instance");
- return null;
- }
- MediaMetadata metadata = media.getMetadata();
- int version = metadata.getInt(KEY_FORMAT_VERSION);
- if (version <= 0 || version > MAX_VERSION_FORWARD_COMPATIBILITY) {
- Log.w(TAG, "MediaInfo object obtained from the cast device is not compatible with this" +
- "version of AntennaPod CastUtils, curVer=" + FORMAT_VERSION_VALUE +
- ", object version=" + version);
- return null;
- }
- Playable result = null;
- if (searchFeedMedia) {
- long mediaId = metadata.getInt(KEY_MEDIA_ID);
- if (mediaId > 0) {
- FeedMedia fMedia = DBReader.getFeedMedia(mediaId);
- if (fMedia != null) {
- try {
- fMedia.loadMetadata();
- if (matches(media, fMedia)) {
- result = fMedia;
- Log.d(TAG, "FeedMedia object obtained matches the MediaInfo provided. id=" + mediaId);
- } else {
- Log.d(TAG, "FeedMedia object obtained does NOT match the MediaInfo provided. id=" + mediaId);
- }
- } catch (Playable.PlayableException e) {
- Log.e(TAG, "Unable to load FeedMedia metadata to compare with MediaInfo", e);
- }
- } else {
- Log.d(TAG, "Unable to find in database a FeedMedia with id=" + mediaId);
- }
- }
- if (result == null) {
- FeedItem feedItem = DBReader.getFeedItem(metadata.getString(KEY_FEED_URL),
- metadata.getString(KEY_EPISODE_IDENTIFIER));
- if (feedItem != null) {
- result = feedItem.getMedia();
- Log.d(TAG, "Found episode that matches the MediaInfo provided. Using its media, if existing.");
- }
- }
- }
- if (result == null) {
- List<WebImage> imageList = metadata.getImages();
- String imageUrl = null;
- if (!imageList.isEmpty()) {
- imageUrl = imageList.get(0).getUrl().toString();
- }
- result = new RemoteMedia(media.getContentId(),
- metadata.getString(KEY_EPISODE_IDENTIFIER),
- metadata.getString(KEY_FEED_URL),
- metadata.getString(MediaMetadata.KEY_SUBTITLE),
- metadata.getString(MediaMetadata.KEY_TITLE),
- metadata.getString(KEY_EPISODE_LINK),
- metadata.getString(MediaMetadata.KEY_ARTIST),
- imageUrl,
- metadata.getString(KEY_FEED_WEBSITE),
- media.getContentType(),
- metadata.getDate(MediaMetadata.KEY_RELEASE_DATE).getTime());
- String notes = metadata.getString(KEY_EPISODE_NOTES);
- if (!TextUtils.isEmpty(notes)) {
- ((RemoteMedia) result).setNotes(notes);
- }
- Log.d(TAG, "Converted MediaInfo into RemoteMedia");
- }
- if (result.getDuration() == 0 && media.getStreamDuration() > 0) {
- result.setDuration((int) media.getStreamDuration());
- }
- return result;
- }
-
- /**
- * Compares a {@link MediaInfo} instance with a {@link FeedMedia} one and evaluates whether they
- * represent the same podcast episode.
- *
- * @param info the {@link MediaInfo} object to be compared.
- * @param media the {@link FeedMedia} object to be compared.
- * @return <true>true</true> if there's a match, <code>false</code> otherwise.
- *
- * @see RemoteMedia#equals(Object)
- */
- public static boolean matches(MediaInfo info, FeedMedia media) {
- if (info == null || media == null) {
- return false;
- }
- if (!TextUtils.equals(info.getContentId(), media.getStreamUrl())) {
- return false;
- }
- MediaMetadata metadata = info.getMetadata();
- FeedItem fi = media.getItem();
- if (fi == null || metadata == null ||
- !TextUtils.equals(metadata.getString(KEY_EPISODE_IDENTIFIER), fi.getItemIdentifier())) {
- return false;
- }
- Feed feed = fi.getFeed();
- return feed != null && TextUtils.equals(metadata.getString(KEY_FEED_URL), feed.getDownload_url());
- }
-
- /**
- * Compares a {@link MediaInfo} instance with a {@link RemoteMedia} one and evaluates whether they
- * represent the same podcast episode.
- *
- * @param info the {@link MediaInfo} object to be compared.
- * @param media the {@link RemoteMedia} object to be compared.
- * @return <true>true</true> if there's a match, <code>false</code> otherwise.
- *
- * @see RemoteMedia#equals(Object)
- */
- public static boolean matches(MediaInfo info, RemoteMedia media) {
- if (info == null || media == null) {
- return false;
- }
- if (!TextUtils.equals(info.getContentId(), media.getStreamUrl())) {
- return false;
- }
- MediaMetadata metadata = info.getMetadata();
- return metadata != null &&
- TextUtils.equals(metadata.getString(KEY_EPISODE_IDENTIFIER), media.getEpisodeIdentifier()) &&
- TextUtils.equals(metadata.getString(KEY_FEED_URL), media.getFeedUrl());
- }
-
- /**
- * Compares a {@link MediaInfo} instance with a {@link Playable} and evaluates whether they
- * represent the same podcast episode. Useful every time we get a MediaInfo from the Cast Device
- * and want to avoid unnecessary conversions.
- *
- * @param info the {@link MediaInfo} object to be compared.
- * @param media the {@link Playable} object to be compared.
- * @return <true>true</true> if there's a match, <code>false</code> otherwise.
- *
- * @see RemoteMedia#equals(Object)
- */
- public static boolean matches(MediaInfo info, Playable media) {
- if (info == null || media == null) {
- return false;
- }
- if (media instanceof RemoteMedia) {
- return matches(info, (RemoteMedia) media);
- }
- return media instanceof FeedMedia && matches(info, (FeedMedia) media);
- }
-
-
- //TODO Queue handling perhaps
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/cast/DefaultCastConsumer.java b/core/src/main/java/de/danoeh/antennapod/core/cast/DefaultCastConsumer.java
deleted file mode 100644
index fe4183d54..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/cast/DefaultCastConsumer.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package de.danoeh.antennapod.core.cast;
-
-import com.google.android.libraries.cast.companionlibrary.cast.callbacks.VideoCastConsumerImpl;
-
-public class DefaultCastConsumer extends VideoCastConsumerImpl implements CastConsumer {
- @Override
- public void onStreamVolumeChanged(double value, boolean isMute) {
- // no-op
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/cast/RemoteMedia.java b/core/src/main/java/de/danoeh/antennapod/core/cast/RemoteMedia.java
deleted file mode 100644
index e2d8f8ad5..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/cast/RemoteMedia.java
+++ /dev/null
@@ -1,357 +0,0 @@
-package de.danoeh.antennapod.core.cast;
-
-import android.content.SharedPreferences;
-import android.net.Uri;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-
-import com.google.android.gms.cast.MediaInfo;
-import com.google.android.gms.cast.MediaMetadata;
-import com.google.android.gms.common.images.WebImage;
-
-import java.util.Calendar;
-import java.util.Date;
-import java.util.List;
-import java.util.concurrent.Callable;
-
-import de.danoeh.antennapod.core.feed.Chapter;
-import de.danoeh.antennapod.core.feed.Feed;
-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.storage.DBReader;
-import de.danoeh.antennapod.core.util.ChapterUtils;
-import de.danoeh.antennapod.core.util.playback.Playable;
-
-import org.apache.commons.lang3.builder.HashCodeBuilder;
-
-/**
- * Playable implementation for media on a Cast Device for which a local version of
- * {@link de.danoeh.antennapod.core.feed.FeedMedia} hasn't been found.
- */
-public class RemoteMedia implements Playable {
- public static final String TAG = "RemoteMedia";
-
- public static final int PLAYABLE_TYPE_REMOTE_MEDIA = 3;
-
- private String downloadUrl;
- private String itemIdentifier;
- private String feedUrl;
- private String feedTitle;
- private String episodeTitle;
- private String episodeLink;
- private String feedAuthor;
- private String imageUrl;
- private String feedLink;
- private String mime_type;
- private Date pubDate;
- private String notes;
- private List<Chapter> chapters;
- private int duration;
- private int position;
- private long lastPlayedTime;
-
- public RemoteMedia(String downloadUrl, String itemId, String feedUrl, String feedTitle,
- String episodeTitle, String episodeLink, String feedAuthor,
- String imageUrl, String feedLink, String mime_type, Date pubDate) {
- this.downloadUrl = downloadUrl;
- this.itemIdentifier = itemId;
- this.feedUrl = feedUrl;
- this.feedTitle = feedTitle;
- this.episodeTitle = episodeTitle;
- this.episodeLink = episodeLink;
- this.feedAuthor = feedAuthor;
- this.imageUrl = imageUrl;
- this.feedLink = feedLink;
- this.mime_type = mime_type;
- this.pubDate = pubDate;
- }
-
- public void setNotes(String notes) {
- this.notes = notes;
- }
-
- public MediaInfo extractMediaInfo() {
- MediaMetadata metadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_GENERIC);
-
- metadata.putString(MediaMetadata.KEY_TITLE, episodeTitle);
- metadata.putString(MediaMetadata.KEY_SUBTITLE, feedTitle);
- if (!TextUtils.isEmpty(imageUrl)) {
- metadata.addImage(new WebImage(Uri.parse(imageUrl)));
- }
- Calendar calendar = Calendar.getInstance();
- calendar.setTime(pubDate);
- metadata.putDate(MediaMetadata.KEY_RELEASE_DATE, calendar);
- if (!TextUtils.isEmpty(feedAuthor)) {
- metadata.putString(MediaMetadata.KEY_ARTIST, feedAuthor);
- }
- if (!TextUtils.isEmpty(feedUrl)) {
- metadata.putString(CastUtils.KEY_FEED_URL, feedUrl);
- }
- if (!TextUtils.isEmpty(feedLink)) {
- metadata.putString(CastUtils.KEY_FEED_WEBSITE, feedLink);
- }
- if (!TextUtils.isEmpty(itemIdentifier)) {
- metadata.putString(CastUtils.KEY_EPISODE_IDENTIFIER, itemIdentifier);
- } else {
- metadata.putString(CastUtils.KEY_EPISODE_IDENTIFIER, downloadUrl);
- }
- if (!TextUtils.isEmpty(episodeLink)) {
- metadata.putString(CastUtils.KEY_EPISODE_LINK, episodeLink);
- }
- String notes = this.notes;
- if (notes != null) {
- if (notes.length() > CastUtils.EPISODE_NOTES_MAX_LENGTH) {
- notes = notes.substring(0, CastUtils.EPISODE_NOTES_MAX_LENGTH);
- }
- metadata.putString(CastUtils.KEY_EPISODE_NOTES, notes);
- }
- // Default id value
- metadata.putInt(CastUtils.KEY_MEDIA_ID, 0);
- metadata.putInt(CastUtils.KEY_FORMAT_VERSION, CastUtils.FORMAT_VERSION_VALUE);
-
- MediaInfo.Builder builder = new MediaInfo.Builder(downloadUrl)
- .setContentType(mime_type)
- .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
- .setMetadata(metadata);
- if (duration > 0) {
- builder.setStreamDuration(duration);
- }
- return builder.build();
- }
-
- public String getEpisodeIdentifier() {
- return itemIdentifier;
- }
-
- public String getFeedUrl() {
- return feedUrl;
- }
-
- public FeedMedia lookForFeedMedia() {
- FeedItem feedItem = DBReader.getFeedItem(feedUrl, itemIdentifier);
- if (feedItem == null) {
- return null;
- }
- return feedItem.getMedia();
- }
-
- @Override
- public void writeToPreferences(SharedPreferences.Editor prefEditor) {
- //it seems pointless to do it, since the session should be kept by the remote device.
- }
-
- @Override
- public void loadMetadata() throws PlayableException {
- //Already loaded
- }
-
- @Override
- public void loadChapterMarks() {
- ChapterUtils.loadChaptersFromStreamUrl(this);
- }
-
- @Override
- public String getEpisodeTitle() {
- return episodeTitle;
- }
-
- @Override
- public List<Chapter> getChapters() {
- return chapters;
- }
-
- @Override
- public String getWebsiteLink() {
- if (episodeLink != null) {
- return episodeLink;
- } else {
- return feedUrl;
- }
- }
-
- @Override
- public String getPaymentLink() {
- return null;
- }
-
- @Override
- public String getFeedTitle() {
- return feedTitle;
- }
-
- @Override
- public Object getIdentifier() {
- return itemIdentifier + "@" + feedUrl;
- }
-
- @Override
- public int getDuration() {
- return duration;
- }
-
- @Override
- public int getPosition() {
- return position;
- }
-
- @Override
- public long getLastPlayedTime() {
- return lastPlayedTime;
- }
-
- @Override
- public MediaType getMediaType() {
- return MediaType.fromMimeType(mime_type);
- }
-
- @Override
- public String getLocalMediaUrl() {
- return null;
- }
-
- @Override
- public String getStreamUrl() {
- return downloadUrl;
- }
-
- @Override
- public boolean localFileAvailable() {
- return false;
- }
-
- @Override
- public boolean streamAvailable() {
- return true;
- }
-
- @Override
- public void saveCurrentPosition(SharedPreferences pref, int newPosition, long timestamp) {
- //we're not saving playback information for this kind of items on preferences
- setPosition(newPosition);
- setLastPlayedTime(timestamp);
- }
-
- @Override
- public void setPosition(int newPosition) {
- position = newPosition;
- }
-
- @Override
- public void setDuration(int newDuration) {
- duration = newDuration;
- }
-
- @Override
- public void setLastPlayedTime(long lastPlayedTimestamp) {
- lastPlayedTime = lastPlayedTimestamp;
- }
-
- @Override
- public void onPlaybackStart() {
- // no-op
- }
-
- @Override
- public void onPlaybackCompleted() {
- // no-op
- }
-
- @Override
- public int getPlayableType() {
- return PLAYABLE_TYPE_REMOTE_MEDIA;
- }
-
- @Override
- public void setChapters(List<Chapter> chapters) {
- this.chapters = chapters;
- }
-
- @Override
- @Nullable
- public String getImageLocation() {
- return imageUrl;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public Callable<String> loadShownotes() {
- return () -> (notes != null) ? notes : "";
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(downloadUrl);
- dest.writeString(itemIdentifier);
- dest.writeString(feedUrl);
- dest.writeString(feedTitle);
- dest.writeString(episodeTitle);
- dest.writeString(episodeLink);
- dest.writeString(feedAuthor);
- dest.writeString(imageUrl);
- dest.writeString(feedLink);
- dest.writeString(mime_type);
- dest.writeLong(pubDate.getTime());
- dest.writeString(notes);
- dest.writeInt(duration);
- dest.writeInt(position);
- dest.writeLong(lastPlayedTime);
- }
-
- public static final Parcelable.Creator<RemoteMedia> CREATOR = new Parcelable.Creator<RemoteMedia>() {
- @Override
- public RemoteMedia createFromParcel(Parcel in) {
- RemoteMedia result = new RemoteMedia(in.readString(), in.readString(), in.readString(),
- in.readString(), in.readString(), in.readString(), in.readString(), in.readString(),
- in.readString(), in.readString(), new Date(in.readLong()));
- result.setNotes(in.readString());
- result.setDuration(in.readInt());
- result.setPosition(in.readInt());
- result.setLastPlayedTime(in.readLong());
- return result;
- }
-
- @Override
- public RemoteMedia[] newArray(int size) {
- return new RemoteMedia[size];
- }
- };
-
- @Override
- public boolean equals(Object other) {
- if (other instanceof RemoteMedia) {
- RemoteMedia rm = (RemoteMedia) other;
- return TextUtils.equals(downloadUrl, rm.downloadUrl) &&
- TextUtils.equals(feedUrl, rm.feedUrl) &&
- TextUtils.equals(itemIdentifier, rm.itemIdentifier);
- }
- if (other instanceof FeedMedia) {
- FeedMedia fm = (FeedMedia) other;
- if (!TextUtils.equals(downloadUrl, fm.getStreamUrl())) {
- return false;
- }
- FeedItem fi = fm.getItem();
- if (fi == null || !TextUtils.equals(itemIdentifier, fi.getItemIdentifier())) {
- return false;
- }
- Feed feed = fi.getFeed();
- return feed != null && TextUtils.equals(feedUrl, feed.getDownload_url());
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return new HashCodeBuilder()
- .append(downloadUrl)
- .append(feedUrl)
- .append(itemIdentifier)
- .toHashCode();
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/cast/SwitchableMediaRouteActionProvider.java b/core/src/main/java/de/danoeh/antennapod/core/cast/SwitchableMediaRouteActionProvider.java
deleted file mode 100644
index f063cf5e3..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/cast/SwitchableMediaRouteActionProvider.java
+++ /dev/null
@@ -1,106 +0,0 @@
-package de.danoeh.antennapod.core.cast;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.FragmentManager;
-import android.support.v7.app.MediaRouteActionProvider;
-import android.support.v7.app.MediaRouteChooserDialogFragment;
-import android.support.v7.app.MediaRouteControllerDialogFragment;
-import android.support.v7.media.MediaRouter;
-import android.util.Log;
-
-/**
- * <p>Action Provider that extends {@link MediaRouteActionProvider} and allows the client to
- * disable completely the button by calling {@link #setEnabled(boolean)}.</p>
- *
- * <p>It is disabled by default, so if a client wants to initially have it enabled it must call
- * <code>setEnabled(true)</code>.</p>
- */
-public class SwitchableMediaRouteActionProvider extends MediaRouteActionProvider {
- public static final String TAG = "SwitchblMediaRtActProv";
-
- private static final String CHOOSER_FRAGMENT_TAG =
- "android.support.v7.mediarouter:MediaRouteChooserDialogFragment";
- private static final String CONTROLLER_FRAGMENT_TAG =
- "android.support.v7.mediarouter:MediaRouteControllerDialogFragment";
- private boolean enabled;
-
- public SwitchableMediaRouteActionProvider(Context context) {
- super(context);
- enabled = false;
- }
-
- /**
- * <p>Sets whether the Media Router button should be allowed to become visible or not.</p>
- *
- * <p>It's invisible by default.</p>
- */
- public void setEnabled(boolean newVal) {
- enabled = newVal;
- refreshVisibility();
- }
-
- @Override
- public boolean isVisible() {
- return enabled && super.isVisible();
- }
-
- @Override
- public boolean onPerformDefaultAction() {
- if (!super.onPerformDefaultAction()) {
- // there is no button, but we should still show the dialog if it's the case.
- if (!isVisible()) {
- return false;
- }
- FragmentManager fm = getFragmentManager();
- if (fm == null) {
- return false;
- }
- MediaRouter.RouteInfo route = MediaRouter.getInstance(getContext()).getSelectedRoute();
- if (route.isDefault() || !route.matchesSelector(getRouteSelector())) {
- if (fm.findFragmentByTag(CHOOSER_FRAGMENT_TAG) != null) {
- Log.w(TAG, "showDialog(): Route chooser dialog already showing!");
- return false;
- }
- MediaRouteChooserDialogFragment f =
- getDialogFactory().onCreateChooserDialogFragment();
- f.setRouteSelector(getRouteSelector());
- f.show(fm, CHOOSER_FRAGMENT_TAG);
- } else {
- if (fm.findFragmentByTag(CONTROLLER_FRAGMENT_TAG) != null) {
- Log.w(TAG, "showDialog(): Route controller dialog already showing!");
- return false;
- }
- MediaRouteControllerDialogFragment f =
- getDialogFactory().onCreateControllerDialogFragment();
- f.show(fm, CONTROLLER_FRAGMENT_TAG);
- }
- return true;
-
- } else {
- return true;
- }
- }
-
- private FragmentManager getFragmentManager() {
- Activity activity = getActivity();
- if (activity instanceof FragmentActivity) {
- return ((FragmentActivity)activity).getSupportFragmentManager();
- }
- return null;
- }
-
- private Activity getActivity() {
- // Gross way of unwrapping the Activity so we can get the FragmentManager
- Context context = getContext();
- while (context instanceof ContextWrapper) {
- if (context instanceof Activity) {
- return (Activity)context;
- }
- context = ((ContextWrapper)context).getBaseContext();
- }
- return null;
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java
deleted file mode 100644
index 068669af9..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java
+++ /dev/null
@@ -1,568 +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.cast.RemoteMedia;
-import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
-import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.storage.DBReader;
-import de.danoeh.antennapod.core.storage.DBWriter;
-import de.danoeh.antennapod.core.storage.PodDBAdapter;
-import de.danoeh.antennapod.core.util.ChapterUtils;
-import de.danoeh.antennapod.core.util.playback.Playable;
-
-public class FeedMedia extends FeedFile implements Playable {
- private static final String TAG = "FeedMedia";
-
- public static final int FEEDFILETYPE_FEEDMEDIA = 2;
- public static final int PLAYABLE_TYPE_FEEDMEDIA = 1;
-
- public static final String PREF_MEDIA_ID = "FeedMedia.PrefMediaId";
- public static final String PREF_FEED_ID = "FeedMedia.PrefFeedId";
-
- /**
- * Indicates we've checked on the size of the item via the network
- * and got an invalid response. Using Integer.MIN_VALUE because
- * 1) we'll still check on it in case it gets downloaded (it's <= 0)
- * 2) By default all FeedMedia have a size of 0 if we don't know it,
- * so this won't conflict with existing practice.
- */
- private static final int CHECKED_ON_SIZE_BUT_UNKNOWN = Integer.MIN_VALUE;
-
- private int duration;
- private int position; // Current position in file
- private long lastPlayedTime; // Last time this media was played (in ms)
- private int played_duration; // How many ms of this file have been played (for autoflattring)
- private long size; // File size in Byte
- private String mime_type;
- @Nullable private volatile FeedItem item;
- private Date playbackCompletionDate;
-
- // if null: unknown, will be checked
- private Boolean hasEmbeddedPicture;
-
- /* Used for loading item when restoring from parcel. */
- private long itemID;
-
- public FeedMedia(FeedItem i, String download_url, long size,
- String mime_type) {
- super(null, download_url, false);
- this.item = i;
- this.size = size;
- this.mime_type = mime_type;
- }
-
- public FeedMedia(long id, FeedItem item, int duration, int position,
- long size, String mime_type, String file_url, String download_url,
- boolean downloaded, Date playbackCompletionDate, int played_duration,
- long lastPlayedTime) {
- super(file_url, download_url, downloaded);
- this.id = id;
- this.item = item;
- this.duration = duration;
- this.position = position;
- this.played_duration = played_duration;
- this.size = size;
- this.mime_type = mime_type;
- this.playbackCompletionDate = playbackCompletionDate == null
- ? null : (Date) playbackCompletionDate.clone();
- this.lastPlayedTime = lastPlayedTime;
- }
-
- public FeedMedia(long id, FeedItem item, int duration, int position,
- long size, String mime_type, String file_url, String download_url,
- boolean downloaded, Date playbackCompletionDate, int played_duration,
- Boolean hasEmbeddedPicture, long lastPlayedTime) {
- this(id, item, duration, position, size, mime_type, file_url, download_url, downloaded,
- playbackCompletionDate, played_duration, lastPlayedTime);
- this.hasEmbeddedPicture = hasEmbeddedPicture;
- }
-
- public static FeedMedia fromCursor(Cursor cursor) {
- int indexId = cursor.getColumnIndex(PodDBAdapter.KEY_ID);
- int indexPlaybackCompletionDate = cursor.getColumnIndex(PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE);
- int indexDuration = cursor.getColumnIndex(PodDBAdapter.KEY_DURATION);
- int indexPosition = cursor.getColumnIndex(PodDBAdapter.KEY_POSITION);
- int indexSize = cursor.getColumnIndex(PodDBAdapter.KEY_SIZE);
- int indexMimeType = cursor.getColumnIndex(PodDBAdapter.KEY_MIME_TYPE);
- int indexFileUrl = cursor.getColumnIndex(PodDBAdapter.KEY_FILE_URL);
- int indexDownloadUrl = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOAD_URL);
- int indexDownloaded = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOADED);
- int indexPlayedDuration = cursor.getColumnIndex(PodDBAdapter.KEY_PLAYED_DURATION);
- int indexLastPlayedTime = cursor.getColumnIndex(PodDBAdapter.KEY_LAST_PLAYED_TIME);
-
- long mediaId = cursor.getLong(indexId);
- Date playbackCompletionDate = null;
- long playbackCompletionTime = cursor.getLong(indexPlaybackCompletionDate);
- if (playbackCompletionTime > 0) {
- playbackCompletionDate = new Date(playbackCompletionTime);
- }
-
- Boolean hasEmbeddedPicture;
- switch(cursor.getInt(cursor.getColumnIndex(PodDBAdapter.KEY_HAS_EMBEDDED_PICTURE))) {
- case 1:
- hasEmbeddedPicture = Boolean.TRUE;
- break;
- case 0:
- hasEmbeddedPicture = Boolean.FALSE;
- break;
- default:
- hasEmbeddedPicture = null;
- break;
- }
-
- return new FeedMedia(
- mediaId,
- null,
- cursor.getInt(indexDuration),
- cursor.getInt(indexPosition),
- cursor.getLong(indexSize),
- cursor.getString(indexMimeType),
- cursor.getString(indexFileUrl),
- cursor.getString(indexDownloadUrl),
- cursor.getInt(indexDownloaded) > 0,
- playbackCompletionDate,
- cursor.getInt(indexPlayedDuration),
- hasEmbeddedPicture,
- cursor.getLong(indexLastPlayedTime)
- );
- }
-
-
- @Override
- public String getHumanReadableIdentifier() {
- if (item != null && item.getTitle() != null) {
- return item.getTitle();
- } else {
- return download_url;
- }
- }
-
- /**
- * Uses mimetype to determine the type of media.
- */
- public MediaType getMediaType() {
- return MediaType.fromMimeType(mime_type);
- }
-
- public void updateFromOther(FeedMedia other) {
- super.updateFromOther(other);
- if (other.size > 0) {
- size = other.size;
- }
- if (other.mime_type != null) {
- mime_type = other.mime_type;
- }
- }
-
- public boolean compareWithOther(FeedMedia other) {
- if (super.compareWithOther(other)) {
- return true;
- }
- if (other.mime_type != null) {
- if (mime_type == null || !mime_type.equals(other.mime_type)) {
- return true;
- }
- }
- if (other.size > 0 && other.size != size) {
- return true;
- }
- return false;
- }
-
- /**
- * Reads playback preferences to determine whether this FeedMedia object is
- * currently being played.
- */
- public boolean isPlaying() {
- return PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA
- && PlaybackPreferences.getCurrentlyPlayingFeedMediaId() == id;
- }
-
- /**
- * Reads playback preferences to determine whether this FeedMedia object is
- * currently being played and the current player status is playing.
- */
- public boolean isCurrentlyPlaying() {
- return isPlaying() &&
- ((PlaybackPreferences.getCurrentPlayerStatus() == PlaybackPreferences.PLAYER_STATUS_PLAYING));
- }
-
- /**
- * Reads playback preferences to determine whether this FeedMedia object is
- * currently being played and the current player status is paused.
- */
- public boolean isCurrentlyPaused() {
- return isPlaying() &&
- ((PlaybackPreferences.getCurrentPlayerStatus() == PlaybackPreferences.PLAYER_STATUS_PAUSED));
- }
-
-
- public boolean hasAlmostEnded() {
- int smartMarkAsPlayedSecs = UserPreferences.getSmartMarkAsPlayedSecs();
- return this.position >= this.duration - smartMarkAsPlayedSecs * 1000;
- }
-
- @Override
- public int getTypeAsInt() {
- return FEEDFILETYPE_FEEDMEDIA;
- }
-
- public int getDuration() {
- return duration;
- }
-
- public void setDuration(int duration) {
- this.duration = duration;
- }
-
- @Override
- public void setLastPlayedTime(long lastPlayedTime) {
- this.lastPlayedTime = lastPlayedTime;
- }
-
- public int getPlayedDuration() {
- return played_duration;
- }
-
- public void setPlayedDuration(int played_duration) {
- this.played_duration = played_duration;
- }
-
- public int getPosition() {
- return position;
- }
-
- @Override
- public long getLastPlayedTime() {
- return lastPlayedTime;
- }
-
- public void setPosition(int position) {
- this.position = position;
- if(position > 0 && item != null && item.isNew()) {
- this.item.setPlayed(false);
- }
- }
-
- public long getSize() {
- return size;
- }
-
- public void setSize(long size) {
- this.size = size;
- }
-
- /**
- * Indicates we asked the service what the size was, but didn't
- * get a valid answer and we shoudln't check using the network again.
- */
- public void setCheckedOnSizeButUnknown() {
- this.size = CHECKED_ON_SIZE_BUT_UNKNOWN;
- }
-
- public boolean checkedOnSizeButUnknown() {
- return (CHECKED_ON_SIZE_BUT_UNKNOWN == this.size);
- }
-
- public String getMime_type() {
- return mime_type;
- }
-
- public void setMime_type(String mime_type) {
- this.mime_type = mime_type;
- }
-
- @Nullable
- public FeedItem getItem() {
- return item;
- }
-
- /**
- * Sets the item object of this FeedMedia. If the given
- * FeedItem object is not null, it's 'media'-attribute value
- * will also be set to this media object.
- */
- public void setItem(FeedItem item) {
- this.item = item;
- if (item != null && item.getMedia() != this) {
- item.setMedia(this);
- }
- }
-
- public Date getPlaybackCompletionDate() {
- return playbackCompletionDate == null
- ? null : (Date) playbackCompletionDate.clone();
- }
-
- public void setPlaybackCompletionDate(Date playbackCompletionDate) {
- this.playbackCompletionDate = playbackCompletionDate == null
- ? null : (Date) playbackCompletionDate.clone();
- }
-
- public boolean isInProgress() {
- return (this.position > 0);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- public boolean hasEmbeddedPicture() {
- 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/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
deleted file mode 100644
index e2d63a385..000000000
--- a/core/src/main/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/main/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java
deleted file mode 100644
index 4262b8a70..000000000
--- a/core/src/main/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/main/java/de/danoeh/antennapod/core/util/playback/Playable.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java
deleted file mode 100644
index 201efbc81..000000000
--- a/core/src/main/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);
- }
-
- }
-}