Rewrote most parts of PlaybackService and Mediaplayer Activity

This commit is contained in:
daniel oeh
2012-06-22 13:49:01 +02:00
parent 940f133e4c
commit 524161783a
8 changed files with 306 additions and 143 deletions

View File

@ -8,6 +8,7 @@
<item android:id="@+id/mark_unread_item" android:title="@string/mark_unread_label" android:visible="false" android:showAsAction="collapseActionView"></item> <item android:id="@+id/mark_unread_item" android:title="@string/mark_unread_label" android:visible="false" android:showAsAction="collapseActionView"></item>
<item android:id="@+id/add_to_queue_item" android:title="@string/add_to_queue_label" android:visible="false" android:showAsAction="collapseActionView"></item> <item android:id="@+id/add_to_queue_item" android:title="@string/add_to_queue_label" android:visible="false" android:showAsAction="collapseActionView"></item>
<item android:id="@+id/remove_from_queue_item" android:title="@string/remove_from_queue_label" android:visible="false" android:showAsAction="collapseActionView"></item> <item android:id="@+id/remove_from_queue_item" android:title="@string/remove_from_queue_label" android:visible="false" android:showAsAction="collapseActionView"></item>
<item android:id="@+id/stream_item" android:title="@string/stream_label" android:visible="false" android:showAsAction="collapseActionView"></item>
</menu> </menu>

View File

@ -35,7 +35,7 @@
<!-- Mediaplayer status Messages --> <!-- Mediaplayer status Messages -->
<string name="player_paused_msg">Paused</string> <string name="player_paused_msg">Paused</string>
<string name="player_error_msg">Error!</string> <string name="player_error_msg">Error!</string>
<string name="player_stopped_msg">Stopped</string> <string name="player_stopped_msg">No media playing</string>
<string name="player_preparing_msg">Preparing...</string> <string name="player_preparing_msg">Preparing...</string>
<string name="mark_read_label">Mark read</string> <string name="mark_read_label">Mark read</string>
<string name="mark_unread_label">Mark unread</string> <string name="mark_unread_label">Mark unread</string>
@ -51,4 +51,6 @@
<string name="show_player_label">Show player</string> <string name="show_player_label">Show player</string>
<string name="add_to_queue_label">Add to Queue</string> <string name="add_to_queue_label">Add to Queue</string>
<string name="remove_from_queue_label">Remove from Queue</string> <string name="remove_from_queue_label">Remove from Queue</string>
<string name="player_ready_msg">Ready</string>
<string name="stream_label">Stream</string>
</resources> </resources>

View File

@ -6,6 +6,8 @@ import android.app.Application;
public class PodcastApp extends Application { public class PodcastApp extends Application {
private static final String TAG = "PodcastApp"; private static final String TAG = "PodcastApp";
public static final String PREF_NAME = "PodfetcherPrefs";
private static PodcastApp singleton; private static PodcastApp singleton;

View File

@ -66,7 +66,7 @@ public class ItemviewActivity extends SherlockActivity {
butPlay.setOnClickListener(new View.OnClickListener() { butPlay.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
manager.playMedia(v.getContext(), item.getMedia()); //manager.playMedia(v.getContext(), item.getMedia());
} }
}); });

View File

