package edu.grinnell.kdic;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.media.AudioManager;
import android.media.AudioManager.OnAudioFocusChangeListener;
import android.media.MediaPlayer;
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.Toast;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import edu.grinnell.kdic.schedule.Schedule;
/**
* Service used to play the radio from the stream.
*/
public class RadioService extends Service {
public static final String TAG = RadioService.class.getSimpleName();
private static final int NOTIFICATION_ID = 1;
private AudioManager audioManager;
private OnAudioFocusChangeListener audioFocusListener;
private boolean isLoaded;
private boolean isLoading;
private WifiManager.WifiLock wifiLock;
private MediaPlayer mediaPlayer;
// Binder given to clients
private final IBinder mBinder = new RadioBinder();
// timer for stopping stream after pause
private Timer timer = new Timer();
private static final long STOP_STREAM_DELAY = 30 * 1000;
// reset the media player 30 seconds after pause
private Runnable runOnStreamPrepared;
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class RadioBinder extends Binder {
RadioService getService() {
// Return this instance of RadioBinder so clients can call public methods
return RadioService.this;
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind: ");
return mBinder;
}
//
// @Override
// public void onRebind(Intent intent) {
//
// Log.d(TAG, "onRebound");
//
// // remove the notification once MainActivity binds to radio
// hideNotification();
//
// }
//
// @Override
// public boolean onUnbind(Intent intent) {
// Log.d(TAG, "Unbound");
//
// if (isPlaying() || isLoading)
// showNotification();
//
// return super.onUnbind(intent);
// }
@Override
public void onCreate() {
// obtain WifiLock
wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL, "myWifiLock");
setupMediaPlayer();
setupAudioManager();
// time change?? show change
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null && intent.getAction() != null) {
switch (intent.getAction()) {
case Constants.ACTION_STOP_RADIO_SERVICE:
Log.d(TAG, "Stop action");
hideNotification();
stopSelf();
break;
case Constants.ACTION_STREAM_PLAY_PAUSE:
if (isPlaying()) {
pause();
showNotification();
stopForeground(false); // stop making it a foreground service but leave the notification there
} else {
play();
showNotification();
}
break;
default:
break;
}
}
return super.onStartCommand(intent, flags, startId);
}
private void setupMediaPlayer() {
mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
// reset the media player if an error occurs
mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
Toast.makeText(RadioService.this, "There was an error playing the stream. Reloading...",
Toast.LENGTH_SHORT).show();
mediaPlayer.reset();
return false;
}
});
}
private void setupAudioManager() {
// obtain audioManager for requesting audio focus
audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audioFocusListener = new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
Log.d(TAG, "AudioManager: AUDIOFOCUS_GAIN");
// resume playback
if (mediaPlayer == null) setupMediaPlayer();
else if (!mediaPlayer.isPlaying()) play();
mediaPlayer.setVolume(1.0f, 1.0f);
break;
case AudioManager.AUDIOFOCUS_LOSS:
// Lost focus for an unbounded amount of time: stop playback and release media player
Log.d(TAG, "AudioManager: AUDIOFOCUS_LOSS");
if (isPlaying()) reset();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
Log.d(TAG, "AudioManager: AUDIOFOCUS_LOSS_TRANSIENT");
// Lost focus for a short time, but we have to stop
// playback. We don't release the media player because playback
// is likely to resume
if (isPlaying()) pause();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
Log.d(TAG, "AudioManager: AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK");
// Lost focus for a short time, but it's ok to keep playing
// at an attenuated level
if (isPlaying()) mediaPlayer.setVolume(0.2f, 0.2f);
break;
}
}
};
}
private void prepStreamAndPlay() {
if (mediaPlayer != null) {
// callback for once the stream is prepared
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
Log.d(TAG, "Stream Prepared");
isLoading = false;
isLoaded = true;
play();
if (runOnStreamPrepared != null) runOnStreamPrepared.run();
}
});
try {
// set the URL for the stream
mediaPlayer.setDataSource(Constants.STREAM_URL);
// prepare the stream asynchronously
isLoading = true;
mediaPlayer.prepareAsync();
} catch (IOException e) {
e.printStackTrace();
}
} else { // mediaplayer == null
setupMediaPlayer();
prepStreamAndPlay();
}
}
public void setRunOnStreamPrepared(final Runnable runOnStreamPrepared) {
this.runOnStreamPrepared = runOnStreamPrepared;
}
public void play() {
if (isLoaded) { // if stream is loaded
timer.cancel(); // cancel the stop timer if it is loaded
// request focus to play audio
int result = audioManager.requestAudioFocus(audioFocusListener, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// could not get audio focus.
Toast.makeText(RadioService.this, "Cannot play audio.", Toast.LENGTH_SHORT).show();
Log.i(TAG, "Audio Manager request not granted.");
} else {
Log.d(TAG, "AUDIO REQUEST GRANTED.");
}
if (!wifiLock.isHeld()) wifiLock.acquire(); // don't let the wifi radio turn off
mediaPlayer.start(); // play
} else {
prepStreamAndPlay();
}
}
public void pause() {
if (mediaPlayer.isPlaying()) {
mediaPlayer.pause();
audioManager.abandonAudioFocus(audioFocusListener); // abandon the audio focus
timer = new Timer();
final TimerTask stopPlayerTask = new TimerTask() {
@Override
public void run() {
reset();
this.cancel();
}
};
timer.schedule(stopPlayerTask, STOP_STREAM_DELAY);
}
if (wifiLock.isHeld())
wifiLock.release(); // release wifi lock
}
/**
* reset the media player so that the stream needs to be loaded again
*/
public void reset() {
if (wifiLock.isHeld())
wifiLock.release(); // let the wifi radio turn off
audioManager.abandonAudioFocus(audioFocusListener); // abandon the audio focus
isLoaded = false;
if (mediaPlayer != null)
mediaPlayer.reset();
Log.d(TAG, "Stopping stream!");
hideNotification();
}
public boolean isPlaying() {
return mediaPlayer != null && (mediaPlayer.isPlaying() || isLoading);
}
public boolean isLoaded() {
return isLoaded;
}
public boolean isLoading() {
return isLoading;
}
public void showNotification() {
Show currentShow = Schedule.getCurrentShow(this);
String title = currentShow != null ? currentShow.getTitle() : "Auto Play";
Intent playPauseIntent = new Intent(this, RadioService.class);
playPauseIntent.setAction(Constants.ACTION_STREAM_PLAY_PAUSE);
PendingIntent playPausePendingIntent = PendingIntent.getService(
this,
0,
playPauseIntent,
PendingIntent.FLAG_UPDATE_CURRENT
);
Intent deleteIntent = new Intent(this, RadioService.class);
deleteIntent.setAction(Constants.ACTION_STOP_RADIO_SERVICE);
PendingIntent pendingDelete = PendingIntent.getService(
this,
0,
deleteIntent,
PendingIntent.FLAG_ONE_SHOT
);
Intent notifyIntent = new Intent(this, MainActivity.class);
notifyIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
notifyIntent.putExtra("isPlaying", isPlaying());
PendingIntent notifyPendingIntent =
PendingIntent.getActivity(
this,
0,
notifyIntent,
PendingIntent.FLAG_UPDATE_CURRENT
);
// Instantiate a Builder object.
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setSmallIcon(isPlaying() ? R.drawable.ic_play_arrow_white_24dp : R.drawable.ic_pause_white_24dp)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),
R.drawable.ic_launcher))
.setContentTitle(title)
.addAction(isPlaying() ? R.drawable.ic_pause_white_24dp : R.drawable.ic_play_arrow_white_24dp,
"Play/Pause", playPausePendingIntent)
.addAction(R.drawable.ic_close_white_24dp, "Close", pendingDelete)
.setContentText("KDIC - Grinnell College Radio")
.setShowWhen(false) // hide the time
.setDeleteIntent(pendingDelete)
.setColor(getResources().getColor(R.color.accent))
.setContentIntent(notifyPendingIntent);
startForeground(NOTIFICATION_ID, builder.build());
/*
// Creates an Intent for the Activity
Intent notifyIntent = new Intent(this, MainActivity.class);
notifyIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
notifyIntent.setAction(Constants.ACTION_STREAM_PLAY_PAUSE);
// Creates the PendingIntent
PendingIntent notifyPendingIntent =
PendingIntent.getActivity(
this,
0,
notifyIntent,
PendingIntent.FLAG_UPDATE_CURRENT
);
// Puts the PendingIntent into the notification builder
builder.setContentIntent(notifyPendingIntent);
// Notifications are issued by sending them to the
// NotificationManager system service.
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// Builds an anonymous Notification object from the builder, and
// passes it to the NotificationManager
mNotificationManager.notify(NOTIFICATION_ID, builder.build());
*/
}
public void hideNotification() {
stopForeground(true);
/*
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.cancel(NOTIFICATION_ID);
*/
}
@Override
public boolean stopService(Intent name) {
Log.d(TAG, "RadioService stopped.");
reset();
// remove the notification
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.cancel(NOTIFICATION_ID);
if (mediaPlayer != null) mediaPlayer.release();
mediaPlayer = null;
return super.stopService(name);
}
@Override
public void onDestroy() {
Log.d(TAG, "RadioService destroyed.");
reset();
if (mediaPlayer != null) mediaPlayer.release();
mediaPlayer = null;
}
}