add a CastManager, and other changes
This commit is contained in:
parent
2057a92a19
commit
af7526a409
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue