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