@ -6,6 +6,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.media.MediaPlayer; import android.media.MediaPlayer;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
@ -22,7 +23,9 @@ import android.widget.TextView;
import com.actionbarsherlock.app.SherlockActivity; import com.actionbarsherlock.app.SherlockActivity;
import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.Menu;
import de.podfetcher.PodcastApp;
import de.podfetcher.R; import de.podfetcher.R;
import de.podfetcher.feed.FeedManager;
import de.podfetcher.feed.FeedMedia; import de.podfetcher.feed.FeedMedia;
import de.podfetcher.service.PlaybackService; import de.podfetcher.service.PlaybackService;
import de.podfetcher.service.PlayerStatus; import de.podfetcher.service.PlayerStatus;
@ -30,16 +33,18 @@ import de.podfetcher.util.Converter;
public class MediaplayerActivity extends SherlockActivity { public class MediaplayerActivity extends SherlockActivity {
private final String TAG = "MediaplayerActivity"; private final String TAG = "MediaplayerActivity";
private static final int DEFAULT_SEEK_DELTA = 30000; // Seek-Delta to use when using FF or Rev Buttons private static final int DEFAULT_SEEK_DELTA = 30000; // Seek-Delta to use
// when using FF or
// Rev Buttons
private PlaybackService playbackService; private PlaybackService playbackService;
private MediaPositionObserver positionObserver; private MediaPositionObserver positionObserver;
private FeedMedia media; private FeedMedia media;
private PlayerStatus status; private PlayerStatus status;
private FeedManager manager;
// Widgets // Widgets
private ImageView imgvCover; private ImageView imgvCover;
private TextView txtvStatus; private TextView txtvStatus;
@ -49,7 +54,7 @@ public class MediaplayerActivity extends SherlockActivity {
private ImageButton butPlay; private ImageButton butPlay;
private ImageButton butRev; private ImageButton butRev;
private ImageButton butFF; private ImageButton butFF;
@Override @Override
protected void onStop() { protected void onStop() {
super.onStop(); super.onStop();
@ -66,15 +71,13 @@ public class MediaplayerActivity extends SherlockActivity {
// TODO Auto-generated method stub // TODO Auto-generated method stub
return super.onCreateOptionsMenu(menu); return super.onCreateOptionsMenu(menu);
} }
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
Log.d(TAG, "Resuming Activity"); Log.d(TAG, "Resuming Activity");
bindToService(); bindToService();
registerReceiver(statusUpdate, new IntentFilter(PlaybackService.ACTION_PLAYER_STATUS_CHANGED));
} }
@Override @Override
@ -82,22 +85,47 @@ public class MediaplayerActivity extends SherlockActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
Log.d(TAG, "Creating Activity"); Log.d(TAG, "Creating Activity");
this.setContentView(R.layout.mediaplayer_activity); this.setContentView(R.layout.mediaplayer_activity);
manager = FeedManager.getInstance();
setupGUI(); setupGUI();
bindToService(); bindToService();
registerReceiver(statusUpdate, new IntentFilter(PlaybackService.ACTION_PLAYER_STATUS_CHANGED));
} }
private void bindToService() { private void bindToService() {
if(!bindService(new Intent(this, PlaybackService.class), mConnection, 0)) { Intent serviceIntent = new Intent(this, PlaybackService.class);
status = PlayerStatus.STOPPED; boolean bound = false;
handleStatus(); if (!PlaybackService.isRunning) {
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 {
Log.d(TAG, "No last played media found");
status = PlayerStatus.STOPPED;
handleStatus();
}
} else {
bound = bindService(serviceIntent, mConnection, 0);
} }
Log.d(TAG, "Result for service binding: " + bound);
} }
private void handleStatus() { private void handleStatus() {
switch (status) { switch (status) {
case ERROR: case ERROR:
setStatusMsg(R.string.player_error_msg, View.VISIBLE); setStatusMsg(R.string.player_error_msg, View.VISIBLE);
handleError(); handleError();
@ -120,51 +148,73 @@ public class MediaplayerActivity extends SherlockActivity {
case PREPARING: case PREPARING:
setStatusMsg(R.string.player_preparing_msg, View.VISIBLE); setStatusMsg(R.string.player_preparing_msg, View.VISIBLE);
break; break;
case STOPPED:
setStatusMsg(R.string.player_stopped_msg, View.VISIBLE);
imgvCover.setImageBitmap(null);
break;
case PREPARED:
loadMediaInfo();
setStatusMsg(R.string.player_ready_msg, View.VISIBLE);
butPlay.setImageResource(android.R.drawable.ic_media_play);
} }
} }
private void setStatusMsg(int resId, int visibility) { private void setStatusMsg(int resId, int visibility) {
if(visibility == View.VISIBLE) { if (visibility == View.VISIBLE) {
txtvStatus.setText(resId); txtvStatus.setText(resId);
} }
txtvStatus.setVisibility(visibility); txtvStatus.setVisibility(visibility);
} }
private void setupPositionObserver() { private void setupPositionObserver() {
if (positionObserver == null) { if (positionObserver == null || positionObserver.isCancelled()) {
positionObserver = new MediaPositionObserver() { positionObserver = new MediaPositionObserver() {
@Override @Override
protected void onProgressUpdate(Integer... values) { protected void onProgressUpdate(Void... v) {
super.onProgressUpdate(values); super.onProgressUpdate();
txtvPosition.setText( txtvPosition.setText(Converter
Converter.getDurationStringLong(values[0])); .getDurationStringLong(playbackService.getPlayer()
.getCurrentPosition()));
float progress = ((float) values[0]) / getDuration();
sbPosition.setProgress((int) (progress * 100)); updateProgressbarPosition();
} }
}; };
positionObserver.execute(playbackService.getPlayer()); positionObserver.execute(playbackService.getPlayer());
} }
} }
private void updateProgressbarPosition() {
MediaPlayer player = playbackService.getPlayer();
float progress = ((float) player.getCurrentPosition())
/ player.getDuration();
sbPosition.setProgress((int) (progress * sbPosition.getMax()));
}
private void loadMediaInfo() { private void loadMediaInfo() {
if (media != null) { if (media != null) {
MediaPlayer player = playbackService.getPlayer(); MediaPlayer player = playbackService.getPlayer();
getSupportActionBar().setSubtitle(media.getItem().getTitle()); getSupportActionBar().setSubtitle(media.getItem().getTitle());
getSupportActionBar().setTitle( getSupportActionBar()
media.getItem().getFeed().getTitle()); .setTitle(media.getItem().getFeed().getTitle());
imgvCover.setImageBitmap( imgvCover.setImageBitmap(media.getItem().getFeed().getImage()
media.getItem().getFeed().getImage().getImageBitmap()); .getImageBitmap());
txtvPosition.setText(Converter.getDurationStringLong((player.getCurrentPosition()))); txtvPosition.setText(Converter.getDurationStringLong((player
txtvLength.setText(Converter.getDurationStringLong(player.getDuration())); .getCurrentPosition())));
txtvLength.setText(Converter.getDurationStringLong(player
.getDuration()));
if (playbackService != null) {
updateProgressbarPosition();
} else {
sbPosition.setProgress(0);
}
} }
} }
private void setupGUI() { private void setupGUI() {
imgvCover = (ImageView) findViewById(R.id.imgvCover); imgvCover = (ImageView) findViewById(R.id.imgvCover);
txtvPosition = (TextView) findViewById(R.id.txtvPosition); txtvPosition = (TextView) findViewById(R.id.txtvPosition);
@ -174,22 +224,23 @@ public class MediaplayerActivity extends SherlockActivity {
butPlay = (ImageButton) findViewById(R.id.butPlay); butPlay = (ImageButton) findViewById(R.id.butPlay);
butRev = (ImageButton) findViewById(R.id.butRev); butRev = (ImageButton) findViewById(R.id.butRev);
butFF = (ImageButton) findViewById(R.id.butFF); butFF = (ImageButton) findViewById(R.id.butFF);
sbPosition.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { sbPosition.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
int duration; int duration;
float prog; float prog;
@Override @Override
public void onProgressChanged(SeekBar seekBar, int progress, public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) { boolean fromUser) {
if (fromUser) { if (fromUser) {
prog = progress / 100.0f; prog = progress / 100.0f;
duration = playbackService.getPlayer().getDuration(); duration = playbackService.getPlayer().getDuration();
txtvPosition.setText(Converter.getDurationStringLong((int) (prog * duration))); txtvPosition.setText(Converter
.getDurationStringLong((int) (prog * duration)));
} }
} }
@Override @Override
public void onStartTrackingTouch(SeekBar seekBar) { public void onStartTrackingTouch(SeekBar seekBar) {
// interrupt position Observer, restart later // interrupt position Observer, restart later
@ -198,25 +249,26 @@ public class MediaplayerActivity extends SherlockActivity {
positionObserver = null; positionObserver = null;
} }
} }
@Override @Override
public void onStopTrackingTouch(SeekBar seekBar) { public void onStopTrackingTouch(SeekBar seekBar) {
playbackService.seek((int) (prog * duration)); playbackService.seek((int) (prog * duration));
setupPositionObserver(); setupPositionObserver();
} }
}); });
butPlay.setOnClickListener(new OnClickListener() { butPlay.setOnClickListener(new OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (status == PlayerStatus.PLAYING) { if (status == PlayerStatus.PLAYING) {
playbackService.pause(); playbackService.pause();
} else if (status == PlayerStatus.PAUSED) { } else if (status == PlayerStatus.PAUSED
|| status == PlayerStatus.PREPARED) {
playbackService.play(); playbackService.play();
} }
} }
}); });
butFF.setOnClickListener(new OnClickListener() { butFF.setOnClickListener(new OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -225,7 +277,7 @@ public class MediaplayerActivity extends SherlockActivity {
} }
} }
}); });
butRev.setOnClickListener(new OnClickListener() { butRev.setOnClickListener(new OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -235,17 +287,19 @@ public class MediaplayerActivity extends SherlockActivity {
} }
}); });
} }
private void handleError() { private void handleError() {
// TODO implement // TODO implement
} }
private ServiceConnection mConnection = new ServiceConnection() { private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) { public void onServiceConnected(ComponentName className, IBinder service) {
playbackService = ((PlaybackService.LocalBinder)service).getService(); playbackService = ((PlaybackService.LocalBinder) service)
.getService();
status = playbackService.getStatus(); status = playbackService.getStatus();
media = playbackService.getMedia(); media = playbackService.getMedia();
registerReceiver(statusUpdate, new IntentFilter(
PlaybackService.ACTION_PLAYER_STATUS_CHANGED));
handleStatus(); handleStatus();
Log.d(TAG, "Connection to Service established"); Log.d(TAG, "Connection to Service established");
} }
@ -254,55 +308,48 @@ public class MediaplayerActivity extends SherlockActivity {
public void onServiceDisconnected(ComponentName name) { public void onServiceDisconnected(ComponentName name) {
playbackService = null; playbackService = null;
Log.d(TAG, "Disconnected from Service"); Log.d(TAG, "Disconnected from Service");
} }
}; };
private BroadcastReceiver statusUpdate = new BroadcastReceiver() { private BroadcastReceiver statusUpdate = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Received statusUpdate Intent."); Log.d(TAG, "Received statusUpdate Intent.");
status = playbackService.getStatus(); status = playbackService.getStatus();
handleStatus(); handleStatus();
} }
}; };
/** Refreshes the current position of the media file that is playing. */ /** Refreshes the current position of the media file that is playing. */
public class MediaPositionObserver extends AsyncTask<MediaPlayer, Integer, Boolean> { public class MediaPositionObserver extends
AsyncTask<MediaPlayer, Void, Void> {
private static final int WAITING_INTERVALL = 1000; private static final int WAITING_INTERVALL = 1000;
private MediaPlayer player; private MediaPlayer player;
private int duration;
@Override @Override
protected void onCancelled(Boolean result) { protected void onCancelled() {
Log.d(TAG, "Task was cancelled"); Log.d(TAG, "Task was cancelled");
} }
@Override @Override
protected Boolean doInBackground(MediaPlayer... p) { protected Void doInBackground(MediaPlayer... p) {
Log.d(TAG, "Background Task started"); Log.d(TAG, "Background Task started");
player = p[0]; player = p[0];
duration = player.getDuration();
while (player.isPlaying() && !isCancelled()) {
while(player.isPlaying() && !isCancelled()) {
try { try {
Thread.sleep(WAITING_INTERVALL); Thread.sleep(WAITING_INTERVALL);
} catch(InterruptedException e) { } catch (InterruptedException e) {
Log.d(TAG, "Thread was interrupted while waiting. Finishing now"); Log.d(TAG,
return false; "Thread was interrupted while waiting. Finishing now");
} }
publishProgress(player.getCurrentPosition()); publishProgress();
} }
Log.d(TAG, "Background Task finished"); Log.d(TAG, "Background Task finished");
return true; return null;
}
public int getDuration() {
return duration;
} }
} }
} }

