diff options
author | Domingos Lopes <domingos86lopes@gmail.com> | 2016-03-19 01:32:55 -0400 |
---|---|---|
committer | Domingos Lopes <domingos86lopes+github@gmail.com> | 2016-04-23 21:39:52 -0400 |
commit | 8061d94c1b2dcdc99fc0a3c4554a00f01ca4941f (patch) | |
tree | cdc3f9ca984e174ffcf517f1f2434f59e79990ee | |
parent | 1b6459c8ee2dfcb40a4909aa87ff2ea100b8221e (diff) | |
download | AntennaPod-8061d94c1b2dcdc99fc0a3c4554a00f01ca4941f.zip |
add chromecast support initialization logic and introduce chromecast preference
8 files changed, 228 insertions, 4 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/config/CastCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/CastCallbacksImpl.java new file mode 100644 index 000000000..b92544201 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/config/CastCallbacksImpl.java @@ -0,0 +1,20 @@ +package de.danoeh.antennapod.config; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; + +import de.danoeh.antennapod.activity.AudioplayerActivity; +import de.danoeh.antennapod.core.CastCallbacks; + +public class CastCallbacksImpl implements CastCallbacks { + @Override + public Class<? extends Activity> getCastActivity() { + return AudioplayerActivity.class; + } + + @Override + public Intent getCastActivityIntent(Context context) { + return new Intent(context, getCastActivity()); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java b/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java index 932b9d22f..154b10cb3 100644 --- a/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java +++ b/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java @@ -16,5 +16,6 @@ public class ClientConfigurator { ClientConfig.playbackServiceCallbacks = new PlaybackServiceCallbacksImpl(); ClientConfig.flattrCallbacks = new FlattrCallbacksImpl(); ClientConfig.dbTasksCallbacks = new DBTasksCallbacksImpl(); + ClientConfig.castCallbacks = new CastCallbacksImpl(); } } diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index ecdcd3517..57829e3e1 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -293,5 +293,14 @@ android:key="prefAbout" android:title="@string/about_pref"/> </PreferenceCategory> + + <PreferenceCategory android:title="@string/experimental_pref"> + <de.danoeh.antennapod.preferences.SwitchCompatPreference + android:defaultValue="false" + android:enabled="true" + android:key="prefCast" + android:summary="@string/pref_cast_message" + android:title="@string/pref_cast_title"/> + </PreferenceCategory> </PreferenceScreen> diff --git a/core/src/main/java/de/danoeh/antennapod/core/CastCallbacks.java b/core/src/main/java/de/danoeh/antennapod/core/CastCallbacks.java new file mode 100644 index 000000000..a2f17ba93 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/CastCallbacks.java @@ -0,0 +1,16 @@ +package de.danoeh.antennapod.core; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; + +/** + * Callbacks for the Cast features on the core module. + */ +public interface CastCallbacks { + + Class<? extends Activity> getCastActivity(); + + Intent getCastActivityIntent(Context context); + +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/ClientConfig.java b/core/src/main/java/de/danoeh/antennapod/core/ClientConfig.java index a96affb23..d7735b34e 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/ClientConfig.java +++ b/core/src/main/java/de/danoeh/antennapod/core/ClientConfig.java @@ -5,6 +5,7 @@ import android.content.Context; 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.CastUtils; import de.danoeh.antennapod.core.util.NetworkUtils; /** @@ -30,6 +31,8 @@ public class ClientConfig { public static DBTasksCallbacks dbTasksCallbacks; + public static CastCallbacks castCallbacks; + private static boolean initialized = false; public static synchronized void initialize(Context context) { @@ -41,6 +44,7 @@ public class ClientConfig { UpdateManager.init(context); PlaybackPreferences.init(context); NetworkUtils.init(context); + CastUtils.initializeCastManager(context); initialized = true; } diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java index 569dfd2c4..879861a3f 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java +++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java @@ -111,6 +111,7 @@ public class UserPreferences { public static final String PREF_SONIC = "prefSonic"; public static final String PREF_STEREO_TO_MONO = "PrefStereoToMono"; public static final String PREF_NORMALIZER = "prefNormalizer"; + public static final String PREF_CAST_ENABLED = "prefCast"; //Used for enabling Chromecast support public static final int EPISODE_CLEANUP_QUEUE = -1; public static final int EPISODE_CLEANUP_NULL = -2; public static final int EPISODE_CLEANUP_DEFAULT = 0; @@ -493,16 +494,16 @@ public class UserPreferences { public static void setVolume(int leftVolume, int rightVolume) { assert(0 <= leftVolume && leftVolume <= 100); - assert(0 <= rightVolume && rightVolume <= 100); + assert (0 <= rightVolume && rightVolume <= 100); prefs.edit() - .putInt(PREF_LEFT_VOLUME, leftVolume) + .putInt(PREF_LEFT_VOLUME, leftVolume) .putInt(PREF_RIGHT_VOLUME, rightVolume) .apply(); } public static void setAutodownloadSelectedNetworks(String[] value) { prefs.edit() - .putString(PREF_AUTODL_SELECTED_NETWORKS, TextUtils.join(",", value)) + .putString(PREF_AUTODL_SELECTED_NETWORKS, TextUtils.join(",", value)) .apply(); } @@ -540,7 +541,7 @@ public class UserPreferences { throw new IllegalArgumentException("Flattr threshold must be in range [0.0, 1.0]"); } prefs.edit() - .putBoolean(PREF_AUTO_FLATTR, enabled) + .putBoolean(PREF_AUTO_FLATTR, enabled) .putFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD, autoFlattrThreshold) .apply(); } @@ -800,4 +801,11 @@ public class UserPreferences { public static int readEpisodeCacheSize(String valueFromPrefs) { return readEpisodeCacheSizeInternal(valueFromPrefs); } + + /** + * Evaluates whether Cast support (Chromecast, Audio Cast, etc) is enabled on the preferences. + */ + public static boolean isCastEnabled() { + return prefs.getBoolean(PREF_CAST_ENABLED, false); + } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/CastUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/CastUtils.java new file mode 100644 index 000000000..95103fb6c --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/util/CastUtils.java @@ -0,0 +1,164 @@ +package de.danoeh.antennapod.core.util; + +import android.content.Context; +import android.content.SharedPreferences; +import android.net.Uri; +import android.preference.PreferenceManager; +import android.support.v7.media.MediaRouter; +import android.util.Log; + +import com.google.android.gms.cast.CastDevice; +import com.google.android.gms.cast.CastMediaControlIntent; +import com.google.android.gms.cast.MediaInfo; +import com.google.android.gms.cast.MediaMetadata; +import com.google.android.gms.common.images.WebImage; +import com.google.android.libraries.cast.companionlibrary.cast.CastConfiguration; +import com.google.android.libraries.cast.companionlibrary.cast.VideoCastManager; +import com.google.android.libraries.cast.companionlibrary.cast.callbacks.VideoCastConsumer; +import com.google.android.libraries.cast.companionlibrary.cast.callbacks.VideoCastConsumerImpl; + +import java.util.Calendar; + +import de.danoeh.antennapod.core.ClientConfig; +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.preferences.UserPreferences; +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 CAST_APP_ID = CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID; + + public static final String KEY_MEDIA_ID = "CastUtils.Id"; + + public static void initializeCastManager(Context context){ + // TODO check for cast support enabled + VideoCastManager.initialize(context, new CastConfiguration.Builder(CastUtils.CAST_APP_ID) + .enableDebug() + .enableLockScreen() + .enableNotification() + .enableWifiReconnection() + .enableAutoReconnect() + .setTargetActivity(ClientConfig.castCallbacks.getCastActivity()) + .build()); + VideoCastManager.getInstance().addVideoCastConsumer(castConsumer); + PreferenceManager.getDefaultSharedPreferences(context) + .registerOnSharedPreferenceChangeListener(changeListener); + } + + public static boolean isCastable(Playable media){ + if (media == null || media instanceof ExternalMedia) { + return false; + } + if (media instanceof FeedMedia){ + String url = media.getStreamUrl(); + if(url == null || url.isEmpty()){ + return false; + } + switch (media.getMediaType()) { + case UNKNOWN: + return false; + case AUDIO: + return audioCapable; + case VIDEO: + return videoCapable; + } + } + 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 && image.getDownload_url() != null && + !image.getDownload_url().isEmpty()) { + 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); + + } + //metadata.putString(MediaMetadata.KEY_ARTIST, null); + metadata.putString(KEY_MEDIA_ID, media.getIdentifier().toString()); + + return new MediaInfo.Builder(media.getStreamUrl()) + .setContentType(media.getMime_type()) + .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) + .setMetadata(metadata) + .build(); + } + + + private static SharedPreferences.OnSharedPreferenceChangeListener changeListener = + (preference, key) -> { + if (UserPreferences.PREF_CAST_ENABLED.equals(key)){ + if (UserPreferences.isCastEnabled()){ + // TODO enable all cast-related features + } else { + // TODO disable all cast-related features + } + } + }; + + // Ideally, all these fields and methods should be part of the CastManager implementation + private static boolean videoCapable = true; + private static boolean audioCapable = true; + + public static boolean isVideoCapable(CastDevice device, boolean defaultValue){ + if (device == null) { + return defaultValue; + } + return device.hasCapability(CastDevice.CAPABILITY_VIDEO_OUT); + } + + public static boolean isAudioCapable(CastDevice device, boolean defaultValue){ + if (device == null) { + return defaultValue; + } + return device.hasCapability(CastDevice.CAPABILITY_AUDIO_OUT); + } + + private static VideoCastConsumer castConsumer = new VideoCastConsumerImpl() { + @Override + public void onDeviceSelected(CastDevice device, MediaRouter.RouteInfo routeInfo) { + // If no device is selected, we assume both audio and video are castable + videoCapable = isVideoCapable(device, true); + audioCapable = isAudioCapable(device, true); + } + }; + + //TODO Queue handling perhaps +} diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 9026e2129..8a3f32b5f 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -403,6 +403,8 @@ <string name="pref_faq">FAQ</string> <string name="pref_known_issues">Known issues</string> <string name="pref_no_browser_found">No web browser found.</string> + <string name="pref_cast_title">Cast support</string> + <string name="pref_cast_message">Enable support for remote media playback on Cast devices (such as Chromecast, Audio Speakers or Android TV)</string> <!-- Auto-Flattr dialog --> <string name="auto_flattr_enable">Enable automatic flattring</string> |