diff options
Diffstat (limited to 'src/de/danoeh/antennapod/service/playback')
5 files changed, 0 insertions, 2710 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 6c292c08a..000000000 --- a/src/de/danoeh/antennapod/service/playback/PlaybackService.java +++ /dev/null @@ -1,1132 +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) { - if (UserPreferences.isPersistNotify()) { - mediaPlayer.pause(false, true); - } - else { - 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) { - if (UserPreferences.isPersistNotify()) { - mediaPlayer.pause(false, true); - } - else { - 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; - case KeyEvent.KEYCODE_MEDIA_STOP: - if (status == PlayerStatus.PLAYING) { - mediaPlayer.pause(true, true); - } - stopForeground(true); // gets rid of persistent notification - 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(); - if (UserPreferences.isPersistNotify() && android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - // do not remove notification on pause based on user pref and whether android version supports expanded notifications - } - else { - // remove notifcation on pause - 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( // pause button 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); - Intent playButtonIntent = new Intent( // play button intent - PlaybackService.this, PlaybackService.class); - playButtonIntent.putExtra( - MediaButtonReceiver.EXTRA_KEYCODE, - KeyEvent.KEYCODE_MEDIA_PLAY); - PendingIntent playButtonPendingIntent = PendingIntent - .getService(PlaybackService.this, 1, - playButtonIntent, - PendingIntent.FLAG_UPDATE_CURRENT); - Intent stopButtonIntent = new Intent( // stop button intent - PlaybackService.this, PlaybackService.class); - stopButtonIntent.putExtra( - MediaButtonReceiver.EXTRA_KEYCODE, - KeyEvent.KEYCODE_MEDIA_STOP); - PendingIntent stopButtonPendingIntent = PendingIntent - .getService(PlaybackService.this, 2, - stopButtonIntent, - 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) - .setPriority(UserPreferences.getNotifyPriority()) // set notification priority - .addAction(android.R.drawable.ic_media_play, //play action - getString(R.string.play_label), - playButtonPendingIntent) - .addAction(android.R.drawable.ic_media_pause, //pause action - getString(R.string.pause_label), - pauseButtonPendingIntent) - .addAction(android.R.drawable.ic_menu_close_clear_cancel, // stop action - getString(R.string.stop_label), - stopButtonPendingIntent); - 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()) { - if (UserPreferences.isPersistNotify()) { - mediaPlayer.pause(false, true); - } - else { - 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 9978fff3c..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 ec28724ed..000000000 --- a/src/de/danoeh/antennapod/service/playback/PlayerWidgetService.java +++ /dev/null @@ -1,201 +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; - - /** Controls write access to playbackservice reference */ - private Object psLock; - - /** 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; - psLock = new Object(); - } - - @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"); - synchronized (psLock) { - playbackService = ((PlaybackService.LocalBinder) service) - .getService(); - startViewUpdaterIfNotRunning(); - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - synchronized (psLock) { - playbackService = null; - if (BuildConfig.DEBUG) - Log.d(TAG, "Disconnected from service"); - } - } - - }; - - private void startViewUpdaterIfNotRunning() { - if (!isUpdating) { - ViewUpdater updateThread = new ViewUpdater(this); - updateThread.start(); - } - } - - 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() { - synchronized (psLock) { - service.updateViews(); - } - } - - } - -} |