diff options
Diffstat (limited to 'src')
4 files changed, 691 insertions, 1389 deletions
diff --git a/src/de/danoeh/antennapod/service/playback/PlaybackService.java b/src/de/danoeh/antennapod/service/playback/PlaybackService.java index 8303173dd..74e1c0c56 100644 --- a/src/de/danoeh/antennapod/service/playback/PlaybackService.java +++ b/src/de/danoeh/antennapod/service/playback/PlaybackService.java @@ -1,23 +1,13 @@ package de.danoeh.antennapod.service.playback; -import java.io.IOException; -import java.util.List; -import java.util.concurrent.*; - import android.annotation.SuppressLint; import android.app.Notification; import android.app.PendingIntent; import android.app.Service; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; +import android.content.*; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.AudioManager; -import android.media.AudioManager.OnAudioFocusChangeListener; import android.media.MediaMetadataRetriever; import android.media.MediaPlayer; import android.media.RemoteControlClient; @@ -39,20 +29,15 @@ import de.danoeh.antennapod.preferences.PlaybackPreferences; import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.receiver.MediaButtonReceiver; import de.danoeh.antennapod.receiver.PlayerWidget; -import de.danoeh.antennapod.storage.DBReader; import de.danoeh.antennapod.storage.DBTasks; import de.danoeh.antennapod.storage.DBWriter; import de.danoeh.antennapod.util.BitmapDecoder; import de.danoeh.antennapod.util.QueueAccess; -import de.danoeh.antennapod.util.DuckType; -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.PlayableException; -import de.danoeh.antennapod.util.playback.VideoPlayer; import de.danoeh.antennapod.util.playback.PlaybackController; +import java.util.List; + /** * Controls the MediaPlayer that plays a FeedMedia-file */ @@ -122,12 +107,12 @@ public class PlaybackService extends Service { */ public static final int NOTIFICATION_TYPE_PLAYBACK_END = 7; - /** + /** * Playback speed has changed - * */ + */ public static final int NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE = 8; - - /** + + /** * Returned by getPositionSafe() or getDurationSafe() if the playbackService * is in an invalid state. */ @@ -140,44 +125,11 @@ public class PlaybackService extends Service { private static final int NOTIFICATION_ID = 1; - private volatile IPlayer player; - private RemoteControlClient remoteControlClient; - private AudioManager audioManager; - private ComponentName mediaButtonReceiver; - - private volatile Playable media; - - /** - * True if media should be streamed (Extracted from Intent Extra) . - */ - private boolean shouldStream; - - private boolean startWhenPrepared; - private PlayerStatus status; - - private PositionSaver positionSaver; - private ScheduledFuture positionSaverFuture; - - private WidgetUpdateWorker widgetUpdater; - private ScheduledFuture widgetUpdaterFuture; - - private SleepTimer sleepTimer; - private Future sleepTimerFuture; - - private static final int SCHED_EX_POOL_SIZE = 3; - private ScheduledThreadPoolExecutor schedExecutor; - private ExecutorService dbLoaderExecutor; - - private volatile PlayerStatus statusBeforeSeek; - - private static boolean playingVideo; - - /** - * True if mediaplayer was paused because it lost audio focus temporarily - */ - private boolean pausedBecauseOfTransientAudiofocusLoss; + private RemoteControlClient remoteControlClient; + private PlaybackServiceMediaPlayer mediaPlayer; + private PlaybackServiceTaskManager taskManager; - private Thread chapterLoader; + private static volatile MediaType currentMediaType = MediaType.UNKNOWN; private final IBinder mBinder = new LocalBinder(); @@ -203,7 +155,7 @@ public class PlaybackService extends Service { */ public static Intent getPlayerActivityIntent(Context context) { if (isRunning) { - if (playingVideo) { + if (currentMediaType == MediaType.VIDEO) { return new Intent(context, VideoplayerActivity.class); } else { return new Intent(context, AudioplayerActivity.class); @@ -237,36 +189,7 @@ public class PlaybackService extends Service { if (AppConfig.DEBUG) Log.d(TAG, "Service created."); isRunning = true; - pausedBecauseOfTransientAudiofocusLoss = false; - status = PlayerStatus.STOPPED; - audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); - schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOL_SIZE, - new ThreadFactory() { - - @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(r); - t.setPriority(Thread.MIN_PRIORITY); - return t; - } - }, new RejectedExecutionHandler() { - - @Override - public void rejectedExecution(Runnable r, - ThreadPoolExecutor executor) { - Log.w(TAG, "SchedEx rejected submission of new task"); - } - } - ); - dbLoaderExecutor = Executors.newSingleThreadExecutor(); - mediaButtonReceiver = new ComponentName(getPackageName(), - MediaButtonReceiver.class.getName()); - audioManager.registerMediaButtonEventReceiver(mediaButtonReceiver); - if (android.os.Build.VERSION.SDK_INT >= 14) { - audioManager - .registerRemoteControlClient(setupRemoteControlClient()); - } registerReceiver(headsetDisconnected, new IntentFilter( Intent.ACTION_HEADSET_PLUG)); registerReceiver(shutdownReceiver, new IntentFilter( @@ -275,50 +198,12 @@ public class PlaybackService extends Service { AudioManager.ACTION_AUDIO_BECOMING_NOISY)); registerReceiver(skipCurrentEpisodeReceiver, new IntentFilter( ACTION_SKIP_CURRENT_EPISODE)); - EventDistributor.getInstance().register(eventDistributorListener); - loadQueue(); - } + remoteControlClient = setupRemoteControlClient(); + taskManager = new PlaybackServiceTaskManager(this, taskManagerCallback); + mediaPlayer = new PlaybackServiceMediaPlayer(this, mediaPlayerCallback); - private IPlayer createMediaPlayer() { - if (player != null) { - player.release(); - } - IPlayer player; - if (media == null || media.getMediaType() == MediaType.VIDEO) { - player = new VideoPlayer(); - } else { - player = new AudioPlayer(this); - } - return createMediaPlayer(player); } - private IPlayer createMediaPlayer(IPlayer mp) { - if (mp != null && media != null) { - if (media.getMediaType() == MediaType.AUDIO) { - ((AudioPlayer) mp).setOnPreparedListener(audioPreparedListener); - ((AudioPlayer) mp) - .setOnCompletionListener(audioCompletionListener); - ((AudioPlayer) mp) - .setOnSeekCompleteListener(audioSeekCompleteListener); - ((AudioPlayer) mp).setOnErrorListener(audioErrorListener); - ((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; - } - @SuppressLint("NewApi") @Override public void onDestroy() { @@ -326,23 +211,14 @@ public class PlaybackService extends Service { if (AppConfig.DEBUG) Log.d(TAG, "Service is about to be destroyed"); isRunning = false; - if (chapterLoader != null) { - chapterLoader.interrupt(); - } - disableSleepTimer(); + currentMediaType = MediaType.UNKNOWN; + unregisterReceiver(headsetDisconnected); unregisterReceiver(shutdownReceiver); unregisterReceiver(audioBecomingNoisy); unregisterReceiver(skipCurrentEpisodeReceiver); - EventDistributor.getInstance().unregister(eventDistributorListener); - if (android.os.Build.VERSION.SDK_INT >= 14) { - audioManager.unregisterRemoteControlClient(remoteControlClient); - } - audioManager.unregisterMediaButtonEventReceiver(mediaButtonReceiver); - audioManager.abandonAudioFocus(audioFocusChangeListener); - player.release(); - stopWidgetUpdater(); - updateWidget(); + mediaPlayer.shutdown(); + taskManager.shutdown(); } @Override @@ -352,77 +228,6 @@ public class PlaybackService extends Service { return mBinder; } - private final EventDistributor.EventListener eventDistributorListener = new EventDistributor.EventListener() { - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((EventDistributor.QUEUE_UPDATE & arg) != 0) { - loadQueue(); - } - } - }; - - private final OnAudioFocusChangeListener audioFocusChangeListener = new OnAudioFocusChangeListener() { - - @Override - public void onAudioFocusChange(int focusChange) { - switch (focusChange) { - case AudioManager.AUDIOFOCUS_LOSS: - if (AppConfig.DEBUG) - Log.d(TAG, "Lost audio focus"); - pause(true, false); - stopSelf(); - break; - case AudioManager.AUDIOFOCUS_GAIN: - if (AppConfig.DEBUG) - Log.d(TAG, "Gained audio focus"); - if (pausedBecauseOfTransientAudiofocusLoss) { - audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, - AudioManager.ADJUST_RAISE, 0); - play(); - } - break; - case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: - if (status == PlayerStatus.PLAYING) { - if (!UserPreferences.shouldPauseForFocusLoss()) { - if (AppConfig.DEBUG) - Log.d(TAG, "Lost audio focus temporarily. Ducking..."); - audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, - AudioManager.ADJUST_LOWER, 0); - pausedBecauseOfTransientAudiofocusLoss = true; - } else { - if (AppConfig.DEBUG) - Log.d(TAG, "Lost audio focus temporarily. Could duck, but won't, pausing..."); - pause(false, false); - pausedBecauseOfTransientAudiofocusLoss = true; - } - } - break; - case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: - if (status == PlayerStatus.PLAYING) { - if (AppConfig.DEBUG) - Log.d(TAG, "Lost audio focus temporarily. Pausing..."); - pause(false, false); - pausedBecauseOfTransientAudiofocusLoss = true; - } - } - } - }; - - /** - * 1. Check type of intent - * 1.1 Keycode -> handle keycode -> done - * 1.2 Playable -> Step 2 - * 2. Handle playable - * 2.1 Check current status - * 2.1.1 Not playing -> play new playable - * 2.1.2 Playing, new playable is the same -> play if playback is currently paused - * 2.1.3 Playing, new playable different -> Stop playback of old media - * - * @param intent - * @param flags - * @param startId - * @return - */ @Override public int onStartCommand(Intent intent, int flags, int startId) { super.onStartCommand(intent, flags, startId); @@ -441,79 +246,62 @@ public class PlaybackService extends Service { Log.d(TAG, "Received media button event"); handleKeycode(keycode); } else { - boolean playbackType = intent.getBooleanExtra(EXTRA_SHOULD_STREAM, + boolean stream = intent.getBooleanExtra(EXTRA_SHOULD_STREAM, true); - if (media == null) { - media = playable; - shouldStream = playbackType; - startWhenPrepared = intent.getBooleanExtra( - EXTRA_START_WHEN_PREPARED, false); - initMediaplayer(intent.getBooleanExtra(EXTRA_PREPARE_IMMEDIATELY, false)); - sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0); - } - if (media != null) { - if (!playable.getIdentifier().equals(media.getIdentifier())) { - // different media or different playback type - pause(true, false); - player.reset(); - media = playable; - shouldStream = playbackType; - startWhenPrepared = intent.getBooleanExtra(EXTRA_START_WHEN_PREPARED, false); - initMediaplayer(intent.getBooleanExtra(EXTRA_PREPARE_IMMEDIATELY, false)); - sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0); - } else { - // same media and same playback type - if (status == PlayerStatus.PAUSED) { - play(); - } - } - } + boolean startWhenPrepared = intent.getBooleanExtra(EXTRA_START_WHEN_PREPARED, false); + boolean prepareImmediately = intent.getBooleanExtra(EXTRA_PREPARE_IMMEDIATELY, false); + sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0); + mediaPlayer.playMediaObject(playable, stream, startWhenPrepared, prepareImmediately); } return Service.START_NOT_STICKY; } - /** Handles media button events */ - private void handleKeycode(int keycode) { - if (AppConfig.DEBUG) - Log.d(TAG, "Handling keycode: " + keycode); - switch (keycode) { - case KeyEvent.KEYCODE_HEADSETHOOK: - case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: - if (status == PlayerStatus.PLAYING) { - pause(true, true); - } else if (status == PlayerStatus.PAUSED) { - play(); - } else if (status == PlayerStatus.PREPARING) { - setStartWhenPrepared(!startWhenPrepared); - } else if (status == PlayerStatus.INITIALIZED) { - startWhenPrepared = true; - prepare(); - } - break; - case KeyEvent.KEYCODE_MEDIA_PLAY: - if (status == PlayerStatus.PAUSED) { - play(); - } else if (status == PlayerStatus.INITIALIZED) { - startWhenPrepared = true; - prepare(); - } - break; - case KeyEvent.KEYCODE_MEDIA_PAUSE: - if (status == PlayerStatus.PLAYING) { - pause(true, true); - } - break; - case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: { - seekDelta(PlaybackController.DEFAULT_SEEK_DELTA); - break; - } - case KeyEvent.KEYCODE_MEDIA_REWIND: { - seekDelta(-PlaybackController.DEFAULT_SEEK_DELTA); - break; - } - } - } + /** + * Handles media button events + */ + private void handleKeycode(int keycode) { + if (AppConfig.DEBUG) + Log.d(TAG, "Handling keycode: " + keycode); + + final PlayerStatus status = mediaPlayer.getPSMPInfo().playerStatus; + switch (keycode) { + case KeyEvent.KEYCODE_HEADSETHOOK: + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + if (status == PlayerStatus.PLAYING) { + mediaPlayer.pause(true, true); + } else if (status == PlayerStatus.PAUSED) { + mediaPlayer.resume(); + } else if (status == PlayerStatus.PREPARING) { + mediaPlayer.setStartWhenPrepared(!mediaPlayer.isStartWhenPrepared()); + } else if (status == PlayerStatus.INITIALIZED) { + mediaPlayer.setStartWhenPrepared(true); + mediaPlayer.prepare(); + } + break; + case KeyEvent.KEYCODE_MEDIA_PLAY: + if (status == PlayerStatus.PAUSED) { + mediaPlayer.resume(); + } else if (status == PlayerStatus.INITIALIZED) { + mediaPlayer.setStartWhenPrepared(true); + mediaPlayer.prepare(); + } + break; + case KeyEvent.KEYCODE_MEDIA_PAUSE: + if (status == PlayerStatus.PLAYING) { + mediaPlayer.pause(true, true); + } + break; + case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: { + mediaPlayer.seekDelta(PlaybackController.DEFAULT_SEEK_DELTA); + break; + } + case KeyEvent.KEYCODE_MEDIA_REWIND: { + mediaPlayer.seekDelta(-PlaybackController.DEFAULT_SEEK_DELTA); + break; + } + } + } /** * Called by a mediaplayer Activity as soon as it has prepared its @@ -522,354 +310,152 @@ public class PlaybackService extends Service { public void setVideoSurface(SurfaceHolder sh) { if (AppConfig.DEBUG) Log.d(TAG, "Setting display"); - player.setDisplay(null); - player.setDisplay(sh); - if (status == PlayerStatus.STOPPED - || status == PlayerStatus.AWAITING_VIDEO_SURFACE) { - try { - InitTask initTask = new InitTask() { - - @Override - protected void onPostExecute(Playable result) { - if (status == PlayerStatus.INITIALIZING) { - if (result != null) { - try { - if (shouldStream) { - player.setDataSource(media - .getStreamUrl()); - setStatus(PlayerStatus.PREPARING); - player.prepareAsync(); - } else { - player.setDataSource(media - .getLocalMediaUrl()); - setStatus(PlayerStatus.PREPARING); - player.prepareAsync(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } else { - setStatus(PlayerStatus.ERROR); - sendBroadcast(new Intent( - ACTION_SHUTDOWN_PLAYBACK_SERVICE)); - } - } - } - - @Override - protected void onPreExecute() { - setStatus(PlayerStatus.INITIALIZING); - } - - }; - initTask.executeAsync(media); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } catch (SecurityException e) { - e.printStackTrace(); - } catch (IllegalStateException e) { - e.printStackTrace(); - } - } - + mediaPlayer.setVideoSurface(sh); } /** * Called when the surface holder of the mediaplayer has to be changed. */ private void resetVideoSurface() { - if (AppConfig.DEBUG) - Log.d(TAG, "Resetting video surface"); - cancelPositionSaver(); - player.setDisplay(null); - player.reset(); - player = createMediaPlayer(); - status = PlayerStatus.STOPPED; + taskManager.cancelPositionSaver(); + mediaPlayer.resetVideoSurface(); } - public void notifyVideoSurfaceAbandoned() { - resetVideoSurface(); - if (media != null) { - initMediaplayer(true); - } - } - - /** - * Called after service has extracted the media it is supposed to play. - * - * @param prepareImmediately True if service should prepare playback after it has been initialized - */ - private void initMediaplayer(final boolean prepareImmediately) { - if (AppConfig.DEBUG) - Log.d(TAG, "Setting up media player"); - try { - MediaType mediaType = media.getMediaType(); - player = createMediaPlayer(); - if (mediaType == MediaType.AUDIO) { - if (AppConfig.DEBUG) - Log.d(TAG, "Mime type is audio"); - - InitTask initTask = new InitTask() { - - @Override - protected void onPostExecute(Playable result) { - // check if state of service has changed. If it has - // changed, assume that loaded metadata is not needed - // anymore. - if (status == PlayerStatus.INITIALIZING) { - if (result != null) { - playingVideo = false; - try { - if (shouldStream) { - player.setDataSource(media - .getStreamUrl()); - } else if (media.localFileAvailable()) { - player.setDataSource(media - .getLocalMediaUrl()); - } - - if (prepareImmediately) { - setStatus(PlayerStatus.PREPARING); - player.prepareAsync(); - } else { - setStatus(PlayerStatus.INITIALIZED); - } - } catch (IOException e) { - e.printStackTrace(); - media = null; - setStatus(PlayerStatus.ERROR); - sendBroadcast(new Intent( - ACTION_SHUTDOWN_PLAYBACK_SERVICE)); - } - } else { - Log.e(TAG, "InitTask could not load metadata"); - media = null; - setStatus(PlayerStatus.ERROR); - sendBroadcast(new Intent( - ACTION_SHUTDOWN_PLAYBACK_SERVICE)); - } - } else { - if (AppConfig.DEBUG) - Log.d(TAG, - "Status of player has changed during initialization. Stopping init process."); - } - } - - @Override - protected void onPreExecute() { - setStatus(PlayerStatus.INITIALIZING); - } - - }; - initTask.executeAsync(media); - } else if (mediaType == MediaType.VIDEO) { - if (AppConfig.DEBUG) - Log.d(TAG, "Mime type is video"); - playingVideo = true; - setStatus(PlayerStatus.AWAITING_VIDEO_SURFACE); - player.setScreenOnWhilePlaying(true); - } - - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } catch (SecurityException e) { - e.printStackTrace(); - } catch (IllegalStateException e) { - e.printStackTrace(); - } + public void notifyVideoSurfaceAbandoned() { + mediaPlayer.resetVideoSurface(); } - private void setupPositionSaver() { - if (positionSaverFuture == null - || (positionSaverFuture.isCancelled() || positionSaverFuture - .isDone())) { - - positionSaver = new PositionSaver(); - positionSaverFuture = schedExecutor.scheduleAtFixedRate( - positionSaver, PositionSaver.WAITING_INTERVALL, - PositionSaver.WAITING_INTERVALL, TimeUnit.MILLISECONDS); - } - } - - private void cancelPositionSaver() { - if (positionSaverFuture != null) { - boolean result = positionSaverFuture.cancel(true); - if (AppConfig.DEBUG) - Log.d(TAG, "PositionSaver cancelled. Result: " + result); - } - } - - private final com.aocate.media.MediaPlayer.OnPreparedListener audioPreparedListener = new com.aocate.media.MediaPlayer.OnPreparedListener() { + private final PlaybackServiceTaskManager.PSTMCallback taskManagerCallback = new PlaybackServiceTaskManager.PSTMCallback() { @Override - public void onPrepared(com.aocate.media.MediaPlayer mp) { - genericOnPrepared(mp); + public void positionSaverTick() { + saveCurrentPosition(); } - }; - private final android.media.MediaPlayer.OnPreparedListener videoPreparedListener = new android.media.MediaPlayer.OnPreparedListener() { @Override - public void onPrepared(android.media.MediaPlayer mp) { - genericOnPrepared(mp); + public void onSleepTimerExpired() { + mediaPlayer.pause(true, true); + sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); } - }; - private final void genericOnPrepared(Object inObj) { - IPlayer mp = DuckType.coerce(inObj).to(IPlayer.class); - if (AppConfig.DEBUG) - Log.d(TAG, "Resource prepared"); - mp.seekTo(media.getPosition()); - if (media.getDuration() == 0) { - if (AppConfig.DEBUG) - Log.d(TAG, "Setting duration of media"); - media.setDuration(mp.getDuration()); - } - setStatus(PlayerStatus.PREPARED); - if (chapterLoader != null) { - chapterLoader.interrupt(); - } - chapterLoader = new Thread() { - @Override - public void run() { - if (AppConfig.DEBUG) - Log.d(TAG, "Chapter loader started"); - if (media != null && media.getChapters() == null) { - media.loadChapterMarks(); - if (!isInterrupted() && media.getChapters() != null) { - sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, - 0); - } - } - if (AppConfig.DEBUG) - Log.d(TAG, "Chapter loader stopped"); - } - }; - chapterLoader.start(); - if (startWhenPrepared) { - play(); + @Override + public void onWidgetUpdaterTick() { + updateWidget(); } - } - private final com.aocate.media.MediaPlayer.OnSeekCompleteListener audioSeekCompleteListener = new com.aocate.media.MediaPlayer.OnSeekCompleteListener() { @Override - public void onSeekComplete(com.aocate.media.MediaPlayer mp) { - genericSeekCompleteListener(); + public void onChapterLoaded(Playable media) { + sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0); } }; - private final android.media.MediaPlayer.OnSeekCompleteListener videoSeekCompleteListener = new android.media.MediaPlayer.OnSeekCompleteListener() { + private final PlaybackServiceMediaPlayer.PSMPCallback mediaPlayerCallback = new PlaybackServiceMediaPlayer.PSMPCallback() { @Override - public void onSeekComplete(android.media.MediaPlayer mp) { - genericSeekCompleteListener(); - } - }; + public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) { + switch (newInfo.playerStatus) { + case PREPARED: + taskManager.startChapterLoader(newInfo.playable); + break; - private final void genericSeekCompleteListener() { - if (status == PlayerStatus.SEEKING) { - setStatus(statusBeforeSeek); - } - } + case PAUSED: + taskManager.cancelPositionSaver(); + saveCurrentPosition(); + taskManager.cancelWidgetUpdater(); + stopForeground(true); + break; - 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); - } - }; + case STOPPED: + setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING); + stopSelf(); + break; - private final android.media.MediaPlayer.OnInfoListener videoInfoListener = new android.media.MediaPlayer.OnInfoListener() { - @Override - public boolean onInfo(android.media.MediaPlayer mp, int what, int extra) { - return genericInfoListener(what); - } - }; + case PLAYING: + if (AppConfig.DEBUG) + Log.d(TAG, "Audiofocus successfully requested"); + if (AppConfig.DEBUG) + Log.d(TAG, "Resuming/Starting playback"); + + currentMediaType = mediaPlayer.getCurrentMediaType(); + writePlaybackPreferences(); + taskManager.startPositionSaver(); + taskManager.startWidgetUpdater(); + setupNotification(newInfo); + break; + + } - private boolean genericInfoListener(int what) { - switch (what) { - case MediaPlayer.MEDIA_INFO_BUFFERING_START: - sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_START, 0); - return true; - case MediaPlayer.MEDIA_INFO_BUFFERING_END: - sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_END, 0); - return true; - default: - return false; + sendBroadcast(new Intent(ACTION_PLAYER_STATUS_CHANGED)); + updateWidget(); + refreshRemoteControlClientState(newInfo); + bluetoothNotifyChange(newInfo); } - } - 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); + public void shouldStop() { + stopSelf(); } - }; - private final android.media.MediaPlayer.OnErrorListener videoErrorListener = new android.media.MediaPlayer.OnErrorListener() { @Override - public boolean onError(android.media.MediaPlayer mp, int what, int extra) { - return genericOnError(mp, what, extra); + public void playbackSpeedChanged(float s) { + sendNotificationBroadcast( + NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE, 0); } - }; - 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()) { - pause(true, true); + @Override + public void onBufferingUpdate(int percent) { + sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_UPDATE, percent); } - sendNotificationBroadcast(NOTIFICATION_TYPE_ERROR, what); - setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING); - stopSelf(); - 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(); + public boolean onMediaPlayerInfo(int code) { + switch (code) { + case MediaPlayer.MEDIA_INFO_BUFFERING_START: + sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_START, 0); + return true; + case MediaPlayer.MEDIA_INFO_BUFFERING_END: + sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_END, 0); + return true; + default: + return false; + } } - }; - private final android.media.MediaPlayer.OnCompletionListener videoCompletionListener = new android.media.MediaPlayer.OnCompletionListener() { @Override - public void onCompletion(android.media.MediaPlayer mp) { - genericOnCompletion(); + public boolean onMediaPlayerError(Object inObj, int what, int extra) { + final String TAG = "PlaybackService.onErrorListener"; + Log.w(TAG, "An error has occured: " + what + " " + extra); + if (mediaPlayer.getPSMPInfo().playerStatus == PlayerStatus.PLAYING) { + mediaPlayer.pause(true, false); + } + sendNotificationBroadcast(NOTIFICATION_TYPE_ERROR, what); + setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING); + stopSelf(); + return true; } - }; - - private void genericOnCompletion() { - endPlayback(true); - } - private final com.aocate.media.MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener = new com.aocate.media.MediaPlayer.OnBufferingUpdateListener() { @Override - public void onBufferingUpdate(com.aocate.media.MediaPlayer mp, - int percent) { - genericOnBufferingUpdate(percent); + public boolean endPlayback(boolean playNextEpisode) { + endPlayback(true); + return true; } - }; - private final android.media.MediaPlayer.OnBufferingUpdateListener videoBufferingUpdateListener = new android.media.MediaPlayer.OnBufferingUpdateListener() { @Override - public void onBufferingUpdate(android.media.MediaPlayer mp, int percent) { - genericOnBufferingUpdate(percent); + public RemoteControlClient getRemoteControlClient() { + return remoteControlClient; } }; - private void genericOnBufferingUpdate(int percent) { - sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_UPDATE, percent); - } - private void endPlayback(boolean playNextEpisode) { if (AppConfig.DEBUG) Log.d(TAG, "Playback ended"); - audioManager.abandonAudioFocus(audioFocusChangeListener); - // Save state - cancelPositionSaver(); + final Playable media = mediaPlayer.getPSMPInfo().playable; + if (media == null) { + Log.e(TAG, "Cannot end playback: media was null"); + return; + } + + taskManager.cancelPositionSaver(); boolean isInQueue = false; FeedItem nextItem = null; @@ -878,14 +464,13 @@ public class PlaybackService extends Service { FeedItem item = ((FeedMedia) media).getItem(); DBWriter.markItemRead(PlaybackService.this, item, true, true); nextItem = DBTasks.getQueueSuccessorOfItem(this, item.getId(), queue); - isInQueue = media instanceof FeedMedia - && QueueAccess.ItemListAccess(queue).contains(((FeedMedia) media).getItem().getId()); + isInQueue = QueueAccess.ItemListAccess(queue).contains(((FeedMedia) media).getItem().getId()); if (isInQueue) { DBWriter.removeQueueItem(PlaybackService.this, item.getId(), true); } DBWriter.addItemToPlaybackHistory(PlaybackService.this, (FeedMedia) media); long autoDeleteMediaId = ((FeedComponent) media).getId(); - if (shouldStream) { + if (mediaPlayer.isStreaming()) { autoDeleteMediaId = -1; } } @@ -893,15 +478,19 @@ public class PlaybackService extends Service { // Load next episode if previous episode was in the queue and if there // is an episode in the queue left. // Start playback immediately if continuous playback is enabled + Playable nextMedia = null; boolean loadNextItem = isInQueue && nextItem != null; playNextEpisode = playNextEpisode && loadNextItem && UserPreferences.isFollowQueue(); if (loadNextItem) { if (AppConfig.DEBUG) Log.d(TAG, "Loading next item in queue"); - media = nextItem.getMedia(); + nextMedia = nextItem.getMedia(); } final boolean prepareImmediately; + final boolean startWhenPrepared; + final boolean stream; + if (playNextEpisode) { if (AppConfig.DEBUG) Log.d(TAG, "Playback of next episode will start immediately."); @@ -909,28 +498,18 @@ public class PlaybackService extends Service { } else { if (AppConfig.DEBUG) Log.d(TAG, "No more episodes available to play"); - media = null; + prepareImmediately = startWhenPrepared = false; stopForeground(true); stopWidgetUpdater(); } int notificationCode = 0; - if (media != null) { - shouldStream = !media.localFileAvailable(); - if (media.getMediaType() == MediaType.AUDIO) { - notificationCode = EXTRA_CODE_AUDIO; - playingVideo = false; - } else if (media.getMediaType() == MediaType.VIDEO) { - notificationCode = EXTRA_CODE_VIDEO; - } - } writePlaybackPreferences(); - if (media != null) { - resetVideoSurface(); - refreshRemoteControlClientState(); - initMediaplayer(prepareImmediately); + if (nextMedia != null) { + stream = !media.localFileAvailable(); + mediaPlayer.playMediaObject(nextMedia, stream, startWhenPrepared, prepareImmediately); sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, notificationCode); } else { @@ -939,797 +518,461 @@ public class PlaybackService extends Service { } } - public void setSleepTimer(long waitingTime) { - if (AppConfig.DEBUG) - Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime) - + " milliseconds"); - if (sleepTimerFuture != null) { - sleepTimerFuture.cancel(true); - } - sleepTimer = new SleepTimer(waitingTime); - sleepTimerFuture = schedExecutor.submit(sleepTimer); - sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); - } - - public void disableSleepTimer() { - if (sleepTimerFuture != null) { - if (AppConfig.DEBUG) - Log.d(TAG, "Disabling sleep timer"); - sleepTimerFuture.cancel(true); - sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); - } - } - - /** - * Saves the current position and pauses playback. Note that, if audiofocus - * is abandoned, the lockscreen controls will also disapear. - * - * @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 void pause(boolean abandonFocus, boolean reinit) { - if (player.isPlaying()) { - if (AppConfig.DEBUG) - Log.d(TAG, "Pausing playback."); - player.pause(); - cancelPositionSaver(); - saveCurrentPosition(); - setStatus(PlayerStatus.PAUSED); - if (abandonFocus) { - audioManager.abandonAudioFocus(audioFocusChangeListener); - pausedBecauseOfTransientAudiofocusLoss = false; - disableSleepTimer(); - } - stopWidgetUpdater(); - stopForeground(true); - if (shouldStream && reinit) { - reinit(); - } - } - } - - /** Pauses playback and destroys service. Recommended for video playback. */ - public void stop() { - if (AppConfig.DEBUG) - Log.d(TAG, "Stopping playback"); - if (status == PlayerStatus.PREPARED || status == PlayerStatus.PAUSED - || status == PlayerStatus.STOPPED - || status == PlayerStatus.PLAYING) { - player.stop(); - } - setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING); - stopSelf(); - } - - /** - * Prepared media player for playback if the service is in the INITALIZED - * state. - */ - public void prepare() { - if (status == PlayerStatus.INITIALIZED) { - if (AppConfig.DEBUG) - Log.d(TAG, "Preparing media player"); - setStatus(PlayerStatus.PREPARING); - player.prepareAsync(); - } - } - - /** Resets the media player and moves into INITIALIZED state. */ - public void reinit() { - player.reset(); - player = createMediaPlayer(player); - initMediaplayer(false); - } - - @SuppressLint("NewApi") - public void play() { - if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED - || status == PlayerStatus.STOPPED) { - int focusGained = audioManager.requestAudioFocus( - audioFocusChangeListener, AudioManager.STREAM_MUSIC, - AudioManager.AUDIOFOCUS_GAIN); - - if (focusGained == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { - if (AppConfig.DEBUG) - Log.d(TAG, "Audiofocus successfully requested"); - if (AppConfig.DEBUG) - Log.d(TAG, "Resuming/Starting playback"); - writePlaybackPreferences(); - - setSpeed(Float.parseFloat(UserPreferences.getPlaybackSpeed())); - player.start(); - if (status != PlayerStatus.PAUSED) { - player.seekTo((int) media.getPosition()); - } - setStatus(PlayerStatus.PLAYING); - setupPositionSaver(); - setupWidgetUpdater(); - setupNotification(); - pausedBecauseOfTransientAudiofocusLoss = false; - if (android.os.Build.VERSION.SDK_INT >= 14) { - audioManager - .registerRemoteControlClient(remoteControlClient); - } - audioManager - .registerMediaButtonEventReceiver(mediaButtonReceiver); - media.onPlaybackStart(); - } else { - if (AppConfig.DEBUG) - Log.d(TAG, "Failed to request Audiofocus"); - } - } - } - - private void writePlaybackPreferences() { - if (AppConfig.DEBUG) - Log.d(TAG, "Writing playback preferences"); - - SharedPreferences.Editor editor = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()).edit(); - if (media != null) { - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, - media.getPlayableType()); - editor.putBoolean( - PlaybackPreferences.PREF_CURRENT_EPISODE_IS_STREAM, - shouldStream); - editor.putBoolean( - PlaybackPreferences.PREF_CURRENT_EPISODE_IS_VIDEO, - playingVideo); - if (media instanceof FeedMedia) { - FeedMedia fMedia = (FeedMedia) media; - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - fMedia.getItem().getFeed().getId()); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, - fMedia.getId()); - } else { - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - } - media.writeToPreferences(editor); - } else { - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - } - - editor.commit(); - } - - private void setStatus(PlayerStatus newStatus) { - if (AppConfig.DEBUG) - Log.d(TAG, "Setting status to " + newStatus); - status = newStatus; - sendBroadcast(new Intent(ACTION_PLAYER_STATUS_CHANGED)); - updateWidget(); - refreshRemoteControlClientState(); - bluetoothNotifyChange(); - } - - /** Send ACTION_PLAYER_STATUS_CHANGED without changing the status attribute. */ - private void postStatusUpdateIntent() { - setStatus(status); - } - - private void sendNotificationBroadcast(int type, int code) { - Intent intent = new Intent(ACTION_PLAYER_NOTIFICATION); - intent.putExtra(EXTRA_NOTIFICATION_TYPE, type); - intent.putExtra(EXTRA_NOTIFICATION_CODE, code); - sendBroadcast(intent); - } - - /** Used by setupNotification to load notification data in another thread. */ - private AsyncTask<Void, Void, Void> notificationSetupTask; - - /** Prepares notification and starts the service in the foreground. */ - @SuppressLint("NewApi") - private void setupNotification() { - final PendingIntent pIntent = PendingIntent.getActivity(this, 0, - PlaybackService.getPlayerActivityIntent(this), - PendingIntent.FLAG_UPDATE_CURRENT); - - if (notificationSetupTask != null) { - notificationSetupTask.cancel(true); - } - notificationSetupTask = new AsyncTask<Void, Void, Void>() { - Bitmap icon = null; - - @Override - protected Void doInBackground(Void... params) { - if (AppConfig.DEBUG) - Log.d(TAG, "Starting background work"); - if (android.os.Build.VERSION.SDK_INT >= 11) { - if (media != null && media != null) { - int iconSize = getResources().getDimensionPixelSize( - android.R.dimen.notification_large_icon_width); - icon = BitmapDecoder - .decodeBitmapFromWorkerTaskResource(iconSize, - media); - } - - } - if (icon == null) { - icon = BitmapFactory.decodeResource(getResources(), - R.drawable.ic_stat_antenna); - } - - return null; - } - - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - if (!isCancelled() && status == PlayerStatus.PLAYING - && media != null) { - String contentText = media.getFeedTitle(); - String contentTitle = media.getEpisodeTitle(); - Notification notification = null; - if (android.os.Build.VERSION.SDK_INT >= 16) { - Intent pauseButtonIntent = new Intent( - PlaybackService.this, PlaybackService.class); - pauseButtonIntent.putExtra( - MediaButtonReceiver.EXTRA_KEYCODE, - KeyEvent.KEYCODE_MEDIA_PAUSE); - PendingIntent pauseButtonPendingIntent = PendingIntent - .getService(PlaybackService.this, 0, - pauseButtonIntent, - PendingIntent.FLAG_UPDATE_CURRENT); - Notification.Builder notificationBuilder = new Notification.Builder( - PlaybackService.this) - .setContentTitle(contentTitle) - .setContentText(contentText) - .setOngoing(true) - .setContentIntent(pIntent) - .setLargeIcon(icon) - .setSmallIcon(R.drawable.ic_stat_antenna) - .addAction(android.R.drawable.ic_media_pause, - getString(R.string.pause_label), - pauseButtonPendingIntent); - notification = notificationBuilder.build(); - } else { - NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder( - PlaybackService.this) - .setContentTitle(contentTitle) - .setContentText(contentText).setOngoing(true) - .setContentIntent(pIntent).setLargeIcon(icon) - .setSmallIcon(R.drawable.ic_stat_antenna); - notification = notificationBuilder.getNotification(); - } - startForeground(NOTIFICATION_ID, notification); - if (AppConfig.DEBUG) - Log.d(TAG, "Notification set up"); - } - } - - }; - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - notificationSetupTask - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - notificationSetupTask.execute(); - } - - } - - /** - * Seek a specific position from the current position - * - * @param delta - * offset from current position (positive or negative) - * */ - public void seekDelta(int delta) { - int position = getCurrentPositionSafe(); - if (position != INVALID_TIME) { - seek(player.getCurrentPosition() + delta); - } - } - - public void seek(int i) { - saveCurrentPosition(); - if (status == PlayerStatus.INITIALIZED - || status == PlayerStatus.INITIALIZING - || status == PlayerStatus.PREPARING) { - media.setPosition(i); - setStartWhenPrepared(true); - prepare(); - } else { - if (AppConfig.DEBUG) - Log.d(TAG, "Seeking position " + i); - if (shouldStream) { - if (status != PlayerStatus.SEEKING) { - statusBeforeSeek = status; - } - setStatus(PlayerStatus.SEEKING); - } - player.seekTo(i); - } - } - - public void seekToChapter(Chapter chapter) { - seek((int) chapter.getStart()); - } - - /** Saves the current position of the media file to the DB */ - private synchronized void saveCurrentPosition() { - int position = getCurrentPositionSafe(); - if (position != INVALID_TIME) { - if (AppConfig.DEBUG) - Log.d(TAG, "Saving current position to " + position); - media.saveCurrentPosition(PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()), - position); - } - } - - private void stopWidgetUpdater() { - if (widgetUpdaterFuture != null) { - if (AppConfig.DEBUG) - Log.d(TAG, "Stopping widgetUpdateWorker"); - widgetUpdaterFuture.cancel(true); - } - sendBroadcast(new Intent(PlayerWidget.STOP_WIDGET_UPDATE)); - } - - @SuppressLint("NewApi") - private void setupWidgetUpdater() { - if (widgetUpdaterFuture == null - || (widgetUpdaterFuture.isCancelled() || widgetUpdaterFuture - .isDone())) { - widgetUpdater = new WidgetUpdateWorker(); - widgetUpdaterFuture = schedExecutor.scheduleAtFixedRate( - widgetUpdater, WidgetUpdateWorker.NOTIFICATION_INTERVALL, - WidgetUpdateWorker.NOTIFICATION_INTERVALL, - TimeUnit.MILLISECONDS); - } - } - - private void updateWidget() { - if (AppConfig.DEBUG) - Log.d(TAG, "Sending widget update request"); - PlaybackService.this.sendBroadcast(new Intent( - PlayerWidget.FORCE_WIDGET_UPDATE)); - } - - public boolean sleepTimerActive() { - return sleepTimer != null && sleepTimer.isWaiting(); - } - - public long getSleepTimerTimeLeft() { - if (sleepTimerActive()) { - return sleepTimer.getWaitingTime(); - } else { - return 0; - } - } - - @SuppressLint("NewApi") - private RemoteControlClient setupRemoteControlClient() { - Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); - mediaButtonIntent.setComponent(mediaButtonReceiver); - PendingIntent mediaPendingIntent = PendingIntent.getBroadcast( - getApplicationContext(), 0, mediaButtonIntent, 0); - remoteControlClient = new RemoteControlClient(mediaPendingIntent); - int controlFlags; - if (android.os.Build.VERSION.SDK_INT < 16) { - controlFlags = RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE - | RemoteControlClient.FLAG_KEY_MEDIA_NEXT; - } else { - controlFlags = RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE; - } - remoteControlClient.setTransportControlFlags(controlFlags); - return remoteControlClient; - } - - /** Refresh player status and metadata. */ - @SuppressLint("NewApi") - private void refreshRemoteControlClientState() { - if (android.os.Build.VERSION.SDK_INT >= 14) { - if (remoteControlClient != null) { - switch (status) { - case PLAYING: - remoteControlClient - .setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING); - break; - case PAUSED: - case INITIALIZED: - remoteControlClient - .setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED); - break; - case STOPPED: - remoteControlClient - .setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED); - break; - case ERROR: - remoteControlClient - .setPlaybackState(RemoteControlClient.PLAYSTATE_ERROR); - break; - default: - remoteControlClient - .setPlaybackState(RemoteControlClient.PLAYSTATE_BUFFERING); - } - if (media != null) { - MetadataEditor editor = remoteControlClient - .editMetadata(false); - editor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, - media.getEpisodeTitle()); - - editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, - media.getFeedTitle()); - - editor.apply(); - } - if (AppConfig.DEBUG) - Log.d(TAG, "RemoteControlClient state was refreshed"); - } - } - } - - private void bluetoothNotifyChange() { - boolean isPlaying = false; - - if (status == PlayerStatus.PLAYING) { - isPlaying = true; - } - - if (media != null) { - Intent i = new Intent(AVRCP_ACTION_PLAYER_STATUS_CHANGED); - i.putExtra("id", 1); - i.putExtra("artist", ""); - i.putExtra("album", media.getFeedTitle()); - i.putExtra("track", media.getEpisodeTitle()); - i.putExtra("playing", isPlaying); + public void setSleepTimer(long waitingTime) { + if (AppConfig.DEBUG) + Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime) + + " milliseconds"); + taskManager.setSleepTimer(waitingTime); + sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); + } + + public void disableSleepTimer() { + taskManager.disableSleepTimer(); + sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); + } + + + private void writePlaybackPreferences() { + if (AppConfig.DEBUG) + Log.d(TAG, "Writing playback preferences"); + + SharedPreferences.Editor editor = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()).edit(); + PlaybackServiceMediaPlayer.PSMPInfo info = mediaPlayer.getPSMPInfo(); + MediaType mediaType = mediaPlayer.getCurrentMediaType(); + boolean stream = mediaPlayer.isStreaming(); + + if (info.playable != null) { + editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, + info.playable.getPlayableType()); + editor.putBoolean( + PlaybackPreferences.PREF_CURRENT_EPISODE_IS_STREAM, + stream); + editor.putBoolean( + PlaybackPreferences.PREF_CURRENT_EPISODE_IS_VIDEO, + mediaType == MediaType.VIDEO); + if (info.playable instanceof FeedMedia) { + FeedMedia fMedia = (FeedMedia) info.playable; + editor.putLong( + PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, + fMedia.getItem().getFeed().getId()); + editor.putLong( + PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, + fMedia.getId()); + } else { + editor.putLong( + PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, + PlaybackPreferences.NO_MEDIA_PLAYING); + editor.putLong( + PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, + PlaybackPreferences.NO_MEDIA_PLAYING); + } + info.playable.writeToPreferences(editor); + } else { + editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, + PlaybackPreferences.NO_MEDIA_PLAYING); + editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, + PlaybackPreferences.NO_MEDIA_PLAYING); + editor.putLong( + PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, + PlaybackPreferences.NO_MEDIA_PLAYING); + } + + editor.commit(); + } + + /** + * Send ACTION_PLAYER_STATUS_CHANGED without changing the status attribute. + */ + private void postStatusUpdateIntent() { + sendBroadcast(new Intent(ACTION_PLAYER_STATUS_CHANGED)); + } + + private void sendNotificationBroadcast(int type, int code) { + Intent intent = new Intent(ACTION_PLAYER_NOTIFICATION); + intent.putExtra(EXTRA_NOTIFICATION_TYPE, type); + intent.putExtra(EXTRA_NOTIFICATION_CODE, code); + sendBroadcast(intent); + } + + /** + * Used by setupNotification to load notification data in another thread. + */ + private AsyncTask<Void, Void, Void> notificationSetupTask; + + /** + * Prepares notification and starts the service in the foreground. + */ + @SuppressLint("NewApi") + private void setupNotification(final PlaybackServiceMediaPlayer.PSMPInfo info) { + final PendingIntent pIntent = PendingIntent.getActivity(this, 0, + PlaybackService.getPlayerActivityIntent(this), + PendingIntent.FLAG_UPDATE_CURRENT); + + if (notificationSetupTask != null) { + notificationSetupTask.cancel(true); + } + notificationSetupTask = new AsyncTask<Void, Void, Void>() { + Bitmap icon = null; + + @Override + protected Void doInBackground(Void... params) { + if (AppConfig.DEBUG) + Log.d(TAG, "Starting background work"); + if (android.os.Build.VERSION.SDK_INT >= 11) { + if (info.playable != null) { + int iconSize = getResources().getDimensionPixelSize( + android.R.dimen.notification_large_icon_width); + icon = BitmapDecoder + .decodeBitmapFromWorkerTaskResource(iconSize, + info.playable); + } + + } + if (icon == null) { + icon = BitmapFactory.decodeResource(getResources(), + R.drawable.ic_stat_antenna); + } + + return null; + } + + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + if (!isCancelled() && info.playerStatus == PlayerStatus.PLAYING + && info.playable != null) { + String contentText = info.playable.getFeedTitle(); + String contentTitle = info.playable.getEpisodeTitle(); + Notification notification = null; + if (android.os.Build.VERSION.SDK_INT >= 16) { + Intent pauseButtonIntent = new Intent( + PlaybackService.this, PlaybackService.class); + pauseButtonIntent.putExtra( + MediaButtonReceiver.EXTRA_KEYCODE, + KeyEvent.KEYCODE_MEDIA_PAUSE); + PendingIntent pauseButtonPendingIntent = PendingIntent + .getService(PlaybackService.this, 0, + pauseButtonIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + Notification.Builder notificationBuilder = new Notification.Builder( + PlaybackService.this) + .setContentTitle(contentTitle) + .setContentText(contentText) + .setOngoing(true) + .setContentIntent(pIntent) + .setLargeIcon(icon) + .setSmallIcon(R.drawable.ic_stat_antenna) + .addAction(android.R.drawable.ic_media_pause, + getString(R.string.pause_label), + pauseButtonPendingIntent); + notification = notificationBuilder.build(); + } else { + NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder( + PlaybackService.this) + .setContentTitle(contentTitle) + .setContentText(contentText).setOngoing(true) + .setContentIntent(pIntent).setLargeIcon(icon) + .setSmallIcon(R.drawable.ic_stat_antenna); + notification = notificationBuilder.getNotification(); + } + startForeground(NOTIFICATION_ID, notification); + if (AppConfig.DEBUG) + Log.d(TAG, "Notification set up"); + } + } + + }; + if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { + notificationSetupTask + .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } else { + notificationSetupTask.execute(); + } + + } + + /** + * Saves the current position of the media file to the DB + */ + private synchronized void saveCurrentPosition() { + int position = getCurrentPosition(); + final Playable playable = mediaPlayer.getPSMPInfo().playable; + if (position != INVALID_TIME && playable != null) { + if (AppConfig.DEBUG) + Log.d(TAG, "Saving current position to " + position); + playable.saveCurrentPosition(PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()), + position); + } + } + + private void stopWidgetUpdater() { + taskManager.cancelWidgetUpdater(); + sendBroadcast(new Intent(PlayerWidget.STOP_WIDGET_UPDATE)); + } + + private void updateWidget() { + if (AppConfig.DEBUG) + Log.d(TAG, "Sending widget update request"); + PlaybackService.this.sendBroadcast(new Intent( + PlayerWidget.FORCE_WIDGET_UPDATE)); + } + + public boolean sleepTimerActive() { + return taskManager.isSleepTimerActive(); + } + + public long getSleepTimerTimeLeft() { + return taskManager.getSleepTimerTimeLeft(); + } + + @SuppressLint("NewApi") + private RemoteControlClient setupRemoteControlClient() { + Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + mediaButtonIntent.setComponent(new ComponentName(getPackageName(), + MediaButtonReceiver.class.getName())); + PendingIntent mediaPendingIntent = PendingIntent.getBroadcast( + getApplicationContext(), 0, mediaButtonIntent, 0); + remoteControlClient = new RemoteControlClient(mediaPendingIntent); + int controlFlags; + if (android.os.Build.VERSION.SDK_INT < 16) { + controlFlags = RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE + | RemoteControlClient.FLAG_KEY_MEDIA_NEXT; + } else { + controlFlags = RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE; + } + remoteControlClient.setTransportControlFlags(controlFlags); + return remoteControlClient; + } + + /** + * Refresh player status and metadata. + */ + @SuppressLint("NewApi") + private void refreshRemoteControlClientState(PlaybackServiceMediaPlayer.PSMPInfo info) { + if (android.os.Build.VERSION.SDK_INT >= 14) { + if (remoteControlClient != null) { + switch (info.playerStatus) { + case PLAYING: + remoteControlClient + .setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING); + break; + case PAUSED: + case INITIALIZED: + remoteControlClient + .setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED); + break; + case STOPPED: + remoteControlClient + .setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED); + break; + case ERROR: + remoteControlClient + .setPlaybackState(RemoteControlClient.PLAYSTATE_ERROR); + break; + default: + remoteControlClient + .setPlaybackState(RemoteControlClient.PLAYSTATE_BUFFERING); + } + if (info.playable != null) { + MetadataEditor editor = remoteControlClient + .editMetadata(false); + editor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, + info.playable.getEpisodeTitle()); + + editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, + info.playable.getFeedTitle()); + + editor.apply(); + } + if (AppConfig.DEBUG) + Log.d(TAG, "RemoteControlClient state was refreshed"); + } + } + } + + private void bluetoothNotifyChange(PlaybackServiceMediaPlayer.PSMPInfo info) { + boolean isPlaying = false; + + if (info.playerStatus == PlayerStatus.PLAYING) { + isPlaying = true; + } + + if (info.playable != null) { + Intent i = new Intent(AVRCP_ACTION_PLAYER_STATUS_CHANGED); + i.putExtra("id", 1); + i.putExtra("artist", ""); + i.putExtra("album", info.playable.getFeedTitle()); + i.putExtra("track", info.playable.getEpisodeTitle()); + i.putExtra("playing", isPlaying); if (queue != null) { i.putExtra("ListSize", queue.size()); } - i.putExtra("duration", media.getDuration()); - i.putExtra("position", media.getPosition()); - sendBroadcast(i); + i.putExtra("duration", info.playable.getDuration()); + i.putExtra("position", info.playable.getPosition()); + sendBroadcast(i); + } + } + + /** + * Pauses playback when the headset is disconnected and the preference is + * set + */ + private BroadcastReceiver headsetDisconnected = new BroadcastReceiver() { + private static final String TAG = "headsetDisconnected"; + private static final int UNPLUGGED = 0; + + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction() != null && + intent.getAction().equals(Intent.ACTION_HEADSET_PLUG)) { + int state = intent.getIntExtra("state", -1); + if (state != -1) { + if (AppConfig.DEBUG) + Log.d(TAG, "Headset plug event. State is " + state); + if (state == UNPLUGGED) { + if (AppConfig.DEBUG) + Log.d(TAG, "Headset was unplugged during playback."); + pauseIfPauseOnDisconnect(); + } + } else { + Log.e(TAG, "Received invalid ACTION_HEADSET_PLUG intent"); + } + } + } + }; + + private BroadcastReceiver audioBecomingNoisy = new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + // sound is about to change, eg. bluetooth -> speaker + if (AppConfig.DEBUG) + Log.d(TAG, "Pausing playback because audio is becoming noisy"); + pauseIfPauseOnDisconnect(); } - } - - /** - * Pauses playback when the headset is disconnected and the preference is - * set - */ - private BroadcastReceiver headsetDisconnected = new BroadcastReceiver() { - private static final String TAG = "headsetDisconnected"; - private static final int UNPLUGGED = 0; - - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(Intent.ACTION_HEADSET_PLUG)) { - int state = intent.getIntExtra("state", -1); - if (state != -1) { - if (AppConfig.DEBUG) - Log.d(TAG, "Headset plug event. State is " + state); - if (state == UNPLUGGED && status == PlayerStatus.PLAYING) { - if (AppConfig.DEBUG) - Log.d(TAG, "Headset was unplugged during playback."); - pauseIfPauseOnDisconnect(); - } - } else { - Log.e(TAG, "Received invalid ACTION_HEADSET_PLUG intent"); - } - } - } - }; - - private BroadcastReceiver audioBecomingNoisy = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - // sound is about to change, eg. bluetooth -> speaker - if (AppConfig.DEBUG) - Log.d(TAG, "Pausing playback because audio is becoming noisy"); - pauseIfPauseOnDisconnect(); - } - // android.media.AUDIO_BECOMING_NOISY - }; - - /** Pauses playback if PREF_PAUSE_ON_HEADSET_DISCONNECT was set to true. */ - private void pauseIfPauseOnDisconnect() { - if (UserPreferences.isPauseOnHeadsetDisconnect() - && status == PlayerStatus.PLAYING) { - pause(true, true); - } - } - - private BroadcastReceiver shutdownReceiver = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(ACTION_SHUTDOWN_PLAYBACK_SERVICE)) { - schedExecutor.shutdownNow(); - stop(); - media = null; - } - } - - }; - - private BroadcastReceiver skipCurrentEpisodeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(ACTION_SKIP_CURRENT_EPISODE)) { - - if (AppConfig.DEBUG) - Log.d(TAG, "Received SKIP_CURRENT_EPISODE intent"); - if (media != null) { - setStatus(PlayerStatus.STOPPED); - endPlayback(true); - } - } - } + // android.media.AUDIO_BECOMING_NOISY }; - /** Periodically saves the position of the media file */ - class PositionSaver implements Runnable { - public static final int WAITING_INTERVALL = 5000; - - @Override - public void run() { - if (player != null && player.isPlaying()) { - try { - saveCurrentPosition(); - } catch (IllegalStateException e) { - Log.w(TAG, - "saveCurrentPosition was called in illegal state"); - } - } - } - } - - /** Notifies the player widget in the specified intervall */ - class WidgetUpdateWorker implements Runnable { - private static final int NOTIFICATION_INTERVALL = 1000; - - @Override - public void run() { - if (PlaybackService.isRunning) { - updateWidget(); - } - } - } - - /** Sleeps for a given time and then pauses playback. */ - class SleepTimer implements Runnable { - private static final String TAG = "SleepTimer"; - private static final long UPDATE_INTERVALL = 1000L; - private volatile long waitingTime; - private boolean isWaiting; - - public SleepTimer(long waitingTime) { - super(); - this.waitingTime = waitingTime; - } - - @Override - public void run() { - isWaiting = true; - if (AppConfig.DEBUG) - Log.d(TAG, "Starting"); - while (waitingTime > 0) { - try { - Thread.sleep(UPDATE_INTERVALL); - waitingTime -= UPDATE_INTERVALL; - - if (waitingTime <= 0) { - if (AppConfig.DEBUG) - Log.d(TAG, "Waiting completed"); - if (status == PlayerStatus.PLAYING) { - if (AppConfig.DEBUG) - Log.d(TAG, "Pausing playback"); - pause(true, true); - } - postExecute(); - } - } catch (InterruptedException e) { - Log.d(TAG, "Thread was interrupted while waiting"); - break; - } - } - postExecute(); - } - - protected void postExecute() { - isWaiting = false; - sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); - } - - public long getWaitingTime() { - return waitingTime; - } - - public boolean isWaiting() { - return isWaiting; - } - - } - - public static boolean isPlayingVideo() { - return playingVideo; - } - - public boolean isShouldStream() { - return shouldStream; - } - - public PlayerStatus getStatus() { - return status; - } - - public Playable getMedia() { - return media; - } - - public IPlayer getPlayer() { - return player; - } - - public boolean isStartWhenPrepared() { - return startWhenPrepared; - } - - public void setStartWhenPrepared(boolean startWhenPrepared) { - this.startWhenPrepared = startWhenPrepared; - postStatusUpdateIntent(); - } - - public boolean canSetSpeed() { - if (player != null && media != null && media.getMediaType() == MediaType.AUDIO) { - return ((AudioPlayer) player).canSetSpeed(); - } - return false; - } - - public boolean canSetPitch() { - if (player != null && media != null && media.getMediaType() == MediaType.AUDIO) { - return ((AudioPlayer) player).canSetPitch(); - } - return false; - } - - public void setSpeed(float speed) { - if (media != null && media.getMediaType() == MediaType.AUDIO) { - AudioPlayer audioPlayer = (AudioPlayer) player; - if (audioPlayer.canSetSpeed()) { - audioPlayer.setPlaybackSpeed((float) speed); - if (AppConfig.DEBUG) - Log.d(TAG, "Playback speed was set to " + speed); - sendNotificationBroadcast( - NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE, 0); - } - } - } - - public void setPitch(float pitch) { - if (media != null && media.getMediaType() == MediaType.AUDIO) { - AudioPlayer audioPlayer = (AudioPlayer) player; - if (audioPlayer.canSetPitch()) { - audioPlayer.setPlaybackPitch((float) pitch); - } - } - } - - public float getCurrentPlaybackSpeed() { - if (media.getMediaType() == MediaType.AUDIO - && player instanceof AudioPlayer) { - AudioPlayer audioPlayer = (AudioPlayer) player; - if (audioPlayer.canSetSpeed()) { - return audioPlayer.getCurrentSpeedMultiplier(); - } - } - return -1; - } - - /** - * call getDuration() on mediaplayer or return INVALID_TIME if player is in - * an invalid state. This method should be used instead of calling - * getDuration() directly to avoid an error. - */ - public int getDurationSafe() { - if (status != null && player != null) { - switch (status) { - case PREPARED: - case PLAYING: - case PAUSED: - case SEEKING: - try { - return player.getDuration(); - } catch (IllegalStateException e) { - e.printStackTrace(); - return INVALID_TIME; - } - default: - return INVALID_TIME; - } - } else { - return INVALID_TIME; - } - } - - /** - * call getCurrentPosition() on mediaplayer or return INVALID_TIME if player - * is in an invalid state. This method should be used instead of calling - * getCurrentPosition() directly to avoid an error. - */ - public int getCurrentPositionSafe() { - if (status != null && player != null) { - switch (status) { - case PREPARED: - case PLAYING: - case PAUSED: - case SEEKING: - return player.getCurrentPosition(); - default: - return INVALID_TIME; - } - } else { - return INVALID_TIME; - } - } - - private void setCurrentlyPlayingMedia(long id) { - SharedPreferences.Editor editor = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()).edit(); - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, id); - editor.commit(); - } - - private static class InitTask extends AsyncTask<Playable, Void, Playable> { - private Playable playable; - public PlayableException exception; - - @Override - protected Playable doInBackground(Playable... params) { - if (params[0] == null) { - throw new IllegalArgumentException("Playable must not be null"); - } - playable = params[0]; - - try { - playable.loadMetadata(); - } catch (PlayableException e) { - e.printStackTrace(); - exception = e; - return null; - } - return playable; - } - - @SuppressLint("NewApi") - public void executeAsync(Playable playable) { - FlattrUtils.hasToken(); - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - executeOnExecutor(THREAD_POOL_EXECUTOR, playable); - } else { - execute(playable); - } - } - - } - - private void loadQueue() { - dbLoaderExecutor.submit(new QueueLoaderTask()); + /** + * Pauses playback if PREF_PAUSE_ON_HEADSET_DISCONNECT was set to true. + */ + private void pauseIfPauseOnDisconnect() { + if (UserPreferences.isPauseOnHeadsetDisconnect()) { + mediaPlayer.pause(true, true); + } } - private class QueueLoaderTask implements Runnable { + private BroadcastReceiver shutdownReceiver = new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction() != null && + intent.getAction().equals(ACTION_SHUTDOWN_PLAYBACK_SERVICE)) { + stopSelf(); + } + } + + }; + + private BroadcastReceiver skipCurrentEpisodeReceiver = new BroadcastReceiver() { @Override - public void run() { - List<FeedItem> queueRef = DBReader.getQueue(PlaybackService.this); - queue = queueRef; + public void onReceive(Context context, Intent intent) { + if (intent.getAction() != null && + intent.getAction().equals(ACTION_SKIP_CURRENT_EPISODE)) { + if (AppConfig.DEBUG) + Log.d(TAG, "Received SKIP_CURRENT_EPISODE intent"); + endPlayback(true); + } } + }; + + public static MediaType getCurrentMediaType() { + return currentMediaType; + } + + public void resume() { + mediaPlayer.resume(); + } + + public void pause(boolean abandonAudioFocus, boolean reinit) { + mediaPlayer.pause(abandonAudioFocus, reinit); + } + + public void reinit() { + mediaPlayer.reinit(); + } + + public PlayerStatus getStatus() { + return mediaPlayer.getPSMPInfo().playerStatus; + } + + public Playable getPlayable() { + return mediaPlayer.getPSMPInfo().playable; + } + + public void setSpeed(float speed) { + mediaPlayer.setSpeed(speed); + } + + public boolean canSetSpeed() { + return mediaPlayer.canSetSpeed(); + } + + public float getCurrentPlaybackSpeed() { + return mediaPlayer.getPlaybackSpeed(); + } + + public boolean isStartWhenPrepared() { + return mediaPlayer.isStartWhenPrepared(); + } + + public void setStartWhenPrepared(boolean s) { + mediaPlayer.setStartWhenPrepared(s); + } + + + public void seekTo(final int t) { + mediaPlayer.seekTo(t); + } + + + public void seekDelta(final int d) { + mediaPlayer.seekDelta(d); + } + + /** + * @see de.danoeh.antennapod.service.playback.PlaybackServiceMediaPlayer#seekToChapter(de.danoeh.antennapod.feed.Chapter) + */ + public void seekToChapter(Chapter c) { + mediaPlayer.seekToChapter(c); + } + + /** + * call getDuration() on mediaplayer or return INVALID_TIME if player is in + * an invalid state. + */ + public int getDuration() { + return mediaPlayer.getDuration(); + } + + /** + * call getCurrentPosition() on mediaplayer or return INVALID_TIME if player + * is in an invalid state. + */ + public int getCurrentPosition() { + return mediaPlayer.getPosition(); + } + + public boolean isStreaming() { + return mediaPlayer.isStreaming(); + } + + private void setCurrentlyPlayingMedia(long id) { + SharedPreferences.Editor editor = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()).edit(); + editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, id); + editor.commit(); } } diff --git a/src/de/danoeh/antennapod/service/playback/PlaybackServiceMediaPlayer.java b/src/de/danoeh/antennapod/service/playback/PlaybackServiceMediaPlayer.java index b5c78b77b..f2440728f 100644 --- a/src/de/danoeh/antennapod/service/playback/PlaybackServiceMediaPlayer.java +++ b/src/de/danoeh/antennapod/service/playback/PlaybackServiceMediaPlayer.java @@ -7,6 +7,7 @@ import android.media.RemoteControlClient; import android.os.Handler; import android.os.Message; import android.util.Log; +import android.view.SurfaceHolder; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.feed.Chapter; import de.danoeh.antennapod.feed.MediaType; @@ -73,6 +74,7 @@ public class PlaybackServiceMediaPlayer { statusBeforeSeeking = null; pausedBecauseOfTransientAudiofocusLoss = false; mediaType = MediaType.UNKNOWN; + playerStatus = PlayerStatus.STOPPED; } private Handler.Callback handlerCallback = new Handler.Callback() { @@ -475,6 +477,10 @@ public class PlaybackServiceMediaPlayer { return startWhenPrepared.get(); } + public void setStartWhenPrepared(boolean startWhenPrepared) { + this.startWhenPrepared.set(startWhenPrepared); + } + /** * Returns true if the playback speed can be adjusted. This method can also return false if the PSMP object's * internal MediaPlayer cannot be accessed at the moment. @@ -523,10 +529,36 @@ public class PlaybackServiceMediaPlayer { }); } + /** + * Returns the current playback speed. If the playback speed could not be retrieved, 1 is returned. + */ + public float getPlaybackSpeed() { + if (!playerLock.tryLock()) { + return 1; + } + + int retVal = 1; + if (playerStatus == PlayerStatus.PLAYING + || playerStatus == PlayerStatus.PAUSED + || playerStatus == PlayerStatus.PREPARED) { + retVal = mediaPlayer.getCurrentPosition(); + } else if (media != null && media.getPosition() > 0) { + retVal = media.getPosition(); + } + + playerLock.unlock(); + return retVal; + } + public MediaType getCurrentMediaType() { return mediaType; } + public boolean isStreaming() { + return stream; + } + + /** * Releases internally used resources. This method should only be called when the object is not used anymore. */ @@ -537,6 +569,33 @@ public class PlaybackServiceMediaPlayer { } } + public void setVideoSurface(final SurfaceHolder surface) { + executor.submit(new Runnable() { + @Override + public void run() { + playerLock.lock(); + if (mediaPlayer != null) { + mediaPlayer.setDisplay(surface); + } + playerLock.unlock(); + } + }); + } + + public void resetVideoSurface() { + executor.submit(new Runnable() { + @Override + public void run() { + playerLock.lock(); + if (AppConfig.DEBUG) + Log.d(TAG, "Resetting video surface"); + mediaPlayer.setDisplay(null); + reinit(); + playerLock.unlock(); + } + }); + } + /** * Returns a PSMInfo object that contains information about the current state of the PSMP object. * diff --git a/src/de/danoeh/antennapod/service/playback/PlayerWidgetService.java b/src/de/danoeh/antennapod/service/playback/PlayerWidgetService.java index be6e3845e..494d58202 100644 --- a/src/de/danoeh/antennapod/service/playback/PlayerWidgetService.java +++ b/src/de/danoeh/antennapod/service/playback/PlayerWidgetService.java @@ -83,8 +83,8 @@ public class PlayerWidgetService extends Service { PlaybackService.getPlayerActivityIntent(this), 0); views.setOnClickPendingIntent(R.id.layout_left, startMediaplayer); - if (playbackService != null && playbackService.getMedia() != null) { - Playable media = playbackService.getMedia(); + final Playable media = playbackService.getPlayable(); + if (playbackService != null && media != null) { PlayerStatus status = playbackService.getStatus(); views.setTextViewText(R.id.txtvTitle, media.getEpisodeTitle()); @@ -126,8 +126,8 @@ public class PlayerWidgetService extends Service { } private String getProgressString(PlaybackService ps) { - int position = ps.getCurrentPositionSafe(); - int duration = ps.getDurationSafe(); + int position = ps.getCurrentPosition(); + int duration = ps.getDuration(); if (position != PlaybackService.INVALID_TIME && duration != PlaybackService.INVALID_TIME) { return Converter.getDurationStringLong(position) + " / " diff --git a/src/de/danoeh/antennapod/util/playback/PlaybackController.java b/src/de/danoeh/antennapod/util/playback/PlaybackController.java index 6ef76dcf6..38f897262 100644 --- a/src/de/danoeh/antennapod/util/playback/PlaybackController.java +++ b/src/de/danoeh/antennapod/util/playback/PlaybackController.java @@ -30,6 +30,7 @@ import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.R; import de.danoeh.antennapod.feed.Chapter; import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.feed.MediaType; import de.danoeh.antennapod.preferences.PlaybackPreferences; import de.danoeh.antennapod.service.playback.PlaybackService; import de.danoeh.antennapod.service.playback.PlayerStatus; @@ -157,7 +158,7 @@ public abstract class PlaybackController { */ public void pause() { mediaInfoLoaded = false; - if (playbackService != null && playbackService.isPlayingVideo()) { + if (playbackService != null && PlaybackService.getCurrentMediaType() == MediaType.VIDEO) { playbackService.pause(true, true); } } @@ -488,7 +489,7 @@ public abstract class PlaybackController { Log.d(TAG, "Querying service info"); if (playbackService != null) { status = playbackService.getStatus(); - media = playbackService.getMedia(); + media = playbackService.getPlayable(); if (media == null) { Log.w(TAG, "PlaybackService has no media object. Trying to restore last played media."); @@ -541,7 +542,7 @@ public abstract class PlaybackController { */ public void onSeekBarStopTrackingTouch(SeekBar seekBar, float prog) { if (playbackService != null) { - playbackService.seek((int) (prog * media.getDuration())); + playbackService.seekTo((int) (prog * media.getDuration())); setupPositionObserver(); } } @@ -557,7 +558,7 @@ public abstract class PlaybackController { break; case PAUSED: case PREPARED: - playbackService.play(); + playbackService.resume(); break; case PREPARING: playbackService.setStartWhenPrepared(!playbackService @@ -569,7 +570,7 @@ public abstract class PlaybackController { break; case INITIALIZED: playbackService.setStartWhenPrepared(true); - playbackService.prepare(); + playbackService.resume(); break; } } else { @@ -609,7 +610,7 @@ public abstract class PlaybackController { public int getPosition() { if (playbackService != null) { - return playbackService.getCurrentPositionSafe(); + return playbackService.getCurrentPosition(); } else { return PlaybackService.INVALID_TIME; } @@ -617,7 +618,7 @@ public abstract class PlaybackController { public int getDuration() { if (playbackService != null) { - return playbackService.getDurationSafe(); + return playbackService.getDuration(); } else { return PlaybackService.INVALID_TIME; } @@ -691,7 +692,7 @@ public abstract class PlaybackController { public boolean isPlayingVideo() { if (playbackService != null) { - return PlaybackService.isPlayingVideo(); + return PlaybackService.getCurrentMediaType() == MediaType.VIDEO; } return false; } @@ -716,7 +717,7 @@ public abstract class PlaybackController { */ public void reinitServiceIfPaused() { if (playbackService != null - && playbackService.isShouldStream() + && playbackService.isStreaming() && (playbackService.getStatus() == PlayerStatus.PAUSED || (playbackService .getStatus() == PlayerStatus.PREPARING && playbackService .isStartWhenPrepared() == false))) { @@ -733,8 +734,7 @@ public abstract class PlaybackController { @Override public void run() { - if (playbackService != null && playbackService.getPlayer() != null - && playbackService.getPlayer().isPlaying()) { + if (playbackService != null && playbackService.getStatus() == PlayerStatus.PLAYING) { activity.runOnUiThread(new Runnable() { @Override |