Adding audio variable speed playback support at the service level

This commit is contained in:
James Falcon 2013-05-01 21:29:43 -05:00
parent d086579e09
commit a86501c795
5 changed files with 582 additions and 300 deletions

View File

@ -51,9 +51,13 @@ import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.receiver.MediaButtonReceiver; import de.danoeh.antennapod.receiver.MediaButtonReceiver;
import de.danoeh.antennapod.receiver.PlayerWidget; import de.danoeh.antennapod.receiver.PlayerWidget;
import de.danoeh.antennapod.util.BitmapDecoder; import de.danoeh.antennapod.util.BitmapDecoder;
import de.danoeh.antennapod.util.DuckType;
import de.danoeh.antennapod.util.flattr.FlattrUtils; import de.danoeh.antennapod.util.flattr.FlattrUtils;
import de.danoeh.antennapod.util.playback.AudioPlayer;
import de.danoeh.antennapod.util.playback.IPlayer;
import de.danoeh.antennapod.util.playback.Playable; import de.danoeh.antennapod.util.playback.Playable;
import de.danoeh.antennapod.util.playback.Playable.PlayableException; import de.danoeh.antennapod.util.playback.Playable.PlayableException;
import de.danoeh.antennapod.util.playback.VideoPlayer;
/** Controls the MediaPlayer that plays a FeedMedia-file */ /** Controls the MediaPlayer that plays a FeedMedia-file */
public class PlaybackService extends Service { public class PlaybackService extends Service {
@ -73,7 +77,7 @@ public class PlaybackService extends Service {
public static final String EXTRA_PREPARE_IMMEDIATELY = "extra.de.danoeh.antennapod.service.prepareImmediately"; public static final String EXTRA_PREPARE_IMMEDIATELY = "extra.de.danoeh.antennapod.service.prepareImmediately";
public static final String ACTION_PLAYER_STATUS_CHANGED = "action.de.danoeh.antennapod.service.playerStatusChanged"; public static final String ACTION_PLAYER_STATUS_CHANGED = "action.de.danoeh.antennapod.service.playerStatusChanged";
private static final String AVRCP_ACTION_PLAYER_STATUS_CHANGED= "com.android.music.playstatechanged"; private static final String AVRCP_ACTION_PLAYER_STATUS_CHANGED = "com.android.music.playstatechanged";
public static final String ACTION_PLAYER_NOTIFICATION = "action.de.danoeh.antennapod.service.playerNotification"; public static final String ACTION_PLAYER_NOTIFICATION = "action.de.danoeh.antennapod.service.playerNotification";
public static final String EXTRA_NOTIFICATION_CODE = "extra.de.danoeh.antennapod.service.notificationCode"; public static final String EXTRA_NOTIFICATION_CODE = "extra.de.danoeh.antennapod.service.notificationCode";
@ -120,7 +124,7 @@ public class PlaybackService extends Service {
private AudioManager audioManager; private AudioManager audioManager;
private ComponentName mediaButtonReceiver; private ComponentName mediaButtonReceiver;
private MediaPlayer player; private IPlayer player;
private RemoteControlClient remoteControlClient; private RemoteControlClient remoteControlClient;
private Playable media; private Playable media;
@ -215,8 +219,7 @@ public class PlaybackService extends Service {
status = PlayerStatus.STOPPED; status = PlayerStatus.STOPPED;
audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
manager = FeedManager.getInstance(); manager = FeedManager.getInstance();
schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOL_SIZE, schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOL_SIZE, new ThreadFactory() {
new ThreadFactory() {
@Override @Override
public Thread newThread(Runnable r) { public Thread newThread(Runnable r) {
@ -227,43 +230,50 @@ public class PlaybackService extends Service {
}, new RejectedExecutionHandler() { }, new RejectedExecutionHandler() {
@Override @Override
public void rejectedExecution(Runnable r, public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
ThreadPoolExecutor executor) {
Log.w(TAG, "SchedEx rejected submission of new task"); Log.w(TAG, "SchedEx rejected submission of new task");
} }
}); });
player = createMediaPlayer();
mediaButtonReceiver = new ComponentName(getPackageName(), mediaButtonReceiver = new ComponentName(getPackageName(), MediaButtonReceiver.class.getName());
MediaButtonReceiver.class.getName());
audioManager.registerMediaButtonEventReceiver(mediaButtonReceiver); audioManager.registerMediaButtonEventReceiver(mediaButtonReceiver);
if (android.os.Build.VERSION.SDK_INT >= 14) { if (android.os.Build.VERSION.SDK_INT >= 14) {
audioManager audioManager.registerRemoteControlClient(setupRemoteControlClient());
.registerRemoteControlClient(setupRemoteControlClient());
} }
registerReceiver(headsetDisconnected, new IntentFilter( registerReceiver(headsetDisconnected, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
Intent.ACTION_HEADSET_PLUG)); registerReceiver(shutdownReceiver, new IntentFilter(ACTION_SHUTDOWN_PLAYBACK_SERVICE));
registerReceiver(shutdownReceiver, new IntentFilter( registerReceiver(audioBecomingNoisy, new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
ACTION_SHUTDOWN_PLAYBACK_SERVICE)); registerReceiver(skipCurrentEpisodeReceiver, new IntentFilter(ACTION_SKIP_CURRENT_EPISODE));
registerReceiver(audioBecomingNoisy, new IntentFilter(
AudioManager.ACTION_AUDIO_BECOMING_NOISY));
registerReceiver(skipCurrentEpisodeReceiver, new IntentFilter(
ACTION_SKIP_CURRENT_EPISODE));
} }
private MediaPlayer createMediaPlayer() { private IPlayer createMediaPlayer() {
return createMediaPlayer(new MediaPlayer()); IPlayer player;
if (media == null || media.getMediaType() == MediaType.VIDEO) {
player = new VideoPlayer();
} else {
player = new AudioPlayer(this);
}
return createMediaPlayer(player);
} }
private MediaPlayer createMediaPlayer(MediaPlayer mp) { private IPlayer createMediaPlayer(IPlayer mp) {
if (mp != null) { if (mp != null && media != null) {
mp.setOnPreparedListener(preparedListener); if (media.getMediaType() == MediaType.AUDIO) {
mp.setOnCompletionListener(completionListener); ((AudioPlayer) mp).setOnPreparedListener(audioPreparedListener);
mp.setOnSeekCompleteListener(onSeekCompleteListener); ((AudioPlayer) mp).setOnCompletionListener(audioCompletionListener);
mp.setOnErrorListener(onErrorListener); ((AudioPlayer) mp).setOnSeekCompleteListener(audioSeekCompleteListener);
mp.setOnBufferingUpdateListener(onBufferingUpdateListener); ((AudioPlayer) mp).setOnErrorListener(audioErrorListener);
mp.setOnInfoListener(onInfoListener); ((AudioPlayer) mp).setOnBufferingUpdateListener(audioBufferingUpdateListener);
((AudioPlayer) mp).setOnInfoListener(audioInfoListener);
} else {
((VideoPlayer) mp).setOnPreparedListener(videoPreparedListener);
((VideoPlayer) mp).setOnCompletionListener(videoCompletionListener);
((VideoPlayer) mp).setOnSeekCompleteListener(videoSeekCompleteListener);
((VideoPlayer) mp).setOnErrorListener(videoErrorListener);
((VideoPlayer) mp).setOnBufferingUpdateListener(videoBufferingUpdateListener);
((VideoPlayer) mp).setOnInfoListener(videoInfoListener);
}
} }
return mp; return mp;
} }
@ -315,8 +325,7 @@ public class PlaybackService extends Service {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
Log.d(TAG, "Gained audio focus"); Log.d(TAG, "Gained audio focus");
if (pausedBecauseOfTransientAudiofocusLoss) { if (pausedBecauseOfTransientAudiofocusLoss) {
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, 0);
AudioManager.ADJUST_RAISE, 0);
play(); play();
} }
break; break;
@ -324,8 +333,7 @@ public class PlaybackService extends Service {
if (status == PlayerStatus.PLAYING) { if (status == PlayerStatus.PLAYING) {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
Log.d(TAG, "Lost audio focus temporarily. Ducking..."); Log.d(TAG, "Lost audio focus temporarily. Ducking...");
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_LOWER, 0);
AudioManager.ADJUST_LOWER, 0);
pausedBecauseOfTransientAudiofocusLoss = true; pausedBecauseOfTransientAudiofocusLoss = true;
} }
break; break;
@ -354,8 +362,7 @@ public class PlaybackService extends Service {
} else { } else {
Playable playable = intent.getParcelableExtra(EXTRA_PLAYABLE); Playable playable = intent.getParcelableExtra(EXTRA_PLAYABLE);
boolean playbackType = intent.getBooleanExtra(EXTRA_SHOULD_STREAM, boolean playbackType = intent.getBooleanExtra(EXTRA_SHOULD_STREAM, true);
true);
if (playable == null) { if (playable == null) {
Log.e(TAG, "Playable extra wasn't sent to the service"); Log.e(TAG, "Playable extra wasn't sent to the service");
if (media == null) { if (media == null) {
@ -363,22 +370,17 @@ public class PlaybackService extends Service {
} }
// Intent values appear to be valid // Intent values appear to be valid
// check if already playing and playbackType is the same // check if already playing and playbackType is the same
} else if (media == null || playable != media } else if (media == null || playable != media || playbackType != shouldStream) {
|| playbackType != shouldStream) {
pause(true, false); pause(true, false);
player.reset();
sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0); sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0);
if (media == null if (media == null || playable.getIdentifier() != media.getIdentifier()) {
|| playable.getIdentifier() != media.getIdentifier()) {
media = playable; media = playable;
} }
if (media != null) { if (media != null) {
shouldStream = playbackType; shouldStream = playbackType;
startWhenPrepared = intent.getBooleanExtra( startWhenPrepared = intent.getBooleanExtra(EXTRA_START_WHEN_PREPARED, false);
EXTRA_START_WHEN_PREPARED, false); prepareImmediately = intent.getBooleanExtra(EXTRA_PREPARE_IMMEDIATELY, false);
prepareImmediately = intent.getBooleanExtra(
EXTRA_PREPARE_IMMEDIATELY, false);
initMediaplayer(); initMediaplayer();
} else { } else {
@ -442,8 +444,7 @@ public class PlaybackService extends Service {
Log.d(TAG, "Setting display"); Log.d(TAG, "Setting display");
player.setDisplay(null); player.setDisplay(null);
player.setDisplay(sh); player.setDisplay(sh);
if (status == PlayerStatus.STOPPED if (status == PlayerStatus.STOPPED || status == PlayerStatus.AWAITING_VIDEO_SURFACE) {
|| status == PlayerStatus.AWAITING_VIDEO_SURFACE) {
try { try {
InitTask initTask = new InitTask() { InitTask initTask = new InitTask() {
@ -453,13 +454,11 @@ public class PlaybackService extends Service {
if (result != null) { if (result != null) {
try { try {
if (shouldStream) { if (shouldStream) {
player.setDataSource(media player.setDataSource(media.getStreamUrl());
.getStreamUrl());
setStatus(PlayerStatus.PREPARING); setStatus(PlayerStatus.PREPARING);
player.prepareAsync(); player.prepareAsync();
} else { } else {
player.setDataSource(media player.setDataSource(media.getLocalMediaUrl());
.getLocalMediaUrl());
setStatus(PlayerStatus.PREPARING); setStatus(PlayerStatus.PREPARING);
player.prepareAsync(); player.prepareAsync();
} }
@ -468,8 +467,7 @@ public class PlaybackService extends Service {
} }
} else { } else {
setStatus(PlayerStatus.ERROR); setStatus(PlayerStatus.ERROR);
sendBroadcast(new Intent( sendBroadcast(new Intent(ACTION_SHUTDOWN_PLAYBACK_SERVICE));
ACTION_SHUTDOWN_PLAYBACK_SERVICE));
} }
} }
} }
@ -500,7 +498,6 @@ public class PlaybackService extends Service {
player.setDisplay(null); player.setDisplay(null);
player.reset(); player.reset();
player.release(); player.release();
player = createMediaPlayer();
status = PlayerStatus.STOPPED; status = PlayerStatus.STOPPED;
if (media != null) { if (media != null) {
initMediaplayer(); initMediaplayer();
@ -516,6 +513,10 @@ public class PlaybackService extends Service {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
Log.d(TAG, "Setting up media player"); Log.d(TAG, "Setting up media player");
try { try {
if (player != null) {
player.release();
}
player = createMediaPlayer();
MediaType mediaType = media.getMediaType(); MediaType mediaType = media.getMediaType();
if (mediaType == MediaType.AUDIO) { if (mediaType == MediaType.AUDIO) {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
@ -533,11 +534,9 @@ public class PlaybackService extends Service {
playingVideo = false; playingVideo = false;
try { try {
if (shouldStream) { if (shouldStream) {
player.setDataSource(media player.setDataSource(media.getStreamUrl());
.getStreamUrl());
} else if (media.localFileAvailable()) { } else if (media.localFileAvailable()) {
player.setDataSource(media player.setDataSource(media.getLocalMediaUrl());
.getLocalMediaUrl());
} }
if (prepareImmediately) { if (prepareImmediately) {
@ -550,20 +549,17 @@ public class PlaybackService extends Service {
e.printStackTrace(); e.printStackTrace();
media = null; media = null;
setStatus(PlayerStatus.ERROR); setStatus(PlayerStatus.ERROR);
sendBroadcast(new Intent( sendBroadcast(new Intent(ACTION_SHUTDOWN_PLAYBACK_SERVICE));
ACTION_SHUTDOWN_PLAYBACK_SERVICE));
} }
} else { } else {
Log.e(TAG, "InitTask could not load metadata"); Log.e(TAG, "InitTask could not load metadata");
media = null; media = null;
setStatus(PlayerStatus.ERROR); setStatus(PlayerStatus.ERROR);
sendBroadcast(new Intent( sendBroadcast(new Intent(ACTION_SHUTDOWN_PLAYBACK_SERVICE));
ACTION_SHUTDOWN_PLAYBACK_SERVICE));
} }
} else { } else {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
Log.d(TAG, Log.d(TAG, "Status of player has changed during initialization. Stopping init process.");
"Status of player has changed during initialization. Stopping init process.");
} }
} }
@ -592,14 +588,11 @@ public class PlaybackService extends Service {
} }
private void setupPositionSaver() { private void setupPositionSaver() {
if (positionSaverFuture == null if (positionSaverFuture == null || (positionSaverFuture.isCancelled() || positionSaverFuture.isDone())) {
|| (positionSaverFuture.isCancelled() || positionSaverFuture
.isDone())) {
positionSaver = new PositionSaver(); positionSaver = new PositionSaver();
positionSaverFuture = schedExecutor.scheduleAtFixedRate( positionSaverFuture = schedExecutor.scheduleAtFixedRate(positionSaver, PositionSaver.WAITING_INTERVALL, PositionSaver.WAITING_INTERVALL,
positionSaver, PositionSaver.WAITING_INTERVALL, TimeUnit.MILLISECONDS);
PositionSaver.WAITING_INTERVALL, TimeUnit.MILLISECONDS);
} }
} }
@ -611,9 +604,22 @@ public class PlaybackService extends Service {
} }
} }
private MediaPlayer.OnPreparedListener preparedListener = new MediaPlayer.OnPreparedListener() { private final com.aocate.media.MediaPlayer.OnPreparedListener audioPreparedListener = new com.aocate.media.MediaPlayer.OnPreparedListener() {
@Override @Override
public void onPrepared(MediaPlayer mp) { public void onPrepared(com.aocate.media.MediaPlayer mp) {
genericOnPrepared(mp);
}
};
private final android.media.MediaPlayer.OnPreparedListener videoPreparedListener = new android.media.MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(android.media.MediaPlayer mp) {
genericOnPrepared(mp);
}
};
private final void genericOnPrepared(Object inObj) {
IPlayer mp = DuckType.coerce(inObj).to(IPlayer.class);
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
Log.d(TAG, "Resource prepared"); Log.d(TAG, "Resource prepared");
mp.seekTo(media.getPosition()); mp.seekTo(media.getPosition());
@ -634,8 +640,7 @@ public class PlaybackService extends Service {
if (media != null && media.getChapters() == null) { if (media != null && media.getChapters() == null) {
media.loadChapterMarks(); media.loadChapterMarks();
if (!isInterrupted() && media.getChapters() != null) { if (!isInterrupted() && media.getChapters() != null) {
sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0);
0);
} }
} }
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
@ -648,23 +653,42 @@ public class PlaybackService extends Service {
play(); play();
} }
} }
private final com.aocate.media.MediaPlayer.OnSeekCompleteListener audioSeekCompleteListener = new com.aocate.media.MediaPlayer.OnSeekCompleteListener() {
@Override
public void onSeekComplete(com.aocate.media.MediaPlayer mp) {
genericSeekCompleteListener();
}
}; };
private MediaPlayer.OnSeekCompleteListener onSeekCompleteListener = new MediaPlayer.OnSeekCompleteListener() { private final android.media.MediaPlayer.OnSeekCompleteListener videoSeekCompleteListener = new android.media.MediaPlayer.OnSeekCompleteListener() {
@Override @Override
public void onSeekComplete(MediaPlayer mp) { public void onSeekComplete(android.media.MediaPlayer mp) {
genericSeekCompleteListener();
}
};
private final void genericSeekCompleteListener() {
if (status == PlayerStatus.SEEKING) { if (status == PlayerStatus.SEEKING) {
setStatus(statusBeforeSeek); setStatus(statusBeforeSeek);
} }
}
private final com.aocate.media.MediaPlayer.OnInfoListener audioInfoListener = new com.aocate.media.MediaPlayer.OnInfoListener() {
@Override
public boolean onInfo(com.aocate.media.MediaPlayer mp, int what, int extra) {
return genericInfoListener(what);
} }
}; };
private MediaPlayer.OnInfoListener onInfoListener = new MediaPlayer.OnInfoListener() { private final android.media.MediaPlayer.OnInfoListener videoInfoListener = new android.media.MediaPlayer.OnInfoListener() {
@Override @Override
public boolean onInfo(MediaPlayer mp, int what, int extra) { public boolean onInfo(android.media.MediaPlayer mp, int what, int extra) {
return genericInfoListener(what);
}
};
private boolean genericInfoListener(int what) {
switch (what) { switch (what) {
case MediaPlayer.MEDIA_INFO_BUFFERING_START: case MediaPlayer.MEDIA_INFO_BUFFERING_START:
sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_START, 0); sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_START, 0);
@ -676,14 +700,25 @@ public class PlaybackService extends Service {
return false; return false;
} }
} }
private final com.aocate.media.MediaPlayer.OnErrorListener audioErrorListener = new com.aocate.media.MediaPlayer.OnErrorListener() {
@Override
public boolean onError(com.aocate.media.MediaPlayer mp, int what, int extra) {
return genericOnError(mp, what, extra);
}
}; };
private MediaPlayer.OnErrorListener onErrorListener = new MediaPlayer.OnErrorListener() { private final android.media.MediaPlayer.OnErrorListener videoErrorListener = new android.media.MediaPlayer.OnErrorListener() {
private static final String TAG = "PlaybackService.onErrorListener";
@Override @Override
public boolean onError(MediaPlayer mp, int what, int extra) { public boolean onError(android.media.MediaPlayer mp, int what, int extra) {
Log.w(TAG, "An error has occured: " + what); return genericOnError(mp, what, extra);
}
};
private boolean genericOnError(Object inObj, int what, int extra) {
final String TAG = "PlaybackService.onErrorListener";
Log.w(TAG, "An error has occured: " + what + " " + extra);
IPlayer mp = DuckType.coerce(inObj).to(IPlayer.class);
if (mp.isPlaying()) { if (mp.isPlaying()) {
pause(true, true); pause(true, true);
} }
@ -692,25 +727,43 @@ public class PlaybackService extends Service {
stopSelf(); stopSelf();
return true; return true;
} }
private final com.aocate.media.MediaPlayer.OnCompletionListener audioCompletionListener = new com.aocate.media.MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(com.aocate.media.MediaPlayer mp) {
genericOnCompletion();
}
}; };
private MediaPlayer.OnCompletionListener completionListener = new MediaPlayer.OnCompletionListener() { private final android.media.MediaPlayer.OnCompletionListener videoCompletionListener = new android.media.MediaPlayer.OnCompletionListener() {
@Override @Override
public void onCompletion(MediaPlayer mp) { public void onCompletion(android.media.MediaPlayer mp) {
genericOnCompletion();
}
};
private void genericOnCompletion() {
endPlayback(true); endPlayback(true);
} }
};
private MediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() {
private final com.aocate.media.MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener = new com.aocate.media.MediaPlayer.OnBufferingUpdateListener() {
@Override @Override
public void onBufferingUpdate(MediaPlayer mp, int percent) { public void onBufferingUpdate(com.aocate.media.MediaPlayer mp, int percent) {
sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_UPDATE, percent); genericOnBufferingUpdate(percent);
} }
}; };
private final android.media.MediaPlayer.OnBufferingUpdateListener videoBufferingUpdateListener = new android.media.MediaPlayer.OnBufferingUpdateListener() {
@Override
public void onBufferingUpdate(android.media.MediaPlayer mp, int percent) {
genericOnBufferingUpdate(percent);
}
};
private void genericOnBufferingUpdate(int percent) {
sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_UPDATE, percent);
}
private void endPlayback(boolean playNextEpisode) { private void endPlayback(boolean playNextEpisode) {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
Log.d(TAG, "Playback ended"); Log.d(TAG, "Playback ended");
@ -727,8 +780,7 @@ public class PlaybackService extends Service {
((FeedMedia) media).setPlaybackCompletionDate(new Date()); ((FeedMedia) media).setPlaybackCompletionDate(new Date());
manager.markItemRead(PlaybackService.this, item, true, true); manager.markItemRead(PlaybackService.this, item, true, true);
nextItem = manager.getQueueSuccessorOfItem(item); nextItem = manager.getQueueSuccessorOfItem(item);
isInQueue = media instanceof FeedMedia isInQueue = media instanceof FeedMedia && manager.isInQueue(((FeedMedia) media).getItem());
&& manager.isInQueue(((FeedMedia) media).getItem());
if (isInQueue) { if (isInQueue) {
manager.removeQueueItem(PlaybackService.this, item, true); manager.removeQueueItem(PlaybackService.this, item, true);
} }
@ -744,8 +796,7 @@ public class PlaybackService extends Service {
// is an episode in the queue left. // is an episode in the queue left.
// Start playback immediately if continuous playback is enabled // Start playback immediately if continuous playback is enabled
boolean loadNextItem = isInQueue && nextItem != null; boolean loadNextItem = isInQueue && nextItem != null;
playNextEpisode = playNextEpisode && loadNextItem playNextEpisode = playNextEpisode && loadNextItem && UserPreferences.isFollowQueue();
&& UserPreferences.isFollowQueue();
if (loadNextItem) { if (loadNextItem) {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
Log.d(TAG, "Loading next item in queue"); Log.d(TAG, "Loading next item in queue");
@ -779,8 +830,7 @@ public class PlaybackService extends Service {
if (media != null) { if (media != null) {
resetVideoSurface(); resetVideoSurface();
refreshRemoteControlClientState(); refreshRemoteControlClientState();
sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, notificationCode);
notificationCode);
} else { } else {
sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0); sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0);
stopSelf(); stopSelf();
@ -789,8 +839,7 @@ public class PlaybackService extends Service {
public void setSleepTimer(long waitingTime) { public void setSleepTimer(long waitingTime) {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime) Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime) + " milliseconds");
+ " milliseconds");
if (sleepTimerFuture != null) { if (sleepTimerFuture != null) {
sleepTimerFuture.cancel(true); sleepTimerFuture.cancel(true);
} }
@ -819,7 +868,7 @@ public class PlaybackService extends Service {
* file is being streamed * file is being streamed
*/ */
public void pause(boolean abandonFocus, boolean reinit) { public void pause(boolean abandonFocus, boolean reinit) {
if (player.isPlaying()) { if (player != null && player.isPlaying()) {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
Log.d(TAG, "Pausing playback."); Log.d(TAG, "Pausing playback.");
player.pause(); player.pause();
@ -843,9 +892,7 @@ public class PlaybackService extends Service {
public void stop() { public void stop() {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
Log.d(TAG, "Stopping playback"); Log.d(TAG, "Stopping playback");
if (status == PlayerStatus.PREPARED || status == PlayerStatus.PAUSED if (status == PlayerStatus.PREPARED || status == PlayerStatus.PAUSED || status == PlayerStatus.STOPPED || status == PlayerStatus.PLAYING) {
|| status == PlayerStatus.STOPPED
|| status == PlayerStatus.PLAYING) {
player.stop(); player.stop();
} }
setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING); setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING);
@ -868,18 +915,14 @@ public class PlaybackService extends Service {
/** Resets the media player and moves into INITIALIZED state. */ /** Resets the media player and moves into INITIALIZED state. */
public void reinit() { public void reinit() {
player.reset(); player.reset();
player = createMediaPlayer(player);
prepareImmediately = false; prepareImmediately = false;
initMediaplayer(); initMediaplayer();
} }
@SuppressLint("NewApi") @SuppressLint("NewApi")
public void play() { public void play() {
if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED || status == PlayerStatus.STOPPED) {
|| status == PlayerStatus.STOPPED) { int focusGained = audioManager.requestAudioFocus(audioFocusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
int focusGained = audioManager.requestAudioFocus(
audioFocusChangeListener, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
if (focusGained == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { if (focusGained == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
@ -890,7 +933,7 @@ public class PlaybackService extends Service {
player.start(); player.start();
if (status != PlayerStatus.PAUSED) { if (status != PlayerStatus.PAUSED) {
player.seekTo((int) media.getPosition()); player.seekTo(media.getPosition());
} }
setStatus(PlayerStatus.PLAYING); setStatus(PlayerStatus.PLAYING);
setupPositionSaver(); setupPositionSaver();
@ -898,11 +941,9 @@ public class PlaybackService extends Service {
setupNotification(); setupNotification();
pausedBecauseOfTransientAudiofocusLoss = false; pausedBecauseOfTransientAudiofocusLoss = false;
if (android.os.Build.VERSION.SDK_INT >= 14) { if (android.os.Build.VERSION.SDK_INT >= 14) {
audioManager audioManager.registerRemoteControlClient(remoteControlClient);
.registerRemoteControlClient(remoteControlClient);
} }
audioManager audioManager.registerMediaButtonEventReceiver(mediaButtonReceiver);
.registerMediaButtonEventReceiver(mediaButtonReceiver);
media.onPlaybackStart(); media.onPlaybackStart();
} else { } else {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
@ -915,42 +956,24 @@ public class PlaybackService extends Service {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
Log.d(TAG, "Writing playback preferences"); Log.d(TAG, "Writing playback preferences");
SharedPreferences.Editor editor = PreferenceManager SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).edit();
.getDefaultSharedPreferences(getApplicationContext()).edit();
if (media != null) { if (media != null) {
editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, media.getPlayableType());
media.getPlayableType()); editor.putBoolean(PlaybackPreferences.PREF_CURRENT_EPISODE_IS_STREAM, shouldStream);
editor.putBoolean( editor.putBoolean(PlaybackPreferences.PREF_CURRENT_EPISODE_IS_VIDEO, playingVideo);
PlaybackPreferences.PREF_CURRENT_EPISODE_IS_STREAM,
shouldStream);
editor.putBoolean(
PlaybackPreferences.PREF_CURRENT_EPISODE_IS_VIDEO,
playingVideo);
if (media instanceof FeedMedia) { if (media instanceof FeedMedia) {
FeedMedia fMedia = (FeedMedia) media; FeedMedia fMedia = (FeedMedia) media;
editor.putLong( editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, fMedia.getItem().getFeed().getId());
PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, fMedia.getId());
fMedia.getItem().getFeed().getId());
editor.putLong(
PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID,
fMedia.getId());
} else { } else {
editor.putLong( editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, PlaybackPreferences.NO_MEDIA_PLAYING);
PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, PlaybackPreferences.NO_MEDIA_PLAYING);
PlaybackPreferences.NO_MEDIA_PLAYING);
editor.putLong(
PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID,
PlaybackPreferences.NO_MEDIA_PLAYING);
} }
media.writeToPreferences(editor); media.writeToPreferences(editor);
} else { } else {
editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, PlaybackPreferences.NO_MEDIA_PLAYING);
PlaybackPreferences.NO_MEDIA_PLAYING); editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, PlaybackPreferences.NO_MEDIA_PLAYING);
editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, PlaybackPreferences.NO_MEDIA_PLAYING);
PlaybackPreferences.NO_MEDIA_PLAYING);
editor.putLong(
PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID,
PlaybackPreferences.NO_MEDIA_PLAYING);
} }
editor.commit(); editor.commit();
@ -984,8 +1007,7 @@ public class PlaybackService extends Service {
/** Prepares notification and starts the service in the foreground. */ /** Prepares notification and starts the service in the foreground. */
@SuppressLint("NewApi") @SuppressLint("NewApi")
private void setupNotification() { private void setupNotification() {
final PendingIntent pIntent = PendingIntent.getActivity(this, 0, final PendingIntent pIntent = PendingIntent.getActivity(this, 0, PlaybackService.getPlayerActivityIntent(this),
PlaybackService.getPlayerActivityIntent(this),
PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent.FLAG_UPDATE_CURRENT);
if (notificationSetupTask != null) { if (notificationSetupTask != null) {
@ -1000,17 +1022,13 @@ public class PlaybackService extends Service {
Log.d(TAG, "Starting background work"); Log.d(TAG, "Starting background work");
if (android.os.Build.VERSION.SDK_INT >= 11) { if (android.os.Build.VERSION.SDK_INT >= 11) {
if (media != null && media != null) { if (media != null && media != null) {
int iconSize = getResources().getDimensionPixelSize( int iconSize = getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width);
android.R.dimen.notification_large_icon_width); icon = BitmapDecoder.decodeBitmapFromWorkerTaskResource(iconSize, media);
icon = BitmapDecoder
.decodeBitmapFromWorkerTaskResource(iconSize,
media);
} }
} }
if (icon == null) { if (icon == null) {
icon = BitmapFactory.decodeResource(getResources(), icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_stat_antenna);
R.drawable.ic_stat_antenna);
} }
return null; return null;
@ -1019,40 +1037,24 @@ public class PlaybackService extends Service {
@Override @Override
protected void onPostExecute(Void result) { protected void onPostExecute(Void result) {
super.onPostExecute(result); super.onPostExecute(result);
if (!isCancelled() && status == PlayerStatus.PLAYING if (!isCancelled() && status == PlayerStatus.PLAYING && media != null) {
&& media != null) {
String contentText = media.getFeedTitle(); String contentText = media.getFeedTitle();
String contentTitle = media.getEpisodeTitle(); String contentTitle = media.getEpisodeTitle();
Notification notification = null; Notification notification = null;
if (android.os.Build.VERSION.SDK_INT >= 16) { if (android.os.Build.VERSION.SDK_INT >= 16) {
Intent pauseButtonIntent = new Intent( Intent pauseButtonIntent = new Intent(PlaybackService.this, PlaybackService.class);
PlaybackService.this, PlaybackService.class); pauseButtonIntent.putExtra(MediaButtonReceiver.EXTRA_KEYCODE, KeyEvent.KEYCODE_MEDIA_PAUSE);
pauseButtonIntent.putExtra( PendingIntent pauseButtonPendingIntent = PendingIntent.getService(PlaybackService.this, 0, pauseButtonIntent,
MediaButtonReceiver.EXTRA_KEYCODE,
KeyEvent.KEYCODE_MEDIA_PAUSE);
PendingIntent pauseButtonPendingIntent = PendingIntent
.getService(PlaybackService.this, 0,
pauseButtonIntent,
PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent.FLAG_UPDATE_CURRENT);
Notification.Builder notificationBuilder = new Notification.Builder( Notification.Builder notificationBuilder = new Notification.Builder(PlaybackService.this).setContentTitle(contentTitle)
PlaybackService.this) .setContentText(contentText).setOngoing(true).setContentIntent(pIntent).setLargeIcon(icon)
.setContentTitle(contentTitle)
.setContentText(contentText)
.setOngoing(true)
.setContentIntent(pIntent)
.setLargeIcon(icon)
.setSmallIcon(R.drawable.ic_stat_antenna) .setSmallIcon(R.drawable.ic_stat_antenna)
.addAction(android.R.drawable.ic_media_pause, .addAction(android.R.drawable.ic_media_pause, getString(R.string.pause_label), pauseButtonPendingIntent);
getString(R.string.pause_label),
pauseButtonPendingIntent);
notification = notificationBuilder.build(); notification = notificationBuilder.build();
} else { } else {
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder( NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(PlaybackService.this)
PlaybackService.this) .setContentTitle(contentTitle).setContentText(contentText).setOngoing(true).setContentIntent(pIntent)
.setContentTitle(contentTitle) .setLargeIcon(icon).setSmallIcon(R.drawable.ic_stat_antenna);
.setContentText(contentText).setOngoing(true)
.setContentIntent(pIntent).setLargeIcon(icon)
.setSmallIcon(R.drawable.ic_stat_antenna);
notification = notificationBuilder.getNotification(); notification = notificationBuilder.getNotification();
} }
startForeground(NOTIFICATION_ID, notification); startForeground(NOTIFICATION_ID, notification);
@ -1063,8 +1065,7 @@ public class PlaybackService extends Service {
}; };
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
notificationSetupTask notificationSetupTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else { } else {
notificationSetupTask.execute(); notificationSetupTask.execute();
} }
@ -1086,9 +1087,7 @@ public class PlaybackService extends Service {
public void seek(int i) { public void seek(int i) {
saveCurrentPosition(); saveCurrentPosition();
if (status == PlayerStatus.INITIALIZED if (status == PlayerStatus.INITIALIZED || status == PlayerStatus.INITIALIZING || status == PlayerStatus.PREPARING) {
|| status == PlayerStatus.INITIALIZING
|| status == PlayerStatus.PREPARING) {
media.setPosition(i); media.setPosition(i);
setStartWhenPrepared(true); setStartWhenPrepared(true);
prepare(); prepare();
@ -1115,9 +1114,7 @@ public class PlaybackService extends Service {
if (position != INVALID_TIME) { if (position != INVALID_TIME) {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
Log.d(TAG, "Saving current position to " + position); Log.d(TAG, "Saving current position to " + position);
media.saveCurrentPosition(PreferenceManager media.saveCurrentPosition(PreferenceManager.getDefaultSharedPreferences(getApplicationContext()), position);
.getDefaultSharedPreferences(getApplicationContext()),
position);
} }
} }
@ -1132,22 +1129,17 @@ public class PlaybackService extends Service {
@SuppressLint("NewApi") @SuppressLint("NewApi")
private void setupWidgetUpdater() { private void setupWidgetUpdater() {
if (widgetUpdaterFuture == null if (widgetUpdaterFuture == null || (widgetUpdaterFuture.isCancelled() || widgetUpdaterFuture.isDone())) {
|| (widgetUpdaterFuture.isCancelled() || widgetUpdaterFuture
.isDone())) {
widgetUpdater = new WidgetUpdateWorker(); widgetUpdater = new WidgetUpdateWorker();
widgetUpdaterFuture = schedExecutor.scheduleAtFixedRate( widgetUpdaterFuture = schedExecutor.scheduleAtFixedRate(widgetUpdater, WidgetUpdateWorker.NOTIFICATION_INTERVALL,
widgetUpdater, WidgetUpdateWorker.NOTIFICATION_INTERVALL, WidgetUpdateWorker.NOTIFICATION_INTERVALL, TimeUnit.MILLISECONDS);
WidgetUpdateWorker.NOTIFICATION_INTERVALL,
TimeUnit.MILLISECONDS);
} }
} }
private void updateWidget() { private void updateWidget() {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
Log.d(TAG, "Sending widget update request"); Log.d(TAG, "Sending widget update request");
PlaybackService.this.sendBroadcast(new Intent( PlaybackService.this.sendBroadcast(new Intent(PlayerWidget.FORCE_WIDGET_UPDATE));
PlayerWidget.FORCE_WIDGET_UPDATE));
} }
public boolean sleepTimerActive() { public boolean sleepTimerActive() {
@ -1166,13 +1158,11 @@ public class PlaybackService extends Service {
private RemoteControlClient setupRemoteControlClient() { private RemoteControlClient setupRemoteControlClient() {
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.setComponent(mediaButtonReceiver); mediaButtonIntent.setComponent(mediaButtonReceiver);
PendingIntent mediaPendingIntent = PendingIntent.getBroadcast( PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0);
getApplicationContext(), 0, mediaButtonIntent, 0);
remoteControlClient = new RemoteControlClient(mediaPendingIntent); remoteControlClient = new RemoteControlClient(mediaPendingIntent);
int controlFlags; int controlFlags;
if (android.os.Build.VERSION.SDK_INT < 16) { if (android.os.Build.VERSION.SDK_INT < 16) {
controlFlags = RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE controlFlags = RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE | RemoteControlClient.FLAG_KEY_MEDIA_NEXT;
| RemoteControlClient.FLAG_KEY_MEDIA_NEXT;
} else { } else {
controlFlags = RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE; controlFlags = RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE;
} }
@ -1187,34 +1177,26 @@ public class PlaybackService extends Service {
if (remoteControlClient != null) { if (remoteControlClient != null) {
switch (status) { switch (status) {
case PLAYING: case PLAYING:
remoteControlClient remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
break; break;
case PAUSED: case PAUSED:
case INITIALIZED: case INITIALIZED:
remoteControlClient remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED);
.setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED);
break; break;
case STOPPED: case STOPPED:
remoteControlClient remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED);
.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED);
break; break;
case ERROR: case ERROR:
remoteControlClient remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_ERROR);
.setPlaybackState(RemoteControlClient.PLAYSTATE_ERROR);
break; break;
default: default:
remoteControlClient remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_BUFFERING);
.setPlaybackState(RemoteControlClient.PLAYSTATE_BUFFERING);
} }
if (media != null) { if (media != null) {
MetadataEditor editor = remoteControlClient MetadataEditor editor = remoteControlClient.editMetadata(false);
.editMetadata(false); editor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, media.getEpisodeTitle());
editor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE,
media.getEpisodeTitle());
editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, media.getFeedTitle());
media.getFeedTitle());
editor.apply(); editor.apply();
} }
@ -1247,7 +1229,7 @@ public class PlaybackService extends Service {
* Pauses playback when the headset is disconnected and the preference is * Pauses playback when the headset is disconnected and the preference is
* set * set
*/ */
private BroadcastReceiver headsetDisconnected = new BroadcastReceiver() { private final BroadcastReceiver headsetDisconnected = new BroadcastReceiver() {
private static final String TAG = "headsetDisconnected"; private static final String TAG = "headsetDisconnected";
private static final int UNPLUGGED = 0; private static final int UNPLUGGED = 0;
@ -1270,7 +1252,7 @@ public class PlaybackService extends Service {
} }
}; };
private BroadcastReceiver audioBecomingNoisy = new BroadcastReceiver() { private final BroadcastReceiver audioBecomingNoisy = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
@ -1284,13 +1266,12 @@ public class PlaybackService extends Service {
/** Pauses playback if PREF_PAUSE_ON_HEADSET_DISCONNECT was set to true. */ /** Pauses playback if PREF_PAUSE_ON_HEADSET_DISCONNECT was set to true. */
private void pauseIfPauseOnDisconnect() { private void pauseIfPauseOnDisconnect() {
if (UserPreferences.isPauseOnHeadsetDisconnect() if (UserPreferences.isPauseOnHeadsetDisconnect() && status == PlayerStatus.PLAYING) {
&& status == PlayerStatus.PLAYING) {
pause(true, true); pause(true, true);
} }
} }
private BroadcastReceiver shutdownReceiver = new BroadcastReceiver() { private final BroadcastReceiver shutdownReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
@ -1303,7 +1284,7 @@ public class PlaybackService extends Service {
}; };
private BroadcastReceiver skipCurrentEpisodeReceiver = new BroadcastReceiver() { private final BroadcastReceiver skipCurrentEpisodeReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(ACTION_SKIP_CURRENT_EPISODE)) { if (intent.getAction().equals(ACTION_SKIP_CURRENT_EPISODE)) {
@ -1329,8 +1310,7 @@ public class PlaybackService extends Service {
try { try {
saveCurrentPosition(); saveCurrentPosition();
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
Log.w(TAG, Log.w(TAG, "saveCurrentPosition was called in illegal state");
"saveCurrentPosition was called in illegal state");
} }
} }
} }
@ -1419,7 +1399,7 @@ public class PlaybackService extends Service {
return media; return media;
} }
public MediaPlayer getPlayer() { public IPlayer getPlayer() {
return player; return player;
} }
@ -1432,6 +1412,38 @@ public class PlaybackService extends Service {
postStatusUpdateIntent(); postStatusUpdateIntent();
} }
public boolean canSetSpeed() {
if (media.getMediaType() == MediaType.AUDIO) {
return ((AudioPlayer) player).canSetSpeed();
}
return false;
}
public boolean canSetPitch() {
if (media.getMediaType() == MediaType.AUDIO) {
return ((AudioPlayer) player).canSetPitch();
}
return false;
}
public void setSpeed(double speed) {
if (media.getMediaType() == MediaType.AUDIO) {
AudioPlayer audioPlayer = (AudioPlayer) player;
if (audioPlayer.canSetSpeed()) {
audioPlayer.setPlaybackSpeed((float) speed);
}
}
}
public void setPitch(double pitch) {
if (media.getMediaType() == MediaType.AUDIO) {
AudioPlayer audioPlayer = (AudioPlayer) player;
if (audioPlayer.canSetPitch()) {
audioPlayer.setPlaybackPitch((float) pitch);
}
}
}
/** /**
* call getDuration() on mediaplayer or return INVALID_TIME if player is in * call getDuration() on mediaplayer or return INVALID_TIME if player is in
* an invalid state. This method should be used instead of calling * an invalid state. This method should be used instead of calling
@ -1480,8 +1492,7 @@ public class PlaybackService extends Service {
} }
private void setCurrentlyPlayingMedia(long id) { private void setCurrentlyPlayingMedia(long id) {
SharedPreferences.Editor editor = PreferenceManager SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).edit();
.getDefaultSharedPreferences(getApplicationContext()).edit();
editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, id); editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, id);
editor.commit(); editor.commit();
} }

View File

@ -0,0 +1,115 @@
/* Adapted from: http://thinking-in-code.blogspot.com/2008/11/duck-typing-in-java-using-dynamic.html */
package de.danoeh.antennapod.util;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Allows "duck typing" or dynamic invocation based on method signature rather
* than type hierarchy. In other words, rather than checking whether something
* IS-a duck, check whether it WALKS-like-a duck or QUACKS-like a duck.
*
* To use first use the coerce static method to indicate the object you want to
* do Duck Typing for, then specify an interface to the to method which you want
* to coerce the type to, e.g:
*
* public interface Foo { void aMethod(); } class Bar { ... public void
* aMethod() { ... } ... } Bar bar = ...; Foo foo =
* DuckType.coerce(bar).to(Foo.class); foo.aMethod();
*
*
*/
public class DuckType {
private final Object objectToCoerce;
private DuckType(Object objectToCoerce) {
this.objectToCoerce = objectToCoerce;
}
private class CoercedProxy implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Method delegateMethod = findMethodBySignature(method);
assert delegateMethod != null;
return delegateMethod.invoke(DuckType.this.objectToCoerce, args);
}
}
/**
* Specify the duck typed object to coerce.
*
* @param object
* the object to coerce
* @return
*/
public static DuckType coerce(Object object) {
return new DuckType(object);
}
/**
* Coerce the Duck Typed object to the given interface providing it
* implements all the necessary methods.
*
* @param
* @param iface
* @return an instance of the given interface that wraps the duck typed
* class
* @throws ClassCastException
* if the object being coerced does not implement all the
* methods in the given interface.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public <T> T to(Class iface) {
assert iface.isInterface() : "cannot coerce object to a class, must be an interface";
if (isA(iface)) {
return (T) iface.cast(objectToCoerce);
}
if (quacksLikeA(iface)) {
return generateProxy(iface);
}
throw new ClassCastException("Could not coerce object of type " + objectToCoerce.getClass() + " to " + iface);
}
@SuppressWarnings("rawtypes")
private boolean isA(Class iface) {
return objectToCoerce.getClass().isInstance(iface);
}
/**
* Determine whether the duck typed object can be used with the given
* interface.
*
* @param Type
* of the interface to check.
* @param iface
* Interface class to check
* @return true if the object will support all the methods in the interface,
* false otherwise.
*/
@SuppressWarnings("rawtypes")
public boolean quacksLikeA(Class iface) {
for (Method method : iface.getMethods()) {
if (findMethodBySignature(method) == null) {
return false;
}
}
return true;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private <T> T generateProxy(Class iface) {
return (T) Proxy.newProxyInstance(iface.getClassLoader(), new Class[] { iface }, new CoercedProxy());
}
private Method findMethodBySignature(Method method) {
try {
return objectToCoerce.getClass().getMethod(method.getName(), method.getParameterTypes());
} catch (NoSuchMethodException e) {
return null;
}
}
}

View File

@ -0,0 +1,30 @@
package de.danoeh.antennapod.util.playback;
import android.content.Context;
import android.util.Log;
import android.view.SurfaceHolder;
import com.aocate.media.MediaPlayer;
public class AudioPlayer extends MediaPlayer implements IPlayer {
private static final String TAG = "AudioPlayer";
public AudioPlayer(Context context) {
super(context);
}
@Override
public void setScreenOnWhilePlaying(boolean screenOn) {
Log.e(TAG, "Setting screen on while playing not supported in Audio Player");
throw new UnsupportedOperationException("Setting screen on while playing not supported in Audio Player");
}
@Override
public void setDisplay(SurfaceHolder sh) {
if (sh != null) {
Log.e(TAG, "Setting display not supported in Audio Player");
throw new UnsupportedOperationException("Setting display not supported in Audio Player");
}
}
}

View File

@ -0,0 +1,64 @@
package de.danoeh.antennapod.util.playback;
import java.io.IOException;
import android.view.SurfaceHolder;
public interface IPlayer {
boolean canSetPitch();
boolean canSetSpeed();
float getCurrentPitchStepsAdjustment();
int getCurrentPosition();
float getCurrentSpeedMultiplier();
int getDuration();
float getMaxSpeedMultiplier();
float getMinSpeedMultiplier();
boolean isLooping();
boolean isPlaying();
void pause();
void prepare() throws IllegalStateException, IOException;
void prepareAsync();
void release();
void reset();
void seekTo(int msec);
void setAudioStreamType(int streamtype);
void setScreenOnWhilePlaying(boolean screenOn);
void setDataSource(String path) throws IllegalStateException, IOException,
IllegalArgumentException, SecurityException;
void setDisplay(SurfaceHolder sh);
void setEnableSpeedAdjustment(boolean enableSpeedAdjustment);
void setLooping(boolean looping);
void setPitchStepsAdjustment(float pitchSteps);
void setPlaybackPitch(float f);
void setPlaybackSpeed(float f);
void setVolume(float left, float right);
void start();
void stop();
}

View File

@ -0,0 +1,62 @@
package de.danoeh.antennapod.util.playback;
import android.media.MediaPlayer;
import android.util.Log;
public class VideoPlayer extends MediaPlayer implements IPlayer {
private static final String TAG = "VideoPlayer";
@Override
public boolean canSetPitch() {
return false;
}
@Override
public boolean canSetSpeed() {
return false;
}
@Override
public float getCurrentPitchStepsAdjustment() {
return 1;
}
@Override
public float getCurrentSpeedMultiplier() {
return 1;
}
@Override
public float getMaxSpeedMultiplier() {
return 1;
}
@Override
public float getMinSpeedMultiplier() {
return 1;
}
@Override
public void setEnableSpeedAdjustment(boolean enableSpeedAdjustment) throws UnsupportedOperationException {
Log.e(TAG, "Setting enable speed adjustment unsupported in video player");
throw new UnsupportedOperationException("Setting enable speed adjustment unsupported in video player");
}
@Override
public void setPitchStepsAdjustment(float pitchSteps) {
Log.e(TAG, "Setting pitch steps adjustment unsupported in video player");
throw new UnsupportedOperationException("Setting pitch steps adjustment unsupported in video player");
}
@Override
public void setPlaybackPitch(float f) {
Log.e(TAG, "Setting playback pitch unsupported in video player");
throw new UnsupportedOperationException("Setting playback pitch unsupported in video player");
}
@Override
public void setPlaybackSpeed(float f) {
Log.e(TAG, "Setting playback speed unsupported in video player");
throw new UnsupportedOperationException("Setting playback speed unsupported in video player");
}
}