summaryrefslogtreecommitdiff
path: root/src/de/danoeh/antennapod/service/playback
diff options
context:
space:
mode:
Diffstat (limited to 'src/de/danoeh/antennapod/service/playback')
-rw-r--r--src/de/danoeh/antennapod/service/playback/PlaybackService.java1080
-rw-r--r--src/de/danoeh/antennapod/service/playback/PlaybackServiceMediaPlayer.java979
-rw-r--r--src/de/danoeh/antennapod/service/playback/PlaybackServiceTaskManager.java384
-rw-r--r--src/de/danoeh/antennapod/service/playback/PlayerStatus.java14
-rw-r--r--src/de/danoeh/antennapod/service/playback/PlayerWidgetService.java190
5 files changed, 0 insertions, 2647 deletions
diff --git a/src/de/danoeh/antennapod/service/playback/PlaybackService.java b/src/de/danoeh/antennapod/service/playback/PlaybackService.java
deleted file mode 100644
index 59d7ddbb9..000000000
--- a/src/de/danoeh/antennapod/service/playback/PlaybackService.java
+++ /dev/null
@@ -1,1080 +0,0 @@
-package de.danoeh.antennapod.service.playback;
-
-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.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.media.AudioManager;
-import android.media.MediaMetadataRetriever;
-import android.media.MediaPlayer;
-import android.media.RemoteControlClient;
-import android.media.RemoteControlClient.MetadataEditor;
-import android.os.AsyncTask;
-import android.os.Binder;
-import android.os.Build;
-import android.os.IBinder;
-import android.preference.PreferenceManager;
-import android.support.v4.app.NotificationCompat;
-import android.util.Log;
-import android.util.Pair;
-import android.view.KeyEvent;
-import android.view.SurfaceHolder;
-import android.widget.Toast;
-
-import org.apache.commons.lang3.StringUtils;
-
-import java.io.IOException;
-import java.util.List;
-
-import de.danoeh.antennapod.BuildConfig;
-import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.activity.AudioplayerActivity;
-import de.danoeh.antennapod.activity.VideoplayerActivity;
-import de.danoeh.antennapod.asynctask.PicassoProvider;
-import de.danoeh.antennapod.feed.Chapter;
-import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedMedia;
-import de.danoeh.antennapod.feed.MediaType;
-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.DBTasks;
-import de.danoeh.antennapod.storage.DBWriter;
-import de.danoeh.antennapod.util.QueueAccess;
-import de.danoeh.antennapod.util.flattr.FlattrUtils;
-import de.danoeh.antennapod.util.playback.Playable;
-
-/**
- * Controls the MediaPlayer that plays a FeedMedia-file
- */
-public class PlaybackService extends Service {
- /**
- * Logging tag
- */
- private static final String TAG = "PlaybackService";
-
- /**
- * Parcelable of type Playable.
- */
- public static final String EXTRA_PLAYABLE = "PlaybackService.PlayableExtra";
- /**
- * True if media should be streamed.
- */
- public static final String EXTRA_SHOULD_STREAM = "extra.de.danoeh.antennapod.service.shouldStream";
- /**
- * True if playback should be started immediately after media has been
- * prepared.
- */
- public static final String EXTRA_START_WHEN_PREPARED = "extra.de.danoeh.antennapod.service.startWhenPrepared";
-
- 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";
- private static final String AVRCP_ACTION_PLAYER_STATUS_CHANGED = "com.android.music.playstatechanged";
- private static final String AVRCP_ACTION_META_CHANGED = "com.android.music.metachanged";
-
- 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_TYPE = "extra.de.danoeh.antennapod.service.notificationType";
-
- /**
- * If the PlaybackService receives this action, it will stop playback and
- * try to shutdown.
- */
- public static final String ACTION_SHUTDOWN_PLAYBACK_SERVICE = "action.de.danoeh.antennapod.service.actionShutdownPlaybackService";
-
- /**
- * If the PlaybackService receives this action, it will end playback of the
- * current episode and load the next episode if there is one available.
- */
- public static final String ACTION_SKIP_CURRENT_EPISODE = "action.de.danoeh.antennapod.service.skipCurrentEpisode";
-
- /**
- * Used in NOTIFICATION_TYPE_RELOAD.
- */
- public static final int EXTRA_CODE_AUDIO = 1;
- public static final int EXTRA_CODE_VIDEO = 2;
-
- public static final int NOTIFICATION_TYPE_ERROR = 0;
- public static final int NOTIFICATION_TYPE_INFO = 1;
- public static final int NOTIFICATION_TYPE_BUFFER_UPDATE = 2;
-
- /**
- * Receivers of this intent should update their information about the curently playing media
- */
- public static final int NOTIFICATION_TYPE_RELOAD = 3;
- /**
- * The state of the sleeptimer changed.
- */
- public static final int NOTIFICATION_TYPE_SLEEPTIMER_UPDATE = 4;
- public static final int NOTIFICATION_TYPE_BUFFER_START = 5;
- public static final int NOTIFICATION_TYPE_BUFFER_END = 6;
- /**
- * No more episodes are going to be played.
- */
- 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.
- */
- public static final int INVALID_TIME = -1;
-
- /**
- * Is true if service is running.
- */
- public static boolean isRunning = false;
- /**
- * Is true if service has received a valid start command.
- */
- public static boolean started = false;
-
- private static final int NOTIFICATION_ID = 1;
-
- private RemoteControlClient remoteControlClient;
- private PlaybackServiceMediaPlayer mediaPlayer;
- private PlaybackServiceTaskManager taskManager;
-
- private static volatile MediaType currentMediaType = MediaType.UNKNOWN;
-
- private final IBinder mBinder = new LocalBinder();
-
- public class LocalBinder extends Binder {
- public PlaybackService getService() {
- return PlaybackService.this;
- }
- }
-
- @Override
- public boolean onUnbind(Intent intent) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Received onUnbind event");
- return super.onUnbind(intent);
- }
-
- /**
- * Returns an intent which starts an audio- or videoplayer, depending on the
- * type of media that is being played. If the playbackservice is not
- * running, the type of the last played media will be looked up.
- */
- public static Intent getPlayerActivityIntent(Context context) {
- if (isRunning) {
- if (currentMediaType == MediaType.VIDEO) {
- return new Intent(context, VideoplayerActivity.class);
- } else {
- return new Intent(context, AudioplayerActivity.class);
- }
- } else {
- if (PlaybackPreferences.getCurrentEpisodeIsVideo()) {
- return new Intent(context, VideoplayerActivity.class);
- } else {
- return new Intent(context, AudioplayerActivity.class);
- }
- }
- }
-
- /**
- * Same as getPlayerActivityIntent(context), but here the type of activity
- * depends on the FeedMedia that is provided as an argument.
- */
- public static Intent getPlayerActivityIntent(Context context, Playable media) {
- MediaType mt = media.getMediaType();
- if (mt == MediaType.VIDEO) {
- return new Intent(context, VideoplayerActivity.class);
- } else {
- return new Intent(context, AudioplayerActivity.class);
- }
- }
-
- @SuppressLint("NewApi")
- @Override
- public void onCreate() {
- super.onCreate();
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Service created.");
- isRunning = true;
-
- registerReceiver(headsetDisconnected, new IntentFilter(
- Intent.ACTION_HEADSET_PLUG));
- registerReceiver(shutdownReceiver, new IntentFilter(
- ACTION_SHUTDOWN_PLAYBACK_SERVICE));
- registerReceiver(audioBecomingNoisy, new IntentFilter(
- AudioManager.ACTION_AUDIO_BECOMING_NOISY));
- registerReceiver(skipCurrentEpisodeReceiver, new IntentFilter(
- ACTION_SKIP_CURRENT_EPISODE));
- remoteControlClient = setupRemoteControlClient();
- taskManager = new PlaybackServiceTaskManager(this, taskManagerCallback);
- mediaPlayer = new PlaybackServiceMediaPlayer(this, mediaPlayerCallback);
-
- }
-
- @SuppressLint("NewApi")
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Service is about to be destroyed");
- isRunning = false;
- started = false;
- currentMediaType = MediaType.UNKNOWN;
-
- unregisterReceiver(headsetDisconnected);
- unregisterReceiver(shutdownReceiver);
- unregisterReceiver(audioBecomingNoisy);
- unregisterReceiver(skipCurrentEpisodeReceiver);
- mediaPlayer.shutdown();
- taskManager.shutdown();
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Received onBind event");
- return mBinder;
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- super.onStartCommand(intent, flags, startId);
-
- if (BuildConfig.DEBUG)
- Log.d(TAG, "OnStartCommand called");
- final int keycode = intent.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1);
- final Playable playable = intent.getParcelableExtra(EXTRA_PLAYABLE);
- if (keycode == -1 && playable == null) {
- Log.e(TAG, "PlaybackService was started with no arguments");
- stopSelf();
- }
-
- if ((flags & Service.START_FLAG_REDELIVERY) != 0) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "onStartCommand is a redelivered intent, calling stopForeground now.");
- stopForeground(true);
- } else {
-
- if (keycode != -1) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Received media button event");
- handleKeycode(keycode);
- } else {
- started = true;
- boolean stream = intent.getBooleanExtra(EXTRA_SHOULD_STREAM,
- true);
- 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_REDELIVER_INTENT;
- }
-
- /**
- * Handles media button events
- */
- private void handleKeycode(int keycode) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Handling keycode: " + keycode);
-
- final PlaybackServiceMediaPlayer.PSMPInfo info = mediaPlayer.getPSMPInfo();
- final PlayerStatus status = info.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 || status == PlayerStatus.PREPARED) {
- 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 || status == PlayerStatus.PREPARED) {
- 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_NEXT:
- case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
- mediaPlayer.seekDelta(UserPreferences.getSeekDeltaMs());
- break;
- case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
- case KeyEvent.KEYCODE_MEDIA_REWIND:
- mediaPlayer.seekDelta(-UserPreferences.getSeekDeltaMs());
- break;
- default:
- if (info.playable != null && info.playerStatus == PlayerStatus.PLAYING) { // only notify the user about an unknown key event if it is actually doing something
- String message = String.format(getResources().getString(R.string.unknown_media_key), keycode);
- Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
- }
- break;
- }
- }
-
- /**
- * Called by a mediaplayer Activity as soon as it has prepared its
- * mediaplayer.
- */
- public void setVideoSurface(SurfaceHolder sh) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Setting display");
- mediaPlayer.setVideoSurface(sh);
- }
-
- /**
- * Called when the surface holder of the mediaplayer has to be changed.
- */
- private void resetVideoSurface() {
- taskManager.cancelPositionSaver();
- mediaPlayer.resetVideoSurface();
- }
-
- public void notifyVideoSurfaceAbandoned() {
- stopForeground(true);
- mediaPlayer.resetVideoSurface();
- }
-
- private final PlaybackServiceTaskManager.PSTMCallback taskManagerCallback = new PlaybackServiceTaskManager.PSTMCallback() {
- @Override
- public void positionSaverTick() {
- saveCurrentPosition(true, PlaybackServiceTaskManager.POSITION_SAVER_WAITING_INTERVAL);
- }
-
- @Override
- public void onSleepTimerExpired() {
- mediaPlayer.pause(true, true);
- sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0);
- }
-
-
- @Override
- public void onWidgetUpdaterTick() {
- updateWidget();
- }
-
- @Override
- public void onChapterLoaded(Playable media) {
- sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0);
- }
- };
-
- private final PlaybackServiceMediaPlayer.PSMPCallback mediaPlayerCallback = new PlaybackServiceMediaPlayer.PSMPCallback() {
- @Override
- public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) {
- currentMediaType = mediaPlayer.getCurrentMediaType();
- switch (newInfo.playerStatus) {
- case INITIALIZED:
- writePlaybackPreferences();
- break;
-
- case PREPARED:
- taskManager.startChapterLoader(newInfo.playable);
- break;
-
- case PAUSED:
- taskManager.cancelPositionSaver();
- saveCurrentPosition(false, 0);
- taskManager.cancelWidgetUpdater();
- stopForeground(true);
- break;
-
- case STOPPED:
- //setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING);
- //stopSelf();
- break;
-
- case PLAYING:
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Audiofocus successfully requested");
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Resuming/Starting playback");
-
- taskManager.startPositionSaver();
- taskManager.startWidgetUpdater();
- setupNotification(newInfo);
- break;
- case ERROR:
- writePlaybackPreferencesNoMediaPlaying();
- break;
-
- }
-
- sendBroadcast(new Intent(ACTION_PLAYER_STATUS_CHANGED));
- updateWidget();
- refreshRemoteControlClientState(newInfo);
- bluetoothNotifyChange(newInfo, AVRCP_ACTION_PLAYER_STATUS_CHANGED);
- bluetoothNotifyChange(newInfo, AVRCP_ACTION_META_CHANGED);
- }
-
- @Override
- public void shouldStop() {
- stopSelf();
- }
-
- @Override
- public void playbackSpeedChanged(float s) {
- sendNotificationBroadcast(
- NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE, 0);
- }
-
- @Override
- public void onBufferingUpdate(int percent) {
- sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_UPDATE, percent);
- }
-
- @Override
- 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;
- }
- }
-
- @Override
- 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);
- writePlaybackPreferencesNoMediaPlaying();
- stopSelf();
- return true;
- }
-
- @Override
- public boolean endPlayback(boolean playNextEpisode) {
- PlaybackService.this.endPlayback(true);
- return true;
- }
-
- @Override
- public RemoteControlClient getRemoteControlClient() {
- return remoteControlClient;
- }
- };
-
- private void endPlayback(boolean playNextEpisode) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Playback ended");
-
- 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;
-
- if (media instanceof FeedMedia) {
- FeedItem item = ((FeedMedia) media).getItem();
- DBWriter.markItemRead(PlaybackService.this, item, true, true);
-
- try {
- final List<FeedItem> queue = taskManager.getQueue();
- isInQueue = QueueAccess.ItemListAccess(queue).contains(((FeedMedia) media).getItem().getId());
- nextItem = DBTasks.getQueueSuccessorOfItem(this, item.getId(), queue);
- } catch (InterruptedException e) {
- e.printStackTrace();
- // isInQueue remains false
- }
- if (isInQueue) {
- DBWriter.removeQueueItem(PlaybackService.this, item.getId(), true);
- }
- DBWriter.addItemToPlaybackHistory(PlaybackService.this, (FeedMedia) media);
-
- // auto-flattr if enabled
- if (isAutoFlattrable(media) && UserPreferences.getAutoFlattrPlayedDurationThreshold() == 1.0f) {
- DBTasks.flattrItemIfLoggedIn(PlaybackService.this, item);
- }
- }
-
- // 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 (BuildConfig.DEBUG)
- Log.d(TAG, "Loading next item in queue");
- nextMedia = nextItem.getMedia();
- }
- final boolean prepareImmediately;
- final boolean startWhenPrepared;
- final boolean stream;
-
- if (playNextEpisode) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Playback of next episode will start immediately.");
- prepareImmediately = startWhenPrepared = true;
- } else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "No more episodes available to play");
-
- prepareImmediately = startWhenPrepared = false;
- stopForeground(true);
- stopWidgetUpdater();
- }
-
- writePlaybackPreferencesNoMediaPlaying();
- if (nextMedia != null) {
- stream = !media.localFileAvailable();
- mediaPlayer.playMediaObject(nextMedia, stream, startWhenPrepared, prepareImmediately);
- sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD,
- (nextMedia.getMediaType() == MediaType.VIDEO) ? EXTRA_CODE_VIDEO : EXTRA_CODE_AUDIO);
- } else {
- sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0);
- mediaPlayer.stop();
- //stopSelf();
- }
- }
-
- public void setSleepTimer(long waitingTime) {
- if (BuildConfig.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 writePlaybackPreferencesNoMediaPlaying() {
- SharedPreferences.Editor editor = PreferenceManager
- .getDefaultSharedPreferences(getApplicationContext()).edit();
- 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 writePlaybackPreferences() {
- if (BuildConfig.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 (BuildConfig.DEBUG)
- Log.d(TAG, "Starting background work");
- if (android.os.Build.VERSION.SDK_INT >= 11) {
- if (info.playable != null) {
- try {
- int iconSize = getResources().getDimensionPixelSize(
- android.R.dimen.notification_large_icon_width);
- icon = PicassoProvider.getMediaMetadataPicassoInstance(PlaybackService.this)
- .load(info.playable.getImageUri())
- .resize(iconSize, iconSize)
- .get();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- }
- 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 (BuildConfig.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
- *
- * @param updatePlayedDuration true if played_duration should be updated. This applies only to FeedMedia objects
- * @param deltaPlayedDuration value by which played_duration should be increased.
- */
- private synchronized void saveCurrentPosition(boolean updatePlayedDuration, int deltaPlayedDuration) {
- int position = getCurrentPosition();
- int duration = getDuration();
- float playbackSpeed = getCurrentPlaybackSpeed();
- final Playable playable = mediaPlayer.getPSMPInfo().playable;
- if (position != INVALID_TIME && duration != INVALID_TIME && playable != null) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Saving current position to " + position);
- if (updatePlayedDuration && playable instanceof FeedMedia) {
- FeedMedia m = (FeedMedia) playable;
- FeedItem item = m.getItem();
- m.setPlayedDuration(m.getPlayedDuration() + ((int) (deltaPlayedDuration * playbackSpeed)));
- // Auto flattr
- if (isAutoFlattrable(m) &&
- (m.getPlayedDuration() > UserPreferences.getAutoFlattrPlayedDurationThreshold() * duration)) {
-
- if (BuildConfig.DEBUG)
- Log.d(TAG, "saveCurrentPosition: performing auto flattr since played duration " + Integer.toString(m.getPlayedDuration())
- + " is " + UserPreferences.getAutoFlattrPlayedDurationThreshold() * 100 + "% of file duration " + Integer.toString(duration));
- DBTasks.flattrItemIfLoggedIn(this, item);
- }
- }
- playable.saveCurrentPosition(PreferenceManager
- .getDefaultSharedPreferences(getApplicationContext()),
- position
- );
- }
- }
-
- private void stopWidgetUpdater() {
- taskManager.cancelWidgetUpdater();
- sendBroadcast(new Intent(PlayerWidget.STOP_WIDGET_UPDATE));
- }
-
- private void updateWidget() {
- 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() {
- if (Build.VERSION.SDK_INT < 14) {
- return null;
- }
-
- 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 (BuildConfig.DEBUG)
- Log.d(TAG, "RemoteControlClient state was refreshed");
- }
- }
- }
-
- private void bluetoothNotifyChange(PlaybackServiceMediaPlayer.PSMPInfo info, String whatChanged) {
- boolean isPlaying = false;
-
- if (info.playerStatus == PlayerStatus.PLAYING) {
- isPlaying = true;
- }
-
- if (info.playable != null) {
- Intent i = new Intent(whatChanged);
- i.putExtra("id", 1);
- i.putExtra("artist", "");
- i.putExtra("album", info.playable.getFeedTitle());
- i.putExtra("track", info.playable.getEpisodeTitle());
- i.putExtra("playing", isPlaying);
- final List<FeedItem> queue = taskManager.getQueueIfLoaded();
- if (queue != null) {
- i.putExtra("ListSize", queue.size());
- }
- 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 (StringUtils.equals(intent.getAction(), Intent.ACTION_HEADSET_PLUG)) {
- int state = intent.getIntExtra("state", -1);
- if (state != -1) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Headset plug event. State is " + state);
- if (state == UNPLUGGED) {
- if (BuildConfig.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 (BuildConfig.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()) {
- mediaPlayer.pause(true, true);
- }
- }
-
- private BroadcastReceiver shutdownReceiver = new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (StringUtils.equals(intent.getAction(), ACTION_SHUTDOWN_PLAYBACK_SERVICE)) {
- stopSelf();
- }
- }
-
- };
-
- private BroadcastReceiver skipCurrentEpisodeReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (StringUtils.equals(intent.getAction(), ACTION_SKIP_CURRENT_EPISODE)) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Received SKIP_CURRENT_EPISODE intent");
- mediaPlayer.endPlayback();
- }
- }
- };
-
- public static MediaType getCurrentMediaType() {
- return currentMediaType;
- }
-
- public void resume() {
- mediaPlayer.resume();
- }
-
- public void prepare() {
- mediaPlayer.prepare();
- }
-
- public void pause(boolean abandonAudioFocus, boolean reinit) {
- mediaPlayer.pause(abandonAudioFocus, reinit);
- }
-
- public void reinit() {
- mediaPlayer.reinit();
- }
-
- public PlaybackServiceMediaPlayer.PSMPInfo getPSMPInfo() {
- return mediaPlayer.getPSMPInfo();
- }
-
- 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();
- }
-
- public Pair<Integer, Integer> getVideoSize() {
- return mediaPlayer.getVideoSize();
- }
-
- private boolean isAutoFlattrable(Playable p) {
- if (p != null && p instanceof FeedMedia) {
- FeedMedia media = (FeedMedia) p;
- FeedItem item = ((FeedMedia) p).getItem();
- return item != null && FlattrUtils.hasToken() && UserPreferences.isAutoFlattr() && item.getPaymentLink() != null && item.getFlattrStatus().getUnflattred();
- } else {
- return false;
- }
- }
-}
diff --git a/src/de/danoeh/antennapod/service/playback/PlaybackServiceMediaPlayer.java b/src/de/danoeh/antennapod/service/playback/PlaybackServiceMediaPlayer.java
deleted file mode 100644
index 49f20012d..000000000
--- a/src/de/danoeh/antennapod/service/playback/PlaybackServiceMediaPlayer.java
+++ /dev/null
@@ -1,979 +0,0 @@
-package de.danoeh.antennapod.service.playback;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.media.AudioManager;
-import android.media.RemoteControlClient;
-import android.net.wifi.WifiManager;
-import android.os.PowerManager;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-import android.util.Pair;
-import android.view.SurfaceHolder;
-
-import org.apache.commons.lang3.Validate;
-
-import java.io.IOException;
-import java.util.concurrent.LinkedBlockingDeque;
-import java.util.concurrent.RejectedExecutionHandler;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.locks.ReentrantLock;
-
-import de.danoeh.antennapod.BuildConfig;
-import de.danoeh.antennapod.feed.Chapter;
-import de.danoeh.antennapod.feed.MediaType;
-import de.danoeh.antennapod.preferences.UserPreferences;
-import de.danoeh.antennapod.receiver.MediaButtonReceiver;
-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.VideoPlayer;
-
-/**
- * Manages the MediaPlayer object of the PlaybackService.
- */
-public class PlaybackServiceMediaPlayer {
- public static final String TAG = "PlaybackServiceMediaPlayer";
-
- /**
- * Return value of some PSMP methods if the method call failed.
- */
- public static final int INVALID_TIME = -1;
-
- private final AudioManager audioManager;
-
- private volatile PlayerStatus playerStatus;
- private volatile PlayerStatus statusBeforeSeeking;
- private volatile IPlayer mediaPlayer;
- private volatile Playable media;
-
- private volatile boolean stream;
- private volatile MediaType mediaType;
- private volatile AtomicBoolean startWhenPrepared;
- private volatile boolean pausedBecauseOfTransientAudiofocusLoss;
- private volatile Pair<Integer, Integer> videoSize;
-
- /**
- * Some asynchronous calls might change the state of the MediaPlayer object. Therefore calls in other threads
- * have to wait until these operations have finished.
- */
- private final ReentrantLock playerLock;
-
- private final PSMPCallback callback;
- private final Context context;
-
- private final ThreadPoolExecutor executor;
-
- /**
- * A wifi-lock that is acquired if the media file is being streamed.
- */
- private WifiManager.WifiLock wifiLock;
-
- public PlaybackServiceMediaPlayer(Context context, PSMPCallback callback) {
- Validate.notNull(context);
- Validate.notNull(callback);
-
- this.context = context;
- this.callback = callback;
- this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- this.playerLock = new ReentrantLock();
- this.startWhenPrepared = new AtomicBoolean(false);
- executor = new ThreadPoolExecutor(1, 1, 5, TimeUnit.MINUTES, new LinkedBlockingDeque<Runnable>(),
- new RejectedExecutionHandler() {
- @Override
- public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
- if (BuildConfig.DEBUG) Log.d(TAG, "Rejected execution of runnable");
- }
- }
- );
-
- mediaPlayer = null;
- statusBeforeSeeking = null;
- pausedBecauseOfTransientAudiofocusLoss = false;
- mediaType = MediaType.UNKNOWN;
- playerStatus = PlayerStatus.STOPPED;
- videoSize = null;
- }
-
- /**
- * Starts or prepares playback of the specified Playable object. If another Playable object is already being played, the currently playing
- * episode will be stopped and replaced with the new Playable object. If the Playable object is already being played, the method will
- * not do anything.
- * Whether playback starts immediately depends on the given parameters. See below for more details.
- * <p/>
- * States:
- * During execution of the method, the object will be in the INITIALIZING state. The end state depends on the given parameters.
- * <p/>
- * If 'prepareImmediately' is set to true, the method will go into PREPARING state and after that into PREPARED state. If
- * 'startWhenPrepared' is set to true, the method will additionally go into PLAYING state.
- * <p/>
- * If an unexpected error occurs while loading the Playable's metadata or while setting the MediaPlayers data source, the object
- * will enter the ERROR state.
- * <p/>
- * This method is executed on an internal executor service.
- *
- * @param playable The Playable object that is supposed to be played. This parameter must not be null.
- * @param stream The type of playback. If false, the Playable object MUST provide access to a locally available file via
- * getLocalMediaUrl. If true, the Playable object MUST provide access to a resource that can be streamed by
- * the Android MediaPlayer via getStreamUrl.
- * @param startWhenPrepared Sets the 'startWhenPrepared' flag. This flag determines whether playback will start immediately after the
- * episode has been prepared for playback. Setting this flag to true does NOT mean that the episode will be prepared
- * for playback immediately (see 'prepareImmediately' parameter for more details)
- * @param prepareImmediately Set to true if the method should also prepare the episode for playback.
- */
- public void playMediaObject(final Playable playable, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) {
- Validate.notNull(playable);
-
- if (BuildConfig.DEBUG) Log.d(TAG, "Play media object.");
- executor.submit(new Runnable() {
- @Override
- public void run() {
- playerLock.lock();
- try {
- playMediaObject(playable, false, stream, startWhenPrepared, prepareImmediately);
- } catch (RuntimeException e) {
- e.printStackTrace();
- throw e;
- } finally {
- playerLock.unlock();
- }
- }
- });
- }
-
- /**
- * Internal implementation of playMediaObject. This method has an additional parameter that allows the caller to force a media player reset even if
- * the given playable parameter is the same object as the currently playing media.
- * <p/>
- * This method requires the playerLock and is executed on the caller's thread.
- *
- * @see #playMediaObject(de.danoeh.antennapod.util.playback.Playable, boolean, boolean, boolean)
- */
- private void playMediaObject(final Playable playable, final boolean forceReset, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) {
- Validate.notNull(playable);
- if (!playerLock.isHeldByCurrentThread())
- throw new IllegalStateException("method requires playerLock");
-
-
- if (media != null) {
- if (!forceReset && media.getIdentifier().equals(playable.getIdentifier())) {
- // episode is already playing -> ignore method call
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Method call to playMediaObject was ignored: media file already playing.");
- return;
- } else {
- // stop playback of this episode
- if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PLAYING || playerStatus == PlayerStatus.PREPARED) {
- mediaPlayer.stop();
- }
- setPlayerStatus(PlayerStatus.INDETERMINATE, null);
- }
- }
-
- this.media = playable;
- this.stream = stream;
- this.mediaType = media.getMediaType();
- this.videoSize = null;
- createMediaPlayer();
- PlaybackServiceMediaPlayer.this.startWhenPrepared.set(startWhenPrepared);
- setPlayerStatus(PlayerStatus.INITIALIZING, media);
- try {
- media.loadMetadata();
- if (stream) {
- mediaPlayer.setDataSource(media.getStreamUrl());
- } else {
- mediaPlayer.setDataSource(media.getLocalMediaUrl());
- }
- setPlayerStatus(PlayerStatus.INITIALIZED, media);
-
- if (mediaType == MediaType.VIDEO) {
- VideoPlayer vp = (VideoPlayer) mediaPlayer;
- // vp.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT);
- }
-
- if (prepareImmediately) {
- setPlayerStatus(PlayerStatus.PREPARING, media);
- mediaPlayer.prepare();
- onPrepared(startWhenPrepared);
- }
-
- } catch (Playable.PlayableException e) {
- e.printStackTrace();
- setPlayerStatus(PlayerStatus.ERROR, null);
- } catch (IOException e) {
- e.printStackTrace();
- setPlayerStatus(PlayerStatus.ERROR, null);
- } catch (IllegalStateException e) {
- e.printStackTrace();
- setPlayerStatus(PlayerStatus.ERROR, null);
- }
- }
-
-
- /**
- * Resumes playback if the PSMP object is in PREPARED or PAUSED state. If the PSMP object is in an invalid state.
- * nothing will happen.
- * <p/>
- * This method is executed on an internal executor service.
- */
- public void resume() {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- playerLock.lock();
- resumeSync();
- playerLock.unlock();
- }
- });
- }
-
- private void resumeSync() {
- if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PREPARED) {
- int focusGained = audioManager.requestAudioFocus(
- audioFocusChangeListener, AudioManager.STREAM_MUSIC,
- AudioManager.AUDIOFOCUS_GAIN);
- if (focusGained == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
- acquireWifiLockIfNecessary();
- setSpeed(Float.parseFloat(UserPreferences.getPlaybackSpeed()));
- mediaPlayer.start();
- if (playerStatus == PlayerStatus.PREPARED && media.getPosition() > 0) {
- mediaPlayer.seekTo(media.getPosition());
- }
-
- setPlayerStatus(PlayerStatus.PLAYING, media);
- pausedBecauseOfTransientAudiofocusLoss = false;
- if (android.os.Build.VERSION.SDK_INT >= 14) {
- RemoteControlClient remoteControlClient = callback.getRemoteControlClient();
- if (remoteControlClient != null) {
- audioManager
- .registerRemoteControlClient(remoteControlClient);
- }
- }
- audioManager
- .registerMediaButtonEventReceiver(new ComponentName(context.getPackageName(),
- MediaButtonReceiver.class.getName()));
- media.onPlaybackStart();
-
- } else {
- if (BuildConfig.DEBUG) Log.e(TAG, "Failed to request audio focus");
- }
- } else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Call to resume() was ignored because current state of PSMP object is " + playerStatus);
- }
- }
-
-
- /**
- * Saves the current position and pauses playback. Note that, if audiofocus
- * is abandoned, the lockscreen controls will also disapear.
- * <p/>
- * This method is executed on an internal executor service.
- *
- * @param abandonFocus is true if the service should release audio focus
- * @param reinit is true if service should reinit after pausing if the media
- * file is being streamed
- */
- public void pause(final boolean abandonFocus, final boolean reinit) {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- playerLock.lock();
- releaseWifiLockIfNecessary();
- if (playerStatus == PlayerStatus.PLAYING) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Pausing playback.");
- mediaPlayer.pause();
- setPlayerStatus(PlayerStatus.PAUSED, media);
-
- if (abandonFocus) {
- audioManager.abandonAudioFocus(audioFocusChangeListener);
- pausedBecauseOfTransientAudiofocusLoss = false;
- }
- if (stream && reinit) {
- reinit();
- }
- } else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Ignoring call to pause: Player is in " + playerStatus + " state");
- }
-
- playerLock.unlock();
- }
- });
- }
-
- /**
- * Prepared media player for playback if the service is in the INITALIZED
- * state.
- * <p/>
- * This method is executed on an internal executor service.
- */
- public void prepare() {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- playerLock.lock();
-
- if (playerStatus == PlayerStatus.INITIALIZED) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Preparing media player");
- setPlayerStatus(PlayerStatus.PREPARING, media);
- try {
- mediaPlayer.prepare();
- onPrepared(startWhenPrepared.get());
- } catch (IOException e) {
- e.printStackTrace();
- setPlayerStatus(PlayerStatus.ERROR, null);
- }
- }
- playerLock.unlock();
-
- }
- });
- }
-
- /**
- * Called after media player has been prepared. This method is executed on the caller's thread.
- */
- void onPrepared(final boolean startWhenPrepared) {
- playerLock.lock();
-
- if (playerStatus != PlayerStatus.PREPARING) {
- playerLock.unlock();
- throw new IllegalStateException("Player is not in PREPARING state");
- }
-
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Resource prepared");
-
- if (mediaType == MediaType.VIDEO) {
- VideoPlayer vp = (VideoPlayer) mediaPlayer;
- videoSize = new Pair<Integer, Integer>(vp.getVideoWidth(), vp.getVideoHeight());
- }
-
- if (media.getPosition() > 0) {
- mediaPlayer.seekTo(media.getPosition());
- }
-
- if (media.getDuration() == 0) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Setting duration of media");
- media.setDuration(mediaPlayer.getDuration());
- }
- setPlayerStatus(PlayerStatus.PREPARED, media);
-
- if (startWhenPrepared) {
- resumeSync();
- }
-
- playerLock.unlock();
- }
-
- /**
- * Resets the media player and moves it into INITIALIZED state.
- * <p/>
- * This method is executed on an internal executor service.
- */
- public void reinit() {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- playerLock.lock();
- releaseWifiLockIfNecessary();
- if (media != null) {
- playMediaObject(media, true, stream, startWhenPrepared.get(), false);
- } else if (mediaPlayer != null) {
- mediaPlayer.reset();
- } else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Call to reinit was ignored: media and mediaPlayer were null");
- }
- playerLock.unlock();
- }
- });
- }
-
-
- /**
- * Seeks to the specified position. If the PSMP object is in an invalid state, this method will do nothing.
- *
- * @param t The position to seek to in milliseconds. t < 0 will be interpreted as t = 0
- * <p/>
- * This method is executed on the caller's thread.
- */
- private void seekToSync(int t) {
- if (t < 0) {
- t = 0;
- }
- playerLock.lock();
-
- if (playerStatus == PlayerStatus.PLAYING
- || playerStatus == PlayerStatus.PAUSED
- || playerStatus == PlayerStatus.PREPARED) {
- if (stream) {
- // statusBeforeSeeking = playerStatus;
- // setPlayerStatus(PlayerStatus.SEEKING, media);
- }
- mediaPlayer.seekTo(t);
-
- } else if (playerStatus == PlayerStatus.INITIALIZED) {
- media.setPosition(t);
- startWhenPrepared.set(true);
- prepare();
- }
- playerLock.unlock();
- }
-
- /**
- * Seeks to the specified position. If the PSMP object is in an invalid state, this method will do nothing.
- * Invalid time values (< 0) will be ignored.
- * <p/>
- * This method is executed on an internal executor service.
- */
- public void seekTo(final int t) {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- seekToSync(t);
- }
- });
- }
-
- /**
- * Seek a specific position from the current position
- *
- * @param d offset from current position (positive or negative)
- */
- public void seekDelta(final int d) {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- playerLock.lock();
- int currentPosition = getPosition();
- if (currentPosition != INVALID_TIME) {
- seekToSync(currentPosition + d);
- } else {
- Log.e(TAG, "getPosition() returned INVALID_TIME in seekDelta");
- }
-
- playerLock.unlock();
- }
- });
- }
-
- /**
- * Seek to the start of the specified chapter.
- */
- public void seekToChapter(Chapter c) {
- Validate.notNull(c);
-
- seekTo((int) c.getStart());
- }
-
- /**
- * Returns the duration of the current media object or INVALID_TIME if the duration could not be retrieved.
- */
- public int getDuration() {
- if (!playerLock.tryLock()) {
- return INVALID_TIME;
- }
-
- int retVal = INVALID_TIME;
- if (playerStatus == PlayerStatus.PLAYING
- || playerStatus == PlayerStatus.PAUSED
- || playerStatus == PlayerStatus.PREPARED) {
- retVal = mediaPlayer.getDuration();
- } else if (media != null && media.getDuration() > 0) {
- retVal = media.getDuration();
- }
-
- playerLock.unlock();
- return retVal;
- }
-
- /**
- * Returns the position of the current media object or INVALID_TIME if the position could not be retrieved.
- */
- public int getPosition() {
- if (!playerLock.tryLock()) {
- return INVALID_TIME;
- }
-
- int retVal = INVALID_TIME;
- 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 boolean isStartWhenPrepared() {
- return startWhenPrepared.get();
- }
-
- public void setStartWhenPrepared(boolean startWhenPrepared) {
- this.startWhenPrepared.set(startWhenPrepared);
- }
-
- /**
- * Returns true if the playback speed can be adjusted.
- */
- public boolean canSetSpeed() {
- boolean retVal = false;
- if (mediaPlayer != null && media != null && media.getMediaType() == MediaType.AUDIO) {
- retVal = (mediaPlayer).canSetSpeed();
- }
- return retVal;
- }
-
- /**
- * Sets the playback speed.
- * This method is executed on the caller's thread.
- */
- private void setSpeedSync(float speed) {
- playerLock.lock();
- if (media != null && media.getMediaType() == MediaType.AUDIO) {
- if (mediaPlayer.canSetSpeed()) {
- mediaPlayer.setPlaybackSpeed((float) speed);
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Playback speed was set to " + speed);
- callback.playbackSpeedChanged(speed);
- }
- }
- playerLock.unlock();
- }
-
- /**
- * Sets the playback speed.
- * This method is executed on an internal executor service.
- */
- public void setSpeed(final float speed) {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- setSpeedSync(speed);
- }
- });
- }
-
- /**
- * Returns the current playback speed. If the playback speed could not be retrieved, 1 is returned.
- */
- public float getPlaybackSpeed() {
- if (!playerLock.tryLock()) {
- return 1;
- }
-
- float retVal = 1;
- if ((playerStatus == PlayerStatus.PLAYING
- || playerStatus == PlayerStatus.PAUSED
- || playerStatus == PlayerStatus.PREPARED) && mediaPlayer.canSetSpeed()) {
- retVal = mediaPlayer.getCurrentSpeedMultiplier();
- }
- 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.
- */
- public void shutdown() {
- executor.shutdown();
- if (mediaPlayer != null) {
- mediaPlayer.release();
- }
- releaseWifiLockIfNecessary();
- }
-
- 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 (BuildConfig.DEBUG)
- Log.d(TAG, "Resetting video surface");
- mediaPlayer.setDisplay(null);
- reinit();
- playerLock.unlock();
- }
- });
- }
-
- /**
- * Return width and height of the currently playing video as a pair.
- *
- * @return Width and height as a Pair or null if the video size could not be determined. The method might still
- * return an invalid non-null value if the getVideoWidth() and getVideoHeight() methods of the media player return
- * invalid values.
- */
- public Pair<Integer, Integer> getVideoSize() {
- if (!playerLock.tryLock()) {
- // use cached value if lock can't be aquired
- return videoSize;
- }
- Pair<Integer, Integer> res;
- if (mediaPlayer == null || playerStatus == PlayerStatus.ERROR || mediaType != MediaType.VIDEO) {
- res = null;
- } else {
- VideoPlayer vp = (VideoPlayer) mediaPlayer;
- videoSize = new Pair<Integer, Integer>(vp.getVideoWidth(), vp.getVideoHeight());
- res = videoSize;
- }
- playerLock.unlock();
- return res;
- }
-
- /**
- * Returns a PSMInfo object that contains information about the current state of the PSMP object.
- *
- * @return The PSMPInfo object.
- */
- public synchronized PSMPInfo getPSMPInfo() {
- return new PSMPInfo(playerStatus, media);
- }
-
- /**
- * Sets the player status of the PSMP object. PlayerStatus and media attributes have to be set at the same time
- * so that getPSMPInfo can't return an invalid state (e.g. status is PLAYING, but media is null).
- * <p/>
- * This method will notify the callback about the change of the player status (even if the new status is the same
- * as the old one).
- *
- * @param newStatus The new PlayerStatus. This must not be null.
- * @param newMedia The new playable object of the PSMP object. This can be null.
- */
- private synchronized void setPlayerStatus(PlayerStatus newStatus, Playable newMedia) {
- Validate.notNull(newStatus);
-
- if (BuildConfig.DEBUG) Log.d(TAG, "Setting player status to " + newStatus);
-
- this.playerStatus = newStatus;
- this.media = newMedia;
- callback.statusChanged(new PSMPInfo(playerStatus, media));
- }
-
- private IPlayer createMediaPlayer() {
- if (mediaPlayer != null) {
- mediaPlayer.release();
- }
- if (media == null || media.getMediaType() == MediaType.VIDEO) {
- mediaPlayer = new VideoPlayer();
- } else {
- mediaPlayer = new AudioPlayer(context);
- }
- mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
- mediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
- return setMediaPlayerListeners(mediaPlayer);
- }
-
- private final AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
-
- @Override
- public void onAudioFocusChange(final int focusChange) {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- playerLock.lock();
-
- // If there is an incoming call, playback should be paused permanently
- TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- final int callState = (tm != null) ? tm.getCallState() : 0;
- if (BuildConfig.DEBUG) Log.d(TAG, "Call state: " + callState);
- Log.i(TAG, "Call state:" + callState);
-
- if (focusChange == AudioManager.AUDIOFOCUS_LOSS || callState != TelephonyManager.CALL_STATE_IDLE) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Lost audio focus");
- pause(true, false);
- callback.shouldStop();
- } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Gained audio focus");
- if (pausedBecauseOfTransientAudiofocusLoss) { // we paused => play now
- resume();
- } else { // we ducked => raise audio level back
- audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
- AudioManager.ADJUST_RAISE, 0);
- }
- } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
- if (playerStatus == PlayerStatus.PLAYING) {
- if (!UserPreferences.shouldPauseForFocusLoss()) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Lost audio focus temporarily. Ducking...");
- audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
- AudioManager.ADJUST_LOWER, 0);
- pausedBecauseOfTransientAudiofocusLoss = false;
- } else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Lost audio focus temporarily. Could duck, but won't, pausing...");
- pause(false, false);
- pausedBecauseOfTransientAudiofocusLoss = true;
- }
- }
- } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
- if (playerStatus == PlayerStatus.PLAYING) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Lost audio focus temporarily. Pausing...");
- pause(false, false);
- pausedBecauseOfTransientAudiofocusLoss = true;
- }
- playerLock.unlock();
- }
- }
- });
- }
- };
-
-
- public void endPlayback() {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- playerLock.lock();
- releaseWifiLockIfNecessary();
-
- if (playerStatus != PlayerStatus.INDETERMINATE) {
- setPlayerStatus(PlayerStatus.INDETERMINATE, media);
- }
- if (mediaPlayer != null) {
- mediaPlayer.reset();
-
- }
- audioManager.abandonAudioFocus(audioFocusChangeListener);
- callback.endPlayback(true);
-
- playerLock.unlock();
- }
- });
- }
-
- /**
- * Moves the PlaybackServiceMediaPlayer into STOPPED state. This call is only valid if the player is currently in
- * INDETERMINATE state, for example after a call to endPlayback.
- * This method will only take care of changing the PlayerStatus of this object! Other tasks like
- * abandoning audio focus have to be done with other methods.
- */
- public void stop() {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- playerLock.lock();
- releaseWifiLockIfNecessary();
-
- if (playerStatus == PlayerStatus.INDETERMINATE) {
- setPlayerStatus(PlayerStatus.STOPPED, null);
- } else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Ignored call to stop: Current player state is: " + playerStatus);
- }
- playerLock.unlock();
-
- }
- });
- }
-
- private synchronized void acquireWifiLockIfNecessary() {
- if (stream) {
- if (wifiLock == null) {
- wifiLock = ((WifiManager) context.getSystemService(Context.WIFI_SERVICE))
- .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG);
- wifiLock.setReferenceCounted(false);
- }
- wifiLock.acquire();
- }
- }
-
- private synchronized void releaseWifiLockIfNecessary() {
- if (wifiLock != null && wifiLock.isHeld()) {
- wifiLock.release();
- }
- }
-
- /**
- * Holds information about a PSMP object.
- */
- public class PSMPInfo {
- public PlayerStatus playerStatus;
- public Playable playable;
-
- public PSMPInfo(PlayerStatus playerStatus, Playable playable) {
- this.playerStatus = playerStatus;
- this.playable = playable;
- }
- }
-
- public static interface PSMPCallback {
- public void statusChanged(PSMPInfo newInfo);
-
- public void shouldStop();
-
- public void playbackSpeedChanged(float s);
-
- public void onBufferingUpdate(int percent);
-
- public boolean onMediaPlayerInfo(int code);
-
- public boolean onMediaPlayerError(Object inObj, int what, int extra);
-
- public boolean endPlayback(boolean playNextEpisode);
-
- public RemoteControlClient getRemoteControlClient();
- }
-
- private IPlayer setMediaPlayerListeners(IPlayer mp) {
- if (mp != null && media != null) {
- if (media.getMediaType() == MediaType.AUDIO) {
- ((AudioPlayer) mp)
- .setOnCompletionListener(audioCompletionListener);
- ((AudioPlayer) mp)
- .setOnSeekCompleteListener(audioSeekCompleteListener);
- ((AudioPlayer) mp).setOnErrorListener(audioErrorListener);
- ((AudioPlayer) mp)
- .setOnBufferingUpdateListener(audioBufferingUpdateListener);
- ((AudioPlayer) mp).setOnInfoListener(audioInfoListener);
- } else {
- ((VideoPlayer) mp)
- .setOnCompletionListener(videoCompletionListener);
- ((VideoPlayer) mp)
- .setOnSeekCompleteListener(videoSeekCompleteListener);
- ((VideoPlayer) mp).setOnErrorListener(videoErrorListener);
- ((VideoPlayer) mp)
- .setOnBufferingUpdateListener(videoBufferingUpdateListener);
- ((VideoPlayer) mp).setOnInfoListener(videoInfoListener);
- }
- }
- return mp;
- }
-
- 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 final android.media.MediaPlayer.OnCompletionListener videoCompletionListener = new android.media.MediaPlayer.OnCompletionListener() {
- @Override
- public void onCompletion(android.media.MediaPlayer mp) {
- genericOnCompletion();
- }
- };
-
- private void genericOnCompletion() {
- endPlayback();
- }
-
- 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);
- }
- };
-
- 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) {
- callback.onBufferingUpdate(percent);
- }
-
- 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 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);
- }
- };
-
- private boolean genericInfoListener(int what) {
- return callback.onMediaPlayerInfo(what);
- }
-
- 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 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);
- }
- };
-
- private boolean genericOnError(Object inObj, int what, int extra) {
- return callback.onMediaPlayerError(inObj, what, extra);
- }
-
- 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 final android.media.MediaPlayer.OnSeekCompleteListener videoSeekCompleteListener = new android.media.MediaPlayer.OnSeekCompleteListener() {
- @Override
- public void onSeekComplete(android.media.MediaPlayer mp) {
- genericSeekCompleteListener();
- }
- };
-
- private final void genericSeekCompleteListener() {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- playerLock.lock();
- if (playerStatus == PlayerStatus.SEEKING) {
- setPlayerStatus(statusBeforeSeeking, media);
- }
- playerLock.unlock();
- }
- });
- }
-}
diff --git a/src/de/danoeh/antennapod/service/playback/PlaybackServiceTaskManager.java b/src/de/danoeh/antennapod/service/playback/PlaybackServiceTaskManager.java
deleted file mode 100644
index 680ec2e2f..000000000
--- a/src/de/danoeh/antennapod/service/playback/PlaybackServiceTaskManager.java
+++ /dev/null
@@ -1,384 +0,0 @@
-package de.danoeh.antennapod.service.playback;
-
-import android.content.Context;
-import android.util.Log;
-
-import org.apache.commons.lang3.Validate;
-
-import de.danoeh.antennapod.BuildConfig;
-import de.danoeh.antennapod.feed.EventDistributor;
-import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.storage.DBReader;
-import de.danoeh.antennapod.util.playback.Playable;
-
-import java.util.List;
-import java.util.concurrent.*;
-
-/**
- * Manages the background tasks of PlaybackSerivce, i.e.
- * the sleep timer, the position saver, the widget updater and
- * the queue loader.
- * <p/>
- * The PlaybackServiceTaskManager(PSTM) uses a callback object (PSTMCallback)
- * to notify the PlaybackService about updates from the running tasks.
- */
-public class PlaybackServiceTaskManager {
- private static final String TAG = "PlaybackServiceTaskManager";
-
- /**
- * Update interval of position saver in milliseconds.
- */
- public static final int POSITION_SAVER_WAITING_INTERVAL = 5000;
- /**
- * Notification interval of widget updater in milliseconds.
- */
- public static final int WIDGET_UPDATER_NOTIFICATION_INTERVAL = 1500;
-
- private static final int SCHED_EX_POOL_SIZE = 2;
- private final ScheduledThreadPoolExecutor schedExecutor;
-
- private ScheduledFuture positionSaverFuture;
- private ScheduledFuture widgetUpdaterFuture;
- private ScheduledFuture sleepTimerFuture;
- private volatile Future<List<FeedItem>> queueFuture;
- private volatile Future chapterLoaderFuture;
-
- private SleepTimer sleepTimer;
-
- private final Context context;
- private final PSTMCallback callback;
-
- /**
- * Sets up a new PSTM. This method will also start the queue loader task.
- *
- * @param context
- * @param callback A PSTMCallback object for notifying the user about updates. Must not be null.
- */
- public PlaybackServiceTaskManager(Context context, PSTMCallback callback) {
- Validate.notNull(context);
- Validate.notNull(callback);
-
- this.context = context;
- this.callback = callback;
- 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;
- }
- });
- loadQueue();
- EventDistributor.getInstance().register(eventDistributorListener);
- }
-
- private final EventDistributor.EventListener eventDistributorListener = new EventDistributor.EventListener() {
- @Override
- public void update(EventDistributor eventDistributor, Integer arg) {
- if ((EventDistributor.QUEUE_UPDATE & arg) != 0) {
- cancelQueueLoader();
- loadQueue();
- }
- }
- };
-
- private synchronized boolean isQueueLoaderActive() {
- return queueFuture != null && !queueFuture.isDone();
- }
-
- private synchronized void cancelQueueLoader() {
- if (isQueueLoaderActive()) {
- queueFuture.cancel(true);
- }
- }
-
- private synchronized void loadQueue() {
- if (!isQueueLoaderActive()) {
- queueFuture = schedExecutor.submit(new Callable<List<FeedItem>>() {
- @Override
- public List<FeedItem> call() throws Exception {
- return DBReader.getQueue(context);
- }
- });
- }
- }
-
- /**
- * Returns the queue if it is already loaded or null if it hasn't been loaded yet.
- * In order to wait until the queue has been loaded, use getQueue()
- */
- public synchronized List<FeedItem> getQueueIfLoaded() {
- if (queueFuture.isDone()) {
- try {
- return queueFuture.get();
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (ExecutionException e) {
- e.printStackTrace();
- }
- }
- return null;
- }
-
- /**
- * Returns the queue or waits until the PSTM has loaded the queue from the database.
- */
- public synchronized List<FeedItem> getQueue() throws InterruptedException {
- try {
- return queueFuture.get();
- } catch (ExecutionException e) {
- throw new IllegalArgumentException(e);
- }
- }
-
- /**
- * Starts the position saver task. If the position saver is already active, nothing will happen.
- */
- public synchronized void startPositionSaver() {
- if (!isPositionSaverActive()) {
- Runnable positionSaver = new Runnable() {
- @Override
- public void run() {
- callback.positionSaverTick();
- }
- };
- positionSaverFuture = schedExecutor.scheduleWithFixedDelay(positionSaver, POSITION_SAVER_WAITING_INTERVAL,
- POSITION_SAVER_WAITING_INTERVAL, TimeUnit.MILLISECONDS);
-
- if (BuildConfig.DEBUG) Log.d(TAG, "Started PositionSaver");
- } else {
- if (BuildConfig.DEBUG) Log.d(TAG, "Call to startPositionSaver was ignored.");
- }
- }
-
- /**
- * Returns true if the position saver is currently running.
- */
- public synchronized boolean isPositionSaverActive() {
- return positionSaverFuture != null && !positionSaverFuture.isCancelled() && !positionSaverFuture.isDone();
- }
-
- /**
- * Cancels the position saver. If the position saver is not running, nothing will happen.
- */
- public synchronized void cancelPositionSaver() {
- if (isPositionSaverActive()) {
- positionSaverFuture.cancel(false);
- if (BuildConfig.DEBUG) Log.d(TAG, "Cancelled PositionSaver");
- }
- }
-
- /**
- * Starts the widget updater task. If the widget updater is already active, nothing will happen.
- */
- public synchronized void startWidgetUpdater() {
- if (!isWidgetUpdaterActive()) {
- Runnable widgetUpdater = new Runnable() {
- @Override
- public void run() {
- callback.onWidgetUpdaterTick();
- }
- };
- widgetUpdaterFuture = schedExecutor.scheduleWithFixedDelay(widgetUpdater, WIDGET_UPDATER_NOTIFICATION_INTERVAL,
- WIDGET_UPDATER_NOTIFICATION_INTERVAL, TimeUnit.MILLISECONDS);
-
- if (BuildConfig.DEBUG) Log.d(TAG, "Started WidgetUpdater");
- } else {
- if (BuildConfig.DEBUG) Log.d(TAG, "Call to startWidgetUpdater was ignored.");
- }
- }
-
- /**
- * Starts a new sleep timer with the given waiting time. If another sleep timer is already active, it will be
- * cancelled first.
- * After waitingTime has elapsed, onSleepTimerExpired() will be called.
- *
- * @throws java.lang.IllegalArgumentException if waitingTime <= 0
- */
- public synchronized void setSleepTimer(long waitingTime) {
- Validate.isTrue(waitingTime > 0, "Waiting time <= 0");
-
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime)
- + " milliseconds");
- if (isSleepTimerActive()) {
- sleepTimerFuture.cancel(true);
- }
- sleepTimer = new SleepTimer(waitingTime);
- sleepTimerFuture = schedExecutor.schedule(sleepTimer, 0, TimeUnit.MILLISECONDS);
- }
-
- /**
- * Returns true if the sleep timer is currently active.
- */
- public synchronized boolean isSleepTimerActive() {
- return sleepTimer != null && sleepTimerFuture != null && !sleepTimerFuture.isCancelled() && !sleepTimerFuture.isDone() && sleepTimer.isWaiting;
- }
-
- /**
- * Disables the sleep timer. If the sleep timer is not active, nothing will happen.
- */
- public synchronized void disableSleepTimer() {
- if (isSleepTimerActive()) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Disabling sleep timer");
- sleepTimerFuture.cancel(true);
- }
- }
-
- /**
- * Returns the current sleep timer time or 0 if the sleep timer is not active.
- */
- public synchronized long getSleepTimerTimeLeft() {
- if (isSleepTimerActive()) {
- return sleepTimer.getWaitingTime();
- } else {
- return 0;
- }
- }
-
-
- /**
- * Returns true if the widget updater is currently running.
- */
- public synchronized boolean isWidgetUpdaterActive() {
- return widgetUpdaterFuture != null && !widgetUpdaterFuture.isCancelled() && !widgetUpdaterFuture.isDone();
- }
-
- /**
- * Cancels the widget updater. If the widget updater is not running, nothing will happen.
- */
- public synchronized void cancelWidgetUpdater() {
- if (isWidgetUpdaterActive()) {
- widgetUpdaterFuture.cancel(false);
- if (BuildConfig.DEBUG) Log.d(TAG, "Cancelled WidgetUpdater");
- }
- }
-
- private synchronized void cancelChapterLoader() {
- if (isChapterLoaderActive()) {
- chapterLoaderFuture.cancel(true);
- }
- }
-
- private synchronized boolean isChapterLoaderActive() {
- return chapterLoaderFuture != null && !chapterLoaderFuture.isDone();
- }
-
- /**
- * Starts a new thread that loads the chapter marks from a playable object. If another chapter loader is already active,
- * it will be cancelled first.
- * On completion, the callback's onChapterLoaded method will be called.
- */
- public synchronized void startChapterLoader(final Playable media) {
- Validate.notNull(media);
-
- if (isChapterLoaderActive()) {
- cancelChapterLoader();
- }
-
- Runnable chapterLoader = new Runnable() {
- @Override
- public void run() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Chapter loader started");
- if (media.getChapters() == null) {
- media.loadChapterMarks();
- if (!Thread.currentThread().isInterrupted() && media.getChapters() != null) {
- callback.onChapterLoaded(media);
- }
- }
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Chapter loader stopped");
- }
- };
- chapterLoaderFuture = schedExecutor.submit(chapterLoader);
- }
-
-
- /**
- * Cancels all tasks. The PSTM will be in the initial state after execution of this method.
- */
- public synchronized void cancelAllTasks() {
- cancelPositionSaver();
- cancelWidgetUpdater();
- disableSleepTimer();
- cancelQueueLoader();
- cancelChapterLoader();
- }
-
- /**
- * Cancels all tasks and shuts down the internal executor service of the PSTM. The object should not be used after
- * execution of this method.
- */
- public synchronized void shutdown() {
- EventDistributor.getInstance().unregister(eventDistributorListener);
- cancelAllTasks();
- schedExecutor.shutdown();
- }
-
- /**
- * Sleeps for a given time and then pauses playback.
- */
- private class SleepTimer implements Runnable {
- private static final String TAG = "SleepTimer";
- private static final long UPDATE_INTERVALL = 1000L;
- private volatile long waitingTime;
- private volatile boolean isWaiting;
-
- public SleepTimer(long waitingTime) {
- super();
- this.waitingTime = waitingTime;
- isWaiting = true;
- }
-
- @Override
- public void run() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Starting");
- while (waitingTime > 0) {
- try {
- Thread.sleep(UPDATE_INTERVALL);
- waitingTime -= UPDATE_INTERVALL;
-
- if (waitingTime <= 0) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Waiting completed");
- postExecute();
- if (!Thread.currentThread().isInterrupted()) {
- callback.onSleepTimerExpired();
- }
-
- }
- } catch (InterruptedException e) {
- Log.d(TAG, "Thread was interrupted while waiting");
- break;
- }
- }
- postExecute();
- }
-
- protected void postExecute() {
- isWaiting = false;
- }
-
- public long getWaitingTime() {
- return waitingTime;
- }
-
- public boolean isWaiting() {
- return isWaiting;
- }
-
- }
-
- public static interface PSTMCallback {
- void positionSaverTick();
-
- void onSleepTimerExpired();
-
- void onWidgetUpdaterTick();
-
- void onChapterLoaded(Playable media);
- }
-}
diff --git a/src/de/danoeh/antennapod/service/playback/PlayerStatus.java b/src/de/danoeh/antennapod/service/playback/PlayerStatus.java
deleted file mode 100644
index 3d2b4ad39..000000000
--- a/src/de/danoeh/antennapod/service/playback/PlayerStatus.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package de.danoeh.antennapod.service.playback;
-
-public enum PlayerStatus {
- INDETERMINATE, // player is currently changing its state, listeners should wait until the player has left this state.
- ERROR,
- PREPARING,
- PAUSED,
- PLAYING,
- STOPPED,
- PREPARED,
- SEEKING,
- INITIALIZING, // playback service is loading the Playable's metadata
- INITIALIZED // playback service was started, data source of media player was set.
-}
diff --git a/src/de/danoeh/antennapod/service/playback/PlayerWidgetService.java b/src/de/danoeh/antennapod/service/playback/PlayerWidgetService.java
deleted file mode 100644
index 71bc40c2a..000000000
--- a/src/de/danoeh/antennapod/service/playback/PlayerWidgetService.java
+++ /dev/null
@@ -1,190 +0,0 @@
-package de.danoeh.antennapod.service.playback;
-
-import android.app.PendingIntent;
-import android.app.Service;
-import android.appwidget.AppWidgetManager;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Build;
-import android.os.IBinder;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-import android.widget.RemoteViews;
-import de.danoeh.antennapod.BuildConfig;
-import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.receiver.MediaButtonReceiver;
-import de.danoeh.antennapod.receiver.PlayerWidget;
-import de.danoeh.antennapod.util.Converter;
-import de.danoeh.antennapod.util.playback.Playable;
-
-/** Updates the state of the player widget */
-public class PlayerWidgetService extends Service {
- private static final String TAG = "PlayerWidgetService";
-
- private PlaybackService playbackService;
- /** True while service is updating the widget */
- private volatile boolean isUpdating;
-
- public PlayerWidgetService() {
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Service created");
- isUpdating = false;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Service is about to be destroyed");
- try {
- unbindService(mConnection);
- } catch (IllegalArgumentException e) {
- Log.w(TAG, "IllegalArgumentException when trying to unbind service");
- }
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- if (!isUpdating) {
- if (playbackService == null && PlaybackService.isRunning) {
- bindService(new Intent(this, PlaybackService.class),
- mConnection, 0);
- } else {
- startViewUpdaterIfNotRunning();
- }
- } else {
- if (BuildConfig.DEBUG)
- Log.d(TAG,
- "Service was called while updating. Ignoring update request");
- }
- return Service.START_NOT_STICKY;
- }
-
- private void updateViews() {
- if (playbackService == null) {
- return;
- }
- isUpdating = true;
-
- ComponentName playerWidget = new ComponentName(this, PlayerWidget.class);
- AppWidgetManager manager = AppWidgetManager.getInstance(this);
- RemoteViews views = new RemoteViews(getPackageName(),
- R.layout.player_widget);
- PendingIntent startMediaplayer = PendingIntent.getActivity(this, 0,
- PlaybackService.getPlayerActivityIntent(this), 0);
-
- views.setOnClickPendingIntent(R.id.layout_left, startMediaplayer);
- final Playable media = playbackService.getPlayable();
- if (playbackService != null && media != null) {
- PlayerStatus status = playbackService.getStatus();
-
- views.setTextViewText(R.id.txtvTitle, media.getEpisodeTitle());
-
- if (status == PlayerStatus.PLAYING) {
- String progressString = getProgressString(playbackService);
- if (progressString != null) {
- views.setTextViewText(R.id.txtvProgress, progressString);
- }
- views.setImageViewResource(R.id.butPlay, R.drawable.av_pause_dark);
- if (Build.VERSION.SDK_INT >= 15) {
- views.setContentDescription(R.id.butPlay, getString(R.string.pause_label));
- }
- } else {
- views.setImageViewResource(R.id.butPlay, R.drawable.av_play_dark);
- if (Build.VERSION.SDK_INT >= 15) {
- views.setContentDescription(R.id.butPlay, getString(R.string.play_label));
- }
- }
- views.setOnClickPendingIntent(R.id.butPlay,
- createMediaButtonIntent());
- } else {
- views.setViewVisibility(R.id.txtvProgress, View.INVISIBLE);
- views.setTextViewText(R.id.txtvTitle,
- this.getString(R.string.no_media_playing_label));
- views.setImageViewResource(R.id.butPlay, R.drawable.av_play);
-
- }
-
- manager.updateAppWidget(playerWidget, views);
- isUpdating = false;
- }
-
- /** Creates an intent which fakes a mediabutton press */
- private PendingIntent createMediaButtonIntent() {
- KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN,
- KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
- Intent startingIntent = new Intent(
- MediaButtonReceiver.NOTIFY_BUTTON_RECEIVER);
- startingIntent.putExtra(Intent.EXTRA_KEY_EVENT, event);
-
- return PendingIntent.getBroadcast(this, 0, startingIntent, 0);
- }
-
- private String getProgressString(PlaybackService ps) {
- int position = ps.getCurrentPosition();
- int duration = ps.getDuration();
- if (position != PlaybackService.INVALID_TIME
- && duration != PlaybackService.INVALID_TIME) {
- return Converter.getDurationStringLong(position) + " / "
- + Converter.getDurationStringLong(duration);
- } else {
- return null;
- }
- }
-
- private ServiceConnection mConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Connection to service established");
- playbackService = ((PlaybackService.LocalBinder) service)
- .getService();
- startViewUpdaterIfNotRunning();
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- playbackService = null;
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Disconnected from service");
- }
-
- };
-
- private void startViewUpdaterIfNotRunning() {
- if (!isUpdating) {
- ViewUpdater updateThread = new ViewUpdater(this);
- updateThread.start();
- }
- }
-
- static class ViewUpdater extends Thread {
- private static final String THREAD_NAME = "ViewUpdater";
- private PlayerWidgetService service;
-
- public ViewUpdater(PlayerWidgetService service) {
- super();
- setName(THREAD_NAME);
- this.service = service;
-
- }
-
- @Override
- public void run() {
- service.updateViews();
- }
-
- }
-
-}