diff options
4 files changed, 722 insertions, 450 deletions
diff --git a/src/de/danoeh/antennapod/activity/AudioplayerActivity.java b/src/de/danoeh/antennapod/activity/AudioplayerActivity.java index 3e9eed1ef..3587775a5 100644 --- a/src/de/danoeh/antennapod/activity/AudioplayerActivity.java +++ b/src/de/danoeh/antennapod/activity/AudioplayerActivity.java @@ -17,6 +17,7 @@ import com.viewpagerindicator.TabPageIndicator; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.SCListAdapter; +import de.danoeh.antennapod.feed.FeedMedia; import de.danoeh.antennapod.feed.SimpleChapter; import de.danoeh.antennapod.fragment.CoverFragment; import de.danoeh.antennapod.fragment.ItemDescriptionFragment; @@ -58,6 +59,8 @@ public class AudioplayerActivity extends MediaplayerActivity { viewpager = (ViewPager) findViewById(R.id.viewpager); tabs = (TabPageIndicator) findViewById(R.id.tabs); + FeedMedia media = controller.getMedia(); + int tabcount = 2; if (media != null && media.getItem().getSimpleChapters() != null) { tabcount = 3; @@ -77,13 +80,12 @@ public class AudioplayerActivity extends MediaplayerActivity { @Override protected void loadMediaInfo() { super.loadMediaInfo(); - if (!mediaInfoLoaded && media != null) { + if (controller.getMedia() != null) { pagerAdapter.notifyDataSetChanged(); - } } - public static class MediaPlayerPagerAdapter extends + public class MediaPlayerPagerAdapter extends FragmentStatePagerAdapter { private int numItems; private AudioplayerActivity activity; @@ -103,15 +105,16 @@ public class AudioplayerActivity extends MediaplayerActivity { @Override public Fragment getItem(int position) { - if (activity.media != null) { + FeedMedia media = controller.getMedia(); + if (media != null) { switch (position) { case POS_COVER: activity.coverFragment = CoverFragment - .newInstance(activity.media.getItem()); + .newInstance(media.getItem()); return activity.coverFragment; case POS_DESCR: activity.descriptionFragment = ItemDescriptionFragment - .newInstance(activity.media.getItem()); + .newInstance(media.getItem()); return activity.descriptionFragment; case POS_CHAPTERS: sCChapterFragment = new SherlockListFragment() { @@ -122,15 +125,13 @@ public class AudioplayerActivity extends MediaplayerActivity { super.onListItemClick(l, v, position, id); SimpleChapter chapter = (SimpleChapter) this .getListAdapter().getItem(position); - if (activity.playbackService != null) { - activity.playbackService.seekToChapter(chapter); - } + controller.seekToChapter(chapter); } }; sCChapterFragment.setListAdapter(new SCListAdapter( - activity, 0, activity.media.getItem() + activity, 0, media.getItem() .getSimpleChapters())); return sCChapterFragment; diff --git a/src/de/danoeh/antennapod/activity/MediaplayerActivity.java b/src/de/danoeh/antennapod/activity/MediaplayerActivity.java index 771450cbf..6d56861e9 100644 --- a/src/de/danoeh/antennapod/activity/MediaplayerActivity.java +++ b/src/de/danoeh/antennapod/activity/MediaplayerActivity.java @@ -1,28 +1,16 @@ package de.danoeh.antennapod.activity; -import android.annotation.SuppressLint; import android.app.AlertDialog; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.content.IntentFilter; -import android.content.ServiceConnection; -import android.content.SharedPreferences; import android.content.res.Configuration; import android.graphics.PixelFormat; -import android.media.MediaPlayer; -import android.os.AsyncTask; import android.os.Bundle; -import android.os.IBinder; import android.util.Log; -import android.view.View; -import android.view.View.OnClickListener; import android.widget.ImageButton; import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; +import android.widget.SeekBar.OnSeekBarChangeListener; import com.actionbarsherlock.app.SherlockFragmentActivity; import com.actionbarsherlock.view.Menu; @@ -30,15 +18,13 @@ import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; import de.danoeh.antennapod.dialog.TimeDialog; import de.danoeh.antennapod.feed.FeedManager; import de.danoeh.antennapod.feed.FeedMedia; -import de.danoeh.antennapod.service.PlaybackService; -import de.danoeh.antennapod.service.PlayerStatus; import de.danoeh.antennapod.util.Converter; import de.danoeh.antennapod.util.MediaPlayerError; +import de.danoeh.antennapod.util.PlaybackController; import de.danoeh.antennapod.util.StorageUtils; import de.danoeh.antennapod.util.menuhandler.FeedItemMenuHandler; @@ -50,16 +36,10 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity implements OnSeekBarChangeListener { private static final String TAG = "MediaplayerActivity"; - static final int DEFAULT_SEEK_DELTA = 30000; - - /** True if media information was loaded. */ - protected boolean mediaInfoLoaded = false; - protected PlaybackService playbackService; - protected MediaPositionObserver positionObserver; - protected FeedMedia media; - protected PlayerStatus status; protected FeedManager manager; + protected PlaybackController controller; + protected TextView txtvPosition; protected TextView txtvLength; protected SeekBar sbPosition; @@ -67,6 +47,86 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity protected ImageButton butRev; protected ImageButton butFF; + public MediaplayerActivity() { + super(); + controller = new PlaybackController(this) { + + @Override + public void setupGUI() { + MediaplayerActivity.this.setupGUI(); + } + + @Override + public void onPositionObserverUpdate() { + MediaplayerActivity.this.onPositionObserverUpdate(); + } + + @Override + public void onBufferStart() { + MediaplayerActivity.this.onBufferStart(); + } + + @Override + public void onBufferEnd() { + MediaplayerActivity.this.onBufferEnd(); + } + + @Override + public void onBufferUpdate(float progress) { + MediaplayerActivity.this.onBufferUpdate(progress); + } + + @Override + public void handleError(int code) { + MediaplayerActivity.this.handleError(code); + } + + @Override + public void onReloadNotification(int code) { + MediaplayerActivity.this.onReloadNotification(code); + } + + @Override + public void onSleepTimerUpdate() { + invalidateOptionsMenu(); + } + + @Override + public ImageButton getPlayButton() { + return butPlay; + } + + @Override + public void postStatusMsg(int msg) { + MediaplayerActivity.this.postStatusMsg(msg); + } + + @Override + public void clearStatusMsg() { + MediaplayerActivity.this.clearStatusMsg(); + } + + @Override + public void loadMediaInfo() { + MediaplayerActivity.this.loadMediaInfo(); + } + + @Override + public void onAwaitingVideoSurface() { + MediaplayerActivity.this.onAwaitingVideoSurface(); + } + + @Override + public void onServiceQueried() { + MediaplayerActivity.this.onServiceQueried(); + } + }; + } + + protected void onServiceQueried() { + invalidateOptionsMenu(); + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -78,77 +138,14 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity manager = FeedManager.getInstance(); getWindow().setFormat(PixelFormat.TRANSPARENT); getSupportActionBar().setDisplayHomeAsUpEnabled(true); - bindToService(); } @Override protected void onPause() { super.onPause(); - mediaInfoLoaded = false; + controller.pause(); } - protected OnClickListener playbuttonListener = new OnClickListener() { - @Override - public void onClick(View v) { - if (playbackService != null) { - switch (status) { - case PLAYING: - playbackService.pause(true); - break; - case PAUSED: - case PREPARED: - playbackService.play(); - break; - case PREPARING: - playbackService.setStartWhenPrepared(!playbackService - .isStartWhenPrepared()); - } - } else { - Log.w(TAG, - "Play/Pause button was pressed, but playbackservice was null!"); - } - } - - }; - protected ServiceConnection mConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName className, IBinder service) { - playbackService = ((PlaybackService.LocalBinder) service) - .getService(); - - registerReceiver(statusUpdate, new IntentFilter( - PlaybackService.ACTION_PLAYER_STATUS_CHANGED)); - - registerReceiver(notificationReceiver, new IntentFilter( - PlaybackService.ACTION_PLAYER_NOTIFICATION)); - - queryService(); - if (AppConfig.DEBUG) - Log.d(TAG, "Connection to Service established"); - } - - @Override - public void onServiceDisconnected(ComponentName name) { - playbackService = null; - if (AppConfig.DEBUG) - Log.d(TAG, "Disconnected from Service"); - - } - }; - protected BroadcastReceiver statusUpdate = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (AppConfig.DEBUG) - Log.d(TAG, "Received statusUpdate Intent."); - if (playbackService != null) { - status = playbackService.getStatus(); - handleStatus(); - } else { - Log.w(TAG, - "Couldn't receive status update: playbackService was null"); - } - } - }; - /** * Should be used to switch to another player activity if the mime type is * not the correct one for the current activity. @@ -166,56 +163,12 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity */ protected abstract void onBufferEnd(); - protected BroadcastReceiver notificationReceiver = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - int type = intent.getIntExtra( - PlaybackService.EXTRA_NOTIFICATION_TYPE, -1); - int code = intent.getIntExtra( - PlaybackService.EXTRA_NOTIFICATION_CODE, -1); - if (code != -1 && type != -1) { - switch (type) { - case PlaybackService.NOTIFICATION_TYPE_ERROR: - handleError(code); - break; - case PlaybackService.NOTIFICATION_TYPE_BUFFER_UPDATE: - if (sbPosition != null) { - float progress = ((float) code) / 100; - sbPosition.setSecondaryProgress((int) progress - * sbPosition.getMax()); - } - break; - case PlaybackService.NOTIFICATION_TYPE_RELOAD: - if (positionObserver != null) { - positionObserver.cancel(true); - positionObserver = null; - } - mediaInfoLoaded = false; - onReloadNotification(intent.getIntExtra( - PlaybackService.EXTRA_NOTIFICATION_CODE, -1)); - queryService(); - - break; - case PlaybackService.NOTIFICATION_TYPE_SLEEPTIMER_UPDATE: - invalidateOptionsMenu(); - break; - case PlaybackService.NOTIFICATION_TYPE_BUFFER_START: - onBufferStart(); - break; - case PlaybackService.NOTIFICATION_TYPE_BUFFER_END: - onBufferEnd(); - break; - } - - } else { - if (AppConfig.DEBUG) - Log.d(TAG, "Bad arguments. Won't handle intent"); - } - + protected void onBufferUpdate(float progress) { + if (sbPosition != null) { + sbPosition.setSecondaryProgress((int) progress + * sbPosition.getMax()); } - - }; + } /** Current screen orientation. */ protected int orientation; @@ -225,26 +178,7 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity super.onStop(); if (AppConfig.DEBUG) Log.d(TAG, "Activity stopped"); - try { - unregisterReceiver(statusUpdate); - } catch (IllegalArgumentException e) { - // ignore - } - - try { - unregisterReceiver(notificationReceiver); - } catch (IllegalArgumentException e) { - // ignore - } - - try { - unbindService(mConnection); - } catch (IllegalArgumentException e) { - // ignore - } - if (positionObserver != null) { - positionObserver.cancel(true); - } + controller.release(); } @Override @@ -256,6 +190,7 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity @Override public boolean onPrepareOptionsMenu(Menu menu) { + FeedMedia media = controller.getMedia(); menu.findItem(R.id.support_item).setVisible( media != null && media.getItem().getPaymentLink() != null); @@ -264,10 +199,8 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity menu.findItem(R.id.visit_website_item).setVisible( media != null && media.getItem().getLink() != null); - boolean sleepTimerSet = playbackService != null - && playbackService.sleepTimerActive(); - boolean sleepTimerNotSet = playbackService != null - && !playbackService.sleepTimerActive(); + boolean sleepTimerSet = controller.sleepTimerActive(); + boolean sleepTimerNotSet = controller.sleepTimerNotActive(); menu.findItem(R.id.set_sleeptimer_item).setVisible(sleepTimerNotSet); menu.findItem(R.id.disable_sleeptimer_item).setVisible(sleepTimerSet); return true; @@ -281,11 +214,11 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity MainActivity.class)); break; case R.id.disable_sleeptimer_item: - if (playbackService != null) { + if (controller.serviceAvailable()) { AlertDialog.Builder stDialog = new AlertDialog.Builder(this); stDialog.setTitle(R.string.sleep_timer_label); stDialog.setMessage(getString(R.string.time_left_label) - + Converter.getDurationStringLong((int) playbackService + + Converter.getDurationStringLong((int) controller .getSleepTimerTimeLeft())); stDialog.setPositiveButton(R.string.disable_sleeptimer_label, new DialogInterface.OnClickListener() { @@ -294,9 +227,7 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); - if (playbackService != null) { - playbackService.disableSleepTimer(); - } + controller.disableSleepTimer(); } }); stDialog.setNegativeButton(R.string.cancel_label, @@ -312,16 +243,14 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity } break; case R.id.set_sleeptimer_item: - if (playbackService != null) { + if (controller.serviceAvailable()) { TimeDialog td = new TimeDialog(this, R.string.set_sleeptimer_label, R.string.set_sleeptimer_label) { @Override public void onTimeEntered(long millis) { - if (playbackService != null) { - playbackService.setSleepTimer(millis); - } + controller.setSleepTimer(millis); } }; td.show(); @@ -329,8 +258,8 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity } default: - return FeedItemMenuHandler.onMenuItemClicked(this, item, - media.getItem()); + return FeedItemMenuHandler.onMenuItemClicked(this, item, controller + .getMedia().getItem()); } return true; } @@ -341,8 +270,7 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity if (AppConfig.DEBUG) Log.d(TAG, "Resuming Activity"); StorageUtils.checkStorageAvailability(this); - bindToService(); - + controller.init(); } @Override @@ -353,101 +281,6 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity } /** - * Tries to establish a connection to the PlaybackService. If it isn't - * running, the PlaybackService will be started with the last played media - * as the arguments of the launch intent. - */ - protected void bindToService() { - Intent serviceIntent = new Intent(this, PlaybackService.class); - boolean bound = false; - if (!PlaybackService.isRunning) { - if (AppConfig.DEBUG) - Log.d(TAG, "Trying to restore last played media"); - SharedPreferences prefs = getApplicationContext() - .getSharedPreferences(PodcastApp.PREF_NAME, 0); - long mediaId = prefs.getLong(PlaybackService.PREF_LAST_PLAYED_ID, - -1); - long feedId = prefs.getLong( - PlaybackService.PREF_LAST_PLAYED_FEED_ID, -1); - if (mediaId != -1 && feedId != -1) { - serviceIntent.putExtra(PlaybackService.EXTRA_FEED_ID, feedId); - serviceIntent.putExtra(PlaybackService.EXTRA_MEDIA_ID, mediaId); - serviceIntent.putExtra( - PlaybackService.EXTRA_START_WHEN_PREPARED, false); - serviceIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, - prefs.getBoolean(PlaybackService.PREF_LAST_IS_STREAM, - true)); - startService(serviceIntent); - bound = bindService(serviceIntent, mConnection, - Context.BIND_AUTO_CREATE); - } else { - if (AppConfig.DEBUG) - Log.d(TAG, "No last played media found"); - status = PlayerStatus.STOPPED; - setupGUI(); - handleStatus(); - } - } else { - bound = bindService(serviceIntent, mConnection, 0); - } - if (AppConfig.DEBUG) - Log.d(TAG, "Result for service binding: " + bound); - } - - /** - * Is called whenever the PlaybackService changes it's status. This method - * should be used to update the GUI or start/cancel AsyncTasks. - */ - private void handleStatus() { - switch (status) { - - case ERROR: - postStatusMsg(R.string.player_error_msg); - break; - case PAUSED: - postStatusMsg(R.string.player_paused_msg); - loadMediaInfo(); - if (positionObserver != null) { - positionObserver.cancel(true); - positionObserver = null; - } - butPlay.setImageResource(R.drawable.av_play); - break; - case PLAYING: - clearStatusMsg(); - loadMediaInfo(); - setupPositionObserver(); - butPlay.setImageResource(R.drawable.av_pause); - break; - case PREPARING: - postStatusMsg(R.string.player_preparing_msg); - loadMediaInfo(); - if (playbackService != null) { - if (playbackService.isStartWhenPrepared()) { - butPlay.setImageResource(R.drawable.av_pause); - } else { - butPlay.setImageResource(R.drawable.av_play); - } - } - break; - case STOPPED: - postStatusMsg(R.string.player_stopped_msg); - break; - case PREPARED: - loadMediaInfo(); - postStatusMsg(R.string.player_ready_msg); - butPlay.setImageResource(R.drawable.av_play); - break; - case SEEKING: - postStatusMsg(R.string.player_seeking_msg); - break; - case AWAITING_VIDEO_SURFACE: - onAwaitingVideoSurface(); - break; - } - } - - /** * Called by 'handleStatus()' when the PlaybackService is in the * AWAITING_VIDEO_SURFACE state. */ @@ -458,45 +291,25 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity protected abstract void clearStatusMsg(); protected void onPositionObserverUpdate() { - int currentPosition = playbackService.getPlayer().getCurrentPosition(); - media.setPosition(currentPosition); - txtvPosition.setText(Converter.getDurationStringLong(currentPosition)); - txtvLength.setText(Converter.getDurationStringLong(playbackService - .getPlayer().getDuration())); - updateProgressbarPosition(); - } - - @SuppressLint("NewApi") - private void setupPositionObserver() { - if (positionObserver == null || positionObserver.isCancelled()) { - if (AppConfig.DEBUG) - Log.d(TAG, "Setting up position observer"); - positionObserver = new MediaPositionObserver() { - - @Override - protected void onProgressUpdate(Void... v) { - super.onProgressUpdate(); - onPositionObserverUpdate(); - } - - }; - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - positionObserver.executeOnExecutor( - AsyncTask.THREAD_POOL_EXECUTOR, - playbackService.getPlayer()); - } else { - positionObserver.execute(playbackService.getPlayer()); - } - + int currentPosition = controller.getPosition(); + int duration = controller.getDuration(); + if (currentPosition != PlaybackController.INVALID_TIME + && duration != PlaybackController.INVALID_TIME) { + controller.getMedia().setPosition(currentPosition); + txtvPosition.setText(Converter + .getDurationStringLong(currentPosition)); + txtvLength.setText(Converter.getDurationStringLong(duration)); + updateProgressbarPosition(currentPosition, duration); + } else { + Log.w(TAG, + "Could not react to position observer update because of invalid time"); } } - private void updateProgressbarPosition() { + private void updateProgressbarPosition(int position, int duration) { if (AppConfig.DEBUG) Log.d(TAG, "Updating progressbar info"); - MediaPlayer player = playbackService.getPlayer(); - float progress = ((float) player.getCurrentPosition()) - / player.getDuration(); + float progress = ((float) position) / duration; sbPosition.setProgress((int) (progress * sbPosition.getMax())); } @@ -507,26 +320,23 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity * FeedMedia object. */ protected void loadMediaInfo() { - if (!mediaInfoLoaded) { - if (AppConfig.DEBUG) - Log.d(TAG, "Loading media info"); - if (media != null) { - getSupportActionBar().setSubtitle(media.getItem().getTitle()); - getSupportActionBar().setTitle( - media.getItem().getFeed().getTitle()); - txtvPosition.setText(Converter.getDurationStringLong((media - .getPosition()))); - - if (media.getDuration() != 0) { - txtvLength.setText(Converter.getDurationStringLong(media - .getDuration())); - float progress = ((float) media.getPosition()) - / media.getDuration(); - sbPosition.setProgress((int) (progress * sbPosition - .getMax())); - } + if (AppConfig.DEBUG) + Log.d(TAG, "Loading media info"); + FeedMedia media = controller.getMedia(); + if (media != null) { + getSupportActionBar().setSubtitle(media.getItem().getTitle()); + getSupportActionBar() + .setTitle(media.getItem().getFeed().getTitle()); + txtvPosition.setText(Converter.getDurationStringLong((media + .getPosition()))); + + if (media.getDuration() != 0) { + txtvLength.setText(Converter.getDurationStringLong(media + .getDuration())); + float progress = ((float) media.getPosition()) + / media.getDuration(); + sbPosition.setProgress((int) (progress * sbPosition.getMax())); } - mediaInfoLoaded = true; } } @@ -545,25 +355,12 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity // BUTTON SETUP - butPlay.setOnClickListener(playbuttonListener); + butPlay.setOnClickListener(controller.newOnPlayButtonClickListener()); - butFF.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (status == PlayerStatus.PLAYING) { - playbackService.seekDelta(DEFAULT_SEEK_DELTA); - } - } - }); + butFF.setOnClickListener(controller.newOnFFButtonClickListener()); + + butRev.setOnClickListener(controller.newOnRevButtonClickListener()); - butRev.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (status == PlayerStatus.PLAYING) { - playbackService.seekDelta(-DEFAULT_SEEK_DELTA); - } - } - }); } void handleError(int errorCode) { @@ -582,100 +379,24 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity errorDialog.create().show(); } - /** - * Called when connection to playback service has been established or - * information has to be refreshed - */ - void queryService() { - if (AppConfig.DEBUG) - Log.d(TAG, "Querying service info"); - if (playbackService != null) { - status = playbackService.getStatus(); - media = playbackService.getMedia(); - invalidateOptionsMenu(); - - setupGUI(); - handleStatus(); - - } else { - Log.e(TAG, - "queryService() was called without an existing connection to playbackservice"); - } - } - - /** Refreshes the current position of the media file that is playing. */ - public class MediaPositionObserver extends - AsyncTask<MediaPlayer, Void, Void> { - - private static final String TAG = "MediaPositionObserver"; - private static final int WAITING_INTERVALL = 1000; - private MediaPlayer player; - - @Override - protected void onCancelled() { - if (AppConfig.DEBUG) - Log.d(TAG, "Task was cancelled"); - } - - @Override - protected Void doInBackground(MediaPlayer... p) { - if (AppConfig.DEBUG) - Log.d(TAG, "Background Task started"); - player = p[0]; - try { - while (player.isPlaying() && !isCancelled()) { - try { - Thread.sleep(WAITING_INTERVALL); - } catch (InterruptedException e) { - if (AppConfig.DEBUG) - Log.d(TAG, - "Thread was interrupted while waiting. Finishing now"); - return null; - } - publishProgress(); - - } - } catch (IllegalStateException e) { - if (AppConfig.DEBUG) - Log.d(TAG, "player is in illegal state, exiting now"); - } - if (AppConfig.DEBUG) - Log.d(TAG, "Background Task finished"); - return null; - } - } - - // OnSeekbarChangeListener - private int duration; - private float prog; + float prog; + int duration; @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - if (fromUser && PlaybackService.isRunning) { - prog = progress / ((float) seekBar.getMax()); - duration = playbackService.getPlayer().getDuration(); - txtvPosition.setText(Converter - .getDurationStringLong((int) (prog * duration))); - } - + controller.onSeekBarProgressChanged(seekBar, progress, fromUser, prog, + duration, txtvPosition); } @Override public void onStartTrackingTouch(SeekBar seekBar) { - // interrupt position Observer, restart later - if (positionObserver != null) { - positionObserver.cancel(true); - positionObserver = null; - } + controller.onSeekBarStartTrackingTouch(seekBar); } @Override public void onStopTrackingTouch(SeekBar seekBar) { - if (PlaybackService.isRunning) { - playbackService.seek((int) (prog * duration)); - setupPositionObserver(); - } + controller.onSeekBarStopTrackingTouch(seekBar, prog, duration); } } diff --git a/src/de/danoeh/antennapod/activity/VideoplayerActivity.java b/src/de/danoeh/antennapod/activity/VideoplayerActivity.java index ddeef7ef0..d8a03b9d9 100644 --- a/src/de/danoeh/antennapod/activity/VideoplayerActivity.java +++ b/src/de/danoeh/antennapod/activity/VideoplayerActivity.java @@ -18,7 +18,6 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.service.PlaybackService; import de.danoeh.antennapod.service.PlayerStatus; - /** Activity for playing audio files. */ public class VideoplayerActivity extends MediaplayerActivity implements SurfaceHolder.Callback { @@ -44,19 +43,6 @@ public class VideoplayerActivity extends MediaplayerActivity implements if (videoControlsToggler != null) { videoControlsToggler.cancel(true); } - if (PlaybackService.isRunning && playbackService != null - && PlaybackService.isPlayingVideo()) { - playbackService.pause(true); - } - } - - @Override - protected void onStop() { - super.onStop(); - if (PlaybackService.isRunning && playbackService != null - && PlaybackService.isPlayingVideo()) { - playbackService.stop(); - } } @Override @@ -66,7 +52,6 @@ public class VideoplayerActivity extends MediaplayerActivity implements videoview = (VideoView) findViewById(R.id.videoview); progressIndicator = (ProgressBar) findViewById(R.id.progressIndicator); videoview.getHolder().addCallback(this); - videoview.setOnClickListener(playbuttonListener); videoview.setOnTouchListener(onVideoviewTouched); setupVideoControlsToggler(); @@ -80,7 +65,7 @@ public class VideoplayerActivity extends MediaplayerActivity implements if (AppConfig.DEBUG) Log.d(TAG, "Videosurface already created, setting videosurface now"); - playbackService.setVideoSurface(videoview.getHolder()); + controller.setVideoSurface(videoview.getHolder()); } } @@ -194,9 +179,9 @@ public class VideoplayerActivity extends MediaplayerActivity implements if (AppConfig.DEBUG) Log.d(TAG, "Videoview holder created"); videoSurfaceCreated = true; - if (status == PlayerStatus.AWAITING_VIDEO_SURFACE) { - if (playbackService != null) { - playbackService.setVideoSurface(holder); + if (controller.getStatus() == PlayerStatus.AWAITING_VIDEO_SURFACE) { + if (controller.serviceAvailable()) { + controller.setVideoSurface(holder); } else { Log.e(TAG, "Could'nt attach surface to mediaplayer - reference to service was null"); diff --git a/src/de/danoeh/antennapod/util/PlaybackController.java b/src/de/danoeh/antennapod/util/PlaybackController.java new file mode 100644 index 000000000..94deb0c77 --- /dev/null +++ b/src/de/danoeh/antennapod/util/PlaybackController.java @@ -0,0 +1,565 @@ +package de.danoeh.antennapod.util; + +import de.danoeh.antennapod.AppConfig; +import de.danoeh.antennapod.PodcastApp; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.feed.SimpleChapter; +import de.danoeh.antennapod.service.PlaybackService; +import de.danoeh.antennapod.service.PlayerStatus; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.media.MediaPlayer; +import android.os.AsyncTask; +import android.os.IBinder; +import android.util.Log; +import android.view.SurfaceHolder; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ImageButton; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; +import android.widget.TextView; + +/** Communicates with the playback service. */ +public abstract class PlaybackController { + private static final String TAG = "PlaybackController"; + + /** + * Returned by getPosition() or getDuration() if the playbackService is in + * an invalid state. + */ + public static final int INVALID_TIME = -1; + + static final int DEFAULT_SEEK_DELTA = 30000; + + private Activity activity; + + private PlaybackService playbackService; + private FeedMedia media; + private PlayerStatus status; + + protected MediaPositionObserver positionObserver; + + private boolean mediaInfoLoaded = false; + + public PlaybackController(Activity activity) { + this.activity = activity; + } + + /** + * Creates a new connection to the playbackService. Should be called in the + * activity's onResume() method. + */ + public void init() { + bindToService(); + } + + /** + * Should be called if the PlaybackController is no longer needed, for + * example in the activity's onStop() method. + */ + public void release() { + if (AppConfig.DEBUG) + Log.d(TAG, "Releasing PlaybackController"); + + try { + activity.unregisterReceiver(statusUpdate); + } catch (IllegalArgumentException e) { + // ignore + } + + try { + activity.unregisterReceiver(notificationReceiver); + } catch (IllegalArgumentException e) { + // ignore + } + + try { + activity.unbindService(mConnection); + } catch (IllegalArgumentException e) { + // ignore + } + if (positionObserver != null) { + positionObserver.cancel(true); + } + } + + /** Should be called in the activity's onPause() method. */ + public void pause() { + mediaInfoLoaded = false; + if (playbackService != null && playbackService.isPlayingVideo()) { + playbackService.pause(true); + } + } + + /** + * Tries to establish a connection to the PlaybackService. If it isn't + * running, the PlaybackService will be started with the last played media + * as the arguments of the launch intent. + */ + private void bindToService() { + Intent serviceIntent = new Intent(activity, PlaybackService.class); + boolean bound = false; + if (!PlaybackService.isRunning) { + if (AppConfig.DEBUG) + Log.d(TAG, "Trying to restore last played media"); + SharedPreferences prefs = activity.getApplicationContext() + .getSharedPreferences(PodcastApp.PREF_NAME, 0); + long mediaId = prefs.getLong(PlaybackService.PREF_LAST_PLAYED_ID, + -1); + long feedId = prefs.getLong( + PlaybackService.PREF_LAST_PLAYED_FEED_ID, -1); + if (mediaId != -1 && feedId != -1) { + serviceIntent.putExtra(PlaybackService.EXTRA_FEED_ID, feedId); + serviceIntent.putExtra(PlaybackService.EXTRA_MEDIA_ID, mediaId); + serviceIntent.putExtra( + PlaybackService.EXTRA_START_WHEN_PREPARED, false); + serviceIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, + prefs.getBoolean(PlaybackService.PREF_LAST_IS_STREAM, + true)); + activity.startService(serviceIntent); + bound = activity.bindService(serviceIntent, mConnection, + Context.BIND_AUTO_CREATE); + } else { + if (AppConfig.DEBUG) + Log.d(TAG, "No last played media found"); + status = PlayerStatus.STOPPED; + setupGUI(); + handleStatus(); + } + } else { + bound = activity.bindService(serviceIntent, mConnection, 0); + } + if (AppConfig.DEBUG) + Log.d(TAG, "Result for service binding: " + bound); + } + + public abstract void setupGUI(); + + @SuppressLint("NewApi") + private void setupPositionObserver() { + if (positionObserver == null || positionObserver.isCancelled()) { + if (AppConfig.DEBUG) + Log.d(TAG, "Setting up position observer"); + positionObserver = new MediaPositionObserver() { + + @Override + protected void onProgressUpdate(Void... v) { + super.onProgressUpdate(); + onPositionObserverUpdate(); + } + + }; + if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { + positionObserver.executeOnExecutor( + AsyncTask.THREAD_POOL_EXECUTOR, + playbackService.getPlayer()); + } else { + positionObserver.execute(playbackService.getPlayer()); + } + + } + } + + public abstract void onPositionObserverUpdate(); + + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + playbackService = ((PlaybackService.LocalBinder) service) + .getService(); + + activity.registerReceiver(statusUpdate, new IntentFilter( + PlaybackService.ACTION_PLAYER_STATUS_CHANGED)); + + activity.registerReceiver(notificationReceiver, new IntentFilter( + PlaybackService.ACTION_PLAYER_NOTIFICATION)); + + queryService(); + if (AppConfig.DEBUG) + Log.d(TAG, "Connection to Service established"); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + playbackService = null; + if (AppConfig.DEBUG) + Log.d(TAG, "Disconnected from Service"); + + } + }; + + protected BroadcastReceiver statusUpdate = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (AppConfig.DEBUG) + Log.d(TAG, "Received statusUpdate Intent."); + if (playbackService != null) { + status = playbackService.getStatus(); + handleStatus(); + } else { + Log.w(TAG, + "Couldn't receive status update: playbackService was null"); + } + } + }; + + protected BroadcastReceiver notificationReceiver = new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + int type = intent.getIntExtra( + PlaybackService.EXTRA_NOTIFICATION_TYPE, -1); + int code = intent.getIntExtra( + PlaybackService.EXTRA_NOTIFICATION_CODE, -1); + if (code != -1 && type != -1) { + switch (type) { + case PlaybackService.NOTIFICATION_TYPE_ERROR: + handleError(code); + break; + case PlaybackService.NOTIFICATION_TYPE_BUFFER_UPDATE: + float progress = ((float) code) / 100; + onBufferUpdate(progress); + break; + case PlaybackService.NOTIFICATION_TYPE_RELOAD: + if (positionObserver != null) { + positionObserver.cancel(true); + positionObserver = null; + } + mediaInfoLoaded = false; + onReloadNotification(intent.getIntExtra( + PlaybackService.EXTRA_NOTIFICATION_CODE, -1)); + queryService(); + + break; + case PlaybackService.NOTIFICATION_TYPE_SLEEPTIMER_UPDATE: + onSleepTimerUpdate(); + break; + case PlaybackService.NOTIFICATION_TYPE_BUFFER_START: + onBufferStart(); + break; + case PlaybackService.NOTIFICATION_TYPE_BUFFER_END: + onBufferEnd(); + break; + } + + } else { + if (AppConfig.DEBUG) + Log.d(TAG, "Bad arguments. Won't handle intent"); + } + + } + + }; + + /** Called when the currently displayed information should be refreshed. */ + public abstract void onReloadNotification(int code); + + public abstract void onBufferStart(); + + public abstract void onBufferEnd(); + + public abstract void onBufferUpdate(float progress); + + public abstract void onSleepTimerUpdate(); + + public abstract void handleError(int code); + + /** + * Is called whenever the PlaybackService changes it's status. This method + * should be used to update the GUI or start/cancel AsyncTasks. + */ + private void handleStatus() { + switch (status) { + + case ERROR: + postStatusMsg(R.string.player_error_msg); + break; + case PAUSED: + postStatusMsg(R.string.player_paused_msg); + checkMediaInfoLoaded(); + if (positionObserver != null) { + positionObserver.cancel(true); + positionObserver = null; + } + updatePlayButtonAppearance(R.drawable.av_play); + break; + case PLAYING: + clearStatusMsg(); + checkMediaInfoLoaded(); + setupPositionObserver(); + updatePlayButtonAppearance(R.drawable.av_pause); + break; + case PREPARING: + postStatusMsg(R.string.player_preparing_msg); + checkMediaInfoLoaded(); + if (playbackService != null) { + if (playbackService.isStartWhenPrepared()) { + updatePlayButtonAppearance(R.drawable.av_pause); + } else { + updatePlayButtonAppearance(R.drawable.av_play); + } + } + break; + case STOPPED: + postStatusMsg(R.string.player_stopped_msg); + break; + case PREPARED: + checkMediaInfoLoaded(); + postStatusMsg(R.string.player_ready_msg); + updatePlayButtonAppearance(R.drawable.av_play); + break; + case SEEKING: + postStatusMsg(R.string.player_seeking_msg); + break; + case AWAITING_VIDEO_SURFACE: + onAwaitingVideoSurface(); + break; + } + } + + private void checkMediaInfoLoaded() { + if (!mediaInfoLoaded) { + loadMediaInfo(); + } + mediaInfoLoaded = true; + } + + private void updatePlayButtonAppearance(int resource) { + ImageButton butPlay = getPlayButton(); + butPlay.setImageResource(resource); + } + + public abstract ImageButton getPlayButton(); + + public abstract void postStatusMsg(int msg); + + public abstract void clearStatusMsg(); + + public abstract void loadMediaInfo(); + + public abstract void onAwaitingVideoSurface(); + + /** + * Called when connection to playback service has been established or + * information has to be refreshed + */ + void queryService() { + if (AppConfig.DEBUG) + Log.d(TAG, "Querying service info"); + if (playbackService != null) { + status = playbackService.getStatus(); + media = playbackService.getMedia(); + onServiceQueried(); + + setupGUI(); + handleStatus(); + + } else { + Log.e(TAG, + "queryService() was called without an existing connection to playbackservice"); + } + } + + public abstract void onServiceQueried(); + + /** Refreshes the current position of the media file that is playing. */ + public class MediaPositionObserver extends + AsyncTask<MediaPlayer, Void, Void> { + + private static final String TAG = "MediaPositionObserver"; + private static final int WAITING_INTERVALL = 1000; + private MediaPlayer player; + + @Override + protected void onCancelled() { + if (AppConfig.DEBUG) + Log.d(TAG, "Task was cancelled"); + } + + @Override + protected Void doInBackground(MediaPlayer... p) { + if (AppConfig.DEBUG) + Log.d(TAG, "Background Task started"); + player = p[0]; + try { + while (player.isPlaying() && !isCancelled()) { + try { + Thread.sleep(WAITING_INTERVALL); + } catch (InterruptedException e) { + if (AppConfig.DEBUG) + Log.d(TAG, + "Thread was interrupted while waiting. Finishing now"); + return null; + } + publishProgress(); + + } + } catch (IllegalStateException e) { + if (AppConfig.DEBUG) + Log.d(TAG, "player is in illegal state, exiting now"); + } + if (AppConfig.DEBUG) + Log.d(TAG, "Background Task finished"); + return null; + } + } + + /** + * Should be used by classes which implement the OnSeekBarChanged interface. + */ + public void onSeekBarProgressChanged(SeekBar seekBar, int progress, + boolean fromUser, float prog, int duration, TextView txtvPosition) { + if (fromUser && PlaybackService.isRunning) { + prog = progress / ((float) seekBar.getMax()); + duration = playbackService.getPlayer().getDuration(); + txtvPosition.setText(Converter + .getDurationStringLong((int) (prog * duration))); + } + + } + + /** + * Should be used by classes which implement the OnSeekBarChanged interface. + */ + public void onSeekBarStartTrackingTouch(SeekBar seekBar) { + // interrupt position Observer, restart later + if (positionObserver != null) { + positionObserver.cancel(true); + positionObserver = null; + } + } + + /** + * Should be used by classes which implement the OnSeekBarChanged interface. + */ + public void onSeekBarStopTrackingTouch(SeekBar seekBar, float prog, int duration) { + if (PlaybackService.isRunning) { + playbackService.seek((int) (prog * duration)); + setupPositionObserver(); + } + } + + public OnClickListener newOnPlayButtonClickListener() { + return new OnClickListener() { + @Override + public void onClick(View v) { + if (playbackService != null) { + switch (status) { + case PLAYING: + playbackService.pause(true); + break; + case PAUSED: + case PREPARED: + playbackService.play(); + break; + case PREPARING: + playbackService.setStartWhenPrepared(!playbackService + .isStartWhenPrepared()); + } + } else { + Log.w(TAG, + "Play/Pause button was pressed, but playbackservice was null!"); + } + } + + }; + } + + public OnClickListener newOnRevButtonClickListener() { + return new OnClickListener() { + @Override + public void onClick(View v) { + if (status == PlayerStatus.PLAYING) { + playbackService.seekDelta(-DEFAULT_SEEK_DELTA); + } + } + }; + } + + public OnClickListener newOnFFButtonClickListener() { + return new OnClickListener() { + @Override + public void onClick(View v) { + if (status == PlayerStatus.PLAYING) { + playbackService.seekDelta(DEFAULT_SEEK_DELTA); + } + } + }; + } + + public boolean serviceAvailable() { + return playbackService != null; + } + + public int getPosition() { + if (playbackService != null) { + return playbackService.getPlayer().getCurrentPosition(); + } else { + return INVALID_TIME; + } + } + + public int getDuration() { + if (playbackService != null) { + return playbackService.getPlayer().getDuration(); + } else { + return INVALID_TIME; + } + } + + public FeedMedia getMedia() { + return media; + } + + public boolean sleepTimerActive() { + return playbackService != null && playbackService.sleepTimerActive(); + } + + public boolean sleepTimerNotActive() { + return playbackService != null && !playbackService.sleepTimerActive(); + } + + public void disableSleepTimer() { + if (playbackService != null) { + playbackService.disableSleepTimer(); + } + } + + public long getSleepTimerTimeLeft() { + if (playbackService != null) { + return playbackService.getSleepTimerTimeLeft(); + } else { + return INVALID_TIME; + } + } + + public void setSleepTimer(long time) { + if (playbackService != null) { + playbackService.setSleepTimer(time); + } + } + + public void seekToChapter(SimpleChapter chapter) { + if (playbackService != null) { + playbackService.seekToChapter(chapter); + } + } + + public void setVideoSurface(SurfaceHolder holder) { + if (playbackService != null) { + playbackService.setVideoSurface(holder); + } + } + + public PlayerStatus getStatus() { + return status; + } +} |