Home | History | Annotate | Download | only in musicplayer
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.example.android.musicplayer;
     18 
     19 import android.app.Notification;
     20 import android.app.NotificationManager;
     21 import android.app.PendingIntent;
     22 import android.app.Service;
     23 import android.content.ComponentName;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.graphics.Bitmap;
     27 import android.graphics.BitmapFactory;
     28 import android.media.AudioManager;
     29 import android.media.MediaMetadataRetriever;
     30 import android.media.MediaPlayer;
     31 import android.media.MediaPlayer.OnCompletionListener;
     32 import android.media.MediaPlayer.OnErrorListener;
     33 import android.media.MediaPlayer.OnPreparedListener;
     34 import android.media.RemoteControlClient;
     35 import android.net.Uri;
     36 import android.net.wifi.WifiManager;
     37 import android.net.wifi.WifiManager.WifiLock;
     38 import android.os.IBinder;
     39 import android.os.PowerManager;
     40 import android.util.Log;
     41 import android.widget.Toast;
     42 
     43 import java.io.IOException;
     44 
     45 /**
     46  * Service that handles media playback. This is the Service through which we perform all the media
     47  * handling in our application. Upon initialization, it starts a {@link MusicRetriever} to scan
     48  * the user's media. Then, it waits for Intents (which come from our main activity,
     49  * {@link MainActivity}, which signal the service to perform specific operations: Play, Pause,
     50  * Rewind, Skip, etc.
     51  */
     52 public class MusicService extends Service implements OnCompletionListener, OnPreparedListener,
     53                 OnErrorListener, MusicFocusable,
     54                 PrepareMusicRetrieverTask.MusicRetrieverPreparedListener {
     55 
     56     // The tag we put on debug messages
     57     final static String TAG = "RandomMusicPlayer";
     58 
     59     // These are the Intent actions that we are prepared to handle. Notice that the fact these
     60     // constants exist in our class is a mere convenience: what really defines the actions our
     61     // service can handle are the <action> tags in the <intent-filters> tag for our service in
     62     // AndroidManifest.xml.
     63     public static final String ACTION_TOGGLE_PLAYBACK =
     64             "com.example.android.musicplayer.action.TOGGLE_PLAYBACK";
     65     public static final String ACTION_PLAY = "com.example.android.musicplayer.action.PLAY";
     66     public static final String ACTION_PAUSE = "com.example.android.musicplayer.action.PAUSE";
     67     public static final String ACTION_STOP = "com.example.android.musicplayer.action.STOP";
     68     public static final String ACTION_SKIP = "com.example.android.musicplayer.action.SKIP";
     69     public static final String ACTION_REWIND = "com.example.android.musicplayer.action.REWIND";
     70     public static final String ACTION_URL = "com.example.android.musicplayer.action.URL";
     71 
     72     // The volume we set the media player to when we lose audio focus, but are allowed to reduce
     73     // the volume instead of stopping playback.
     74     public static final float DUCK_VOLUME = 0.1f;
     75 
     76     // our media player
     77     MediaPlayer mPlayer = null;
     78 
     79     // our AudioFocusHelper object, if it's available (it's available on SDK level >= 8)
     80     // If not available, this will be null. Always check for null before using!
     81     AudioFocusHelper mAudioFocusHelper = null;
     82 
     83     // indicates the state our service:
     84     enum State {
     85         Retrieving, // the MediaRetriever is retrieving music
     86         Stopped,    // media player is stopped and not prepared to play
     87         Preparing,  // media player is preparing...
     88         Playing,    // playback active (media player ready!). (but the media player may actually be
     89                     // paused in this state if we don't have audio focus. But we stay in this state
     90                     // so that we know we have to resume playback once we get focus back)
     91         Paused      // playback paused (media player ready!)
     92     };
     93 
     94     State mState = State.Retrieving;
     95 
     96     // if in Retrieving mode, this flag indicates whether we should start playing immediately
     97     // when we are ready or not.
     98     boolean mStartPlayingAfterRetrieve = false;
     99 
    100     // if mStartPlayingAfterRetrieve is true, this variable indicates the URL that we should
    101     // start playing when we are ready. If null, we should play a random song from the device
    102     Uri mWhatToPlayAfterRetrieve = null;
    103 
    104     enum PauseReason {
    105         UserRequest,  // paused by user request
    106         FocusLoss,    // paused because of audio focus loss
    107     };
    108 
    109     // why did we pause? (only relevant if mState == State.Paused)
    110     PauseReason mPauseReason = PauseReason.UserRequest;
    111 
    112     // do we have audio focus?
    113     enum AudioFocus {
    114         NoFocusNoDuck,    // we don't have audio focus, and can't duck
    115         NoFocusCanDuck,   // we don't have focus, but can play at a low volume ("ducking")
    116         Focused           // we have full audio focus
    117     }
    118     AudioFocus mAudioFocus = AudioFocus.NoFocusNoDuck;
    119 
    120     // title of the song we are currently playing
    121     String mSongTitle = "";
    122 
    123     // whether the song we are playing is streaming from the network
    124     boolean mIsStreaming = false;
    125 
    126     // Wifi lock that we hold when streaming files from the internet, in order to prevent the
    127     // device from shutting off the Wifi radio
    128     WifiLock mWifiLock;
    129 
    130     // The ID we use for the notification (the onscreen alert that appears at the notification
    131     // area at the top of the screen as an icon -- and as text as well if the user expands the
    132     // notification area).
    133     final int NOTIFICATION_ID = 1;
    134 
    135     // Our instance of our MusicRetriever, which handles scanning for media and
    136     // providing titles and URIs as we need.
    137     MusicRetriever mRetriever;
    138 
    139     // our RemoteControlClient object, which will use remote control APIs available in
    140     // SDK level >= 14, if they're available.
    141     RemoteControlClientCompat mRemoteControlClientCompat;
    142 
    143     // Dummy album art we will pass to the remote control (if the APIs are available).
    144     Bitmap mDummyAlbumArt;
    145 
    146     // The component name of MusicIntentReceiver, for use with media button and remote control
    147     // APIs
    148     ComponentName mMediaButtonReceiverComponent;
    149 
    150     AudioManager mAudioManager;
    151     NotificationManager mNotificationManager;
    152 
    153     Notification mNotification = null;
    154 
    155     /**
    156      * Makes sure the media player exists and has been reset. This will create the media player
    157      * if needed, or reset the existing media player if one already exists.
    158      */
    159     void createMediaPlayerIfNeeded() {
    160         if (mPlayer == null) {
    161             mPlayer = new MediaPlayer();
    162 
    163             // Make sure the media player will acquire a wake-lock while playing. If we don't do
    164             // that, the CPU might go to sleep while the song is playing, causing playback to stop.
    165             //
    166             // Remember that to use this, we have to declare the android.permission.WAKE_LOCK
    167             // permission in AndroidManifest.xml.
    168             mPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
    169 
    170             // we want the media player to notify us when it's ready preparing, and when it's done
    171             // playing:
    172             mPlayer.setOnPreparedListener(this);
    173             mPlayer.setOnCompletionListener(this);
    174             mPlayer.setOnErrorListener(this);
    175         }
    176         else
    177             mPlayer.reset();
    178     }
    179 
    180     @Override
    181     public void onCreate() {
    182         Log.i(TAG, "debug: Creating service");
    183 
    184         // Create the Wifi lock (this does not acquire the lock, this just creates it)
    185         mWifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
    186                         .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
    187 
    188         mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    189         mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
    190 
    191         // Create the retriever and start an asynchronous task that will prepare it.
    192         mRetriever = new MusicRetriever(getContentResolver());
    193         (new PrepareMusicRetrieverTask(mRetriever,this)).execute();
    194 
    195         // create the Audio Focus Helper, if the Audio Focus feature is available (SDK 8 or above)
    196         if (android.os.Build.VERSION.SDK_INT >= 8)
    197             mAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this);
    198         else
    199             mAudioFocus = AudioFocus.Focused; // no focus feature, so we always "have" audio focus
    200 
    201         mDummyAlbumArt = BitmapFactory.decodeResource(getResources(), R.drawable.dummy_album_art);
    202 
    203         mMediaButtonReceiverComponent = new ComponentName(this, MusicIntentReceiver.class);
    204     }
    205 
    206     /**
    207      * Called when we receive an Intent. When we receive an intent sent to us via startService(),
    208      * this is the method that gets called. So here we react appropriately depending on the
    209      * Intent's action, which specifies what is being requested of us.
    210      */
    211     @Override
    212     public int onStartCommand(Intent intent, int flags, int startId) {
    213         String action = intent.getAction();
    214         if (action.equals(ACTION_TOGGLE_PLAYBACK)) processTogglePlaybackRequest();
    215         else if (action.equals(ACTION_PLAY)) processPlayRequest();
    216         else if (action.equals(ACTION_PAUSE)) processPauseRequest();
    217         else if (action.equals(ACTION_SKIP)) processSkipRequest();
    218         else if (action.equals(ACTION_STOP)) processStopRequest();
    219         else if (action.equals(ACTION_REWIND)) processRewindRequest();
    220         else if (action.equals(ACTION_URL)) processAddRequest(intent);
    221 
    222         return START_NOT_STICKY; // Means we started the service, but don't want it to
    223                                  // restart in case it's killed.
    224     }
    225 
    226     void processTogglePlaybackRequest() {
    227         if (mState == State.Paused || mState == State.Stopped) {
    228             processPlayRequest();
    229         } else {
    230             processPauseRequest();
    231         }
    232     }
    233 
    234     void processPlayRequest() {
    235         if (mState == State.Retrieving) {
    236             // If we are still retrieving media, just set the flag to start playing when we're
    237             // ready
    238             mWhatToPlayAfterRetrieve = null; // play a random song
    239             mStartPlayingAfterRetrieve = true;
    240             return;
    241         }
    242 
    243         tryToGetAudioFocus();
    244 
    245         // actually play the song
    246 
    247         if (mState == State.Stopped) {
    248             // If we're stopped, just go ahead to the next song and start playing
    249             playNextSong(null);
    250         }
    251         else if (mState == State.Paused) {
    252             // If we're paused, just continue playback and restore the 'foreground service' state.
    253             mState = State.Playing;
    254             setUpAsForeground(mSongTitle + " (playing)");
    255             configAndStartMediaPlayer();
    256         }
    257 
    258         // Tell any remote controls that our playback state is 'playing'.
    259         if (mRemoteControlClientCompat != null) {
    260             mRemoteControlClientCompat
    261                     .setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
    262         }
    263     }
    264 
    265     void processPauseRequest() {
    266         if (mState == State.Retrieving) {
    267             // If we are still retrieving media, clear the flag that indicates we should start
    268             // playing when we're ready
    269             mStartPlayingAfterRetrieve = false;
    270             return;
    271         }
    272 
    273         if (mState == State.Playing) {
    274             // Pause media player and cancel the 'foreground service' state.
    275             mState = State.Paused;
    276             mPlayer.pause();
    277             relaxResources(false); // while paused, we always retain the MediaPlayer
    278             // do not give up audio focus
    279         }
    280 
    281         // Tell any remote controls that our playback state is 'paused'.
    282         if (mRemoteControlClientCompat != null) {
    283             mRemoteControlClientCompat
    284                     .setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED);
    285         }
    286     }
    287 
    288     void processRewindRequest() {
    289         if (mState == State.Playing || mState == State.Paused)
    290             mPlayer.seekTo(0);
    291     }
    292 
    293     void processSkipRequest() {
    294         if (mState == State.Playing || mState == State.Paused) {
    295             tryToGetAudioFocus();
    296             playNextSong(null);
    297         }
    298     }
    299 
    300     void processStopRequest() {
    301         processStopRequest(false);
    302     }
    303 
    304     void processStopRequest(boolean force) {
    305         if (mState == State.Playing || mState == State.Paused || force) {
    306             mState = State.Stopped;
    307 
    308             // let go of all resources...
    309             relaxResources(true);
    310             giveUpAudioFocus();
    311 
    312             // Tell any remote controls that our playback state is 'paused'.
    313             if (mRemoteControlClientCompat != null) {
    314                 mRemoteControlClientCompat
    315                         .setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED);
    316             }
    317 
    318             // service is no longer necessary. Will be started again if needed.
    319             stopSelf();
    320         }
    321     }
    322 
    323     /**
    324      * Releases resources used by the service for playback. This includes the "foreground service"
    325      * status and notification, the wake locks and possibly the MediaPlayer.
    326      *
    327      * @param releaseMediaPlayer Indicates whether the Media Player should also be released or not
    328      */
    329     void relaxResources(boolean releaseMediaPlayer) {
    330         // stop being a foreground service
    331         stopForeground(true);
    332 
    333         // stop and release the Media Player, if it's available
    334         if (releaseMediaPlayer && mPlayer != null) {
    335             mPlayer.reset();
    336             mPlayer.release();
    337             mPlayer = null;
    338         }
    339 
    340         // we can also release the Wifi lock, if we're holding it
    341         if (mWifiLock.isHeld()) mWifiLock.release();
    342     }
    343 
    344     void giveUpAudioFocus() {
    345         if (mAudioFocus == AudioFocus.Focused && mAudioFocusHelper != null
    346                                 && mAudioFocusHelper.abandonFocus())
    347             mAudioFocus = AudioFocus.NoFocusNoDuck;
    348     }
    349 
    350     /**
    351      * Reconfigures MediaPlayer according to audio focus settings and starts/restarts it. This
    352      * method starts/restarts the MediaPlayer respecting the current audio focus state. So if
    353      * we have focus, it will play normally; if we don't have focus, it will either leave the
    354      * MediaPlayer paused or set it to a low volume, depending on what is allowed by the
    355      * current focus settings. This method assumes mPlayer != null, so if you are calling it,
    356      * you have to do so from a context where you are sure this is the case.
    357      */
    358     void configAndStartMediaPlayer() {
    359         if (mAudioFocus == AudioFocus.NoFocusNoDuck) {
    360             // If we don't have audio focus and can't duck, we have to pause, even if mState
    361             // is State.Playing. But we stay in the Playing state so that we know we have to resume
    362             // playback once we get the focus back.
    363             if (mPlayer.isPlaying()) mPlayer.pause();
    364             return;
    365         }
    366         else if (mAudioFocus == AudioFocus.NoFocusCanDuck)
    367             mPlayer.setVolume(DUCK_VOLUME, DUCK_VOLUME);  // we'll be relatively quiet
    368         else
    369             mPlayer.setVolume(1.0f, 1.0f); // we can be loud
    370 
    371         if (!mPlayer.isPlaying()) mPlayer.start();
    372     }
    373 
    374     void processAddRequest(Intent intent) {
    375         // user wants to play a song directly by URL or path. The URL or path comes in the "data"
    376         // part of the Intent. This Intent is sent by {@link MainActivity} after the user
    377         // specifies the URL/path via an alert box.
    378         if (mState == State.Retrieving) {
    379             // we'll play the requested URL right after we finish retrieving
    380             mWhatToPlayAfterRetrieve = intent.getData();
    381             mStartPlayingAfterRetrieve = true;
    382         }
    383         else if (mState == State.Playing || mState == State.Paused || mState == State.Stopped) {
    384             Log.i(TAG, "Playing from URL/path: " + intent.getData().toString());
    385             tryToGetAudioFocus();
    386             playNextSong(intent.getData().toString());
    387         }
    388     }
    389 
    390     void tryToGetAudioFocus() {
    391         if (mAudioFocus != AudioFocus.Focused && mAudioFocusHelper != null
    392                         && mAudioFocusHelper.requestFocus())
    393             mAudioFocus = AudioFocus.Focused;
    394     }
    395 
    396     /**
    397      * Starts playing the next song. If manualUrl is null, the next song will be randomly selected
    398      * from our Media Retriever (that is, it will be a random song in the user's device). If
    399      * manualUrl is non-null, then it specifies the URL or path to the song that will be played
    400      * next.
    401      */
    402     void playNextSong(String manualUrl) {
    403         mState = State.Stopped;
    404         relaxResources(false); // release everything except MediaPlayer
    405 
    406         try {
    407             MusicRetriever.Item playingItem = null;
    408             if (manualUrl != null) {
    409                 // set the source of the media player to a manual URL or path
    410                 createMediaPlayerIfNeeded();
    411                 mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    412                 mPlayer.setDataSource(manualUrl);
    413                 mIsStreaming = manualUrl.startsWith("http:") || manualUrl.startsWith("https:");
    414 
    415                 playingItem = new MusicRetriever.Item(0, null, manualUrl, null, 0);
    416             }
    417             else {
    418                 mIsStreaming = false; // playing a locally available song
    419 
    420                 playingItem = mRetriever.getRandomItem();
    421                 if (playingItem == null) {
    422                     Toast.makeText(this,
    423                             "No available music to play. Place some music on your external storage "
    424                             + "device (e.g. your SD card) and try again.",
    425                             Toast.LENGTH_LONG).show();
    426                     processStopRequest(true); // stop everything!
    427                     return;
    428                 }
    429 
    430                 // set the source of the media player a a content URI
    431                 createMediaPlayerIfNeeded();
    432                 mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    433                 mPlayer.setDataSource(getApplicationContext(), playingItem.getURI());
    434             }
    435 
    436             mSongTitle = playingItem.getTitle();
    437 
    438             mState = State.Preparing;
    439             setUpAsForeground(mSongTitle + " (loading)");
    440 
    441             // Use the media button APIs (if available) to register ourselves for media button
    442             // events
    443 
    444             MediaButtonHelper.registerMediaButtonEventReceiverCompat(
    445                     mAudioManager, mMediaButtonReceiverComponent);
    446 
    447             // Use the remote control APIs (if available) to set the playback state
    448 
    449             if (mRemoteControlClientCompat == null) {
    450                 Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
    451                 intent.setComponent(mMediaButtonReceiverComponent);
    452                 mRemoteControlClientCompat = new RemoteControlClientCompat(
    453                         PendingIntent.getBroadcast(this /*context*/,
    454                                 0 /*requestCode, ignored*/, intent /*intent*/, 0 /*flags*/));
    455                 RemoteControlHelper.registerRemoteControlClient(mAudioManager,
    456                         mRemoteControlClientCompat);
    457             }
    458 
    459             mRemoteControlClientCompat.setPlaybackState(
    460                     RemoteControlClient.PLAYSTATE_PLAYING);
    461 
    462             mRemoteControlClientCompat.setTransportControlFlags(
    463                     RemoteControlClient.FLAG_KEY_MEDIA_PLAY |
    464                     RemoteControlClient.FLAG_KEY_MEDIA_PAUSE |
    465                     RemoteControlClient.FLAG_KEY_MEDIA_NEXT |
    466                     RemoteControlClient.FLAG_KEY_MEDIA_STOP);
    467 
    468             // Update the remote controls
    469             mRemoteControlClientCompat.editMetadata(true)
    470                     .putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, playingItem.getArtist())
    471                     .putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, playingItem.getAlbum())
    472                     .putString(MediaMetadataRetriever.METADATA_KEY_TITLE, playingItem.getTitle())
    473                     .putLong(MediaMetadataRetriever.METADATA_KEY_DURATION,
    474                             playingItem.getDuration())
    475                     // TODO: fetch real item artwork
    476                     .putBitmap(
    477                             RemoteControlClientCompat.MetadataEditorCompat.METADATA_KEY_ARTWORK,
    478                             mDummyAlbumArt)
    479                     .apply();
    480 
    481             // starts preparing the media player in the background. When it's done, it will call
    482             // our OnPreparedListener (that is, the onPrepared() method on this class, since we set
    483             // the listener to 'this').
    484             //
    485             // Until the media player is prepared, we *cannot* call start() on it!
    486             mPlayer.prepareAsync();
    487 
    488             // If we are streaming from the internet, we want to hold a Wifi lock, which prevents
    489             // the Wifi radio from going to sleep while the song is playing. If, on the other hand,
    490             // we are *not* streaming, we want to release the lock if we were holding it before.
    491             if (mIsStreaming) mWifiLock.acquire();
    492             else if (mWifiLock.isHeld()) mWifiLock.release();
    493         }
    494         catch (IOException ex) {
    495             Log.e("MusicService", "IOException playing next song: " + ex.getMessage());
    496             ex.printStackTrace();
    497         }
    498     }
    499 
    500     /** Called when media player is done playing current song. */
    501     public void onCompletion(MediaPlayer player) {
    502         // The media player finished playing the current song, so we go ahead and start the next.
    503         playNextSong(null);
    504     }
    505 
    506     /** Called when media player is done preparing. */
    507     public void onPrepared(MediaPlayer player) {
    508         // The media player is done preparing. That means we can start playing!
    509         mState = State.Playing;
    510         updateNotification(mSongTitle + " (playing)");
    511         configAndStartMediaPlayer();
    512     }
    513 
    514     /** Updates the notification. */
    515     void updateNotification(String text) {
    516         PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,
    517                 new Intent(getApplicationContext(), MainActivity.class),
    518                 PendingIntent.FLAG_UPDATE_CURRENT);
    519         mNotification.setLatestEventInfo(getApplicationContext(), "RandomMusicPlayer", text, pi);
    520         mNotificationManager.notify(NOTIFICATION_ID, mNotification);
    521     }
    522 
    523     /**
    524      * Configures service as a foreground service. A foreground service is a service that's doing
    525      * something the user is actively aware of (such as playing music), and must appear to the
    526      * user as a notification. That's why we create the notification here.
    527      */
    528     void setUpAsForeground(String text) {
    529         PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,
    530                 new Intent(getApplicationContext(), MainActivity.class),
    531                 PendingIntent.FLAG_UPDATE_CURRENT);
    532         mNotification = new Notification();
    533         mNotification.tickerText = text;
    534         mNotification.icon = R.drawable.ic_stat_playing;
    535         mNotification.flags |= Notification.FLAG_ONGOING_EVENT;
    536         mNotification.setLatestEventInfo(getApplicationContext(), "RandomMusicPlayer",
    537                 text, pi);
    538         startForeground(NOTIFICATION_ID, mNotification);
    539     }
    540 
    541     /**
    542      * Called when there's an error playing media. When this happens, the media player goes to
    543      * the Error state. We warn the user about the error and reset the media player.
    544      */
    545     public boolean onError(MediaPlayer mp, int what, int extra) {
    546         Toast.makeText(getApplicationContext(), "Media player error! Resetting.",
    547             Toast.LENGTH_SHORT).show();
    548         Log.e(TAG, "Error: what=" + String.valueOf(what) + ", extra=" + String.valueOf(extra));
    549 
    550         mState = State.Stopped;
    551         relaxResources(true);
    552         giveUpAudioFocus();
    553         return true; // true indicates we handled the error
    554     }
    555 
    556     public void onGainedAudioFocus() {
    557         Toast.makeText(getApplicationContext(), "gained audio focus.", Toast.LENGTH_SHORT).show();
    558         mAudioFocus = AudioFocus.Focused;
    559 
    560         // restart media player with new focus settings
    561         if (mState == State.Playing)
    562             configAndStartMediaPlayer();
    563     }
    564 
    565     public void onLostAudioFocus(boolean canDuck) {
    566         Toast.makeText(getApplicationContext(), "lost audio focus." + (canDuck ? "can duck" :
    567             "no duck"), Toast.LENGTH_SHORT).show();
    568         mAudioFocus = canDuck ? AudioFocus.NoFocusCanDuck : AudioFocus.NoFocusNoDuck;
    569 
    570         // start/restart/pause media player with new focus settings
    571         if (mPlayer != null && mPlayer.isPlaying())
    572             configAndStartMediaPlayer();
    573     }
    574 
    575     public void onMusicRetrieverPrepared() {
    576         // Done retrieving!
    577         mState = State.Stopped;
    578 
    579         // If the flag indicates we should start playing after retrieving, let's do that now.
    580         if (mStartPlayingAfterRetrieve) {
    581             tryToGetAudioFocus();
    582             playNextSong(mWhatToPlayAfterRetrieve == null ?
    583                     null : mWhatToPlayAfterRetrieve.toString());
    584         }
    585     }
    586 
    587 
    588     @Override
    589     public void onDestroy() {
    590         // Service is being killed, so make sure we release our resources
    591         mState = State.Stopped;
    592         relaxResources(true);
    593         giveUpAudioFocus();
    594     }
    595 
    596     @Override
    597     public IBinder onBind(Intent arg0) {
    598         return null;
    599     }
    600 }
    601