package com.turtleplayer.player;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.*;
import android.util.Log;
import com.turtleplayer.Player;
import com.turtleplayer.model.Track;
import com.turtleplayer.preferences.Preferences;
import com.turtleplayer.presentation.InstanceFormatter;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
/**
* TURTLE PLAYER
* <p/>
* Licensed under MIT & GPL
* <p/>
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
* OR OTHER DEALINGS IN THE SOFTWARE.
* <p/>
* More Information @ www.turtle-player.co.uk
*
* @author Simon Honegger (Hoene84)
*/
/**
* Access for native Android Media player.<br />
*
* take a look at: http://developer.android.com/reference/android/media/MediaPlayer.html#StateDiagram
* <br /><br />
* {@link #getMp()} garantees media player is at least in idle state<br />
* {@link #initialized} garantees media player is at least in initialized state<br />
*/
public class PlayerService extends Service implements Output
{
private final static int NOTIFICATION_ID = 22;
public enum Notify
{
STARTED,
STOPPED,
CHANGED
}
public final static String PARAM_OBJECT_TRACK = "track";
public final static String PARAM_INTEGER_MILLIS = "millis";
public class PlayerServiceBinder extends Binder {
public PlayerService getPlayerService() {
return PlayerService.this;
}
public void register(Messenger messenger)
{
clients.add(messenger);
}
public void unregister(Messenger messenger)
{
clients.remove(messenger);
}
}
Binder playerServiceBinder = new PlayerServiceBinder();
private final Set<Messenger> clients = new HashSet<Messenger>();
@Override
public IBinder onBind(Intent intent)
{
return playerServiceBinder;
}
private MediaPlayer mp = null; //use getMp to access plz
private boolean isPlaying = false;
private Track currTrack = null;
private boolean initialized = false; //indicates the player is at least in Initialized mode
@Override
public void onCreate()
{
Log.i(Preferences.TAG, "PlayerService.onCreate() called");
super.onCreate();
}
@Override
public void onDestroy()
{
Log.i(Preferences.TAG, "PlayerService.onDestroy() called");
super.onDestroy();
release();
}
@Override
public boolean onUnbind(Intent intent)
{
Log.i(Preferences.TAG, "PlayerService.onUnbind() called");
return super.onUnbind(intent);
}
public void change(Track t){
if(t != null)
{
boolean wasPlaying = isPlaying;
prepare(t);
if(wasPlaying){
playInternal();
}
notifyTrackChanged(t, getMp().getDuration());
}
}
public void play(Track t)
{
if(t != null)
{
boolean wasPlaying = isPlaying;
prepare(t);
playInternal();
if(!wasPlaying)
{
notifyStarted();
}
notifyTrackChanged(t, getMp().getDuration());
}
}
public void toggle()
{
if(initialized)
{
if(isPlaying)
{
pause();
}
else
{
play();
}
}
}
/**
* @return true if this call had an effect
*/
public boolean pause()
{
boolean stopped = pauseInternal();
if(stopped)
{
notifyStopped();
}
return stopped;
}
public boolean pauseInternal()
{
if(initialized && isPlaying)
{
getMp().pause();
isPlaying = false;
return true;
}
return false;
}
/**
* @return true if this call had an effect
*/
public boolean play(){
boolean started = playInternal();
if(started)
{
notifyStarted();
}
return started;
}
/**
* @return true if this call had an effect
*/
public boolean playInternal(){
if(!isPlaying && initialized)
{
getMp().start();
isPlaying = true;
return true;
}
return false;
}
public void goToMillis(int millis)
{
if(initialized)
{
getMp().seekTo(Math.max(Math.min(millis, getMp().getDuration()), 0));
}
}
public int getCurrentMillis()
{
return initialized ? getMp().getCurrentPosition() : 0;
}
public Track getCurrTrack()
{
return currTrack;
}
public void setOnCompletionListener(MediaPlayer.OnCompletionListener listener)
{
getMp().setOnCompletionListener(listener);
}
/**
* @return MediaPlayer at least in idle state
*/
private MediaPlayer getMp(){
if(mp == null)
{
mp = new MediaPlayer();
mp.reset();
}
return mp;
}
/**
* releases the mp and kill the reference because the old instance is not usable anymore
*/
private void release()
{
initialized = false;
pause();
setOnCompletionListener(null);
getMp().release();
mp = null;
}
private void prepare(Track t)
{
if (t != null)
{
try
{
final MediaPlayer mediaPlayer = getMp();
mp.reset();
mediaPlayer.setDataSource(t.getFullPath());
mediaPlayer.prepare();
initialized = true;
isPlaying = false;
currTrack = t;
}
catch (IOException e)
{
Log.v(Preferences.TAG, e.getMessage());
}
}
}
//---------------------------------- Observable
private void notifyTrackChanged(Track track, int lengthInMillis){
if(isPlaying)
{
startForeground(NOTIFICATION_ID, getNotification());
}
Bundle params = new Bundle();
params.putSerializable(PARAM_OBJECT_TRACK, track);
params.putInt(PARAM_INTEGER_MILLIS, lengthInMillis);
notifyClients(Notify.CHANGED, params);
}
private void notifyStarted(){
startForeground(NOTIFICATION_ID, getNotification());
notifyClients(Notify.STARTED, null);
}
private void notifyStopped(){
stopForeground(true);
notifyClients(Notify.STOPPED, null);
}
private Notification getNotification()
{
Notification note = new Notification(com.turtleplayerv2.R.drawable.play96, getText(com.turtleplayerv2.R.string.app_name), System.currentTimeMillis());
Intent i=new Intent(this, Player.class);
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|
Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pi=PendingIntent.getActivity(this, 0,i, 0);
note.setLatestEventInfo(this, getText(com.turtleplayerv2.R.string.app_name),
getCurrTrack() == null ? "" : getCurrTrack().accept(InstanceFormatter.LIST),
pi);
note.flags|=Notification.FLAG_NO_CLEAR;
return note;
}
private void notifyClients(Notify notification, Bundle params)
{
final Set<Messenger> clientsToRemove = new HashSet<Messenger>();
for(Messenger client : clients)
{
try {
Message msg = Message.obtain(null, notification.ordinal());
msg.setData(params);
client.send(msg);
} catch (RemoteException e) {
// If we get here, the client is dead, and we should remove it from the list
Log.d(Preferences.TAG, "Client does not respond, remove it");
clientsToRemove.add(client);
}
}
for(Messenger client : clientsToRemove)
{
clients.remove(client);
}
}
}