package com.ideabag.playtunes.service;
import java.util.ArrayList;
import android.annotation.SuppressLint;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.media.AudioManager;
import android.os.Binder;
import android.os.IBinder;
import android.support.v4.app.Fragment;
import com.google.android.gms.analytics.HitBuilders;
import com.google.android.gms.analytics.Tracker;
import com.ideabag.playtunes.R;
import com.ideabag.playtunes.database.MediaQuery;
import com.ideabag.playtunes.media.AudioFocusHelper;
import com.ideabag.playtunes.media.MusicFocusable;
import com.ideabag.playtunes.media.MusicIntentReceiver;
import com.ideabag.playtunes.media.PlaylistMediaPlayer;
import com.ideabag.playtunes.media.PlaylistMediaPlayer.PlaybackListener;
import com.ideabag.playtunes.util.GAEvent;
import com.ideabag.playtunes.util.GAEvent.AudioControls;
import com.ideabag.playtunes.util.TrackerSingleton;
import com.ideabag.playtunes.util.GAEvent.Categories;
public class MusicPlayerService extends Service implements MusicFocusable {
public static final String ACTION_PLAY = "com.ideabag.playtunes.PLAY";
public static final String ACTION_PLAY_OR_PAUSE = "com.ideabag.playtunes.PLAY_PAUSE";
public static final String ACTION_PAUSE = "com.ideabag.playtunes.PAUSE";
public static final String ACTION_NEXT = "com.ideabag.playtunes.NEXT";
public static final String ACTION_BACK = "com.ideabag.playtunes.BACK";
public static final String ACTION_CLOSE = "com.ideabag.playtunes.CLOSE";
private static final String PREF_KEY_NOWPLAYING_CLASS = "now_playing_class";
private static final String PREF_KEY_NOWPLAYING_NAME = "now_playing_name";
private static final String PREF_KEY_NOWPLAYING_ID = "now_playing_media_id";
private static final String PREF_KEY_NOWPLAYING_QUERY = "now_playing_media_query";
private static final String PREF_KEY_NOWPLAYING_POSITION = "now_playing_position";
private static final String PREF_KEY_NOWPLAYING_SHUFFLE = "now_playing_shuffle";
private static final String PREF_KEY_NOWPLAYING_LOOP = "now_playing_loop";
private Tracker mTracker;
@SuppressWarnings("unused")
private static final String TAG = "MusicPlayerService";
private PlaylistMediaPlayer MediaPlayer;
private PlaybackNotification Notification;
private LockscreenManager Lockscreen;
private AudioManager mAudioManager;
private SharedPreferences mSharedPrefs;
AudioFocusHelper mAudioFocusHelper = null;
// All used to save and restore what the user is playing across app open/close
public String CURRENT_MEDIA_ID = null;
public Class < ? extends Fragment > mPlaylistFragmentClass;
public String mPlaylistMediaID;
public String mPlaylistName;
boolean isServiceStarted = false;
private MusicPlayerService self;
private ArrayList< PlaybackListener > ChangedListeners = new ArrayList< PlaybackListener >();
BroadcastReceiver NotificationActionReceiver = new BroadcastReceiver() {
@Override public void onReceive( Context context, Intent intent ) {
String action = intent.getAction();
if ( action.equals( ACTION_PLAY_OR_PAUSE ) ) {
if ( null != MediaPlayer ) {
if ( MediaPlayer.isPlaying() ) {
mTracker.send( new HitBuilders.EventBuilder()
.setCategory( intent.hasExtra( Categories.LOCKSCREEN ) ? Categories.LOCKSCREEN : Categories.NOTIFICATION )
.setAction( AudioControls.ACTION_PAUSE )
.build());
pause();
} else {
mTracker.send( new HitBuilders.EventBuilder()
.setCategory( intent.hasExtra( Categories.LOCKSCREEN ) ? Categories.LOCKSCREEN : Categories.NOTIFICATION )
.setAction( AudioControls.ACTION_PLAY )
.build());
play();
}
}
} else if ( action.equals( ACTION_NEXT ) ) {
mTracker.send( new HitBuilders.EventBuilder()
.setCategory( intent.hasExtra( Categories.LOCKSCREEN ) ? Categories.LOCKSCREEN : Categories.NOTIFICATION )
.setAction( AudioControls.ACTION_NEXT )
.build());
next();
} else if ( action.equals( ACTION_CLOSE ) ) {
mTracker.send( new HitBuilders.EventBuilder()
.setCategory( intent.hasExtra( Categories.LOCKSCREEN ) ? Categories.LOCKSCREEN : Categories.NOTIFICATION )
.setAction( GAEvent.Notification.ACTION_CLOSE )
.build());
if ( isServiceStarted ) {
isServiceStarted = false;
self.stopSelf();
}
} else if ( action.equals( ACTION_BACK ) ) {
mTracker.send( new HitBuilders.EventBuilder()
.setCategory( intent.hasExtra( Categories.LOCKSCREEN ) ? Categories.LOCKSCREEN : Categories.NOTIFICATION )
.setAction( AudioControls.ACTION_PREV )
.build());
MediaPlayer.back();
}
}
};
@SuppressWarnings("unchecked")
@SuppressLint("NewApi")
@Override public void onCreate() {
super.onCreate();
self = this;
MediaPlayer = new PlaylistMediaPlayer( getBaseContext() );
Notification = new PlaybackNotification( getBaseContext() );
Lockscreen = new LockscreenManager( getBaseContext() );
mSharedPrefs = this.getSharedPreferences( getString( R.string.prefs_file ), Context.MODE_PRIVATE );
mTracker = TrackerSingleton.getDefaultTracker( this );
//
IntentFilter mNotificationIntentFilter = new IntentFilter();
mNotificationIntentFilter.addAction( ACTION_PLAY_OR_PAUSE );
mNotificationIntentFilter.addAction( ACTION_NEXT );
mNotificationIntentFilter.addAction( ACTION_CLOSE );
mNotificationIntentFilter.addAction( ACTION_BACK );
registerReceiver( NotificationActionReceiver, mNotificationIntentFilter );
mAudioManager = ( AudioManager ) getBaseContext().getSystemService( Context.AUDIO_SERVICE );
mAudioManager.registerMediaButtonEventReceiver( new ComponentName( getBaseContext(), MusicIntentReceiver.class ) );
mAudioFocusHelper = new AudioFocusHelper( getApplicationContext(), this );
String mMediaQueryJSONString = mSharedPrefs.getString( PREF_KEY_NOWPLAYING_QUERY, null );
int mPlaylistPosition = mSharedPrefs.getInt( PREF_KEY_NOWPLAYING_POSITION, 0 );
boolean mIsShuffling = mSharedPrefs.getBoolean( PREF_KEY_NOWPLAYING_SHUFFLE, false );
int looping = mSharedPrefs.getInt( PREF_KEY_NOWPLAYING_LOOP, PlaylistMediaPlayer.LOOP_NO );
mPlaylistMediaID = mSharedPrefs.getString( PREF_KEY_NOWPLAYING_ID, null );
mPlaylistName = mSharedPrefs.getString( PREF_KEY_NOWPLAYING_NAME, "" );
String classNameString = mSharedPrefs.getString( PREF_KEY_NOWPLAYING_CLASS, "" );
try {
mPlaylistFragmentClass = (Class<? extends Fragment>) Class.forName( classNameString );
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
MediaPlayer.setPlaybackListener( MediaPlayerListener );
if ( mMediaQueryJSONString != null ) {
MediaQuery mMediaQuery = new MediaQuery( mMediaQueryJSONString );
if ( mMediaQuery != null ) {
MediaPlayer.setPlaylistQuery( mMediaQuery );
MediaPlayer.setPlaylistPosition( mPlaylistPosition );
MediaPlayer.setLooping( looping );
MediaPlayer.setShuffle( mIsShuffling );
}
}
if ( !isServiceStarted ) {
startService( new Intent( this, MusicPlayerService.class ) );
isServiceStarted = true;
}
}
@Override public void onDestroy() {
super.onDestroy();
mAudioFocusHelper.abandonFocus();
Notification.remove();
unregisterReceiver( NotificationActionReceiver );
mAudioManager.unregisterMediaButtonEventReceiver( new ComponentName( getBaseContext(), MusicIntentReceiver.class ) );
ChangedListeners.clear();
Lockscreen.remove();
// Save state
if ( MediaPlayer.getPlaylistQuery() != null ) {
SharedPreferences.Editor edit = mSharedPrefs.edit();
edit.putString( PREF_KEY_NOWPLAYING_QUERY, MediaPlayer.getPlaylistQuery().toJSONString() );
edit.putInt( PREF_KEY_NOWPLAYING_POSITION, MediaPlayer.getPlaylistPosition() );
edit.putString( PREF_KEY_NOWPLAYING_CLASS, mPlaylistFragmentClass.getName() );
edit.putString( PREF_KEY_NOWPLAYING_NAME, mPlaylistName );
edit.putString( PREF_KEY_NOWPLAYING_ID, mPlaylistMediaID );
edit.putInt( PREF_KEY_NOWPLAYING_LOOP, MediaPlayer.getLoopState() );
edit.putBoolean( PREF_KEY_NOWPLAYING_SHUFFLE, MediaPlayer.isShuffling() );
edit.commit();
}
// Clean up Media Player
MediaPlayer.pause();
MediaPlayer.destroy();
// Remove Notification (if showing)
}
public class MusicPlayerServiceBinder extends Binder {
public MusicPlayerService getService() {
return MusicPlayerService.this;
}
}
//
// Service Binder & Public APIs
//
//
//
private final IBinder mBinder = new MusicPlayerServiceBinder();
@Override public IBinder onBind( Intent intent ) {
return mBinder;
}
public void setPlaylist( MediaQuery query, String playlistName, Class < ? extends Fragment > fragmentClass, String playlistMediaID ) {
if ( null != MediaPlayer ) {
MediaPlayer.setPlaylistQuery( query );
this.mPlaylistFragmentClass = fragmentClass;
this.mPlaylistMediaID = playlistMediaID;
this.mPlaylistName = playlistName;
SharedPreferences.Editor edit = mSharedPrefs.edit();
edit.putString( PREF_KEY_NOWPLAYING_ID, playlistMediaID );
edit.putString( PREF_KEY_NOWPLAYING_NAME, playlistName );
edit.putString( PREF_KEY_NOWPLAYING_CLASS, fragmentClass.getName() );
edit.commit();
}
}
//
// rectifyPlaylist is called when the content of the query changes
// for instance, when a song is removed from a playlist or deleted from the device.
//
// - The goal is to switch to the new query cursor, but to retain play position
// - It is possible that the currently playing song is not in the new query
// - It is possible that the currently playing song is in a new position in the query
//
//
public void rectifyPlaylist( MediaQuery query, String playlistName, Class < ? extends Fragment > fragmentClass, String playlistMediaID ) {
if ( null != MediaPlayer ) {
MediaPlayer.setPlaylistQuery( query );
this.mPlaylistFragmentClass = fragmentClass;
this.mPlaylistMediaID = playlistMediaID;
this.mPlaylistName = playlistName;
SharedPreferences.Editor edit = mSharedPrefs.edit();
edit.putString( PREF_KEY_NOWPLAYING_ID, playlistMediaID );
edit.putString( PREF_KEY_NOWPLAYING_NAME, playlistName );
edit.putString( PREF_KEY_NOWPLAYING_CLASS, fragmentClass.getName() );
//edit.
//android.util.Log.i( TAG, "" + playlistMediaID + " " + fragmentClass.getName() );
edit.commit();
}
}
public void setPlaylistPosition( int position ) {
if ( null != MediaPlayer ) {
MediaPlayer.setPlaylistPosition( position );
}
}
public void setSeekPosition( int position ) {
if ( null != MediaPlayer ) {
MediaPlayer.setSeekPosition( position );
}
}
public void next() {
if ( null != MediaPlayer ) {
MediaPlayer.nextTrack();
}
}
public void prev() {
if ( null != MediaPlayer ) {
MediaPlayer.back();
}
}
public void play() {
if ( null != MediaPlayer && !MediaPlayer.isPlaying() ) {
MediaPlayer.play();
}
}
public void pause() {
if ( null != MediaPlayer ) {
MediaPlayer.pause();
}
}
public void setLooping( int repeat ) {
MediaPlayer.setLooping( repeat );
}
public void setShuffle( boolean isShuffling ) {
MediaPlayer.setShuffle( isShuffling );
}
//
// The PlaylistMediaPlayer creates a PlaybackListener interface and uses it as a callback for changes in playback.
// In MusicPlayerService, we use that interface both to update the notification and to rebroadcast that
// callback information to as many clients as are interested.
//
// We use the number of PlaybackListeners as a way to determine if the Service is detached or not.
//
//
public void addPlaybackListener( PlaybackListener listener ) {
this.ChangedListeners.add( listener );
// When we get a PlaybackListener, we immediately fire all the callbacks with the current state information
if ( null != MediaPlayer ) {
listener.onTrackChanged( MediaPlayer.getCurrentMediaID() );
listener.onLoopingChanged( MediaPlayer.getLoopState() );
listener.onShuffleChanged( MediaPlayer.isShuffling() );
listener.onDurationChanged( MediaPlayer.getTrackPosition() , MediaPlayer.getTrackDuration() );
if ( MediaPlayer.isPlaying() ) {
listener.onPlay();
} else {
listener.onPause();
}
}
//
// Listeners are added when an interested Activity UI is shown, so we remove
// the notification in this case.
//
Notification.remove();
}
public void removePlaybackListener( PlaybackListener listener ) {
this.ChangedListeners.remove( listener );
//
// Listeners are removed when an interested Activity or Fragment UI is hidden, so we either
// show the notification in this case and update it with the currently playing song
// or destroy the service if no music is playing.
//
if ( this.ChangedListeners.size() == 0 ) {
if ( MediaPlayer.isPlaying() ) {
Notification.setMediaID( CURRENT_MEDIA_ID );
Notification.play();
Notification.show();
} else if ( isServiceStarted ) {
isServiceStarted = false;
this.stopSelf();
}
}
}
PlaybackListener MediaPlayerListener = new PlaybackListener() {
//
// No null check because null is a meaningful value in this situation
//
@Override public void onTrackChanged( String media_id ) {
CURRENT_MEDIA_ID = media_id;
int count = ChangedListeners.size();
for ( int x = 0; x < count; x++ ) {
ChangedListeners.get( x ).onTrackChanged( media_id );
}
Lockscreen.setMediaID( media_id, MediaPlayer.hasPreviousTrack(), MediaPlayer.hasNextTrack() );
Notification.setMediaID( media_id );
if ( MediaPlayer.isPlaying() ) {
mAudioFocusHelper.requestFocus();
}
}
@Override public void onPlaylistDone() {
mAudioFocusHelper.abandonFocus();
int count = ChangedListeners.size();
for ( int x = 0; x < count; x++ ) {
ChangedListeners.get( x ).onPlaylistDone();
}
Lockscreen.remove();
Notification.remove();
}
@Override public void onLoopingChanged( int loop ) {
int count = ChangedListeners.size();
for ( int x = 0; x < count; x++ ) {
ChangedListeners.get( x ).onLoopingChanged( loop );
}
}
@Override public void onShuffleChanged( boolean isShuffling ) {
int count = ChangedListeners.size();
for ( int x = 0; x < count; x++ ) {
ChangedListeners.get( x ).onShuffleChanged( isShuffling );
}
}
@Override public void onPlay() {
int count = ChangedListeners.size();
mAudioFocusHelper.requestFocus();
for ( int x = 0; x < count; x++ ) {
ChangedListeners.get( x ).onPlay();
}
if ( null == Lockscreen || !Lockscreen.ready() ) {
Lockscreen = new LockscreenManager( getBaseContext() );
Lockscreen.setMediaID( CURRENT_MEDIA_ID, MediaPlayer.hasPreviousTrack(), MediaPlayer.hasNextTrack() );
}
Lockscreen.play();
Notification.play();
}
@Override public void onPause() {
int count = ChangedListeners.size();
for ( int x = 0; x < count; x++ ) {
ChangedListeners.get( x ).onPause();
}
Lockscreen.pause();
Notification.pause();
}
@Override public void onDurationChanged( int position, int duration ) {
int count = ChangedListeners.size();
for ( int x = 0; x < count; x++ ) {
ChangedListeners.get( x ).onDurationChanged( position, duration );
}
}
};
@Override public void onGainedAudioFocus() {
// TODO:
MediaPlayer.stopVolumeDucking();
//
}
@Override public void onLostAudioFocus( boolean canDuck ) {
if ( !canDuck ) {
Lockscreen.remove();
//destroyRemoteControlClient();
pause();
} else {
MediaPlayer.startVolumeDucking();
}
}
}