View File

@ -34,7 +34,7 @@ public class FeedManager {
/** Contains completed Download status entries */ /** Contains completed Download status entries */
private ArrayList<DownloadStatus> downloadLog; private ArrayList<DownloadStatus> downloadLog;
/** Contains the queue of items to be played. */ /** Contains the queue of items to be played. */
private ArrayList<FeedItem> queue; private ArrayList<FeedItem> queue;
@ -59,18 +59,31 @@ public class FeedManager {
/** /**
* Play FeedMedia and start the playback service + launch Mediaplayer * Play FeedMedia and start the playback service + launch Mediaplayer
* Activity. * Activity.
*
* @param context
* for starting the playbackservice
* @param media
* that shall be played
* @param showPlayer
* if Mediaplayer activity shall be started
* @param startWhenPrepared
* if Mediaplayer shall be started after it has been prepared
*/ */
public void playMedia(Context context, FeedMedia media) { public void playMedia(Context context, FeedMedia media, boolean showPlayer,
boolean startWhenPrepared, boolean shouldStream) {
// Start playback Service // Start playback Service
Intent launchIntent = new Intent(context, PlaybackService.class); Intent launchIntent = new Intent(context, PlaybackService.class);
launchIntent.putExtra(PlaybackService.EXTRA_MEDIA_ID, media.getId()); launchIntent.putExtra(PlaybackService.EXTRA_MEDIA_ID, media.getId());
launchIntent.putExtra(PlaybackService.EXTRA_FEED_ID, media.getItem() launchIntent.putExtra(PlaybackService.EXTRA_FEED_ID, media.getItem()
.getFeed().getId()); .getFeed().getId());
launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED, startWhenPrepared);
launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, shouldStream);
context.startService(launchIntent); context.startService(launchIntent);
if (showPlayer) {
// Launch Mediaplayer // Launch Mediaplayer
Intent playerIntent = new Intent(context, MediaplayerActivity.class); Intent playerIntent = new Intent(context, MediaplayerActivity.class);
context.startActivity(playerIntent); context.startActivity(playerIntent);
}
} }
/** Remove media item that has been downloaded. */ /** Remove media item that has been downloaded. */
@ -88,14 +101,15 @@ public class FeedManager {
Log.d(TAG, "Deleting File. Result: " + result); Log.d(TAG, "Deleting File. Result: " + result);
return result; return result;
} }
/** Remove a feed with all its items and media files and its image. */ /** Remove a feed with all its items and media files and its image. */
public boolean deleteFeed(Context context, Feed feed) { public boolean deleteFeed(Context context, Feed feed) {
PodDBAdapter adapter = new PodDBAdapter(context); PodDBAdapter adapter = new PodDBAdapter(context);
// delete image file // delete image file
if (feed.getImage() != null) { if (feed.getImage() != null) {
if (feed.getImage().isDownloaded() && feed.getImage().getFile_url() == null) { if (feed.getImage().isDownloaded()
&& feed.getImage().getFile_url() == null) {
File imageFile = new File(feed.getImage().getFile_url()); File imageFile = new File(feed.getImage().getFile_url());
imageFile.delete(); imageFile.delete();
} }
@ -112,7 +126,7 @@ public class FeedManager {
} }
adapter.removeFeed(feed); adapter.removeFeed(feed);
return feeds.remove(feed); return feeds.remove(feed);
} }
/** /**
@ -145,13 +159,13 @@ public class FeedManager {
} }
return adapter.setDownloadStatus(status); return adapter.setDownloadStatus(status);
} }
public void addQueueItem(Context context, FeedItem item) { public void addQueueItem(Context context, FeedItem item) {
PodDBAdapter adapter = new PodDBAdapter(context); PodDBAdapter adapter = new PodDBAdapter(context);
queue.add(item); queue.add(item);
adapter.setQueue(queue); adapter.setQueue(queue);
} }
public void removeQueueItem(Context context, FeedItem item) { public void removeQueueItem(Context context, FeedItem item) {
boolean removed = queue.remove(item); boolean removed = queue.remove(item);
if (removed) { if (removed) {
@ -159,11 +173,15 @@ public class FeedManager {
adapter.setQueue(queue); adapter.setQueue(queue);
} }
} }
public boolean isInQueue(FeedItem item) { public boolean isInQueue(FeedItem item) {
return queue.contains(item); return queue.contains(item);
} }
public FeedItem getFirstQueueItem() {
return queue.get(0);
}
private void addNewFeed(Context context, Feed feed) { private void addNewFeed(Context context, Feed feed) {
feeds.add(feed); feeds.add(feed);
feed.setId(setFeed(context, feed)); feed.setId(setFeed(context, feed));
@ -447,16 +465,19 @@ public class FeedManager {
} }
adapter.close(); adapter.close();
} }
private void extractQueueFromCursor(Context context) { private void extractQueueFromCursor(Context context) {
PodDBAdapter adapter = new PodDBAdapter(context); PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open(); adapter.open();
Cursor cursor = adapter.getQueueCursor(); Cursor cursor = adapter.getQueueCursor();
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
do { do {
int index = cursor.getInt(cursor.getColumnIndex(PodDBAdapter.KEY_ID)); int index = cursor.getInt(cursor
Feed feed = getFeed(cursor.getLong(cursor.getColumnIndex(PodDBAdapter.KEY_FEED))); .getColumnIndex(PodDBAdapter.KEY_ID));
FeedItem item = getFeedItem(cursor.getColumnIndex(PodDBAdapter.KEY_FEEDITEM), feed); Feed feed = getFeed(cursor.getLong(cursor
.getColumnIndex(PodDBAdapter.KEY_FEED)));
FeedItem item = getFeedItem(
cursor.getColumnIndex(PodDBAdapter.KEY_FEEDITEM), feed);
queue.add(index, item); queue.add(index, item);
} while (cursor.moveToNext()); } while (cursor.moveToNext());
} }

View File

@ -9,6 +9,7 @@ import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.media.AudioManager; import android.media.AudioManager;
@ -21,6 +22,7 @@ import android.os.AsyncTask;
import android.os.Binder; import android.os.Binder;
import android.os.IBinder; import android.os.IBinder;
import de.podfetcher.PodcastApp;
import de.podfetcher.activity.MediaplayerActivity; import de.podfetcher.activity.MediaplayerActivity;
import de.podfetcher.feed.FeedMedia; import de.podfetcher.feed.FeedMedia;
import de.podfetcher.feed.Feed; import de.podfetcher.feed.Feed;
@ -28,22 +30,46 @@ import de.podfetcher.feed.FeedManager;
/** Controls the MediaPlayer that plays a FeedMedia-file */ /** Controls the MediaPlayer that plays a FeedMedia-file */
public class PlaybackService extends Service { public class PlaybackService extends Service {
/** Logging tag */ /** Logging tag */
private static final String TAG = "PlaybackService"; private static final String TAG = "PlaybackService";
/** Contains the id of the media that was played last. */
public static final String PREF_LAST_PLAYED_ID = "de.podfetcher.preferences.lastPlayedId";
/** Contains the feed id of the last played item. */
public static final String PREF_LAST_PLAYED_FEED_ID = "de.podfetcher.preferences.lastPlayedFeedId";
/** True if last played media was streamed. */
public static final String PREF_LAST_IS_STREAM = "de.podfetcher.preferences.lastIsStream";
/** Contains the id of the FeedMedia object. */ /** Contains the id of the FeedMedia object. */
public static final String EXTRA_MEDIA_ID = "extra.de.podfetcher.service.mediaId"; public static final String EXTRA_MEDIA_ID = "extra.de.podfetcher.service.mediaId";
/** Contains the id of the Feed object of the FeedMedia. */ /** Contains the id of the Feed object of the FeedMedia. */
public static final String EXTRA_FEED_ID = "extra.de.podfetcher.service.feedId"; public static final String EXTRA_FEED_ID = "extra.de.podfetcher.service.feedId";
/** True if media should be streamed. */
public static final String EXTRA_SHOULD_STREAM = "extra.de.podfetcher.service.shouldStream";
/**
* True if playback should be started immediately after media has been
* prepared.
*/
public static final String EXTRA_START_WHEN_PREPARED = "extra.de.podfetcher.service.startWhenPrepared";
public static final String ACTION_PLAYER_STATUS_CHANGED = "action.de.podfetcher.service.playerStatusChanged"; public static final String ACTION_PLAYER_STATUS_CHANGED = "action.de.podfetcher.service.playerStatusChanged";
/** Is true if service is running. */
public static boolean isRunning = false;
private static final int NOTIFICATION_ID = 1; private static final int NOTIFICATION_ID = 1;
private NotificationCompat.Builder notificationBuilder; private NotificationCompat.Builder notificationBuilder;
private AudioManager audioManager; private AudioManager audioManager;
private MediaPlayer player; private MediaPlayer player;
private FeedMedia media; private FeedMedia media;
private Feed feed; private Feed feed;
/** True if media should be streamed (Extracted from Intent Extra) . */
private boolean shouldStream;
private boolean startWhenPrepared;
private FeedManager manager; private FeedManager manager;
private PlayerStatus status; private PlayerStatus status;
private PositionSaver positionSaver; private PositionSaver positionSaver;
@ -59,9 +85,23 @@ public class PlaybackService extends Service {
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
isRunning = true;
status = PlayerStatus.STOPPED;
Log.d(TAG, "Service created."); Log.d(TAG, "Service created.");
audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
manager = FeedManager.getInstance(); manager = FeedManager.getInstance();
player = new MediaPlayer();
player.setOnPreparedListener(preparedListener);
player.setOnCompletionListener(completionListener);
}
@Override
public void onDestroy() {
super.onDestroy();
isRunning = false;
Log.d(TAG, "Service is about to be destroyed");
audioManager.abandonAudioFocus(audioFocusChangeListener);
player.release();
} }
@Override @Override
@ -69,10 +109,6 @@ public class PlaybackService extends Service {
return mBinder; return mBinder;
} }
private void setupAudioManager() {
audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
}
private final OnAudioFocusChangeListener audioFocusChangeListener = new OnAudioFocusChangeListener() { private final OnAudioFocusChangeListener audioFocusChangeListener = new OnAudioFocusChangeListener() {
@Override @Override
@ -89,7 +125,8 @@ public class PlaybackService extends Service {
break; break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
Log.d(TAG, "Lost audio focus temporarily. Ducking..."); Log.d(TAG, "Lost audio focus temporarily. Ducking...");
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_LOWER, 0); audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
AudioManager.ADJUST_LOWER, 0);
break; break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
Log.d(TAG, "Lost audio focus temporarily. Pausing..."); Log.d(TAG, "Lost audio focus temporarily. Pausing...");
@ -102,21 +139,39 @@ public class PlaybackService extends Service {
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
long mediaId = intent.getLongExtra(EXTRA_MEDIA_ID, -1); long mediaId = intent.getLongExtra(EXTRA_MEDIA_ID, -1);
long feedId = intent.getLongExtra(EXTRA_FEED_ID, -1); long feedId = intent.getLongExtra(EXTRA_FEED_ID, -1);
boolean playbackType = intent
.getBooleanExtra(EXTRA_SHOULD_STREAM, true);
if (mediaId == -1 || feedId == -1) { if (mediaId == -1 || feedId == -1) {
Log.e(TAG, "Media ID or Feed ID wasn't provided to the Service."); Log.e(TAG, "Media ID or Feed ID wasn't provided to the Service.");
} else { if (media == null || feed == null) {
// Intent values appear to be valid stopSelf();
if (audioManager == null) {
setupAudioManager();
} }
Feed newFeed = manager.getFeed(feedId); // Intent values appear to be valid
FeedMedia newMedia = manager.getFeedMedia(mediaId, newFeed); // check if already playing and playbackType is the same
if (media != null && media != newMedia) { } else if (media == null || mediaId != media.getId()
pause(); || playbackType != shouldStream) {
player.reset(); pause();
player.reset();
if (media == null || mediaId != media.getId()) {
feed = manager.getFeed(feedId);
media = manager.getFeedMedia(mediaId, feed);
}
if (media != null) {
shouldStream = playbackType;
startWhenPrepared = intent.getBooleanExtra(
EXTRA_START_WHEN_PREPARED, false);
try { try {
player.setDataSource(newMedia.getFile_url()); if (shouldStream) {
player.prepare(); player.setDataSource(media.getDownload_url());
setStatus(PlayerStatus.PREPARING);
player.prepareAsync();
} else {
player.setDataSource(media.getFile_url());
setStatus(PlayerStatus.PREPARING);
player.prepare();
}
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
e.printStackTrace(); e.printStackTrace();
} catch (SecurityException e) { } catch (SecurityException e) {
@ -126,19 +181,16 @@ public class PlaybackService extends Service {
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
media = newMedia;
feed = newFeed;
} else if (media == null) { } else {
media = newMedia; Log.e(TAG, "Media is null");
feed = newFeed;
player = MediaPlayer.create(this,
Uri.fromFile(new File(media.getFile_url())));
setStatus(PlayerStatus.PREPARING);
player.setOnPreparedListener(preparedListener);
Log.d(TAG, "Preparing to play file");
} }
} else if (media != null && status != PlayerStatus.PLAYING) {
play();
} else {
Log.w(TAG, "Something went wrong. Shutting down...");
stopSelf();
} }
setupNotification(); setupNotification();
return Service.START_STICKY; return Service.START_STICKY;
@ -168,40 +220,71 @@ public class PlaybackService extends Service {
public void onPrepared(MediaPlayer mp) { public void onPrepared(MediaPlayer mp) {
Log.d(TAG, "Resource prepared"); Log.d(TAG, "Resource prepared");
setStatus(PlayerStatus.PREPARED); setStatus(PlayerStatus.PREPARED);
int focusGained = audioManager.requestAudioFocus( if (startWhenPrepared) {
audioFocusChangeListener, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
if (focusGained == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.d(TAG, "Audiofocus successfully requested");
play(); play();
} else {
Log.d(TAG, "Failed to request Audiofocus");
} }
} }
}; };
private MediaPlayer.OnCompletionListener completionListener = new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
Log.d(TAG, "Playback completed");
positionSaver.cancel(true);
media.setPosition(0);
manager.markItemRead(PlaybackService.this, media.getItem(), true);
if (manager.isInQueue(media.getItem())) {
manager.removeQueueItem(PlaybackService.this, media.getItem());
}
manager.setFeedMedia(PlaybackService.this, media);
setStatus(PlayerStatus.STOPPED);
stopForeground(true);
}
};
public void pause() { public void pause() {
if (player.isPlaying()) { if (player.isPlaying()) {
Log.d(TAG, "Pausing playback."); Log.d(TAG, "Pausing playback.");
player.pause(); player.pause();
saveCurrentPosition(); saveCurrentPosition();
setStatus(PlayerStatus.PAUSED); setStatus(PlayerStatus.PAUSED);
stopForeground(true);
} }
} }
public void play() { public void play() {
if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED) { if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED
Log.d(TAG, "Resuming/Starting playback"); || status == PlayerStatus.STOPPED) {
player.start(); int focusGained = audioManager.requestAudioFocus(
player.seekTo((int) media.getPosition()); audioFocusChangeListener, AudioManager.STREAM_MUSIC,
setStatus(PlayerStatus.PLAYING); AudioManager.AUDIOFOCUS_GAIN);
setupPositionSaver();
} else if (status == PlayerStatus.STOPPED) {
if (focusGained == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.d(TAG, "Audiofocus successfully requested");
Log.d(TAG, "Resuming/Starting playback");
SharedPreferences.Editor editor = getApplicationContext()
.getSharedPreferences(PodcastApp.PREF_NAME, 0).edit();
editor.putLong(PREF_LAST_PLAYED_ID, media.getId());
editor.putLong(PREF_LAST_PLAYED_FEED_ID, feed.getId());
editor.putBoolean(PREF_LAST_IS_STREAM, shouldStream);
editor.commit();
player.start();
player.seekTo((int) media.getPosition());
setStatus(PlayerStatus.PLAYING);
setupPositionSaver();
setupNotification();
} else {
Log.d(TAG, "Failed to request Audiofocus");
}
} }
} }
private void setStatus(PlayerStatus newStatus) { private void setStatus(PlayerStatus newStatus) {
Log.d(TAG, "Setting status to " + newStatus);
status = newStatus; status = newStatus;
sendBroadcast(new Intent(ACTION_PLAYER_STATUS_CHANGED)); sendBroadcast(new Intent(ACTION_PLAYER_STATUS_CHANGED));
} }
@ -279,4 +362,8 @@ public class PlaybackService extends Service {
} }
public boolean isShouldStream() {
return shouldStream;
}
} }

