package se.slide.sgu;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.Notification.Builder;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.os.AsyncTask;
import android.os.IBinder;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.widget.Toast;
import se.slide.sgu.db.DatabaseManager;
import se.slide.sgu.model.Content;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/*
import com.augusto.mymediaplayer.R;
import com.augusto.mymediaplayer.model.Track;
import com.augusto.mymediaplayer.repositories.MusicRepository;
*/
// https://github.com/augusto/android-sandbox/blob/master/src/com/augusto/mymediaplayer/services/AudioPlayer.java
public class AudioPlayer extends Service implements OnCompletionListener {
public static final String INTENT_BASE_NAME = "se.slide.sgu.AudioPlayer";
public static final String UPDATE_PLAYLIST = INTENT_BASE_NAME + ".PLAYLIST_UPDATED";
public static final String QUEUE_TRACK = INTENT_BASE_NAME + ".QUEUE_TRACK";
public static final String PAUSE_TRACK = INTENT_BASE_NAME + ".PAUSE_TRACK";
public static final String PLAY_TRACK = INTENT_BASE_NAME + ".PLAY_TRACK";
public static final String QUEUE_ALBUM = INTENT_BASE_NAME + ".QUEUE_ALBUM";
public static final String PLAY_ALBUM = INTENT_BASE_NAME + ".PLAY_ALBUM";
public static final String PLAY_PAUSE_TRACK = INTENT_BASE_NAME + ".PLAY_PAUSE_TRACK";
public static final String HIDE_PLAYER = INTENT_BASE_NAME + ".HIDE_PLAYER";
public static final String EVENT_PLAY_PAUSE = INTENT_BASE_NAME + ".EVENT_PLAY_PAUSE";
private final String TAG = "AudioPlayer";
private List<Content> tracks = new ArrayList<Content>();
//private List<Track> tracks = new ArrayList<Track>(); // this collection should be encapsulated in another class.
//private MusicRepository musicRepository = new MusicRepository();
private MediaPlayer mediaPlayer;
private boolean paused = false;
private AudioPlayerBroadcastReceiver broadcastReceiver = new AudioPlayerBroadcastReceiver();
PhoneStateListener phoneStateListener;
private final int NOTIFICATION_ID = 24; // Meaning of life, eh?
private final int NOTIFICATION_ID_LOW_MEMORY = 11;
private boolean wasPlaying = false;
private UpdateTrackTask updater;
/*
public class AudioPlayerBinder extends Binder {
public AudioPlayer getService() {
Log.v(TAG, "AudioPlayerBinder: getService() called");
return AudioPlayer.this;
}
}
*/
private final IBinder audioPlayerBinder = new LocalBinder<AudioPlayer>(this);
@Override
public IBinder onBind(Intent intent) {
MyLog.v(TAG, "AudioPlayer: onBind() called");
return audioPlayerBinder; // maybe, return new LocalBinder<AudioPlayer>(this);
}
@Override
public void onCreate() {
MyLog.v(TAG, "AudioPlayer: onCreate() called");
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(PLAY_TRACK);
intentFilter.addAction(QUEUE_TRACK);
intentFilter.addAction(PLAY_ALBUM);
intentFilter.addAction(QUEUE_ALBUM);
intentFilter.addAction(PAUSE_TRACK);
intentFilter.addAction(PLAY_PAUSE_TRACK);
intentFilter.addAction(HIDE_PLAYER);
registerReceiver(broadcastReceiver, intentFilter);
// Let's pay attention to incoming calls and pause the player during the call duration
phoneStateListener = new PhoneStateListener() {
@Override
public void onCallStateChanged(int state, String incomingNumber) {
if (state == TelephonyManager.CALL_STATE_RINGING) {
if (mediaPlayer != null && mediaPlayer.isPlaying())
wasPlaying = true;
else
wasPlaying = false;
pause();
}
else if(state == TelephonyManager.CALL_STATE_IDLE) {
if (wasPlaying)
play();
}
super.onCallStateChanged(state, incomingNumber);
}
};
TelephonyManager mgr = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
if(mgr != null) {
mgr.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
}
}
@Override
public void onStart(Intent intent, int startId) {
MyLog.v(TAG, "AudioPlayer: onStart() called, instance=" + this.hashCode());
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
MyLog.v(TAG, "AudioPlayer: onDestroy() called");
unregisterReceiver(broadcastReceiver);
release();
TelephonyManager mgr = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
if(mgr != null) {
mgr.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
}
}
@Override
public void onLowMemory() {
MyLog.wtf(TAG, "AudioPLayer closes due to low memory");
Notification notification = GlobalContext.INSTANCE.buildNotification(getString(R.string.closing_low_memory_ticker), getString(R.string.closing_low_memory_title), getString(R.string.closing_low_memory_text));
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.notify(NOTIFICATION_ID_LOW_MEMORY, notification);
stopSelf();
}
public void onCompletion(MediaPlayer _mediaPlayer) {
MyLog.d(TAG, "Track on completion");
Content track = getCurrentTrack();
if (track != null) {
track.elapsed = track.duration;
DatabaseManager.getInstance().createOrUpdateContent(track);
}
release();
nextTrack();
}
@SuppressLint("NewApi")
private Notification buildNotification(boolean showTicker) {
Content track = getCurrentTrack();
if (track == null) {
release();
return null;
}
Intent i = new Intent(this, StartActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pi = PendingIntent.getActivity(this, 0, i, 0);
// Create intent for our rich actions
Intent pauseIntent = new Intent(PLAY_PAUSE_TRACK);
PendingIntent pendingPauseIntent = PendingIntent.getBroadcast(this, 0, pauseIntent, 0);
Intent hideIntent = new Intent(HIDE_PLAYER);
PendingIntent pendingHideIntent = PendingIntent.getBroadcast(this, 0, hideIntent, 0);
Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.drawable.ic_actionbar_logo);
String title = null;
if (track.friendlyTitle != null && !track.friendlyTitle.isEmpty())
title = track.friendlyTitle;
else
title = track.title;
if (showTicker) {
builder.setTicker(title);
}
else {
builder.setTicker(null);
}
MyLog.v(TAG, "show ticker = " + showTicker);
builder.setContentTitle(title);
builder.setContentText(track.description);
builder.setWhen(System.currentTimeMillis());
builder.setContentIntent(pi);
if (android.os.Build.VERSION.SDK_INT >= 16) {
if (isPlaying())
builder.addAction(R.drawable.ic_action_playback_pause, getString(R.string.pause), pendingPauseIntent);
else {
builder.addAction(R.drawable.ic_action_playback_play, getString(R.string.play), pendingPauseIntent);
builder.addAction(R.drawable.ic_action_halt, getString(R.string.hide), pendingHideIntent);
}
}
Notification notification = null;
if (android.os.Build.VERSION.SDK_INT >= 16) {
notification = builder.build();
}
else {
notification = builder.getNotification();
}
notification.flags |= Notification.FLAG_NO_CLEAR;
return notification;
}
private void startAsForeground(boolean showTicker) {
Notification note = buildNotification(showTicker);
if (note == null)
return;
startForeground(NOTIFICATION_ID, note);
}
private void updateNotification() {
Notification note = buildNotification(false);
if (note == null)
return;
startForeground(NOTIFICATION_ID, note);
}
private void hideNotification() {
MyLog.v(TAG, "Hiding notification");
stopForeground(true);
}
private void release() {
if( mediaPlayer == null) {
return;
}
if (mediaPlayer.isPlaying()) {
mediaPlayer.stop();
}
mediaPlayer.release();
mediaPlayer = null;
wasPlaying = false;
if (updater != null) {
updater.stopped = true;
}
stopForeground(true);
}
public void addTrack(Content track) {
MyLog.v(TAG, "addTrack " + track);
tracks.add(track);
if( tracks.size() == 1) {
play();
}
}
public void play(Content track) {
stop();
tracks.clear();
tracks.add(track);
play();
}
public void play() {
playListUpdated();
if( tracks.size() == 0) {
return;
}
Content track = tracks.get(0);
if( mediaPlayer != null && paused) {
mediaPlayer.start();
paused = false;
updateNotification();
playPauseUpdated();
startUpdater();
return;
} else if( mediaPlayer != null ) {
release();
}
try {
mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(this, track.asUri());
mediaPlayer.prepare();
mediaPlayer.start();
mediaPlayer.setOnCompletionListener(this);
MyLog.d(TAG, "track elapsed = " + track.elapsed + ", duration = " + track.duration);
if (track.elapsed > 0 && track.elapsed != track.duration)
mediaPlayer.seekTo(track.elapsed);
paused = false;
track.duration = mediaPlayer.getDuration();
startAsForeground(true);
playPauseUpdated();
startUpdater();
} catch (IOException ioe) {
MyLog.wtf(TAG,"error trying to play " + track , ioe);
String message = "error trying to play track: " + track + ".\nError: " + ioe.getMessage();
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
}
}
private void startUpdater() {
if (updater == null || updater.stopped) {
updater = new UpdateTrackTask();
updater.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void)null);
}
}
private void playListUpdated() {
Intent updatePlaylistIntent = new Intent(UPDATE_PLAYLIST);
this.sendBroadcast(updatePlaylistIntent);
}
private void playPauseUpdated() {
Intent playPauselistIntent = new Intent(EVENT_PLAY_PAUSE);
this.sendBroadcast(playPauselistIntent);
}
private void nextTrack() {
tracks.remove(0);
play();
}
public Content[] getQueuedTracks() {
Content[] tracksArray = new Content[tracks.size()];
return tracks.toArray(tracksArray);
}
public void stop() {
release();
playPauseUpdated();
}
public boolean isPlaying() {
if(tracks.isEmpty() || mediaPlayer == null) {
return false;
}
return mediaPlayer.isPlaying();
}
public void pause() {
if( mediaPlayer != null) {
mediaPlayer.pause();
paused = true;
updateNotification();
playPauseUpdated();
}
}
public boolean isPaused() {
if (mediaPlayer != null) {
return paused;
}
else {
return false;
}
}
public Content getCurrentTrack() {
if (tracks.isEmpty()) {
return null;
}
return tracks.get(0);
}
public boolean hasTracks() {
if (tracks.isEmpty() || tracks.size() < 1) {
return false;
}
else {
return true;
}
}
public int elapsed() {
if (mediaPlayer == null) {
return 0;
}
int elapsed = 0;
try {
elapsed = mediaPlayer.getCurrentPosition();
}
catch (Exception e) {
GlobalContext.INSTANCE.sendExceptionToGoogleAnalytics("Trouble in AudioPlayer.elapsed()", Thread.currentThread().getName(), e, false);
}
return elapsed;
}
public void seek(int timeInMillis) {
if(mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.seekTo(timeInMillis);
}
}
public void seekAndPlay(int timeInMillis) {
if(mediaPlayer != null) {
if (!mediaPlayer.isPlaying())
play();
mediaPlayer.seekTo(timeInMillis);
}
}
/*
private void playTrack(long trackId) {
Track track = musicRepository.findTracksId(this, trackId);
play(track);
}
private void queueTrack(long trackId) {
Track track = musicRepository.findTracksId(this, trackId);
addTrack(track);
}
private void playAlbum(long albumId) {
Track[] tracks = musicRepository.findTracksByAlbumId(this, albumId);
this.tracks.clear(); // I DON'T LIKE THIS!!!
stop();
for( Track track : tracks) {
this.tracks.add(track);
}
play();
}
private void queueAlbum(long albumId) {
Track[] tracks = musicRepository.findTracksByAlbumId(this, albumId);
for( Track track : tracks) {
addTrack(track);
}
}
*/
private void playPauseTrack() {
if (paused)
play();
else
pause();
}
private class AudioPlayerBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
long id = intent.getLongExtra("id", -1);
MyLog.v(TAG, "Received intent for action " + intent.getAction() + " for id: " + id);
if(PLAY_ALBUM.equals(action)) {
//playAlbum(id);
} else if(QUEUE_ALBUM.equals(action)) {
//queueAlbum(id);
} else if(PLAY_TRACK.equals(action)) {
//playTrack(id);
} else if(QUEUE_TRACK.equals(action)) {
//queueTrack(id);
} else if(PLAY_PAUSE_TRACK.equals(action)) {
playPauseTrack();
} else if(HIDE_PLAYER.equals(action)) {
hideNotification();
}
else {
MyLog.wtf(TAG, "Action not recognized: " + action);
}
}
}
private class UpdateTrackTask extends AsyncTask<Void, Content, Void> {
public boolean stopped = false;
public boolean paused = false;
@Override
protected Void doInBackground(Void... params) {
while( ! stopped ) {
if( ! paused) {
Content currentTrack = getCurrentTrack();
if(currentTrack != null) {
currentTrack.elapsed = elapsed();
currentTrack.dirty = true;
DatabaseManager.getInstance().createOrUpdateContent(currentTrack);
}
}
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
// Do nothing, we just need to sleep...
}
}
MyLog.v(TAG,"UpdateTrackTask AsyncTask stopped");
return null;
}
public void stop() {
stopped = true;
}
public void pause() {
this.paused = true;
}
public void unPause() {
this.paused = false;
}
}
}