/***************************************************************************
* Copyright 2005-2009 Last.fm Ltd. *
* Portions contributed by Casey Link, Lukasz Wisniewski, *
* Mike Jennings, and Michael Novak Jr. *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
package fm.last.android.player;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.logging.FileHandler;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
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.ServiceConnection;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.AudioManager;
import android.media.MediaMetadataRetriever;
import android.media.MediaPlayer;
import android.media.RemoteControlClient;
import android.media.MediaPlayer.OnBufferingUpdateListener;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaPlayer.OnPreparedListener;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.net.wifi.WifiManager;
import android.os.DeadObjectException;
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcelable;
import android.os.PowerManager;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.DisplayMetrics;
import fm.last.android.AndroidLastFmServerFactory;
import fm.last.android.LastFMApplication;
import fm.last.android.LastFMMediaButtonHandler;
import fm.last.android.LastFm;
import fm.last.android.R;
import fm.last.android.RadioWidgetProvider;
import fm.last.android.activity.Player;
import fm.last.android.activity.Profile;
import fm.last.android.db.RecentStationsDao;
import fm.last.android.scrobbler.ScrobblerService;
import fm.last.android.utils.AsyncTaskEx;
import fm.last.api.Album;
import fm.last.api.LastFmServer;
import fm.last.api.RadioPlayList;
import fm.last.api.RadioTrack;
import fm.last.api.Session;
import fm.last.api.Station;
import fm.last.api.WSError;
import fm.last.util.UrlUtil;
public class RadioPlayerService extends Service implements MusicFocusable {
private MediaPlayer mp = null;
private Station currentStation;
private Session currentSession;
private RadioTrack currentTrack;
private ArrayBlockingQueue<RadioTrack> currentQueue;
private NotificationManager nm = null;
private int bufferPercent;
private WSError mError = null;
private String currentStationURL = null;
private PowerManager.WakeLock wakeLock;
private WifiManager.WifiLock wifiLock;
private AudioManager mAudioManager;
private boolean mUpdatedTrialCount;
public static final int STATE_STOPPED = 0;
public static final int STATE_TUNING = 1;
public static final int STATE_PREPARING = 2;
public static final int STATE_PLAYING = 3;
public static final int STATE_SKIPPING = 4;
public static final int STATE_PAUSED = 5;
public static final int STATE_ERROR = -1;
private int mState = STATE_STOPPED;
private int mPlaylistRetryCount = 0;
private int mAutoSkipCount = 0;
private boolean mDoHasWiFi = false;
private long mStationStartTime = 0;
private long mTrackStartTime = 0;
private int mTrackPosition = 0;
private boolean pauseButtonPressed = false;
private boolean focusLost = false;
private boolean lostDataConnection = false;
private static final int NOTIFY_ID = 1337;
private FadeVolumeTask mFadeVolumeTask = null;
RemoteControlClientCompat mRemoteControlClientCompat;
private Bitmap mArtwork;
public static final String META_CHANGED = "fm.last.android.metachanged";
public static final String PLAYBACK_FINISHED = "fm.last.android.playbackcomplete";
public static final String PLAYBACK_STATE_CHANGED = "fm.last.android.playstatechanged";
public static final String STATION_CHANGED = "fm.last.android.stationchanged";
public static final String PLAYBACK_ERROR = "fm.last.android.playbackerror";
public static final String ARTWORK_AVAILABLE = "fm.last.android.artworkavailable";
public static final String UNKNOWN = "fm.last.android.unknown";
/**
* Used for pausing on incoming call
*/
private TelephonyManager mTelephonyManager;
private Logger logger;
private final float DUCK_VOLUME = 0.1f;
private MusicPlayerFocusHelper mFocusHelper;
public static boolean radioAvailable(Context context) {
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
if (tm == null || tm.getNetworkCountryIso() == null|| tm.getNetworkCountryIso().length() == 0
|| tm.getNetworkCountryIso().equals("us") || tm.getNetworkCountryIso().equals("310") || tm.getNetworkCountryIso().equals("311") || tm.getNetworkCountryIso().equals("312") || tm.getNetworkCountryIso().equals("313") || tm.getNetworkCountryIso().equals("314") || tm.getNetworkCountryIso().equals("315")
|| tm.getNetworkCountryIso().equals("gb") || tm.getNetworkCountryIso().equals("234") || tm.getNetworkCountryIso().equals("235")
|| tm.getNetworkCountryIso().equals("de") || tm.getNetworkCountryIso().equals("262")) {
return true;
}
return false;
}
@Override
public void onCreate() {
super.onCreate();
initializeStaticCompatMethods();
mFocusHelper = new MusicPlayerFocusHelper(this, this);
logger = Logger.getLogger("fm.last.android.player");
try {
if (logger.getHandlers().length < 1) {
FileHandler handler = new FileHandler(getFilesDir().getAbsolutePath() + "/player.log", 4096, 1, true);
handler.setFormatter(new SimpleFormatter());
logger.addHandler(handler);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
logger.info("Player service started");
nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
bufferPercent = 0;
// playing
currentQueue = new ArrayBlockingQueue<RadioTrack>(20);
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Last.fm Player");
WifiManager wm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
wifiLock = wm.createWifiLock("Last.fm Player");
mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
if(!mFocusHelper.isSupported()) {
mTelephonyManager = (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE);
mTelephonyManager.listen(new PhoneStateListener() {
@Override
public void onCallStateChanged(int state, String incomingNumber) {
if (mState != STATE_STOPPED) {
if (state == TelephonyManager.CALL_STATE_IDLE) {
focusGained();
} else { // fade music out to silence
focusLost(true, false);
}
}
super.onCallStateChanged(state, incomingNumber);
}
}, PhoneStateListener.LISTEN_CALL_STATE);
}
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(connectivityListener, intentFilter);
try {
if (getFileStreamPath("player.dat").exists()) {
FileInputStream fileStream = openFileInput("player.dat");
ObjectInputStream objectStream = new ObjectInputStream(fileStream);
Object obj = objectStream.readObject();
currentSession = (Session)obj;
obj = objectStream.readObject();
currentStation = (Station)obj;
obj = objectStream.readObject();
currentStationURL = (String)obj;
obj = objectStream.readObject();
currentTrack = (RadioTrack)obj;
obj = objectStream.readObject();
mStationStartTime = (Long)obj;
obj = objectStream.readObject();
mTrackStartTime = (Long)obj;
obj = objectStream.readObject();
mTrackPosition = (Integer)obj;
objectStream.close();
fileStream.close();
logger.info("Loaded serialized state");
mState = STATE_PAUSED;
}
} catch (Exception e) {
logger.warning("Unable to load serialized state");
currentStation = null;
currentStationURL = null;
currentTrack = null;
mStationStartTime = 0;
mTrackStartTime = 0;
mTrackPosition = 0;
e.printStackTrace();
}
}
BroadcastReceiver connectivityListener = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
NetworkInfo ni = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
if (ni.getState() == NetworkInfo.State.DISCONNECTED || ni.getState() == NetworkInfo.State.SUSPENDED) {
if (mState != STATE_STOPPED && mState != STATE_ERROR && mState != STATE_PAUSED) {
ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
NetworkInfo activeni = cm.getActiveNetworkInfo();
if(activeni != null && activeni.isConnected()) {
logger.info("A network other than the active network has disconnected, ignoring");
return;
}
// Ignore disconnections that don't change our WiFi / cell
// state
if ((ni.getType() == ConnectivityManager.TYPE_WIFI) != mDoHasWiFi) {
return;
}
// We just lost the WiFi connection so update our state
if (ni.getType() == ConnectivityManager.TYPE_WIFI)
mDoHasWiFi = false;
logger.info("Data connection lost! Type: " + ni.getTypeName() + " Subtype: " + ni.getSubtypeName() + "Extra Info: " + ni.getExtraInfo()
+ " Reason: " + ni.getReason());
lostDataConnection = true;
}
} else if (ni.getState() == NetworkInfo.State.CONNECTED && mState != STATE_STOPPED && mState != STATE_ERROR) {
if (lostDataConnection || ni.isFailover() || ni.getType() == ConnectivityManager.TYPE_WIFI) {
if (ni.getType() == ConnectivityManager.TYPE_WIFI) {
if (!mDoHasWiFi)
mDoHasWiFi = true;
else
return;
}
logger.info("New data connection attached! Type: " + ni.getTypeName() + " Subtype: " + ni.getSubtypeName() + "Extra Info: "
+ ni.getExtraInfo() + " Reason: " + ni.getReason());
if(lostDataConnection) {
if(mState == STATE_PAUSED) {
pause();
lostDataConnection = false;
}
}
}
}
}
};
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
onStart(intent, startId);
return START_NOT_STICKY;
}
@Override
public void onStart(Intent intent, int startId) {
if (intent != null && intent.getAction() != null && intent.getAction().equals("fm.last.android.PLAY")) {
String stationURL = intent.getStringExtra("station");
Session session = intent.getParcelableExtra("session");
if(currentStationURL != null && currentStationURL.equals(stationURL)) {
if(mState == STATE_PAUSED)
pause();
if(mState != STATE_STOPPED)
return;
}
if (stationURL != null && stationURL.length() > 0 && session != null) {
new TuneRadioTask(stationURL, session).execute();
}
}
}
@Override
public void onDestroy() {
logger.info("Player service shutting down");
try {
if (mp != null) {
if (mp.isPlaying())
mp.stop();
mp.release();
}
} catch (Exception e) {
}
clearNotification();
unregisterReceiver(connectivityListener);
releaseLocks();
if(mState == STATE_PAUSED) {
serializeCurrentStation();
}
}
private void releaseLocks() {
if(wakeLock != null && wakeLock.isHeld())
wakeLock.release();
if(wifiLock != null && wifiLock.isHeld())
wifiLock.release();
}
private void serializeCurrentStation() {
try {
if (getFileStreamPath("player.dat").exists())
deleteFile("player.dat");
if (mState == STATE_PAUSED && currentTrack != null) {
logger.info("Serializing station info");
FileOutputStream filestream = openFileOutput("player.dat", 0);
ObjectOutputStream objectstream = new ObjectOutputStream(filestream);
objectstream.writeObject(currentSession);
objectstream.writeObject(currentStation);
objectstream.writeObject(currentStationURL);
objectstream.writeObject(currentTrack);
objectstream.writeObject(new Long(mStationStartTime));
objectstream.writeObject(new Long(mTrackStartTime));
objectstream.writeObject(new Integer(mTrackPosition));
objectstream.close();
filestream.close();
}
} catch (Exception e) {
if (getFileStreamPath("player.dat").exists())
deleteFile("player.dat");
logger.severe("Unable to save queue state");
e.printStackTrace();
}
}
public IBinder getBinder() {
return mBinder;
}
@SuppressWarnings("rawtypes")
private void clearNotification() {
try {
Class types[] = { boolean.class };
Object args[] = { true };
Method method = Service.class.getMethod("stopForeground", types);
method.invoke(this, args);
} catch (NoSuchMethodException e) {
nm.cancel(NOTIFY_ID);
Class types[] = { boolean.class };
Object args[] = { false };
Method method;
try {
method = Service.class.getMethod("setForeground", types);
method.invoke(this, args);
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
} catch (Exception e) {
}
if (currentStation != null && mStationStartTime > 0) {
try {
LastFMApplication.getInstance().tracker.trackEvent("Radio", // Category
"Stream", // Action
currentStation.getType(), // Label
(int) ((System.currentTimeMillis() - mStationStartTime) / 1000)); // Value
} catch (Exception e) {
//Google Analytics doesn't appear to be thread safe
}
mStationStartTime = 0;
}
}
@SuppressWarnings("rawtypes")
private void playingNotify() {
if (currentTrack == null || currentTrack.getTitle() == null || currentTrack.getCreator() == null)
return;
Notification notification = new Notification(R.drawable.as_statusbar, getString(R.string.playerservice_streaming_ticker_text, currentTrack.getTitle(),
currentTrack.getCreator()), System.currentTimeMillis());
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, Player.class), 0);
String info = currentTrack.getTitle() + " - " + currentTrack.getCreator();
notification.setLatestEventInfo(this, currentStation.getName(), info, contentIntent);
notification.flags |= Notification.FLAG_ONGOING_EVENT;
RadioWidgetProvider.updateAppWidget(this);
try {
Class types[] = { int.class, Notification.class };
Object args[] = { NOTIFY_ID, notification };
Method method = Service.class.getMethod("startForeground", types);
method.invoke(this, args);
} catch (NoSuchMethodException e) {
nm.notify(NOTIFY_ID, notification);
Class types[] = { boolean.class };
Object args[] = { true };
Method method;
try {
method = Service.class.getMethod("setForeground", types);
method.invoke(this, args);
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
} catch (Exception e) {
}
// Send the now playing info to an OpenWatch-enabled watch
Intent i = new Intent("com.smartmadsoft.openwatch.action.TEXT");
i.putExtra("line1", currentTrack.getTitle());
i.putExtra("line2", currentTrack.getCreator());
sendBroadcast(i);
}
@SuppressWarnings("rawtypes")
private void tuningNotify() {
String info = getString(R.string.playerservice_tuning);
if (currentStation != null) {
info = getString(R.string.playerservice_tuningwithstation, currentStation.getName());
}
Notification notification = new Notification(R.drawable.as_statusbar, getString(R.string.playerservice_tuning), System.currentTimeMillis());
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, Player.class), 0);
notification.setLatestEventInfo(this, info, "", contentIntent);
notification.flags |= Notification.FLAG_ONGOING_EVENT;
RadioWidgetProvider.updateAppWidget(this);
try {
Class types[] = { int.class, Notification.class };
Object args[] = { NOTIFY_ID, notification };
Method method = Service.class.getMethod("startForeground", types);
method.invoke(this, args);
} catch (NoSuchMethodException e) {
nm.notify(NOTIFY_ID, notification);
Class types[] = { boolean.class };
Object args[] = { true };
Method method;
try {
method = Service.class.getMethod("setForeground", types);
method.invoke(this, args);
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
} catch (Exception e) {
}
mStationStartTime = System.currentTimeMillis();
}
private OnCompletionListener mOnCompletionListener = new OnCompletionListener() {
public void onCompletion(MediaPlayer p) {
if(lostDataConnection && bufferPercent < 99) {
logger.info("Track ran out of data, pausing");
pause();
mp.release();
mp = null;
ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
NetworkInfo activeni = cm.getActiveNetworkInfo();
if(activeni != null && activeni.isConnected()) {
logger.info("Another data connection is available, attempting to resume");
pause();
lostDataConnection = false;
}
} else {
logger.info("Track completed normally (bye, laurie!)");
new NextTrackTask().execute((Void) null);
}
}
};
private OnBufferingUpdateListener mOnBufferingUpdateListener = new OnBufferingUpdateListener() {
public void onBufferingUpdate(MediaPlayer p, int percent) {
if (p == mp) {
bufferPercent = percent;
if(percent > 50 && !mUpdatedTrialCount && getSharedPreferences(LastFm.PREFS, 0).getBoolean("lastfm_freetrial", false)) {
int elapsed = getSharedPreferences(LastFm.PREFS, 0).getInt("lastfm_playselapsed", 0);
int left = getSharedPreferences(LastFm.PREFS, 0).getInt("lastfm_playsleft", 30);
elapsed++;
left--;
SharedPreferences.Editor editor = getSharedPreferences(LastFm.PREFS, 0).edit();
editor.putInt("lastfm_playselapsed", elapsed);
editor.putInt("lastfm_playsleft", left);
editor.commit();
mUpdatedTrialCount = true;
}
}
}
};
private OnPreparedListener mOnPreparedListener = new OnPreparedListener() {
public void onPrepared(MediaPlayer p) {
logger.info("Prepared");
if (p == mp) {
logger.info("main player");
if (mState == STATE_PREPARING) {
logger.info("preparing state");
if(mTrackPosition > 0)
p.seekTo(mTrackPosition);
mTrackPosition = 0;
p.start();
try {
playingNotify();
} catch (NullPointerException e) {
}
mState = STATE_PLAYING;
mAutoSkipCount = 0;
logger.info("Ready to produce packets (Hi, Laurie!)");
mUpdatedTrialCount = false;
if( getSharedPreferences(LastFm.PREFS, 0).getBoolean("lastfm_freetrial", false) && getSharedPreferences(LastFm.PREFS, 0).getInt("lastfm_playsleft", 30) <= 5 && !getSharedPreferences(LastFm.PREFS, 0).getBoolean("lastfm_freetrialexpirationwarning", false)) {
Notification notification = new Notification(R.drawable.as_statusbar, getString(R.string.playerservice_trial_almost_expired_title), System.currentTimeMillis());
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("http://www.last.fm/subscribe"));
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent contentIntent = PendingIntent.getActivity(RadioPlayerService.this, 0, i, 0);
notification.setLatestEventInfo(RadioPlayerService.this, getString(R.string.playerservice_trial_almost_expired_title), getString(R.string.playerservice_trial_almost_expired, getSharedPreferences(LastFm.PREFS, 0).getInt("lastfm_playsleft", 30)), contentIntent);
nm.notify(NOTIFY_ID+1, notification);
SharedPreferences.Editor editor = getSharedPreferences(LastFm.PREFS, 0).edit();
editor.putBoolean("lastfm_freetrialexpirationwarning", true);
editor.commit();
}
} else {
p.stop();
}
}
try {
LastFMApplication.getInstance().tracker.trackEvent("Radio", // Category
"Buffering", // Action
currentStation.getType(), // Label
(int) ((System.currentTimeMillis() - mTrackStartTime) / 1000)); // Value
} catch (Exception e) {
//Google Analytics doesn't appear to be thread safe
}
}
};
private OnErrorListener mOnErrorListener = new OnErrorListener() {
public boolean onError(MediaPlayer p, int what, int extra) {
if(mState == STATE_STOPPED)
return true;
if (mp == p) {
if (mAutoSkipCount++ > 4) {
// If we weren't able to start playing after 3 attempts,
// bail out and notify
// the user. This will bring us into a stopped state.
logger.severe("Too many playback errors, entering ERROR state");
mState = STATE_ERROR;
notifyChange(PLAYBACK_ERROR);
clearNotification();
if (wakeLock.isHeld())
wakeLock.release();
if (wifiLock.isHeld())
wifiLock.release();
if (mFocusHelper.isSupported())
mFocusHelper.abandonMusicFocus();
stopSelf();
} else {
if (mState == STATE_PLAYING || mState == STATE_PREPARING) {
logger.severe("Playback error: " + what + ", " + extra);
// ditch our playlist and fetch a new one, in case our
// IP changed
currentQueue.clear();
// Enter a state that will allow nextSong to do its
// thang
mState = STATE_ERROR;
new NextTrackTask().execute((Void) null);
}
if (mState == STATE_PAUSED) {
logger.severe("Playback error while paused, data connection probably timed out.");
}
}
}
return true;
}
};
private void playTrack(RadioTrack track, MediaPlayer p) {
try {
if (p == mp) {
currentTrack = track;
RadioWidgetProvider.updateAppWidget_playing(this, track.getTitle(), track.getCreator(), 0, 0, true, track.getLoved(), false);
}
if(track.getLocationUrl().contains("play.last.fm")) {
URL newURL = UrlUtil.getRedirectedUrl(new URL(track.getLocationUrl()));
track.setLocationUrl(newURL.toString());
}
if (mState == STATE_STOPPED || mState == STATE_PAUSED || mState == STATE_PREPARING) {
logger.severe("playTrack() called from wrong state!");
return;
}
logger.info("Streaming: " + track.getLocationUrl());
p.reset();
p.setWakeMode(this, PowerManager.PARTIAL_WAKE_LOCK);
p.setOnCompletionListener(mOnCompletionListener);
p.setOnBufferingUpdateListener(mOnBufferingUpdateListener);
p.setOnPreparedListener(mOnPreparedListener);
p.setOnErrorListener(mOnErrorListener);
p.setAudioStreamType(AudioManager.STREAM_MUSIC);
p.setDataSource(track.getLocationUrl());
new LoadAlbumArtTask().execute((Void)null);
registerMediaButtonEventReceiverCompat(mAudioManager,
new ComponentName(getApplicationContext(), LastFMMediaButtonHandler.class));
if (mFocusHelper.isSupported())
mFocusHelper.requestMusicFocus();
// Use the remote control APIs (if available) to set the playback state
if (mRemoteControlClientCompat == null) {
Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
intent.setComponent(new ComponentName(this, LastFMMediaButtonHandler.class));
mRemoteControlClientCompat = new RemoteControlClientCompat(
PendingIntent.getBroadcast(this /*context*/,
0 /*requestCode, ignored*/, intent /*intent*/, 0 /*flags*/));
RemoteControlHelper.registerRemoteControlClient(mAudioManager,
mRemoteControlClientCompat);
}
mRemoteControlClientCompat.setPlaybackState(
RemoteControlClient.PLAYSTATE_PLAYING);
mRemoteControlClientCompat.setTransportControlFlags(
RemoteControlClient.FLAG_KEY_MEDIA_PLAY |
RemoteControlClient.FLAG_KEY_MEDIA_PAUSE |
RemoteControlClient.FLAG_KEY_MEDIA_NEXT |
RemoteControlClient.FLAG_KEY_MEDIA_STOP);
// Update the remote controls
mRemoteControlClientCompat.editMetadata(true)
.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, track.getCreator())
.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, track.getAlbum())
.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, track.getTitle())
.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION,
track.getDuration())
.putBitmap(
RemoteControlClientCompat.MetadataEditorCompat.METADATA_KEY_ARTWORK,
mArtwork)
.apply();
// We do this because there has been bugs in our phonecall fade code
// that resulted in the music never becoming audible again after a
// call.
// Leave this precaution here please.
p.setVolume(1.0f, 1.0f);
if (p == mp)
mState = STATE_PREPARING;
mTrackStartTime = System.currentTimeMillis();
p.prepareAsync();
} catch (IllegalStateException e) {
logger.severe(e.toString());
} catch (IOException e) {
logger.severe(e.getMessage());
mState = STATE_PREPARING;
mOnErrorListener.onError(p, 0, 0);
}
}
private void stop() {
mState = STATE_STOPPED;
if (mp != null) {
try {
mp.stop();
mp.release();
mp = null;
} catch (Exception e) {
e.printStackTrace();
}
}
clearNotification();
notifyChange(PLAYBACK_FINISHED);
releaseLocks();
currentQueue.clear();
if(currentStation != null)
RadioWidgetProvider.updateAppWidget_idle(this, currentStation.getName(), false);
if (mFocusHelper.isSupported())
mFocusHelper.abandonMusicFocus();
if (mRemoteControlClientCompat != null)
mRemoteControlClientCompat.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED);
stopSelf();
}
private void nextSong() {
pauseButtonPressed = false;
if (mState == STATE_SKIPPING || mState == STATE_STOPPED) {
logger.severe("nextSong() called in wrong state: " + mState);
return;
}
if (mState == STATE_PLAYING || mState == STATE_PREPARING) {
currentTrack = null;
if (mp != null && mp.isPlaying()) {
mp.stop();
}
}
mTrackPosition = 0;
lostDataConnection = false;
mState = STATE_SKIPPING;
// Check if we're running low on tracks
if (currentQueue.size() < 1) {
mPlaylistRetryCount = 0;
try {
refreshPlaylist();
} catch (WSError e) {
mError = e;
} catch (Exception e) {
e.printStackTrace();
}
}
// Check again, if size still == 0 then the playlist is empty.
if (currentQueue.size() > 0) {
// playTrack will check if mStopping is true, and stop us if the
// user has
// pressed stop while we were fetching the playlist
if(mp == null) {
mp = new MediaPlayer();
}
if(mState == STATE_SKIPPING)
playTrack(currentQueue.poll(), mp);
if(mState == STATE_PREPARING)
notifyChange(META_CHANGED);
} else {
// we ran out of tracks, display a NEC error and stop
clearNotification();
notifyChange(PLAYBACK_ERROR);
mState = STATE_ERROR;
Notification notification = new Notification(R.drawable.as_statusbar, getString(R.string.playerservice_error_ticker_text), System
.currentTimeMillis());
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, Profile.class), 0);
notification.setLatestEventInfo(this, getString(R.string.ERROR_INSUFFICIENT_CONTENT_TITLE), getString(R.string.ERROR_INSUFFICIENT_CONTENT),
contentIntent);
nm.notify(NOTIFY_ID, notification);
stopSelf();
}
}
private void pause() {
logger.info("Pause()" + mState);
if (mState == STATE_STOPPED || mState == STATE_ERROR || currentStation == null)
return;
if (mState != STATE_PAUSED) {
clearNotification();
notifyChange(PLAYBACK_STATE_CHANGED);
notifyChange(ScrobblerService.PLAYBACK_PAUSED);
mp.setOnErrorListener(null);
mp.setOnCompletionListener(null);
try {
mTrackPosition = mp.getCurrentPosition();
if(mp.isPlaying()) {
mp.pause();
} else {
mp.reset();
mp.release();
mp = null;
mTrackPosition = 0;
}
} catch (Exception e) { //Sometimes the MediaPlayer is in a state where it can't pause
e.printStackTrace();
}
mState = STATE_PAUSED;
if(currentTrack != null)
RadioWidgetProvider.updateAppWidget_playing(this, currentTrack.getTitle(), currentTrack.getCreator(), 0, 0, false, false, true);
else
RadioWidgetProvider.updateAppWidget_idle(this, currentStation.getName(), false);
serializeCurrentStation();
releaseLocks();
if (mRemoteControlClientCompat != null)
mRemoteControlClientCompat.setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED);
try {
LastFMApplication.getInstance().tracker.trackEvent("Radio", // Category
"Pause", // Action
"", // Label
0); // Value
} catch (Exception e) {
//Google Analytics doesn't appear to be thread safe
}
} else {
ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
NetworkInfo ni = cm.getActiveNetworkInfo();
mDoHasWiFi = (ni == null || ni.getType() == ConnectivityManager.TYPE_WIFI);
playingNotify();
notifyChange(ScrobblerService.META_CHANGED);
try {
if(currentTrack != null) {
if(mp == null) {
mState = STATE_SKIPPING;
mp = new MediaPlayer();
playTrack(currentTrack, mp);
} else {
mp.start();
mState = STATE_PLAYING;
}
} else {
mState = STATE_PREPARING;
nextSong();
}
} catch (Exception e) { //Sometimes the MediaPlayer is in a state where it can't resume
mState = STATE_PREPARING;
nextSong();
e.printStackTrace();
}
mp.setOnCompletionListener(mOnCompletionListener);
mp.setOnErrorListener(mOnErrorListener);
if (mRemoteControlClientCompat != null)
mRemoteControlClientCompat.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
if (getFileStreamPath("player.dat").exists())
deleteFile("player.dat");
try {
LastFMApplication.getInstance().tracker.trackEvent("Radio", // Category
"Resume", // Action
"", // Label
0); // Value
} catch (Exception e) {
//Google Analytics doesn't appear to be thread safe
}
}
}
private void refreshPlaylist() throws Exception {
if (currentStation == null)
return;
LastFmServer server = AndroidLastFmServerFactory.getServer();
RadioPlayList playlist;
try {
String bitrate;
String rtp = "1";
String discovery = "0";
String multiplier = "2";
ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
NetworkInfo ni = cm.getActiveNetworkInfo();
if(ni != null)
logger.info("Current network type: " + ni.getTypeName());
if (ni != null && ni.getType() == ConnectivityManager.TYPE_MOBILE)
bitrate = "64";
else
bitrate = "128";
mDoHasWiFi = (ni == null || ni.getType() == ConnectivityManager.TYPE_WIFI);
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("highquality", false))
bitrate = "128";
if (!PreferenceManager.getDefaultSharedPreferences(this).getBoolean("scrobble", true))
rtp = "0";
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("discovery", false))
discovery = "1";
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("faststreaming", true))
multiplier = "8";
logger.info("Requesting bitrate: " + bitrate);
playlist = server.getRadioPlayList(bitrate, rtp, discovery, multiplier, currentSession.getKey());
if (playlist == null || playlist.getTracks().length == 0) {
try {
LastFMApplication.getInstance().tracker.trackEvent("Radio", // Category
"Error", // Action
"NotEnoughContent", // Label
0); // Value
} catch (Exception e) {
//Google Analytics doesn't appear to be thread safe
}
throw new WSError("radio.getPlaylist", "insufficient content", WSError.ERROR_NotEnoughContent);
}
SharedPreferences.Editor editor = getSharedPreferences(LastFm.PREFS, 0).edit();
editor.putInt("lastfm_playsleft", playlist.playLeft());
editor.commit();
RadioTrack[] tracks = playlist.getTracks();
logger.info("Got " + tracks.length + " track(s)");
for (int i = 0; i < tracks.length; i++) {
currentQueue.add(tracks[i]);
}
} catch (IOException e) {
// TODO Auto-generated catch block
if (e.getMessage().contains("code 503")) {
if (mPlaylistRetryCount++ < 4) {
logger.warning("Playlist service unavailable, retrying...");
Thread.sleep(2000);
refreshPlaylist();
} else {
throw e;
}
}
} catch (WSError e) {
String message;
if (e.getCode() == WSError.ERROR_NotEnoughContent)
message = "NotEnoughContent";
else
message = e.getMessage();
try {
LastFMApplication.getInstance().tracker.trackEvent("Radio", // Category
"Error", // Action
message, // Label
0); // Value
} catch (SQLiteException e1) {
//Google Analytics doesn't appear to be thread safe
}
logger.severe("Web service error: " + e.getMessage());
mError = e;
throw e;
}
}
private void notifyChange(String what) {
Intent i = new Intent(what);
if (currentTrack != null) {
i.putExtra("artist", currentTrack.getCreator());
i.putExtra("album", currentTrack.getAlbum());
i.putExtra("track", currentTrack.getTitle());
i.putExtra("duration", (long) currentTrack.getDuration());
i.putExtra("trackAuth", currentTrack.getTrackAuth());
i.putExtra("loved", currentTrack.getLoved());
if(mTrackPosition > 0)
i.putExtra("position",(long) mTrackPosition);
}
if (what.equals(PLAYBACK_ERROR) && mError != null) {
i.putExtra("error", (Parcelable) mError);
}
i.putExtra("station", currentStation);
sendBroadcast(i);
}
private void tune(String url, Session session) throws Exception, WSError {
if(!radioAvailable(this)) {
throw new WSError("radio.tune", "Last.fm radio is unavailable in this region", WSError.ERROR_RadioUnavailable);
}
wakeLock.acquire();
wifiLock.acquire();
currentStationURL = url;
if(!mFocusHelper.isSupported()) {
//Stop the standard media player
if(RadioWidgetProvider.isAndroidMusicInstalled(this)) {
try {
bindService(new Intent().setClassName("com.android.music", "com.android.music.MediaPlaybackService"), new ServiceConnection() {
public void onServiceConnected(ComponentName comp, IBinder binder) {
com.android.music.IMediaPlaybackService s = com.android.music.IMediaPlaybackService.Stub.asInterface(binder);
try {
if (s.isPlaying()) {
s.pause();
sendBroadcast(new Intent(ScrobblerService.PLAYBACK_PAUSED));
}
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
unbindService(this);
} catch (Exception e) {
}
}
public void onServiceDisconnected(ComponentName comp) {
}
}, 0);
} catch (Exception e) {
}
}
//Stop the HTC media player
if(RadioWidgetProvider.isHTCMusicInstalled(this)) {
bindService(new Intent().setClassName("com.htc.music", "com.htc.music.MediaPlaybackService"), new ServiceConnection() {
public void onServiceConnected(ComponentName comp, IBinder binder) {
com.htc.music.IMediaPlaybackService s = com.htc.music.IMediaPlaybackService.Stub.asInterface(binder);
try {
if (s.isPlaying()) {
s.pause();
sendBroadcast(new Intent(ScrobblerService.PLAYBACK_PAUSED));
}
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
unbindService(this);
} catch (Exception e) {
}
}
public void onServiceDisconnected(ComponentName comp) {
}
}, 0);
}
}
tuningNotify();
logger.info("Tuning to station: " + url);
if (mState == STATE_PLAYING) {
clearNotification();
mp.stop();
}
mState = STATE_TUNING;
currentQueue.clear();
currentSession = session;
LastFmServer server = AndroidLastFmServerFactory.getServer();
String lang = Locale.getDefault().getLanguage();
if (lang.equalsIgnoreCase("de")) {
currentStation = server.tuneToStation(url, session.getKey(), lang);
} else {
currentStation = server.tuneToStation(url, session.getKey(), null);
}
RadioWidgetProvider.updateAppWidget_idle(RadioPlayerService.this, currentStation.getName(), true);
if (currentStation != null) {
logger.info("Station name: " + currentStation.getName());
mPlaylistRetryCount = 0;
tuningNotify();
refreshPlaylist();
currentStationURL = url;
notifyChange(STATION_CHANGED);
RecentStationsDao.getInstance().appendRecentStation(currentStationURL, currentStation.getName());
} else {
clearNotification();
currentStationURL = null;
wakeLock.release();
wifiLock.release();
stopSelf();
}
}
private class LoadAlbumArtTask extends AsyncTaskEx<Void, Void, Boolean> {
String artistName;
String albumName;
Bitmap art;
@Override
public void onPreExecute() {
mArtwork = BitmapFactory.decodeResource(getResources(), R.drawable.no_artwork);
artistName = currentTrack.getCreator();
albumName = currentTrack.getAlbum();
logger.info("Fetching artwork");
}
@Override
public Boolean doInBackground(Void... params) {
String artUrl = "";
Album album = null;
boolean success = false;
artUrl = currentTrack.getImageUrl();
try {
LastFmServer server = AndroidLastFmServerFactory.getServer();
if (!artistName.equals(RadioPlayerService.UNKNOWN) && albumName != null && albumName.length() > 0) {
album = server.getAlbumInfo(artistName, albumName);
if (album != null) {
DisplayMetrics metrics = new DisplayMetrics();
android.view.WindowManager wm = (android.view.WindowManager)getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(metrics);
int width = metrics.widthPixels;
if(metrics.heightPixels < width)
width = metrics.heightPixels;
if(width > 320)
artUrl = album.getURLforImageSize("mega");
else
artUrl = album.getURLforImageSize("extralarge");
}
}
art = UrlUtil.getImage(new URL(artUrl));
success = true;
} catch (Exception e) {
e.printStackTrace();
} catch (WSError e) {
} catch (OutOfMemoryError e) {
artUrl = album.getURLforImageSize("extralarge");
try {
art = UrlUtil.getImage(new URL(artUrl));
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (Error e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
success = true;
}
return success;
}
@Override
public void onPostExecute(Boolean result) {
if(result && currentTrack != null && currentTrack.getAlbum().equals(albumName) && currentTrack.getCreator().equals(artistName)) {
mArtwork = art;
// Update the remote controls
mRemoteControlClientCompat.editMetadata(true)
.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, currentTrack.getCreator())
.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, currentTrack.getAlbum())
.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, currentTrack.getTitle())
.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION,
currentTrack.getDuration())
.putBitmap(
RemoteControlClientCompat.MetadataEditorCompat.METADATA_KEY_ARTWORK,
mArtwork)
.apply();
logger.info("Album art updated");
notifyChange(ARTWORK_AVAILABLE);
}
}
}
private class TuneRadioTask extends AsyncTaskEx<Void, Void, Void> {
String mStationURL = "";
Session mSession = null;
public TuneRadioTask(String stationURL, Session session) {
mStationURL = stationURL;
mSession = session;
}
@Override
public Void doInBackground(Void... input) {
try {
tune(mStationURL, mSession);
currentTrack = null;
nextSong();
} catch (WSError e) {
mError = e;
currentStationURL = null;
Intent i = new Intent("fm.last.android.ERROR");
i.putExtra("error", (Parcelable) e);
sendBroadcast(i);
logger.severe("Tuning error: " + e.getMessage());
e.printStackTrace();
clearNotification();
stopSelf();
} catch (Exception e) {
currentStationURL = null;
Intent i = new Intent("fm.last.android.ERROR");
i.putExtra("error", (Parcelable) new WSError(e.getClass().getSimpleName(), e.getMessage(), -1));
sendBroadcast(i);
logger.severe("Tuning error: " + e.getMessage());
e.printStackTrace();
clearNotification();
stopSelf();
}
return null;
}
}
private class NextTrackTask extends AsyncTaskEx<Void, Void, Boolean> {
@Override
public Boolean doInBackground(Void... input) {
boolean success = false;
try {
nextSong();
success = true;
} catch (WSError e) {
e.printStackTrace();
mError = e;
success = false;
} catch (Exception e) {
e.printStackTrace();
success = false;
}
return success;
}
@Override
public void onPostExecute(Boolean result) {
if (!result) {
notifyChange(PLAYBACK_ERROR);
mState = STATE_ERROR;
}
}
}
private final IRadioPlayer.Stub mBinder = new IRadioPlayer.Stub() {
public boolean getPauseButtonPressed() throws DeadObjectException {
return pauseButtonPressed;
}
public void pauseButtonPressed() throws DeadObjectException {
pauseButtonPressed = true;
}
public int getState() throws DeadObjectException {
return mState;
}
public void pause() throws DeadObjectException {
RadioPlayerService.this.pause();
}
public void stop() throws DeadObjectException {
logger.info("Stop button pressed");
RadioPlayerService.this.stop();
}
public boolean tune(String url, Session session) throws DeadObjectException, WSError {
mError = null;
try {
RadioPlayerService.this.tune(url, session);
return true;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (WSError e) {
mError = e;
}
notifyChange(PLAYBACK_ERROR);
mState = STATE_ERROR;
return false;
}
public void startRadio() throws RemoteException {
if (Looper.myLooper() == null)
Looper.prepare();
// Enter a TUNING state if the user presses the skip button when the
// player is in a
// STOPPED state
if (mState == STATE_STOPPED)
mState = STATE_TUNING;
currentTrack = null;
RadioWidgetProvider.updateAppWidget(RadioPlayerService.this);
new NextTrackTask().execute((Void) null);
}
public void skip() throws RemoteException {
logger.info("Skip button pressed");
if (Looper.myLooper() == null)
Looper.prepare();
new NextTrackTask().execute((Void) null);
}
public String getAlbumName() throws RemoteException {
if (currentTrack != null)
return currentTrack.getAlbum();
else
return UNKNOWN;
}
public String[] getContext() throws RemoteException {
if (currentTrack != null)
return currentTrack.getContext();
else
return null;
}
public boolean getLoved() throws RemoteException {
return currentTrack != null ? currentTrack.getLoved() : false;
}
public void setLoved(boolean loved) throws RemoteException {
if(currentTrack != null)
currentTrack.setLoved(loved);
}
public String getArtistName() throws RemoteException {
if (currentTrack != null)
return currentTrack.getCreator();
else
return UNKNOWN;
}
public long getDuration() throws RemoteException {
try {
if (mState == STATE_PAUSED && currentTrack != null)
return currentTrack.getDuration();
if (mp != null && mp.isPlaying())
return mp.getDuration();
} catch (Exception e) {
}
return 0;
}
public String getTrackName() throws RemoteException {
if (currentTrack != null)
return currentTrack.getTitle();
else
return UNKNOWN;
}
public boolean isPlaying() throws RemoteException {
return mState != STATE_STOPPED && mState != STATE_ERROR && mState != STATE_PAUSED;
}
public long getPosition() throws RemoteException {
try {
if (mState == STATE_PAUSED && mTrackPosition > 0)
return mTrackPosition;
if (mp != null && mp.isPlaying())
return mp.getCurrentPosition();
} catch (Exception e) {
}
return 0;
}
public Bitmap getArtwork() throws RemoteException {
return mArtwork;
}
public String getStationName() throws RemoteException {
if (currentStation != null)
return currentStation.getName();
return null;
}
public void setSession(Session session) throws RemoteException {
currentSession = session;
}
public int getBufferPercent() throws RemoteException {
return bufferPercent;
}
public String getStationUrl() throws RemoteException {
if (currentStation != null)
return currentStationURL;
return null;
}
public WSError getError() throws RemoteException {
WSError error = mError;
mError = null;
return error;
}
};
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/**
* Class responsible for fading in/out volume, for instance when a phone
* call arrives
*
* @author Lukasz Wisniewski
*
* TODO if volume is not at 1.0 or 0.0 when this starts (eg. old
* fade task didn't finish) then this sounds broken. Hard to fix
* though as you have to recalculate the fade duration etc.
*
* TODO setVolume is not logarithmic, and the ear is. We need a
* natural log scale see:
* http://stackoverflow.com/questions/207016/how
* -to-fade-out-volume-naturally see:
* http://code.google.com/android/
* reference/android/media/MediaPlayer
* .html#setVolume(float,%20float)
*/
private abstract class FadeVolumeTask extends TimerTask {
public static final int FADE_IN = 0;
public static final int FADE_OUT = 1;
private int mCurrentStep = 0;
private int mSteps;
private int mMode;
/**
* Constructor, launches timer immediately
*
* @param mode
* Volume fade mode <code>FADE_IN</code> or
* <code>FADE_OUT</code>
* @param millis
* Time the fade process should take
* @param steps
* Number of volume gradations within given fade time
*/
public FadeVolumeTask(int mode, int millis) {
this.mMode = mode;
this.mSteps = millis / 20; // 20 times per second
this.onPreExecute();
new Timer().scheduleAtFixedRate(this, 0, millis / mSteps);
}
@Override
public void run() {
float volumeValue = 1.0f;
if (mMode == FADE_OUT) {
volumeValue *= (float) (mSteps - mCurrentStep) / (float) mSteps;
} else {
volumeValue *= (float) (mCurrentStep) / (float) mSteps;
}
try {
mp.setVolume(volumeValue, volumeValue);
} catch (Exception e) {
return;
}
if (mCurrentStep >= mSteps) {
this.onPostExecute();
this.cancel();
}
mCurrentStep++;
}
/**
* Task executed before launching timer
*/
public abstract void onPreExecute();
/**
* Task executer after timer finished working
*/
public abstract void onPostExecute();
}
// Backwards compatibility code (methods available as of SDK Level 8)
static {
initializeStaticCompatMethods();
}
static Method sMethodRegisterMediaButtonEventReceiver;
static Method sMethodUnregisterMediaButtonEventReceiver;
private static void initializeStaticCompatMethods() {
try {
sMethodRegisterMediaButtonEventReceiver = AudioManager.class.getMethod(
"registerMediaButtonEventReceiver",
new Class[] { ComponentName.class });
sMethodUnregisterMediaButtonEventReceiver = AudioManager.class.getMethod(
"unregisterMediaButtonEventReceiver",
new Class[] { ComponentName.class });
} catch (NoSuchMethodException e) {
// Silently fail when running on an OS before SDK level 8.
}
}
private static void registerMediaButtonEventReceiverCompat(AudioManager audioManager,
ComponentName receiver) {
if (sMethodRegisterMediaButtonEventReceiver == null)
return;
try {
sMethodRegisterMediaButtonEventReceiver.invoke(audioManager, receiver);
} catch (InvocationTargetException e) {
// Unpack original exception when possible
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else if (cause instanceof Error) {
throw (Error) cause;
} else {
// Unexpected checked exception; wrap and re-throw
throw new RuntimeException(e);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
@SuppressWarnings("unused")
private static void unregisterMediaButtonEventReceiverCompat(AudioManager audioManager,
ComponentName receiver) {
if (sMethodUnregisterMediaButtonEventReceiver == null)
return;
try {
sMethodUnregisterMediaButtonEventReceiver.invoke(audioManager, receiver);
} catch (InvocationTargetException e) {
// Unpack original exception when possible
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else if (cause instanceof Error) {
throw (Error) cause;
} else {
// Unexpected checked exception; wrap and re-throw
throw new RuntimeException(e);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public void focusGained() {
if (mFadeVolumeTask != null)
mFadeVolumeTask.cancel();
if(mState == STATE_PAUSED && focusLost) {
logger.info("fading music back in");
mFadeVolumeTask = new FadeVolumeTask(FadeVolumeTask.FADE_IN, 5000) {
@Override
public void onPreExecute() {
if (mState == STATE_PAUSED)
RadioPlayerService.this.pause();
}
@Override
public void onPostExecute() {
mFadeVolumeTask = null;
}
};
focusLost = false;
} else {
try {
if(mp != null && mp.isPlaying())
mp.setVolume(1.0f, 1.0f);
} catch (Exception e) { //Sometimes the MediaPlayer is in a state where isPlaying() or setVolume() will fail
e.printStackTrace();
}
}
}
public void focusLost(boolean isTransient, boolean canDuck) {
if (mFadeVolumeTask != null)
mFadeVolumeTask.cancel();
if (mp == null || mState == STATE_PAUSED)
return;
if (canDuck) {
try {
mp.setVolume(DUCK_VOLUME, DUCK_VOLUME);
} catch (Exception e) { //Sometimes the MediaPlayer is in a state where setVolume() will fail
e.printStackTrace();
}
} else {
logger.info("fading music out");
mFadeVolumeTask = new FadeVolumeTask(FadeVolumeTask.FADE_OUT, 1500) {
@Override
public void onPreExecute() {
}
@Override
public void onPostExecute() {
RadioPlayerService.this.pause();
mFadeVolumeTask = null;
}
};
focusLost = isTransient;
}
}
}