View File

@ -25,6 +25,7 @@ public class FeedItemMenuHandler {
menu.findItem(R.id.remove_item).setVisible(true); menu.findItem(R.id.remove_item).setVisible(true);
} else if (selectedItem.getMedia().getFile_url() == null) { } else if (selectedItem.getMedia().getFile_url() == null) {
menu.findItem(R.id.download_item).setVisible(true); menu.findItem(R.id.download_item).setVisible(true);
menu.findItem(R.id.stream_item).setVisible(true);
} else { } else {
menu.findItem(R.id.cancel_download_item).setVisible(true); menu.findItem(R.id.cancel_download_item).setVisible(true);
} }
@ -53,7 +54,7 @@ public class FeedItemMenuHandler {
break; break;
case R.id.play_item: case R.id.play_item:
manager.playMedia(context, manager.playMedia(context,
selectedItem.getMedia()); selectedItem.getMedia(), true, true, false);
break; break;
case R.id.remove_item: case R.id.remove_item:
manager.deleteFeedMedia(context, manager.deleteFeedMedia(context,
@ -75,6 +76,8 @@ public class FeedItemMenuHandler {
case R.id.remove_from_queue_item: case R.id.remove_from_queue_item:
manager.removeQueueItem(context, selectedItem); manager.removeQueueItem(context, selectedItem);
break; break;
case R.id.stream_item:
manager.playMedia(context, selectedItem.getMedia(), true, true, true);
} }
// Refresh menu state // Refresh menu state