add a CastManager, and other changes

This commit is contained in:
Domingos Lopes 2016-03-24 01:59:37 -04:00 committed by Domingos Lopes
parent 2057a92a19
commit af7526a409
9 changed files with 2187 additions and 96 deletions

View File

@ -7,9 +7,8 @@ import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;
import com.google.android.libraries.cast.companionlibrary.cast.VideoCastManager;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.cast.CastManager;
import de.danoeh.antennapod.core.preferences.UserPreferences;
/**
@ -19,7 +18,7 @@ import de.danoeh.antennapod.core.preferences.UserPreferences;
public abstract class CastEnabledActivity extends AppCompatActivity
implements SharedPreferences.OnSharedPreferenceChangeListener {
protected VideoCastManager mCastManager;
protected CastManager mCastManager;
private int castUICounter;
protected MenuItem mMediaRouteMenuItem;
protected boolean isCastEnabled;
@ -32,7 +31,7 @@ public abstract class CastEnabledActivity extends AppCompatActivity
registerOnSharedPreferenceChangeListener(this);
castUICounter = 0;
mCastManager = VideoCastManager.getInstance();
mCastManager = CastManager.getInstance();
isCastEnabled = UserPreferences.isCastEnabled();
}

View File

@ -0,0 +1,11 @@
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);
}

View File

@ -0,0 +1,10 @@
package de.danoeh.antennapod.core.cast;
import com.google.android.libraries.cast.companionlibrary.cast.callbacks.VideoCastConsumerImpl;
public class CastConsumerImpl extends VideoCastConsumerImpl implements CastConsumer {
@Override
public void onStreamVolumeChanged(double value, boolean isMute) {
// no-op
}
}

File diff suppressed because it is too large Load Diff

View File

@ -15,18 +15,14 @@ import org.antennapod.audio.MediaPlayer;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
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.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.RewindAfterPauseUtils;
import de.danoeh.antennapod.core.util.playback.AudioPlayer;
import de.danoeh.antennapod.core.util.playback.IPlayer;
@ -73,13 +69,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
this.playerLock = new ReentrantLock();
this.startWhenPrepared = new AtomicBoolean(false);
executor = new ThreadPoolExecutor(1, 1, 5, TimeUnit.MINUTES, new LinkedBlockingDeque<>(),
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
Log.d(TAG, "Rejected execution of runnable");
}
}
);
(r, executor) -> Log.d(TAG, "Rejected execution of runnable"));
mediaPlayer = null;
statusBeforeSeeking = null;
@ -160,21 +150,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
setPlayerStatus(PlayerStatus.PAUSED, media);
}
// smart mark as played
if(media != null && media instanceof FeedMedia) {
FeedMedia oldMedia = (FeedMedia) media;
if(oldMedia.hasAlmostEnded()) {
Log.d(TAG, "smart mark as read");
FeedItem item = oldMedia.getItem();
DBWriter.markItemPlayed(item, FeedItem.PLAYED, false);
DBWriter.removeQueueItem(context, item, false);
DBWriter.addItemToPlaybackHistory(oldMedia);
if (item.getFeed().getPreferences().getCurrentAutoDelete()) {
Log.d(TAG, "Delete " + oldMedia.toString());
DBWriter.deleteFeedMediaOfItem(context, oldMedia.getId());
}
}
}
smartMarkAsPlayed(media);
setPlayerStatus(PlayerStatus.INDETERMINATE, null);
}
@ -498,10 +474,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
}
int retVal = INVALID_TIME;
if (playerStatus == PlayerStatus.PLAYING
|| playerStatus == PlayerStatus.PAUSED
|| playerStatus == PlayerStatus.PREPARED
|| playerStatus == PlayerStatus.SEEKING) {
if (playerStatus.isAtLeast(PlayerStatus.PREPARED)) {
retVal = mediaPlayer.getCurrentPosition();
}
if (retVal <= 0 && media != null && media.getPosition() >= 0) {
@ -653,6 +626,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
@Override
public void shutdownAsync() {
executor.submit(this::shutdown);
executor.shutdown();
}
@Override

View File

@ -38,14 +38,14 @@ import android.widget.Toast;
import com.bumptech.glide.Glide;
import com.google.android.gms.cast.ApplicationMetadata;
import com.google.android.libraries.cast.companionlibrary.cast.BaseCastManager;
import com.google.android.libraries.cast.companionlibrary.cast.VideoCastManager;
import com.google.android.libraries.cast.companionlibrary.cast.callbacks.VideoCastConsumer;
import com.google.android.libraries.cast.companionlibrary.cast.callbacks.VideoCastConsumerImpl;
import java.util.List;
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.CastConsumerImpl;
import de.danoeh.antennapod.core.cast.CastManager;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
@ -248,8 +248,8 @@ public class PlaybackService extends Service implements SharedPreferences.OnShar
Log.d(TAG, "Service created.");
isRunning = true;
VideoCastManager castMgr = VideoCastManager.getInstance();
castMgr.addVideoCastConsumer(castConsumer);
CastManager castMgr = CastManager.getInstance();
castMgr.addCastConsumer(castConsumer);
isCasting = castMgr.isConnected();
registerReceiver(headsetDisconnected, new IntentFilter(
@ -317,7 +317,7 @@ public class PlaybackService extends Service implements SharedPreferences.OnShar
unregisterReceiver(skipCurrentEpisodeReceiver);
unregisterReceiver(pausePlayCurrentEpisodeReceiver);
unregisterReceiver(pauseResumeCurrentEpisodeReceiver);
VideoCastManager.getInstance().removeVideoCastConsumer(castConsumer);
CastManager.getInstance().removeCastConsumer(castConsumer);
mediaPlayer.shutdown();
taskManager.shutdown();
}
@ -366,7 +366,7 @@ public class PlaybackService extends Service implements SharedPreferences.OnShar
sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0);
//If the user asks to play External Media, the casting session, if on, should end.
if (playable instanceof ExternalMedia) {
VideoCastManager.getInstance().disconnect();
CastManager.getInstance().disconnect();
}
mediaPlayer.playMediaObject(playable, stream, startWhenPrepared, prepareImmediately);
}
@ -1545,7 +1545,7 @@ public class PlaybackService extends Service implements SharedPreferences.OnShar
}
};
private VideoCastConsumer castConsumer = new VideoCastConsumerImpl() {
private CastConsumer castConsumer = new CastConsumerImpl() {
@Override
public void onApplicationConnected(ApplicationMetadata appMetadata, String sessionId, boolean wasLaunched) {
Log.d(TAG, "A cast device application was connected");

View File

@ -7,7 +7,10 @@ import android.util.Pair;
import android.view.SurfaceHolder;
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.storage.DBWriter;
import de.danoeh.antennapod.core.util.playback.Playable;
@ -257,6 +260,26 @@ public abstract class PlaybackServiceMediaPlayer {
callback.statusChanged(new PSMPInfo(playerStatus, getPlayable()));
}
protected void smartMarkAsPlayed(Playable media) {
if(media != null && media instanceof FeedMedia) {
FeedMedia oldMedia = (FeedMedia) media;
if(oldMedia.hasAlmostEnded()) {
Log.d(TAG, "smart mark as read");
FeedItem item = oldMedia.getItem();
if (item == null) {
return;
}
DBWriter.markItemPlayed(item, FeedItem.PLAYED, false);
DBWriter.removeQueueItem(context, item, false);
DBWriter.addItemToPlaybackHistory(oldMedia);
if (item.getFeed().getPreferences().getCurrentAutoDelete()) {
Log.d(TAG, "Delete " + oldMedia.toString());
DBWriter.deleteFeedMediaOfItem(context, oldMedia.getId());
}
}
}
}
public interface PSMPCallback {
void statusChanged(PSMPInfo newInfo);

View File

@ -2,11 +2,30 @@ package de.danoeh.antennapod.core.service.playback;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Log;
import android.util.Pair;
import android.view.SurfaceHolder;
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.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import de.danoeh.antennapod.core.cast.CastConsumer;
import de.danoeh.antennapod.core.cast.CastConsumerImpl;
import de.danoeh.antennapod.core.cast.CastManager;
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.util.CastUtils;
import de.danoeh.antennapod.core.util.playback.Playable;
/**
@ -14,15 +33,185 @@ import de.danoeh.antennapod.core.util.playback.Playable;
*/
public class RemotePSMP extends PlaybackServiceMediaPlayer {
public static final String TAG = "RemotePSMP";
private final CastManager castMgr;
private volatile Playable media;
private volatile MediaInfo remoteMedia;
private volatile MediaType mediaType;
private volatile PlayerStatus statusBeforeSeeking;
private volatile AtomicBoolean startWhenPrepared;
/**
* Some asynchronous calls might change the state of the MediaPlayer object. Therefore calls in other threads
* have to wait until these operations have finished.
*/
private final ReentrantLock playerLock;
private final ThreadPoolExecutor executor;
public RemotePSMP(@NonNull Context context, @NonNull PSMPCallback callback) {
super(context, callback);
castMgr = CastManager.getInstance();
statusBeforeSeeking = null;
media = null;
mediaType = null;
this.startWhenPrepared = new AtomicBoolean(false);
playerLock = new ReentrantLock();
executor = new ThreadPoolExecutor(1, 1, 5, TimeUnit.MINUTES, new LinkedBlockingDeque<>(),
(r, executor) -> Log.d(TAG, "Rejected execution of runnable"));
castMgr.addCastConsumer(castConsumer);
//TODO
}
private CastConsumer castConsumer = new CastConsumerImpl() {
@Override
public void onRemoteMediaPlayerMetadataUpdated() {
//TODO check this is indeed a correct behavior
onRemoteMediaPlayerStatusUpdated();
}
@Override
public void onRemoteMediaPlayerStatusUpdated() {
MediaStatus status = castMgr.getMediaStatus();
Playable currentMedia = localVersion(status.getMediaInfo());
switch (status.getPlayerState()) {
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:
//TODO
break;
case MediaStatus.PLAYER_STATE_IDLE:
int reason = status.getIdleReason();
switch (reason) {
case MediaStatus.IDLE_REASON_CANCELED:
setPlayerStatus(PlayerStatus.STOPPED, currentMedia);
break;
case MediaStatus.IDLE_REASON_INTERRUPTED:
setPlayerStatus(PlayerStatus.PREPARING, currentMedia);
break;
case MediaStatus.IDLE_REASON_NONE:
setPlayerStatus(PlayerStatus.INITIALIZED, currentMedia);
break;
case MediaStatus.IDLE_REASON_FINISHED:
//TODO endPlayback and start a new one
break;
case MediaStatus.IDLE_REASON_ERROR:
//TODO what do they mean by error? Can we easily recover by sending a new command?
}
break;
case MediaStatus.PLAYER_STATE_UNKNOWN:
//TODO
}
}
@Override
public void onStreamVolumeChanged(double value, boolean isMute) {
//TODO
}
@Override
public void onMediaLoadResult(int statusCode) {
if (playerStatus == PlayerStatus.PREPARING) {
if (statusCode == CastStatusCodes.SUCCESS) {
executor.execute(RemotePSMP.this::onPrepared);
} else {
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");
}
}
};
private Playable localVersion(MediaInfo info){
// TODO compare with current media. If it doesn't match, then either find a local version for it
// or create an appropriate one.
return media;
}
@Override
public void playMediaObject(@NonNull Playable playable, boolean stream, boolean startWhenPrepared, boolean prepareImmediately) {
//TODO
public void playMediaObject(@NonNull final Playable playable, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) {
Log.d(TAG, "playMediaObject() called");
executor.execute(() -> {
playerLock.lock();
try {
playMediaObject(playable, false, stream, startWhenPrepared, prepareImmediately);
} finally {
playerLock.unlock();
}
});
}
/**
* 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.
* <p/>
* This method requires the playerLock and is executed on the caller's thread.
*
* @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 (!playerLock.isHeldByCurrentThread()) {
throw new IllegalStateException("method requires playerLock");
}
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);
}
}
if (CastUtils.isCastable(playable)) {
this.media = playable;
remoteMedia = CastUtils.convertFromFeedMedia((FeedMedia) media);
//this.stream = stream;
this.mediaType = media.getMediaType();
this.startWhenPrepared.set(startWhenPrepared);
setPlayerStatus(PlayerStatus.INITIALIZING, media);
try {
media.loadMetadata();
executor.execute(() -> updateMediaSessionMetadata(media));
setPlayerStatus(PlayerStatus.INITIALIZED, media);
if (prepareImmediately) {
prepareSync();
}
} catch (Playable.PlayableException e) {
Log.e(TAG, "Error while loading media metadata", e);
setPlayerStatus(PlayerStatus.STOPPED, null);
}
}
}
@Override
@ -37,7 +226,49 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
@Override
public void prepare() {
//TODO
executor.submit( () -> {
playerLock.lock();
prepareSync();
playerLock.unlock();
});
}
private void prepareSync() {
if (playerStatus == PlayerStatus.INITIALIZED) {
Log.d(TAG, "Preparing media player");
setPlayerStatus(PlayerStatus.PREPARING, media);
try {
castMgr.loadMedia(remoteMedia, startWhenPrepared.get(), media.getPosition());
} catch (TransientNetworkDisconnectionException | NoConnectionException e) {
Log.e(TAG, "Error loading media", e);
setPlayerStatus(PlayerStatus.INITIALIZED, media);
}
}
}
/**
* Called after media player has been prepared. This method is executed on the caller's thread.
*/
void onPrepared() {
playerLock.lock();
if (playerStatus != PlayerStatus.PREPARING) {
playerLock.unlock();
Log.w(TAG, "onPrepared() called, but player is not in PREPARING state anymore");
return;
}
Log.d(TAG, "Resource loaded");
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");
}
}
setPlayerStatus(PlayerStatus.PREPARED, media);
playerLock.unlock();
}
@Override
@ -68,8 +299,36 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
@Override
public int getPosition() {
//TODO
return 0;
try {
if (!playerLock.tryLock(50, TimeUnit.MILLISECONDS)) {
return INVALID_TIME;
}
} catch (InterruptedException e) {
return INVALID_TIME;
}
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();
}
playerLock.unlock();
Log.d(TAG, "getPosition() -> " + retVal);
return retVal;
}
@Override
@ -83,37 +342,49 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
//TODO
}
//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() {
//TODO
return false;
}
@Override
public void setSpeed(float speed) {
//TODO
throw new UnsupportedOperationException("Setting playback speed unsupported for Remote Playback");
}
@Override
public float getPlaybackSpeed() {
//TODO
return 0;
return 1;
}
//TODO make device volume being selected by the hardware keys
@Override
public void setVolume(float volumeLeft, float volumeRight) {
//TODO
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() {
//TODO
return false;
}
@Override
public void setDownmix(boolean enable) {
//TODO
throw new UnsupportedOperationException("Setting downmix unsupported in Remote Media Player");
}
@Override
@ -129,6 +400,8 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
@Override
public void shutdown() {
castMgr.removeCastConsumer(castConsumer);
executor.shutdown();
//TODO
}
@ -156,13 +429,16 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
@Override
public Playable getPlayable() {
//TODO
return null;
return media;
}
@Override
protected void setPlayable(Playable playable) {
//TODO
//TODO this looks very wrong
if (playable != media) {
media = playable;
remoteMedia = !(media instanceof FeedMedia) ? null : CastUtils.convertFromFeedMedia((FeedMedia) media);
}
}
@Override

View File

@ -2,7 +2,6 @@ package de.danoeh.antennapod.core.util;
import android.content.Context;
import android.net.Uri;
import android.support.v7.media.MediaRouter;
import android.util.Log;
import com.google.android.gms.cast.CastDevice;
@ -11,13 +10,10 @@ 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.cast.CastManager;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
@ -35,15 +31,11 @@ public class CastUtils {
public static final String KEY_MEDIA_ID = "CastUtils.Id";
public static void initializeCastManager(Context context){
VideoCastManager.initialize(context, new CastConfiguration.Builder(CastUtils.CAST_APP_ID)
CastManager.initialize(context, new CastConfiguration.Builder(CastUtils.CAST_APP_ID)
.enableDebug()
.enableLockScreen()
.enableNotification()
.enableWifiReconnection()
.enableAutoReconnect()
.setTargetActivity(ClientConfig.castCallbacks.getCastActivity())
.build());
VideoCastManager.getInstance().addVideoCastConsumer(castConsumer);
}
public static boolean isCastable(Playable media){
@ -59,9 +51,9 @@ public class CastUtils {
case UNKNOWN:
return false;
case AUDIO:
return audioCapable;
return CastManager.getInstance().hasCapability(CastDevice.CAPABILITY_AUDIO_OUT, true);
case VIDEO:
return videoCapable;
return CastManager.getInstance().hasCapability(CastDevice.CAPABILITY_VIDEO_OUT, true);
}
}
return false;
@ -115,32 +107,5 @@ public class CastUtils {
.build();
}
// 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
}