Make PlaybackServiceMediaPlayer an abstract class and move implementation independent methods inside it

This commit is contained in:
Domingos Lopes 2016-03-20 00:42:21 -04:00 committed by Domingos Lopes
parent 6224f80c89
commit 88d47c178c
7 changed files with 501 additions and 215 deletions

View File

@ -20,7 +20,7 @@ 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.FeedPreferences;
import de.danoeh.antennapod.core.service.playback.IPlaybackServiceMediaPlayer;
import de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer;
import de.danoeh.antennapod.core.service.playback.LocalPSMP;
import de.danoeh.antennapod.core.service.playback.PlayerStatus;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
@ -112,7 +112,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
public void testInit() {
final Context c = getInstrumentation().getTargetContext();
IPlaybackServiceMediaPlayer psmp = new LocalPSMP(c, defaultCallback);
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, defaultCallback);
psmp.shutdown();
}
@ -138,7 +138,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
public void testPlayMediaObjectStreamNoStartNoPrepare() throws InterruptedException {
final Context c = getInstrumentation().getTargetContext();
final CountDownLatch countDownLatch = new CountDownLatch(2);
IPlaybackServiceMediaPlayer.PSMPCallback callback = new IPlaybackServiceMediaPlayer.PSMPCallback() {
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
@Override
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
try {
@ -201,7 +201,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
}
};
IPlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null);
psmp.playMediaObject(p, true, false, false);
boolean res = countDownLatch.await(LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS);
@ -217,7 +217,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
public void testPlayMediaObjectStreamStartNoPrepare() throws InterruptedException {
final Context c = getInstrumentation().getTargetContext();
final CountDownLatch countDownLatch = new CountDownLatch(2);
IPlaybackServiceMediaPlayer.PSMPCallback callback = new IPlaybackServiceMediaPlayer.PSMPCallback() {
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
@Override
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
try {
@ -279,7 +279,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
return false;
}
};
IPlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null);
psmp.playMediaObject(p, true, true, false);
@ -296,7 +296,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
public void testPlayMediaObjectStreamNoStartPrepare() throws InterruptedException {
final Context c = getInstrumentation().getTargetContext();
final CountDownLatch countDownLatch = new CountDownLatch(4);
IPlaybackServiceMediaPlayer.PSMPCallback callback = new IPlaybackServiceMediaPlayer.PSMPCallback() {
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
@Override
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
try {
@ -361,7 +361,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
return false;
}
};
IPlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null);
psmp.playMediaObject(p, true, false, true);
boolean res = countDownLatch.await(LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS);
@ -376,7 +376,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
public void testPlayMediaObjectStreamStartPrepare() throws InterruptedException {
final Context c = getInstrumentation().getTargetContext();
final CountDownLatch countDownLatch = new CountDownLatch(5);
IPlaybackServiceMediaPlayer.PSMPCallback callback = new IPlaybackServiceMediaPlayer.PSMPCallback() {
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
@Override
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
try {
@ -445,7 +445,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
}
};
IPlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null);
psmp.playMediaObject(p, true, true, true);
boolean res = countDownLatch.await(LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS);
@ -459,7 +459,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
public void testPlayMediaObjectLocalNoStartNoPrepare() throws InterruptedException {
final Context c = getInstrumentation().getTargetContext();
final CountDownLatch countDownLatch = new CountDownLatch(2);
IPlaybackServiceMediaPlayer.PSMPCallback callback = new IPlaybackServiceMediaPlayer.PSMPCallback() {
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
@Override
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
try {
@ -522,7 +522,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
}
};
IPlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
psmp.playMediaObject(p, false, false, false);
boolean res = countDownLatch.await(LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS);
@ -537,7 +537,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
public void testPlayMediaObjectLocalStartNoPrepare() throws InterruptedException {
final Context c = getInstrumentation().getTargetContext();
final CountDownLatch countDownLatch = new CountDownLatch(2);
IPlaybackServiceMediaPlayer.PSMPCallback callback = new IPlaybackServiceMediaPlayer.PSMPCallback() {
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
@Override
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
try {
@ -599,7 +599,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
return false;
}
};
IPlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
psmp.playMediaObject(p, false, true, false);
boolean res = countDownLatch.await(LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS);
@ -614,7 +614,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
public void testPlayMediaObjectLocalNoStartPrepare() throws InterruptedException {
final Context c = getInstrumentation().getTargetContext();
final CountDownLatch countDownLatch = new CountDownLatch(4);
IPlaybackServiceMediaPlayer.PSMPCallback callback = new IPlaybackServiceMediaPlayer.PSMPCallback() {
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
@Override
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
try {
@ -679,7 +679,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
return false;
}
};
IPlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
psmp.playMediaObject(p, false, false, true);
boolean res = countDownLatch.await(LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS);
@ -693,7 +693,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
public void testPlayMediaObjectLocalStartPrepare() throws InterruptedException {
final Context c = getInstrumentation().getTargetContext();
final CountDownLatch countDownLatch = new CountDownLatch(5);
IPlaybackServiceMediaPlayer.PSMPCallback callback = new IPlaybackServiceMediaPlayer.PSMPCallback() {
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
@Override
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
try {
@ -762,7 +762,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
return false;
}
};
IPlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
psmp.playMediaObject(p, false, true, true);
boolean res = countDownLatch.await(LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS);
@ -774,7 +774,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
}
private final IPlaybackServiceMediaPlayer.PSMPCallback defaultCallback = new IPlaybackServiceMediaPlayer.PSMPCallback() {
private final PlaybackServiceMediaPlayer.PSMPCallback defaultCallback = new PlaybackServiceMediaPlayer.PSMPCallback() {
@Override
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
checkPSMPInfo(newInfo);
@ -824,7 +824,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
final int latchCount = (stream && reinit) ? 2 : 1;
final CountDownLatch countDownLatch = new CountDownLatch(latchCount);
IPlaybackServiceMediaPlayer.PSMPCallback callback = new IPlaybackServiceMediaPlayer.PSMPCallback() {
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
@Override
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
checkPSMPInfo(newInfo);
@ -900,7 +900,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
return false;
}
};
IPlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
if (initialState == PlayerStatus.PLAYING) {
psmp.playMediaObject(p, stream, true, true);
@ -955,7 +955,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
(initialState == PlayerStatus.PREPARED) ? 1 : 0;
final CountDownLatch countDownLatch = new CountDownLatch(latchCount);
IPlaybackServiceMediaPlayer.PSMPCallback callback = new IPlaybackServiceMediaPlayer.PSMPCallback() {
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
@Override
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
checkPSMPInfo(newInfo);
@ -1016,7 +1016,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
return false;
}
};
IPlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
if (initialState == PlayerStatus.PREPARED || initialState == PlayerStatus.PLAYING || initialState == PlayerStatus.PAUSED) {
boolean startWhenPrepared = (initialState != PlayerStatus.PREPARED);
psmp.playMediaObject(writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL), false, startWhenPrepared, true);
@ -1048,7 +1048,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
final Context c = getInstrumentation().getTargetContext();
final int latchCount = 1;
final CountDownLatch countDownLatch = new CountDownLatch(latchCount);
IPlaybackServiceMediaPlayer.PSMPCallback callback = new IPlaybackServiceMediaPlayer.PSMPCallback() {
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
@Override
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
checkPSMPInfo(newInfo);
@ -1107,7 +1107,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
return false;
}
};
IPlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
if (initialState == PlayerStatus.INITIALIZED
|| initialState == PlayerStatus.PLAYING
@ -1153,7 +1153,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
final Context c = getInstrumentation().getTargetContext();
final int latchCount = 2;
final CountDownLatch countDownLatch = new CountDownLatch(latchCount);
IPlaybackServiceMediaPlayer.PSMPCallback callback = new IPlaybackServiceMediaPlayer.PSMPCallback() {
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
@Override
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
checkPSMPInfo(newInfo);
@ -1211,7 +1211,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
return false;
}
};
IPlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
boolean prepareImmediately = initialState != PlayerStatus.INITIALIZED;
boolean startImmediately = initialState != PlayerStatus.PREPARED;

View File

@ -1,108 +0,0 @@
package de.danoeh.antennapod.core.service.playback;
import android.support.annotation.NonNull;
import android.support.v4.media.session.MediaSessionCompat;
import android.util.Pair;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.MediaType;
import de.danoeh.antennapod.core.util.playback.Playable;
/**
* Interface that allows for different implementations of the PlaybackServiceMediaPlayer for local
* and remote (cast devices) playback.
*/
public interface IPlaybackServiceMediaPlayer {
void playMediaObject(@NonNull Playable playable, boolean stream, boolean startWhenPrepared, boolean prepareImmediately);
void resume();
void pause(boolean abandonFocus, boolean reinit);
void prepare();
void reinit();
void seekTo(int t);
void seekDelta(int d);
void seekToChapter(@NonNull Chapter c);
int getDuration();
int getPosition();
boolean isStartWhenPrepared();
void setStartWhenPrepared(boolean startWhenPrepared);
boolean canSetSpeed();
void setSpeed(float speed);
float getPlaybackSpeed();
void setVolume(float volumeLeft, float volumeRight);
boolean canDownmix();
void setDownmix(boolean enable);
MediaType getCurrentMediaType();
boolean isStreaming();
void shutdown();
void setVideoSurface(SurfaceHolder surface);
void resetVideoSurface();
Pair<Integer, Integer> getVideoSize();
PSMPInfo getPSMPInfo();
PlayerStatus getPlayerStatus();
Playable getPlayable();
void endPlayback(boolean wasSkipped);
void stop();
interface PSMPCallback {
void statusChanged(PSMPInfo newInfo);
void shouldStop();
void playbackSpeedChanged(float s);
void setSpeedAbilityChanged();
void onBufferingUpdate(int percent);
void updateMediaSessionMetadata(Playable p);
boolean onMediaPlayerInfo(int code);
boolean onMediaPlayerError(Object inObj, int what, int extra);
boolean endPlayback(boolean playNextEpisode, boolean wasSkipped);
}
/**
* Holds information about a PSMP object.
*/
class PSMPInfo {
public PlayerStatus playerStatus;
public Playable playable;
public PSMPInfo(PlayerStatus playerStatus, Playable playable) {
this.playerStatus = playerStatus;
this.playable = playable;
}
}
}

View File

@ -36,13 +36,8 @@ import de.danoeh.antennapod.core.util.playback.VideoPlayer;
/**
* Manages the MediaPlayer object of the PlaybackService.
*/
public class LocalPSMP implements IPlaybackServiceMediaPlayer {
public static final String TAG = "PlaybackSvcMediaPlayer";
/**
* Return value of some PSMP methods if the method call failed.
*/
public static final int INVALID_TIME = -1;
public class LocalPSMP extends PlaybackServiceMediaPlayer {
public static final String TAG = "LclPlaybackSvcMPlayer";
private final AudioManager audioManager;
@ -64,9 +59,6 @@ public class LocalPSMP implements IPlaybackServiceMediaPlayer {
private final ReentrantLock playerLock;
private CountDownLatch seekLatch;
private final PSMPCallback callback;
private final Context context;
private final ThreadPoolExecutor executor;
/**
@ -76,8 +68,8 @@ public class LocalPSMP implements IPlaybackServiceMediaPlayer {
public LocalPSMP(@NonNull Context context,
@NonNull PSMPCallback callback) {
this.context = context;
this.callback = callback;
super(context, callback);
this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
this.playerLock = new ReentrantLock();
this.startWhenPrepared = new AtomicBoolean(false);
@ -94,7 +86,6 @@ public class LocalPSMP implements IPlaybackServiceMediaPlayer {
statusBeforeSeeking = null;
pausedBecauseOfTransientAudiofocusLoss = false;
mediaType = MediaType.UNKNOWN;
playerStatus = PlayerStatus.STOPPED;
videoSize = null;
}
@ -703,27 +694,6 @@ public class LocalPSMP implements IPlaybackServiceMediaPlayer {
return res;
}
/**
* Returns a PSMInfo object that contains information about the current state of the PSMP object.
*
* @return The PSMPInfo object.
*/
@Override
public synchronized PSMPInfo getPSMPInfo() {
return new PSMPInfo(playerStatus, media);
}
/**
* Returns the current status, if you need the media and the player status together, you should
* use getPSMPInfo() to make sure they're properly synchronized. Otherwise a race condition
* could result in nonsensical results (like a status of PLAYING, but a null playable)
* @return the current player status
*/
@Override
public PlayerStatus getPlayerStatus() {
return playerStatus;
}
/**
* Returns the current media, if you need the media and the player status together, you should
* use getPSMPInfo() to make sure they're properly synchronized. Otherwise a race condition
@ -735,26 +705,9 @@ public class LocalPSMP implements IPlaybackServiceMediaPlayer {
return media;
}
/**
* Sets the player status of the PSMP object. PlayerStatus and media attributes have to be set at the same time
* so that getPSMPInfo can't return an invalid state (e.g. status is PLAYING, but media is null).
* <p/>
* This method will notify the callback about the change of the player status (even if the new status is the same
* as the old one).
*
* @param newStatus The new PlayerStatus. This must not be null.
* @param newMedia The new playable object of the PSMP object. This can be null.
*/
private synchronized void setPlayerStatus(@NonNull PlayerStatus newStatus, Playable newMedia) {
Log.d(TAG, "Setting player status to " + newStatus);
this.playerStatus = newStatus;
this.media = newMedia;
if (playerStatus != null) {
Log.d(TAG, "playerStatus: " + playerStatus.toString());
}
callback.statusChanged(new PSMPInfo(playerStatus, media));
@Override
protected void setPlayable(Playable playable) {
media = playable;
}
private IPlayer createMediaPlayer() {

View File

@ -174,7 +174,7 @@ public class PlaybackService extends Service implements SharedPreferences.OnShar
private static final int NOTIFICATION_ID = 1;
private IPlaybackServiceMediaPlayer mediaPlayer;
private PlaybackServiceMediaPlayer mediaPlayer;
private PlaybackServiceTaskManager taskManager;
/**
* Only used for Lollipop notifications.
@ -353,7 +353,7 @@ public class PlaybackService extends Service implements SharedPreferences.OnShar
*/
private void handleKeycode(int keycode, int source) {
Log.d(TAG, "Handling keycode: " + keycode);
final IPlaybackServiceMediaPlayer.PSMPInfo info = mediaPlayer.getPSMPInfo();
final PlaybackServiceMediaPlayer.PSMPInfo info = mediaPlayer.getPSMPInfo();
final PlayerStatus status = info.playerStatus;
switch (keycode) {
case KeyEvent.KEYCODE_HEADSETHOOK:
@ -491,9 +491,9 @@ public class PlaybackService extends Service implements SharedPreferences.OnShar
}
};
private final IPlaybackServiceMediaPlayer.PSMPCallback mediaPlayerCallback = new IPlaybackServiceMediaPlayer.PSMPCallback() {
private final PlaybackServiceMediaPlayer.PSMPCallback mediaPlayerCallback = new PlaybackServiceMediaPlayer.PSMPCallback() {
@Override
public void statusChanged(IPlaybackServiceMediaPlayer.PSMPInfo newInfo) {
public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) {
currentMediaType = mediaPlayer.getCurrentMediaType();
updateMediaSession(newInfo.playerStatus);
switch (newInfo.playerStatus) {
@ -779,7 +779,7 @@ public class PlaybackService extends Service implements SharedPreferences.OnShar
SharedPreferences.Editor editor = PreferenceManager
.getDefaultSharedPreferences(getApplicationContext()).edit();
IPlaybackServiceMediaPlayer.PSMPInfo info = mediaPlayer.getPSMPInfo();
PlaybackServiceMediaPlayer.PSMPInfo info = mediaPlayer.getPSMPInfo();
MediaType mediaType = mediaPlayer.getCurrentMediaType();
boolean stream = mediaPlayer.isStreaming();
int playerStatus = getCurrentPlayerStatusAsInt(info.playerStatus);
@ -940,7 +940,7 @@ public class PlaybackService extends Service implements SharedPreferences.OnShar
/**
* Prepares notification and starts the service in the foreground.
*/
private void setupNotification(final IPlaybackServiceMediaPlayer.PSMPInfo info) {
private void setupNotification(final PlaybackServiceMediaPlayer.PSMPInfo info) {
final PendingIntent pIntent = PendingIntent.getActivity(this, 0,
PlaybackService.getPlayerActivityIntent(this),
PendingIntent.FLAG_UPDATE_CURRENT);
@ -1144,7 +1144,7 @@ public class PlaybackService extends Service implements SharedPreferences.OnShar
return taskManager.getSleepTimerTimeLeft();
}
private void bluetoothNotifyChange(IPlaybackServiceMediaPlayer.PSMPInfo info, String whatChanged) {
private void bluetoothNotifyChange(PlaybackServiceMediaPlayer.PSMPInfo info, String whatChanged) {
boolean isPlaying = false;
if (info.playerStatus == PlayerStatus.PLAYING) {
@ -1319,7 +1319,7 @@ public class PlaybackService extends Service implements SharedPreferences.OnShar
mediaPlayer.reinit();
}
public IPlaybackServiceMediaPlayer.PSMPInfo getPSMPInfo() {
public PlaybackServiceMediaPlayer.PSMPInfo getPSMPInfo() {
return mediaPlayer.getPSMPInfo();
}

View File

@ -0,0 +1,286 @@
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 de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.MediaType;
import de.danoeh.antennapod.core.util.playback.Playable;
/*
* An inconvenience of an implementation like this is that some members and methods that once were
* private are now protected, allowing for access from classes of the same package, namely
* PlaybackService. A workaround would be to move this to a dedicated package.
*/
/**
* Abstract class that allows for different implementations of the PlaybackServiceMediaPlayer for local
* and remote (cast devices) playback.
*/
public abstract class PlaybackServiceMediaPlayer {
public static final String TAG = "PlaybackSvcMediaPlayer";
/**
* Return value of some PSMP methods if the method call failed.
*/
public static final int INVALID_TIME = -1;
protected volatile PlayerStatus playerStatus;
protected final PSMPCallback callback;
protected final Context context;
public PlaybackServiceMediaPlayer(@NonNull Context context,
@NonNull PSMPCallback callback){
this.context = context;
this.callback = callback;
playerStatus = PlayerStatus.STOPPED;
}
/**
* Starts or prepares playback of the specified Playable object. If another Playable object is already being played, the currently playing
* episode will be stopped and replaced with the new Playable object. If the Playable object is already being played, the method will
* not do anything.
* Whether playback starts immediately depends on the given parameters. See below for more details.
* <p/>
* States:
* During execution of the method, the object will be in the INITIALIZING state. The end state depends on the given parameters.
* <p/>
* If 'prepareImmediately' is set to true, the method will go into PREPARING state and after that into PREPARED state. If
* 'startWhenPrepared' is set to true, the method will additionally go into PLAYING state.
* <p/>
* If an unexpected error occurs while loading the Playable's metadata or while setting the MediaPlayers data source, the object
* will enter the ERROR state.
* <p/>
* This method is executed on an internal executor service.
*
* @param playable The Playable object that is supposed to be played. This parameter must not be null.
* @param stream The type of playback. If false, the Playable object MUST provide access to a locally available file via
* getLocalMediaUrl. If true, the Playable object MUST provide access to a resource that can be streamed by
* the Android MediaPlayer via getStreamUrl.
* @param startWhenPrepared Sets the 'startWhenPrepared' flag. This flag determines whether playback will start immediately after the
* episode has been prepared for playback. Setting this flag to true does NOT mean that the episode will be prepared
* for playback immediately (see 'prepareImmediately' parameter for more details)
* @param prepareImmediately Set to true if the method should also prepare the episode for playback.
*/
public abstract void playMediaObject(@NonNull Playable playable, boolean stream, boolean startWhenPrepared, boolean prepareImmediately);
/**
* Resumes playback if the PSMP object is in PREPARED or PAUSED state. If the PSMP object is in an invalid state.
* nothing will happen.
* <p/>
* This method is executed on an internal executor service.
*/
public abstract void resume();
/**
* Saves the current position and pauses playback. Note that, if audiofocus
* is abandoned, the lockscreen controls will also disapear.
* <p/>
* This method is executed on an internal executor service.
*
* @param abandonFocus is true if the service should release audio focus
* @param reinit is true if service should reinit after pausing if the media
* file is being streamed
*/
public abstract void pause(boolean abandonFocus, boolean reinit);
/**
* Prepared media player for playback if the service is in the INITALIZED
* state.
* <p/>
* This method is executed on an internal executor service.
*/
public abstract void prepare();
/**
* Resets the media player and moves it into INITIALIZED state.
* <p/>
* This method is executed on an internal executor service.
*/
public abstract void reinit();
/**
* Seeks to the specified position. If the PSMP object is in an invalid state, this method will do nothing.
* Invalid time values (< 0) will be ignored.
* <p/>
* This method is executed on an internal executor service.
*/
public abstract void seekTo(int t);
/**
* Seek a specific position from the current position
*
* @param d offset from current position (positive or negative)
*/
public abstract void seekDelta(int d);
/**
* Seek to the start of the specified chapter.
*/
public abstract void seekToChapter(@NonNull Chapter c);
/**
* Returns the duration of the current media object or INVALID_TIME if the duration could not be retrieved.
*/
public abstract int getDuration();
/**
* Returns the position of the current media object or INVALID_TIME if the position could not be retrieved.
*/
public abstract int getPosition();
public abstract boolean isStartWhenPrepared();
public abstract void setStartWhenPrepared(boolean startWhenPrepared);
/**
* Returns true if the playback speed can be adjusted.
*/
public abstract boolean canSetSpeed();
/**
* Sets the playback speed.
* This method is executed on an internal executor service.
*/
public abstract void setSpeed(float speed);
/**
* Returns the current playback speed. If the playback speed could not be retrieved, 1 is returned.
*/
public abstract float getPlaybackSpeed();
/**
* Sets the playback volume.
* This method is executed on an internal executor service.
*/
public abstract void setVolume(float volumeLeft, float volumeRight);
/**
* Returns true if the mediaplayer can mix stereo down to mono
*/
public abstract boolean canDownmix();
public abstract void setDownmix(boolean enable);
public abstract MediaType getCurrentMediaType();
public abstract boolean isStreaming();
/**
* Releases internally used resources. This method should only be called when the object is not used anymore.
*/
public abstract void shutdown();
public abstract void setVideoSurface(SurfaceHolder surface);
public abstract void resetVideoSurface();
/**
* Return width and height of the currently playing video as a pair.
*
* @return Width and height as a Pair or null if the video size could not be determined. The method might still
* return an invalid non-null value if the getVideoWidth() and getVideoHeight() methods of the media player return
* invalid values.
*/
public abstract Pair<Integer, Integer> getVideoSize();
/**
* Returns a PSMInfo object that contains information about the current state of the PSMP object.
*
* @return The PSMPInfo object.
*/
public final synchronized PSMPInfo getPSMPInfo() {
return new PSMPInfo(playerStatus, getPlayable());
}
/**
* Returns the current status, if you need the media and the player status together, you should
* use getPSMPInfo() to make sure they're properly synchronized. Otherwise a race condition
* could result in nonsensical results (like a status of PLAYING, but a null playable)
* @return the current player status
*/
public PlayerStatus getPlayerStatus() {
return playerStatus;
}
/**
* Returns the current media, if you need the media and the player status together, you should
* use getPSMPInfo() to make sure they're properly synchronized. Otherwise a race condition
* could result in nonsensical results (like a status of PLAYING, but a null playable)
* @return the current media. May be null
*/
public abstract Playable getPlayable();
protected abstract void setPlayable(Playable playable);
public abstract void endPlayback(boolean wasSkipped);
/**
* Moves the LocalPSMP into STOPPED state. This call is only valid if the player is currently in
* INDETERMINATE state, for example after a call to endPlayback.
* This method will only take care of changing the PlayerStatus of this object! Other tasks like
* abandoning audio focus have to be done with other methods.
*/
public abstract void stop();
/**
* Sets the player status of the PSMP object. PlayerStatus and media attributes have to be set at the same time
* so that getPSMPInfo can't return an invalid state (e.g. status is PLAYING, but media is null).
* <p/>
* This method will notify the callback about the change of the player status (even if the new status is the same
* as the old one).
*
* @param newStatus The new PlayerStatus. This must not be null.
* @param newMedia The new playable object of the PSMP object. This can be null.
*/
protected synchronized void setPlayerStatus(@NonNull PlayerStatus newStatus, Playable newMedia) {
Log.d(TAG, "Setting player status to " + newStatus);
this.playerStatus = newStatus;
setPlayable(newMedia);
if (playerStatus != null) {
Log.d(TAG, "playerStatus: " + playerStatus.toString());
}
callback.statusChanged(new PSMPInfo(playerStatus, getPlayable()));
}
public interface PSMPCallback {
void statusChanged(PSMPInfo newInfo);
void shouldStop();
void playbackSpeedChanged(float s);
void setSpeedAbilityChanged();
void onBufferingUpdate(int percent);
void updateMediaSessionMetadata(Playable p);
boolean onMediaPlayerInfo(int code);
boolean onMediaPlayerError(Object inObj, int what, int extra);
boolean endPlayback(boolean playNextEpisode, boolean wasSkipped);
}
/**
* Holds information about a PSMP object.
*/
public class PSMPInfo {
public PlayerStatus playerStatus;
public Playable playable;
public PSMPInfo(PlayerStatus playerStatus, Playable playable) {
this.playerStatus = playerStatus;
this.playable = playable;
}
}
}

View File

@ -0,0 +1,173 @@
package de.danoeh.antennapod.core.service.playback;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Pair;
import android.view.SurfaceHolder;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.MediaType;
import de.danoeh.antennapod.core.util.playback.Playable;
/**
* Implementation of PlaybackServiceMediaPlayer suitable for remote playback on Cast Devices.
*/
public class RemotePSMP extends PlaybackServiceMediaPlayer {
public RemotePSMP(@NonNull Context context, @NonNull PSMPCallback callback) {
super(context, callback);
//TODO
}
@Override
public void playMediaObject(@NonNull Playable playable, boolean stream, boolean startWhenPrepared, boolean prepareImmediately) {
//TODO
}
@Override
public void resume() {
//TODO
}
@Override
public void pause(boolean abandonFocus, boolean reinit) {
//TODO
}
@Override
public void prepare() {
//TODO
}
@Override
public void reinit() {
//TODO
}
@Override
public void seekTo(int t) {
//TODO
}
@Override
public void seekDelta(int d) {
//TODO
}
@Override
public void seekToChapter(@NonNull Chapter c) {
//TODO
}
@Override
public int getDuration() {
//TODO
return 0;
}
@Override
public int getPosition() {
//TODO
return 0;
}
@Override
public boolean isStartWhenPrepared() {
//TODO
return false;
}
@Override
public void setStartWhenPrepared(boolean startWhenPrepared) {
//TODO
}
@Override
public boolean canSetSpeed() {
//TODO
return false;
}
@Override
public void setSpeed(float speed) {
//TODO
}
@Override
public float getPlaybackSpeed() {
//TODO
return 0;
}
@Override
public void setVolume(float volumeLeft, float volumeRight) {
//TODO
}
@Override
public boolean canDownmix() {
//TODO
return false;
}
@Override
public void setDownmix(boolean enable) {
//TODO
}
@Override
public MediaType getCurrentMediaType() {
//TODO
return null;
}
@Override
public boolean isStreaming() {
//TODO
return false;
}
@Override
public void shutdown() {
//TODO
super.shutdown();
}
@Override
public void setVideoSurface(SurfaceHolder surface) {
//TODO
}
@Override
public void resetVideoSurface() {
//TODO
}
@Override
public Pair<Integer, Integer> getVideoSize() {
//TODO
return null;
}
@Override
public Playable getPlayable() {
//TODO
return null;
}
@Override
protected void setPlayable(Playable playable) {
//TODO
}
@Override
public void endPlayback(boolean wasSkipped) {
//TODO
}
@Override
public void stop() {
//TODO
}
}

View File

@ -1,9 +1,7 @@
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;
@ -23,7 +21,6 @@ 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;
@ -38,7 +35,6 @@ public class CastUtils {
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()
@ -48,8 +44,6 @@ public class CastUtils {
.setTargetActivity(ClientConfig.castCallbacks.getCastActivity())
.build());
VideoCastManager.getInstance().addVideoCastConsumer(castConsumer);
PreferenceManager.getDefaultSharedPreferences(context)
.registerOnSharedPreferenceChangeListener(changeListener);
}
public static boolean isCastable(Playable media){
@ -121,18 +115,6 @@ public class CastUtils {
.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;